本系列主要参考《Unity Shaders and Effects Cookbook》一书(感谢原书作者),同时会加上一点个人理解或拓展。
这里是本书所有的插图。这里是本书所需的代码和资源(当然你也可以从官网下载)。
========================================== 分割线==========================================
写在前面- 首先,我们需要一个Cubemap来产生反射效果。所以你可以使用前一节中的Cubemap,或者生成一个新的。这一节中,我们使用的如下所示(你可以在本书资源中找到它):
- 我们还需要一张法线贴图来产生基于法线的反射效果。
- 最后,创建一个新的场景、一个球体、一个平面以及一个平行光。同时,还需要创建一个新的Shader和Material,命名为NormalMappedReflection。
- 首先让我们添加新的properties,使得我们能够添加自己的Cubemap和法线贴图。这个步骤你应该非常熟悉了。向Properties块添加下列代码:
Properties { _MainTint ("Diffuse Tint", Color) = (1,1,1,1) _MainTex ("Base (RGB)", 2D) = "white" {} _NormalMap ("Normal Map", 2D) = "bump" {} _Cubemap ("Cubemap", CUBE) = ""{} _ReflAmount ("Reflection Amount", Range(0,1)) = 0.5 }
- 然后,我们需要在SubShader块声明这些properties,使得我们能够访问Properties块中的这些数据:
CGPROGRAM #pragma surface surf Lambert samplerCUBE _Cubemap; sampler2D _MainTex; sampler2D _NormalMap; float4 _MainTint; float _ReflAmount;
- 然后,修改Input结构体。这是基于法线贴图的反射的精华所在。通过使用INTERNAL_DATA声明,我们可以访问经过法线贴图修改后的平面的法线信息:
struct Input { float2 uv_MainTex; float2 uv_NormalMap; float3 worldRefl; INTERNAL_DATA };
- 最后,我们需要修改surf函数,来得到最后的基于法线贴图的反射效果:
void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap)).rgb; o.Normal = normals; o.Emission = texCUBE (_Cubemap, WorldReflectionVector (IN, o.Normal)).rgb * _ReflAmount; o.Albedo = c.rgb * _MainTint; o.Alpha = c.a; }
Shader "Custom/NormalMappedReflection" { Properties { _MainTint ("Diffuse Tint", Color) = (1,1,1,1) _MainTex ("Base (RGB)", 2D) = "white" {} _NormalMap ("Normal Map", 2D) = "bump" {} _Cubemap ("Cubemap", CUBE) = ""{} _ReflAmount ("Reflection Amount", Range(0,1)) = 0.5 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf Lambert samplerCUBE _Cubemap; sampler2D _MainTex; sampler2D _NormalMap; float4 _MainTint; float _ReflAmount; struct Input { float2 uv_MainTex; float2 uv_NormalMap; float3 worldRefl; INTERNAL_DATA }; void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap)).rgb; o.Normal = normals; o.Emission = texCUBE (_Cubemap, WorldReflectionVector (IN, o.Normal)).rgb * _ReflAmount; o.Albedo = c.rgb * _MainTint; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
效果如下(一个表明凹凸不平的反射球):
float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap)).rgb; o.Normal = normals;
一旦上述代码在Shader中执行后,物体的平面法线将被修改;因此,我们需要使用它来影响我们的反射。我们可以通过声明INTERNAL_DATA来访问修改后的法线信息,然后使用WorldReflectionVector(IN,o.Normal)去查找Cubemap中对应的反射信息。这是Unity提供给我们的另一个内置函数,因此我们不需要再自己写那些冗长的代码,而仅仅需要关注编写Shader中产生关键效果的部分。
float3 viewDir | 包含了视角的观察方向,主要用于计算视差效应(Parallax effects),边缘光照,等等。 |
float4 COLOR | 包含了经过内插值(interpolated)的每个顶点的颜色值。 |
float4 screenPos | 包含了用于反射效果的屏幕坐标系的位置信息。例如,在Unity专业版的WetStreet shader中使用了它。 |
float3 worldPos | 包含了世界坐标系中的位置。 |
float3 worldRefl | 包含了世界坐标系中的反射向量,如果Surface Shader没有重写o.Normal。参考Reflect-Diffuse shader。 |
float3 worldNormal | 包含了世界坐标系中的法线向量,如果Surface Shader没有重写o.Normal。 |
float3 worldRefl; INTERNAL_DATA | 包含了世界坐标系的反射向量,如果Surface Shader重写了o.Normal。为了得到基于逐像素的法线贴图的反射向量,请使用WorldReflectionVector (IN,o.Normal) 。参考Reflect-Bumped shader。 |
float3 worldNormal; INTERNAL_DATA | 包含了世界坐标系的发现向量,如果Surface Shader重写了o.Normal。为了得到基于逐像素的法线贴图的法线向量,请使用WorldNormalVector(IN,o.Normal) 。参考Reflect-Bumped shader。 |