【Unity Shaders】Reflecting Your World —— Unity3D中的遮罩反射(Masking Reflections)

本系列主要参考《Unity Shaders and Effects Cookbook》一书(感谢原书作者),同时会加上一点个人理解或拓展。

这里是本书所有的插图。这里是本书所需的代码和资源(当然你也可以从官网下载)。

========================================== 分割线 ==========================================



写在前面


有时候,我们并不想让物体的所有部分都反射,例如一个物体可能某些部分是玻璃材质的可以反射,而有些是塑料材质就不会反射。

在这篇教程里,我们将会学习一种技术来控制反射范围,这是通过一张texture作为遮罩(mask)来实现的。也就是说,我们可以使用一张texture的灰度值去决定该平面该如何反射,这意味着,一个为黑色的灰度值对应一个不会反射的子平面,而一个白色的灰度值对应一个完全反射的子平面。如今,基本所有的游戏制作都是使用这种方法来控制反射效果的。

下面,我们来看看在Unity里怎么使用Surface Shaders来实现它。



准备工作


  1. 首先,我们需要一个Cubemap,你可以生成一个新的或者使用前一篇用到的Cubemap。本教程所用的Cubemap如下(在本书的附带资源中可以找到):

  2. 我们还需要一个texture来描述我们对象的那些部分是可以反射的,而哪些不可以。记住,黑色表示没有任何反射性,而白色表示可以完全反射。下面的图片是我们将会用到的texture:

  3. 最后,我们需要创建一个新的场景以及场景中的一个对象、平面和一个平行光,来让我们观察反射效果。除此之外,新建一个Shader和对应的Material,并命名为MaskedReflection。

实现


  1. 添加新的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) = ""{}
    	}

  2. SubShader块中添加它们的引用变量:
    		CGPROGRAM
    		#pragma surface surf Lambert
    
    		sampler2D _MainTex;
    		sampler2D _ReflMask;
    		samplerCUBE _Cubemap;
    		float4 _MainTint;
    		float _ReflAmount;

  3. 修改Input结构体:
    		struct Input {
    			float2 uv_MainTex;
    			float3 worldRefl;
    		};
    

  4. 修改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值对应的不同的反射效果:



原文地址:https://www.cnblogs.com/xiaowangba/p/6314698.html