zoukankan      html  css  js  c++  java
  • 【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值对应的不同的反射效果:



  • 相关阅读:
    5.深入TiDB:Insert 语句
    4.深入TiDB:执行计划执行过程详解
    3.深入TiDB:执行优化讲解
    2.深入TiDB:入口代码分析及调试 TiDB
    1.深入TiDB:初见TiDB
    开发必备之单元测试
    如何用好MySQL索引
    SpringBoot运行源码剖析(一)
    深入理解Java虚拟机读后感
    Java并发编程艺术读后感
  • 原文地址:https://www.cnblogs.com/xiaowangba/p/6314698.html
Copyright © 2011-2022 走看看