您的位置:首页 > 移动开发 > Unity3D

【Unity Shader】手游中高光效果的几种实现方法

2016-01-10 11:08 3171 查看

前言

由于手机设备的性能限制,很多效果的计算都得精简和优化才能达到目地。而在Unity中的高光效果,也给予了多种不同的方案,有用于主机的,用于手机的,有限制一盏像素灯的,开发者可以根据不同的情况酌情选择。Unity的Surface Shader提供了很好的封装性,复杂的灯光处理都不需要开发者去关注,也有提供修改光照函数的接口。但是由于项目特殊性,往往这么简单方便的事情会阻碍使用者的自由发挥。所以我们在很多优秀的游戏作品中都能看到很多自研发Shader都是基于Vert&Frag的,也因此让我们看到很多不同的实现原理和方式(Unity大法好)。对于次世代游戏来说,光是必不可少的,尤其是高光和法线效果。在这里,就介绍在手游上能使用的几种实现方法,并且采用Vert&Frag
Shader。

方案

高光效果跟法线,视向,光向都脱不开干系,主要是如何利用它们和如何组织计算。本文大致分为如下几种方式实现:

在顶点函数,世界空间中计算
在顶点函数,切线空间中计算
在片元函数,世界空间中计算
在片元函数,切线空间中计算

分析

在顶点和在片元函数中处理是效率和效果都是不一样的,由于在顶点函数处理是逐顶点计算的,所以是比较快的,但是计算不精准;而在片元中是逐像素计算,所以效率较慢(虽然慢不了多少,但是积少成多),计算更精准。
在世界空间和在切线空间中计算也是得出不一样的效果,在切线空间下效果更加细腻平滑,效果也更好。

实现

在顶点函数,世界空间中计算:
Shader "Yogi/Specular(Vert-World)"
{
Properties
{
_MainTex("Base(RGB) Gloss(A)", 2D) = "white" {}
_Specular("Specular", Range(0, 10)) = 1
_Shininess("Shininess", Range(0.01, 1)) = 0.5
}

SubShader
{
Pass
{
CGPROGRAM
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_fwdbase

sampler2D _MainTex;
fixed4 _MainTex_ST;
fixed _Specular;
fixed _Shininess;

struct a2v
{
fixed4 vertex : POSITION;
fixed3 normal : NORMAL;
fixed4 texcoord : TEXCOORD0;
};

struct v2f
{
fixed4 pos : SV_POSITION;
fixed2 uv : TEXCOORD0;
fixed3 spec : TEXCOORD1;
};

v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

fixed3 normal = mul((fixed3x3)_Object2World, SCALED_NORMAL);
fixed3 viewDir = normalize(WorldSpaceViewDir(v.vertex));
fixed3 lightDir = normalize(WorldSpaceLightDir(v.vertex));
fixed nh = saturate(dot(normal, normalize(viewDir + lightDir)));

o.spec = _LightColor0.rgb * pow(nh, _Shininess * 128) * _Specular;

return o;
}

fixed4 frag(v2f i) : SV_Target
{
fixed4 c = tex2D(_MainTex, i.uv.xy);
fixed4 o = c;

o.rgb += i.spec * c.a;

return o;
}

ENDCG
}
}

FallBack "Mobile/Diffuse"
}


在顶点函数,切线空间中计算:
Shader "Yogi/Specular(Vert-Tangent)"
{
Properties
{
_MainTex("Base(RGB) Gloss(A)", 2D) = "white" {}
_Specular("Specular", Range(0, 10)) = 1
_Shininess("Shininess", Range(0.01, 1)) = 0.5
}

SubShader
{
Pass
{
CGPROGRAM
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_fwdbase

sampler2D _MainTex;
fixed4 _MainTex_ST;
fixed _Specular;
fixed _Shininess;

struct a2v
{
fixed4 vertex : POSITION;
fixed3 normal : NORMAL;
fixed4 tangent : TANGENT;
fixed4 texcoord : TEXCOORD0;
};

struct v2f
{
fixed4 pos : SV_POSITION;
fixed2 uv : TEXCOORD0;
fixed3 spec : TEXCOORD1;
};

v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

TANGENT_SPACE_ROTATION;
fixed3 normal = mul(rotation, SCALED_NORMAL);
float3 viewDir = normalize(mul(rotation, ObjSpaceViewDir(v.vertex)));
float3 lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
fixed nh = saturate(dot(normal, normalize(viewDir + lightDir)));

o.spec = _LightColor0.rgb * pow(nh, _Shininess * 128) * _Specular;

return o;
}

fixed4 frag(v2f i) : SV_Target
{
fixed4 c = tex2D(_MainTex, i.uv.xy);
fixed4 o = c;

o.rgb += i.spec * c.a;

return o;
}

ENDCG
}
}

FallBack "Mobile/Diffuse"
}


在片元函数,世界空间中计算:
Shader "Yogi/Specular(Frag-World)"
{
Properties
{
_MainTex("Base(RGB) Gloss(A)", 2D) = "white" {}
_Specular("Specular", Range(0, 10)) = 1
_Shininess("Shininess", Range(0.01, 1)) = 0.5
}

SubShader
{
Pass
{
CGPROGRAM
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_fwdbase

sampler2D _MainTex;
fixed4 _MainTex_ST;
fixed _Specular;
fixed _Shininess;

struct a2v
{
fixed4 vertex : POSITION;
fixed3 normal : NORMAL;
fixed4 texcoord : TEXCOORD0;
};

struct v2f
{
fixed4 pos : SV_POSITION;
fixed2 uv : TEXCOORD0;
fixed3 normal : TEXCOORD1;
fixed3 viewDir : TEXCOORD2;
fixed3 lightDir : TEXCOORD3;
};

v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

o.normal = mul((float3x3)_Object2World, SCALED_NORMAL);
o.viewDir = normalize(WorldSpaceViewDir(v.vertex));
o.lightDir = normalize(_WorldSpaceLightPos0.xyz);

return o;
}

fixed4 frag(v2f i) : SV_Target
{
fixed4 c = tex2D(_MainTex, i.uv.xy);
fixed4 o = c;

fixed nh = saturate(dot(i.normal, normalize(i.viewDir + i.lightDir)));
fixed3 spec = _LightColor0.rgb * pow(nh, _Shininess * 128) * _Specular;
o.rgb += spec * c.a;

return o;
}

ENDCG
}
}

FallBack "Mobile/Diffuse"
}


在片元函数,切线空间中计算:
Shader "Yogi/Specular(Frag-Tangent)"
{
Properties
{
_MainTex("Base(RGB) Gloss(A)", 2D) = "white" {}
_Specular("Specular", Range(0, 10)) = 1
_Shininess("Shininess", Range(0.01, 1)) = 0.5
}

SubShader
{
Pass
{
CGPROGRAM
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_fwdbase

sampler2D _MainTex;
fixed4 _MainTex_ST;
fixed _Specular;
fixed _Shininess;

struct a2v
{
fixed4 vertex : POSITION;
fixed3 normal : NORMAL;
fixed4 tangent : TANGENT;
fixed4 texcoord : TEXCOORD0;
};

struct v2f
{
fixed4 pos : SV_POSITION;
fixed2 uv : TEXCOORD0;
fixed3 normal : TEXCOORD1;
fixed3 viewDir : TEXCOORD2;
fixed3 lightDir : TEXCOORD3;
};

v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

TANGENT_SPACE_ROTATION;
o.normal = mul(rotation, v.normal);
o.viewDir = normalize(mul(rotation, ObjSpaceViewDir(v.vertex)));
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));

return o;
}

fixed4 frag(v2f i) : SV_Target
{
fixed4 c = tex2D(_MainTex, i.uv.xy);
fixed4 o = c;

fixed nh = saturate(dot(i.normal, normalize(i.viewDir + i.lightDir)));
fixed3 spec = _LightColor0.rgb * pow(nh, _Shininess * 128) * _Specular;
o.rgb += spec * c.a;

return o;
}

ENDCG
}
}

FallBack "Mobile/Diffuse"
}


四个高光效果对比:


最后

开发者可以根据不同的手机配置使用不同的Shader,或者做多个SubShader,通过设定ShaderLod切换。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: