本系列主要参考一书(感谢原书作者),同时会加上一点个人理解或拓展。
是本书所有的插图。是本书所需的代码和资源(当然你也可以从下载)。
========================================== 分割线 ==========================================
写在前面
有时候,我们并不想让物体的所有部分都反射,例如一个物体可能某些部分是玻璃材质的可以反射,而有些是塑料材质就不会反射。
在这篇教程里,我们将会学习一种技术来控制反射范围,这是通过一张texture作为遮罩(mask)来实现的。也就是说,我们可以使用一张texture的灰度值去决定该平面该如何反射,这意味着,一个为黑色的灰度值对应一个不会反射的子平面,而一个白色的灰度值对应一个完全反射的子平面。如今,基本所有的游戏制作都是使用这种方法来控制反射效果的。
下面,我们来看看在Unity里怎么使用Surface Shaders来实现它。
准备工作
- 首先,我们需要一个Cubemap,你可以生成一个新的或者使用用到的Cubemap。本教程所用的Cubemap如下(在中可以找到):
- 我们还需要一个texture来描述我们对象的那些部分是可以反射的,而哪些不可以。记住,黑色表示没有任何反射性,而白色表示可以完全反射。下面的图片是我们将会用到的texture:
- 最后,我们需要创建一个新的场景以及场景中的一个对象、平面和一个平行光,来让我们观察反射效果。除此之外,新建一个Shader和对应的Material,并命名为MaskedReflection。
实现
- 添加新的Properties:
Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _MainTint ("Diffuse Tint", Color) = (1,1,1,1) _ReflAmount ("Reflection Amount", Range(0, 1)) = 1 _Cubemap ("Cubemap", CUBE) = ""{} _ReflMask ("Reflection Mask", 2D) = ""{} }
- 在SubShader块中添加它们的引用变量:
CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; sampler2D _ReflMask; samplerCUBE _Cubemap; float4 _MainTint; float _ReflAmount;
- 修改Input结构体:
struct Input { float2 uv_MainTex; float3 worldRefl; };
- 修改surf函数:
void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); float3 reflection = texCUBE(_Cubemap, IN.worldRefl).rgb; float4 reflMask = tex2D(_ReflMask, IN.uv_MainTex); o.Albedo = c.rgb * _MainTint; o.Emission = (reflection * reflMask.r) * _ReflAmount; o.Alpha = c.a; }
最后整体代码如下:
Shader "Custom/MaskedReflection" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _MainTint ("Diffuse Tint", Color) = (1,1,1,1) _ReflAmount ("Reflection Amount", Range(0, 1)) = 1 _Cubemap ("Cubemap", CUBE) = ""{} _ReflMask ("Reflection Mask", 2D) = ""{} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; sampler2D _ReflMask; samplerCUBE _Cubemap; float4 _MainTint; float _ReflAmount; struct Input { float2 uv_MainTex; float3 worldRefl; }; void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); float3 reflection = texCUBE(_Cubemap, IN.worldRefl).rgb; float4 reflMask = tex2D(_ReflMask, IN.uv_MainTex); o.Albedo = c.rgb * _MainTint; o.Emission = (reflection * reflMask.r) * _ReflAmount; o.Alpha = c.a; } ENDCG } FallBack "Diffuse"}将场景中球体对应的材质设置如下图所示:
最后效果如图,其中左面的球体使用了遮罩反射,对比右面没有使用遮罩反射:
解释
这个Shader非常的简单,仅仅使用了texCUBE函数在Cubemap中采样。这个函数是内置的CGFX函数,它提供给我们一个Cubemap中的颜色值,然后我们将该值应用到平面上。Unity通过Input结构体中的worldRefl变量来帮我们在Cubemap中找到对应的采样位置。正如解释的一样,这个属性将会把摄像机视角的反射向量传递给我们。
一旦我们知道了反射元素(即反射的颜色值),我们就需要接着去采样我们的遮罩贴图。我们可以使用tex2D函数来完成它,这个函数在中就接触过。
当我们知道了两个textures对应的值后,我们就可以把Cubemap的颜色值乘以反射贴图的颜色值,并传递给o.Emission。最后,为了可以全局控制反射密度,我们需要把结果再乘以_ReflAmount属性。这可以帮我们控制平面的整体反射量(越大表明反射度越高,越接近镜子的效果)。
下面展示了不同的_ReflAmount值对应的不同的反射效果: