IT评测·应用市场-qidao123.com

标题: 第四章:反射-Reflecting Your World《Unity Shaders and Effets Cookbook [打印本页]

作者: 傲渊山岳    时间: 2025-3-10 22:23
标题: 第四章:反射-Reflecting Your World《Unity Shaders and Effets Cookbook


Unity Shaders and Effets Cookbook

《着色器和屏幕殊效制作攻略》

这本书可以让你学习到怎样使用着色器和屏幕殊效让你的Unity工程拥有震撼的渲染画面。

                                                                                                       ——
Kenny Lammers

第四章:反射你的天下
第1节. 在 Unity3D 中创建立方体贴图  
1.1、准备工作
1.2、怎样实现...
1.3、实现原理...
1.4、另请参阅
第2节. Unity3D 中实现简朴的立方体贴图反射
2.1、准备工作
2.2、怎样实现...
2.3、实现原理...
2.4、另请参阅
第3节. Unity3D 中实现遮罩反射
3.1、准备工作
3.2、怎样实现...
3.3、实现原理...
第4节. Unity3D 中实现法线贴图和反射
4.1、准备工作
4.2、怎样实现...
4.3、实现原理...
4.5、更多内容
第5节. Unity3D 中实现菲涅耳反射
5.1、准备工作
5.2、怎样实现...
5.3、实现原理...
第6节. 在 Unity3D 中创建简朴的动态立方体贴图体系
6.1、准备工作
6.2、怎样实现...
6.3、实现原理...
6.4、另请参阅

I like this book!


第四章:反射你的天下


        当今,反射是可以让一个 Shader 在感官上具备视觉打击力的一项关键技能,它是在 Shader 表面上模拟环境反射的一个过程,在这个过程中使用了你附近的天下(环境)信息,并且让 Shader 去反射这些天下(环境)的信息。由于在这部分中我们需要使用一种称为 Cubemap 的新型纹理。这种范例的纹理由 6 张纹理组成,并且用这 6 张纹理以立方体的方式围绕当前表面。以是想象一个立方体,立方体的6个面上被赋予了这6张纹理。这将答应我们去捕获附近环境并将其烘焙到纹理中。
        我们将会相识到怎样从我们自己的环境天生我们自己的立方体贴图,也会去研究使用这些天生的立方体贴图在着色器中来创建差异的反射效果。这非常恰当模拟金属、汽车上的漆甚至还有塑料。因此,在本章中,我们将学习以下内容:


第1节. 在 Unity3D 中创建立方体贴图  


        为了学习怎样在 Shader 中创建反射的效果,我们首先需要学习怎样创建自己的立方体贴图(Cubemaps)。你也可以去搜刮一些现有的立方体贴图,但很快你会发现你还是需要自己去制作它,由于找的立方体贴图和你的游戏环境是不匹配的。一般情况下在线找的 Cubemap 都仅用于测试效果。一旦在项目中正式使用的时候,还是需要创建一个自己的立方体贴图才气用往复反射你游戏天下的环境,这是创建一个真实反射效果的关键。
        我们将介绍几种可以直接在 Unity 编辑器中实行此操作的方法。别的,也会介绍怎样创建自己的立方体贴图的独立应用程序。这有利于下一小节知识的推进,由于清楚立方体贴图的天生方式并理解它们,这对对整个章节内容是至关紧张的。
因此,在第一小节中,在创建立方体贴图方面,为我们提供一些差异的技能。

1.1、准备工作

        Unity 为我们提供了 JavaScript 代码,以便能够从我们创建的环境中创建立方体贴图。那么让我们来看看。以下是它的脚本参考链接:www.packtpub.com/support。这将作为我们脚本的底子。我们要将其转换为 C#。在本章的最后一个小节中,我们将介绍怎样创建一个简朴的体系来在多个位置生建立方体贴图,并且角色在环境中移动时,使用该数据在这些反射贴图之间举行交换,这会为我们提供一个半实时反射体系。
        对于本小节,我们只相识立方体贴图的工作原理,这项准备可用于你的游戏的动态反射体系。


 图1.1



图1.2



图1.3


1.2、怎样实现...

 让我们按照以下几个步调开始编写着色器:


图1.2



1.3、实现原理...

        我们首先创建一个新脚本,并将其声明的类继续 ScriptableWizard 。它是用来告诉 Unity3D 我们打算为 Unity 制作一个弹出窗口范例的自界说编辑器。这就是为什么我们必须将此脚本放入名为 Editor 的文件夹中的原因了。否则,Unity 不会将其辨以为自界说编辑器范例的脚本。
        在下一步中声明的变量紧张用来存储我们要创建的立方体贴图(Cubemap)的位置信息,并且这个方法也可以使用在 Project 选项卡中去储存一个新的 Cubemap GameObject 构造函数。有了这些变量我们就可以天生新的 Cubemap 了。
        然后,我们就可以使用 OnWizardUpdate() 函数了,它是 ScriptableWizard 类提供给我们的函数。当 wizard 首次被打开时以及 wizard 的任何一个 GUI 元素发生更改时就会调用该函数。因此,我们可以使用它来验证使用者是否真的启动了 transform 和新的 Cubemap。如果有,我们将 isValid 变量设置为 true;如果没有,我们将 isValid 设置为 false。isValid 是一个内置变量,由 ScriptableWizard 类提供给我们。它的作用只是让你打开和关闭 wizard 底部的 Create 按钮。这可以禁止其他人去运行空位置函数或 Cubemap 。
        一旦我们确保用户为我们提供了正确的数据来处理,我们就可以继续实行 OnWizardCreate() 函数了。这就是 Cubemap 创建的核心所在。首先,创建一个新的 GameObject 构造函数,并确保将其创建为 Camera 范例。然后,使用我们提供的位置信息来定位它。
        此时,我们有一台新相机并对其举行了定位。需要做的就是调用 RenderToCubeMap() 函数,并将用户提供的 Cubemap 传递给它。一旦这个函数运行,Cubemap 的 6 张图像将被创建并组装到用户提供的 Cubemap 对象中。
        最后,我们为 wizard 创建一个菜单选项,以便我们可以从 Unity 的顶部菜单栏中选择此工具。使用该菜单,我们就可以调用 wizard 的 Static 函数,该函数现实上就是用来显示菜单的。这样就完成了在 Unity 编辑器中直接创建一个生建立方体贴图的小工具。

1.4、另请参阅

        让我们看一下其他天生 Cubemap 的应用程序。以下为你提供了一个不错的资源列表,你i可以使用这些资源来创建自己的 Reflection Pipeline 或 Workflow:




第2节. Unity3D 中实现简朴的立方体贴图反射


        现在我们已经知道了怎样去创建自己的自界说立方体贴图,接下来我们可以看看怎样使用这种新的纹理范例来模拟着色器中的反射。使用立方体贴图来实现反射的概念现实上是比较简朴的,而且它为你的着色器效果提供了一个非常强盛的工具。它的工作原理是使用模子表面每个顶点的法线来查找立方体贴图纹理上的位置。此查找将返回一个颜色值,该值模拟 Cubemap 在对象表面上反射的效果。这就是它的底子的概念。
        这个特定的方法将迈出使用立方体贴图举行反射的第一步。Unity 现实上为我们提供了自动获取反射矢量的方法,因此我们不必自己计算。这是在 Input 结构体中使用内置的 worldRefl 向量完成的。这将有助于我们举行 Cubemap 纹理的查找操作。因此,第一步为我们提供了给 Surface Shader 创建反射效果最基本的方法。

2.1、准备工作

        在开始 Shader 代码之前,我们需要通过创建一些资源来布置一个简朴的场景。
        在下 图2.1 的屏幕截图中显示了我们在本节中使用的 Cubemap。你的立方体贴图可能和下 图2.1 展示的会有所差异,但我们只是想使用一个内容比较干净没有任何干扰的环境图。

图2.1


2.2、怎样实现...

        让我们按照以下几个步调来开始编写 Shader 代码。
步调1. 首先,我们在 Properties 块中创建一些新属性。我们需要一个地方来获取立方体贴图纹理并控制反射数量:
  1. Properties
  2. {
  3.    
  4.     _MainTint("Diffuse Tint", color) = (1,1,1,1)
  5.     _MainTex("Main Map", 2D) = "white"{}
  6.     _Cubemap("CubeMap", CUBE) = ""{}
  7.     _ReflAmount("Reflection Amount", Range(0.01,1)) = 0.5
  8. }
复制代码
步调2. 然后,我们需要在 SubShader 块中声明这些属性。这将答应我们从 Properties 块中访问到这些属性的数据。
  1. // 将属性链接到CG程序
  2. sampler2D _MainTex;
  3. samplerCUBE _Cubemap;
  4. half4 _MainTint;
  5. float _ReflAmount;
复制代码
步调3. 为了从表面上模拟到正确反射角度,我们需要得到某种矢量数据,这些数据将为我们提供正确的天下反射方向。为此,我们可以使用 Unity 表面着色器的另一个内置功能。在 Input 结构体中,以下代码将为我们提供一个可以在 Shader 中使用的天下反射向量:
  1. struct Input
  2. {
  3.     float2 uv_MainTex;
  4.     float3 worldRefl;
  5. };
复制代码
步调4. 最后,我们只需要使用 Input 结构体中的天下反射向量和 texCUBE() 函数,对Cubemap 贴图举行采样 的纹理信息。将以下代码添加到 surf() 函数中:
  1. void surf (Input IN, inout SurfaceOutput o)
  2. {
  3.     float4 c = tex2D (_MainTex, IN.uv_MainTex) * _MainTint;
  4.     o.Emission = texCUBE(_Cubemap, IN.worldRefl).rgb * _ReflAmount;
  5.     o.Albedo =  c.rgb;
  6.     o.Alpha = c.a;
  7. }
复制代码
完备代码:
  1. Shader "CookbookShaders/Cubemap reflection in Unity3D"
  2. {
  3.     Properties
  4.     {
  5.         _MainTint("Diffuse Tint", color) = (1,1,1,1)
  6.         _MainTex("Main Map", 2D) = "white"{}
  7.         _Cubemap("CubeMap", CUBE) = ""{}
  8.         _ReflAmount("Reflection Amount", Range(0.01,1)) = 0.5
  9.     }
  10. SubShader
  11.     {
  12.         Tags { "RenderType"="Opaque" }
  13.         LOD 200
  14.         CGPROGRAM
  15.         #pragma surface surf Lambert
  16.         // 将属性链接到CG程序
  17.         sampler2D _MainTex;
  18.         samplerCUBE _Cubemap;
  19.         half4 _MainTint;
  20.         float _ReflAmount;
  21.         struct Input
  22.         {
  23.             float2 uv_MainTex;
  24.             float3 worldRefl;
  25.         };
  26.         
  27.         void surf (Input IN, inout SurfaceOutput o)
  28.         {
  29.             float4 c = tex2D (_MainTex, IN.uv_MainTex) * _MainTint;
  30.             o.Emission = texCUBE(_Cubemap, IN.worldRefl).rgb * _ReflAmount;
  31.             o.Albedo =  c.rgb;
  32.             o.Alpha = c.a;
  33.         }
  34.         ENDCG
  35.     }
  36. FallBack "Diffuse"
  37. }
复制代码
        下 图2.2 显示了我们自己创建的自界说 Cubemap 的结果:

图2.2


2.3、实现原理...

        如果 Shader 没有报错,你应该会看到 Cubemap 被反射到了物体对象上,这样它就能像真实的反射物体一样对立方体贴图举行采样。这统统都是可以实现的,由于 Unity3D 在表面着色器的 Input 结构体中内置了属性。worldRefl 属性为我们提供了正确采样立方体贴图所需的反射向量。只需在 texCube() 函数中使用 worldRefl 属性即可,我们就可以轻松地为立方体贴图采样正确的反射视角。
        如下 图2.3 显示了反射数据的示例:
下 图2.3 显示了一个 Shader 中的反射数据的例子,但看起来像一个调试脚本: 

图2.3


2.4、另请参阅

Unity-shader 怎样采样立方体贴图(Cube Map采样)https://blog.csdn.net/m0_56734636/article/details/144323445?spm=1001.2014.3001.5502https://blog.csdn.net/m0_56734636/article/details/144323445?spm=1001.2014.3001.5502https://blog.csdn.net/m0_56734636/article/details/144323445?spm=1001.2014.3001.5502https://blog.csdn.net/m0_56734636/article/details/144323445?spm=1001.2014.3001.5502https://blog.csdn.net/m0_56734636/article/details/144323445?spm=1001.2014.3001.5502https://blog.csdn.net/m0_56734636/article/details/144323445?spm=1001.2014.3001.5502https://blog.csdn.net/m0_56734636/article/details/144323445?spm=1001.2014.3001.5502
https://blog.csdn.net/m0_56734636/article/details/144323445?spm=1001.2014.3001.5502


第3节. Unity3D 中实现遮罩反射


        拥有反射黑白常赞的,但我们并不是全部的地方都需要反射。险些全部东西都会反射肯定量的环境,因此,我们需要对反射效果举行某种逐像素控制。
        在本小节中,我们会介绍一种技能,该技能答应我们使用纹理作为蒙版来驱动反射量。总的来说,我们可以使用纹理的灰度值来表现表面的反射程度,这意味着纹理中的黑色值表现物体表面完全没有反射,而白色值则表现物体的表面完全反射。在当今的游戏制作流程中都可以看到艺术家可以机动的控制镜面反射的反射量。那么,让我们来看看怎样在 Unity 中使用表面着色器来做到这一点。

3.1、准备工作

        首先让我们给遮罩反射着色器准备一个新的场景。


图3.1



图3.2



3.2、怎样实现...

        设置好场景后,我们现在可以开始编写反射效果所需的代码了。

  1. Properties
  2. {
  3.     _MainTint("Diffuse Tint", color) = (1,1,1,1)
  4.     _MainTex("Main Map", 2D) = "white"{}
  5.     _ReflAmount("Reflection Amount", Range(0.01,1)) = 0.5
  6.     _Cubemap("CubeMap", CUBE) = ""{}
  7.     _ReflMask("Reflection Mask", 2D) = "white"{}
  8. }
复制代码

  1. // 将属性链接到CG程序
  2. sampler2D _MainTex;
  3. sampler2D _ReflMask;
  4. samplerCUBE _Cubemap;
  5. half4 _MainTint;
  6. float _ReflAmount;
复制代码

  1. struct Input
  2. {
  3.    float2 uv_MainTex;
  4.    float3 worldRefl;
  5. };
复制代码

  1. void surf (Input IN, inout SurfaceOutput o)
  2. {
  3.     float4 c = tex2D (_MainTex, IN.uv_MainTex);
  4.     float3 reflection = texCUBE(_Cubemap, IN.worldRefl).rgb;
  5.     float4 reflMask = tex2D(_ReflMask, IN.uv_MainTex);
  6.     o.Albedo =  c.rgb * _MainTint;
  7.     o.Emission = (reflection * reflMask.r) * _ReflAmount;
  8.     o.Alpha = c.a;
  9. }
复制代码

  1. Shader "CookbookShaders/Mask reflection in Unity3D"
  2. {
  3.     Properties
  4.     {
  5.         _MainTint("Diffuse Tint", color) = (1,1,1,1)
  6.         _MainTex("Main Map", 2D) = "white"{}
  7.         _ReflAmount("Reflection Amount", Range(0.01,1)) = 0.5
  8.         _Cubemap("CubeMap", CUBE) = ""{}
  9.         _ReflMask("Reflection Mask", 2D) = "white"{}
  10.     }
  11. SubShader
  12.     {
  13.         Tags { "RenderType"="Opaque" }
  14.         LOD 200
  15.         CGPROGRAM
  16.         #pragma surface surf Lambert
  17.         // 将属性链接到CG程序
  18.         sampler2D _MainTex;
  19.         sampler2D _ReflMask;
  20.         samplerCUBE _Cubemap;
  21.         half4 _MainTint;
  22.         float _ReflAmount;
  23.         struct Input
  24.         {
  25.             float2 uv_MainTex;
  26.             float3 worldRefl;
  27.         };
  28.         
  29.         void surf (Input IN, inout SurfaceOutput o)
  30.         {
  31.             float4 c = tex2D (_MainTex, IN.uv_MainTex);
  32.             float3 reflection = texCUBE(_Cubemap, IN.worldRefl).rgb;
  33.             float4 reflMask = tex2D(_ReflMask, IN.uv_MainTex);
  34.             o.Albedo =  c.rgb * _MainTint;
  35.             o.Emission = (reflection * reflMask.r) * _ReflAmount;
  36.             o.Alpha = c.a;
  37.         }
  38.         ENDCG
  39.     }
  40. FallBack "Diffuse"
  41. }
复制代码
        下 图3.3 显示了在 Unity3D Surface Shader 中使用了反射纹理遮罩的渲染结果:

图3.3


3.3、实现原理...

        此 Shader 的工作原理非常简朴,只需首先使用 texCUBE() 函数对 Cubemap 纹理举行采样即可。此函数内置于 CGFX 语言中。它为我们提供了采样的立方体贴图颜色,然后我们可以将其应用于着色器的表面。Unity 通过在 Input 结构中为我们提供 worldRefl 属性来资助我们完成这项工作。如在上一小节中所述,此属性从摄像机视角为我们提供反射向量。
        得到反射元素后,我们需要对反射遮罩纹理举行采样。这可以直接使用 tex2D() 内置函数来完成,在之前的第 2 章时我们就已经看到了使用纹理实现的此效果。
        将两种纹理范例采样一起存储到 surf() 函数中的一个变量中,我们只需将立方体贴图颜色与反射纹理颜色相乘,并将其传递到表面的 Output 结构体的 o.Emission 参数中。最后,为了全局控制整体反射强度,我们将反射遮罩的结果乘以 _ReflectionAmount 属性。通过_ReflectionAmount 属性我们就可以控制整个表面的反射总量。
        以下截图显示了使用 _ReflectionAmount 属性控制整体反射差异的渲染结果,如 图3.4 所示:

图3.4



第4节. Unity3D 中实现法线贴图和反射


        在某些情况下,我们想让法线贴图可以影响到被反射的立方体贴图。假如你想制作磨砂玻璃的表面或冰块表面效果时。我们无法把物体表面的全部细节用建模的方式表现出来,而且我们需要在游戏中要以 60 fps 的速度运行。以是使用法线贴图是最好的选择,它可以模拟更高分辨率细节的效果,因此我们需要学习怎样将法线贴图信息传递给反射效果。
        为了完成此任务,我们需要用到 Input 结构体的另一个内置参数,该参数将传入由法线映射技能天生的修改后的表面法线。那么,让我们看看怎样修改 Input 结构体来天生这种效果。
4.1、准备工作

        让我们按照以下几个步调创建一个新的场景。


图4.1



图4.2



4.2、怎样实现...

        现在,让我们开始编写 Shader 代码,来学习怎样向 Shader 中添加反射法线。

  1. Properties
  2. {
  3.     _MainTint("Diffuse Tint", color) = (1,1,1,1)
  4.     _MainTex("Main Map", 2D) = "white"{}
  5.     _NormalMap("Normal Map", 2D) = "bump"{}
  6.     _Cubemap("CubeMap", CUBE) = ""{}
  7.     _ReflAmount("Reflection Amount", Range(0.01,1)) = 0.5
  8. }
复制代码

  1. // 将属性链接到CG程序
  2. samplerCUBE _Cubemap;
  3. sampler2D _MainTex;
  4. sampler2D _NormalMap;
  5. half4 _MainTint;
  6. float _ReflAmount;
复制代码

  1. struct Input
  2. {
  3.     float2 uv_MainTex;
  4.     float2 uv_NormalMap;
  5.     float3 worldRefl;
  6.     INTERNAL_DATA
  7. };
复制代码

  1. void surf (Input IN, inout SurfaceOutput o)
  2. {
  3.     half4 c = tex2D (_MainTex, IN.uv_MainTex);
  4.     float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap)).rgb;
  5.     o.Albedo =  c.rgb * _MainTint;
  6.     o.Normal = normals;
  7.     o.Emission = texCUBE(_Cubemap, WorldReflectionVector(IN, o.Normal)).rgb * _ReflAmount;
  8.     o.Alpha = c.a;
  9. }
复制代码

  1. Shader "CookbookShaders/4-4 Normal maps and reflections in Unity3D"
  2. {
  3.     Properties
  4.     {
  5.         _MainTint("Diffuse Tint", color) = (1,1,1,1)
  6.         _MainTex("Main Map", 2D) = "white"{}
  7.         _NormalMap("Normal Map", 2D) = "bump"{}
  8.         _Cubemap("CubeMap", CUBE) = ""{}
  9.         _ReflAmount("Reflection Amount", Range(0.01,1)) = 0.5
  10.     }
  11. SubShader
  12.     {
  13.         Tags { "RenderType"="Opaque" }
  14.         LOD 200
  15.         CGPROGRAM
  16.         #pragma surface surf Lambert
  17.         // 将属性链接到CG程序
  18.         samplerCUBE _Cubemap;
  19.         sampler2D _MainTex;
  20.         sampler2D _NormalMap;
  21.         half4 _MainTint;
  22.         float _ReflAmount;
  23.         struct Input
  24.         {
  25.             float2 uv_MainTex;
  26.             float2 uv_NormalMap;
  27.             float3 worldRefl;
  28.             INTERNAL_DATA
  29.         };
  30.         
  31.         void surf (Input IN, inout SurfaceOutput o)
  32.         {
  33.             half4 c = tex2D (_MainTex, IN.uv_MainTex);
  34.             float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap)).rgb;
  35.             o.Albedo =  c.rgb * _MainTint;
  36.             o.Normal = normals;
  37.             o.Emission = texCUBE(_Cubemap, WorldReflectionVector(IN, o.Normal)).rgb * _ReflAmount;
  38.             o.Alpha = c.a;
  39.         }
  40.         ENDCG
  41.     }
  42. FallBack "Diffuse"
  43. }
复制代码
        以下截图显示了使用法线贴图影响了反射效果的渲染结果,如 图4.2 所示:

图4.2


4.3、实现原理...

        你应该留意到了,这个 Shader 看起来与上一个 Shader 非常相似,但是它们有一个非常紧张的区别。我们想要使用逐像素法线贴图的方式来影响反射的立方体贴图。为此,你必须计算出物体的表面法线之后,法线贴图才可以被应用到着色器上。以是在计算完成法线贴图之后,我们需要添加一段代码:
   float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap)).rgb;
o.Normal = normals;
          一旦计算完 Shader 中的这些代码行后,模子表面的法线就会被修改成我们想要的样子了;接下来,我们需要使用它来扰乱我们的反射。我们可以通过在 Input 结构体中声明 INTERNAL_DATA,然后使用 WorldReflectionVector(IN, o.Normal) 作为立方体贴图的查找信息来访问这个修改后的法线。这是 Unity 为我们提供的另一个内置功能,以是我们不必自己再费力的编码。从而可以专注于编写 Shader 中我们想要的效果。

4.5、更多内容

        在我们的 Input 结构体中,还可以访问更多的其他内置函数,我们在以后的章节中也肯定会使用它们;下表描述了这些内置函数中的每一个的作用以及怎样使用它们:
float3 viewDir表现视角方向,用于计算视差效果、边缘照明等。
float4 COLOR表现逐顶点插值的颜色。
float4 screenPos表现反射屏幕空间的位置。例如,在 Dark Unity 中的 WetStreet 着色器使用。
float3 worldPos表现天下空间位置。
float3 worldRefl如果 Surface Shader 不写入 o.Normal,则将包含天下反射向量。有关示例,请参见 Reflect-Diffuse 着色器。
float3 worldRef; 
INTERNAL_DATA
如果 Surface Shader 不写入 o.Normal,则将包含天下法线向量。
float3 
worldNormal; 
INTERNAL_DATA
如果 Surface Shader 写入 o.Normal,将包含天下反射向量。要获取基于每像素法线贴图的反射向量,请使用 WorldReflectionVector (IN, o.Normal)。例如,请参见 Reflect-Bumped 着色器。
        您也可以转到以下链接来获取有关这些内置函数的更多信息:
Unity - Manual: Introduction to surface shaders in the Built-In Render Pipeline
http://docs.unity3d.com/Documentation/Components/SL-SurfaceShaders.html%C2%A0

第5节. Unity3D 中实现菲涅耳反射


        菲涅耳反射是最常用的反射范例之一。这基本上会增加你看到对象表面的反射量。你险些会在任何范例的表面中看到这一点,但这种效果最常用的表面之一是汽车的车身。我们可以看到表面是反光的,但随着车身表面越来越倾向于你的视线,您会留意到反射和镜面反射变得更增强烈,并产生美丽的边缘光范例效果。 
        但是,并非全部表面都具有相同数量的菲涅耳反射。某些表面(如车身表面)具有高强度的菲涅耳反射,而像塑料片这样的表面具有更暗淡的菲涅耳反射强度。
        本小节将为你提供菲涅耳反射的底子实现方式,由于在现实天下中,菲涅耳反射是相对于观察者对对象表面视角的反射和折射的计算。但是,由于我们没有介绍任何范例的折射技能,让我们来看看大多数游戏作品都实现了什么,以及怎样对其举行修改以创建非常吸引人的视觉反射效果。

5.1、准备工作

        同样,我们仍旧需要创建一个新场景同时在场景中创建一些资产,以便我们专注于正在编写的 Shader。


5.2、怎样实现...

        现在,让我们编写 Shader 代码并让 Fresnel 效果正常工作。

  1. Properties
  2. {
  3.     _MainTint("Diffuse Tint", color) = (1,1,1,1)
  4.     _MainTex("Main Map", 2D) = "white"{}
  5.     _Cubemap("CubeMap", CUBE) = ""{}
  6.     _ReflAmount("Reflection Amount", Range(0.01,1)) = 0.5
  7.     _RimPower("Fresnel Falloff", Range(0.1, 3)) = 2
  8.     _SpecColor("Specular Color", color) = (1,1,1,1)
  9.     _SpecPower("Specular Power", Range(0, 1)) = 0.5
  10. }
复制代码

  1. CGPROGRAM
  2. #pragma surface surf BlinnPhong
  3. #pragma target 3.0
复制代码

  1. // 将属性链接到CG程序
  2. samplerCUBE _Cubemap;
  3. sampler2D _MainTex;
  4. half4 _MainTint;
  5. float _ReflAmount;
  6. float _RimPower;
  7. float _SpecPower;
复制代码

  1. struct Input
  2. {
  3.     float2 uv_MainTex;
  4.     float3 worldRefl;
  5.     float3 viewDir;
  6. };
复制代码

  1. void surf (Input IN, inout SurfaceOutput o)
  2. {
  3.     half4 c = tex2D (_MainTex, IN.uv_MainTex);
  4.     float rim = 1.0 - saturate(dot(o.Normal, normalize(IN.viewDir)));
  5.     rim = pow(rim, _RimPower);
  6.     o.Albedo =  c.rgb * _MainTint;
  7.     o.Emission = (texCUBE(_Cubemap, IN.worldRefl).rgb * _ReflAmount) * rim;
  8.     o.Specular = _SpecPower;
  9.     o.Gloss = 1.0;
  10.     o.Alpha = c.a;
  11. }
复制代码

  1. Shader "CookbookShaders/4-5 Fresnel reflections in Unity3D"
  2. {
  3.     Properties
  4.     {
  5.         _MainTint("Diffuse Tint", color) = (1,1,1,1)
  6.         _MainTex("Main Map", 2D) = "white"{}
  7.         _Cubemap("CubeMap", CUBE) = ""{}
  8.         _ReflAmount("Reflection Amount", Range(0.01,1)) = 0.5
  9.         _RimPower("Fresnel Falloff", Range(0.1, 3)) = 2
  10.         _SpecColor("Specular Color", color) = (1,1,1,1)
  11.         _SpecPower("Specular Power", Range(0, 1)) = 0.5
  12.     }
  13. SubShader
  14.     {
  15.         Tags { "RenderType"="Opaque" }
  16.         LOD 200
  17.         CGPROGRAM
  18.         #pragma surface surf BlinnPhong
  19.         #pragma target 3.0
  20.         // 将属性链接到CG程序
  21.         samplerCUBE _Cubemap;
  22.         sampler2D _MainTex;
  23.         half4 _MainTint;
  24.         float _ReflAmount;
  25.         float _RimPower;
  26.         float _SpecPower;
  27.         struct Input
  28.         {
  29.             float2 uv_MainTex;
  30.             float3 worldRefl;
  31.             float3 viewDir;
  32.         };
  33.         
  34.         void surf (Input IN, inout SurfaceOutput o)
  35.         {
  36.             half4 c = tex2D (_MainTex, IN.uv_MainTex);
  37.             float rim = 1.0 - saturate(dot(o.Normal, normalize(IN.viewDir)));
  38.             rim = pow(rim, _RimPower);
  39.             o.Albedo =  c.rgb * _MainTint;
  40.             o.Emission = (texCUBE(_Cubemap, IN.worldRefl).rgb * _ReflAmount) * rim;
  41.             o.Specular = _SpecPower;
  42.             o.Gloss = 1.0;
  43.             o.Alpha = c.a;
  44.         }
  45.         ENDCG
  46.     }
  47. FallBack "Diffuse"
  48. }
复制代码

        以下屏幕截图演示了简朴 Frensel 效果着色器的终极结果。它也可以简朴的展示为汽车的着色器的底子,如图5.2:

图5.2


5.3、实现原理...

        在此示例中,我们只是创建了一个衰减值,可当做遮罩来遮蔽表面较高和较低的反射率。通过使用视方向到表面法线的比较,我们可以计算面向摄像机的衰减值。然后,我们反转该值以得到在表面边缘更白的蒙版,当表面越面向观察者时会越黑。请参下图5.3以供参考:

图5.3

        然后,我们通过添加 Specular 值和 Diffuse 值来完成着色器,以实现终极的菲涅耳反射着色器。


第6节. 在 Unity3D 中创建简朴的动态立方体贴图体系


        到目前为止,我们已经相识到了许多有用的信息,但是当物体在环境中移动时,我们的反射并不能真正的反射出真实的天下。例如,如果你有一个由多个房间和走廊组成的环境,我们无法为整个关卡烘焙一个立方体贴图,并将其放在单个立方体贴图中。这并不能反射出房间之间合理的环境。我们会得到一个非常静态、无趣的反射。
        有几种方法可以解决这个问题,使一个房间的反射与第二个房间的反射差异。第一种也是最基本的方法是根据 Room 中的位置交换 Cubemap。因此,当您从一个房间移动到另一个房间时,立方体贴图将替换为该房间正确的立方体贴图。第二种方法是角色在环境中移动时,实时更新 Cubemap,终极在游戏举行的每一帧中都会得到一个新的 Cubemap。虽然第二个选项听起来在视觉上更吸引人,由于你会看到立方体贴图之间出现一个短暂切换的一个动作,但它相称昂贵,非常占资源,因此需要与你的游戏所需全部其他资源举行权衡。
        在本小节中我们介绍第一个选项,并向你展示怎样设置一个非常简朴的体系,以便根据环境中基于位置信息来切换两个立方体贴图。本小节的最后一部分提供了有关创建实时反射体系的更多信息,因此,如果你有兴趣并想相识这两种技能之间的区别,那么就可以开始了!

6.1、准备工作


        以下屏幕截图显示了我们准备好的场景的结果,该场景已准备好用于我们的动态反射体系:

图6.1


6.2、怎样实现...

        场景准备就绪后,我们可以按照接下来的几个步调开始编写反射体系代码。

  1. [ExecuteInEditMode]
  2. public class SwapCubemaps : MonoBehaviour
  3. {
复制代码

  1. public Cubemap cubeA;
  2. public Cubemap cubeB;
  3. public Transform posA;
  4. public Transform posB;
  5. private Material curMat;
  6. private Cubemap curCube;
复制代码

  1. void OnDrawGizmos()
  2. {
  3.     Gizmos.color = Color.green;
  4.     if(posA)
  5.     {
  6.         Gizmos.DrawWireSphere(posA.position, 0.5f);
  7.     }
  8.     if(posB)
  9.     {
  10.         Gizmos.DrawWireSphere(posB.position, 0.5f);
  11.     }
  12. }
复制代码

  1. private Cubemap CheckProbeDistance()
  2. {
  3.     float distA = Vector3.Distance(transform.position, posA.position);
  4.     float distB = Vector3.Distance(transform.position, posB.position);
  5.     if(distA < distB)
  6.     {
  7.         return cubeA;
  8.     }
  9.     else if(distB < distA)
  10.     {
  11.         return cubeB;
  12.     }
  13.     else
  14.     {
  15.         return cubeA;
  16.     }
  17. }
复制代码

  1. // Update is called once per frame
  2. void Update()
  3. {
  4.     //curMat = renderer.sharedMaterial;
  5.     curMat = GetComponent<Renderer>().sharedMaterial;
  6.    
  7.     if(curMat)
  8.     {
  9.         curCube = CheckProbeDistance();
  10.         curMat.SetTexture("_Cubemap", curCube);
  11.     }
  12.    
  13. }
复制代码

  1. using System.Collections;using System.Collections.Generic;using UnityEngine;[ExecuteInEditMode]
  2. public class SwapCubemaps : MonoBehaviour
  3. {    public Cubemap cubeA;    public Cubemap cubeB;    public Transform posA;    public Transform posB;    private Material curMat;    private Cubemap curCube;    void OnDrawGizmos()    {        Gizmos.color = Color.green;        if(posA)        {            Gizmos.DrawWireSphere(posA.position, 0.5f);        }        if(posB)        {            Gizmos.DrawWireSphere(posB.position, 0.5f);        }    }    private Cubemap CheckProbeDistance()    {        float distA = Vector3.Distance(transform.position, posA.position);        float distB = Vector3.Distance(transform.position, posB.position);        if(distA < distB)        {            return cubeA;        }        else if(distB < distA)        {            return cubeB;        }        else        {            return cubeA;        }    }        // Start is called before the first frame update    void Start()    {            }    // Update is called once per frame    void Update()    {        //curMat = renderer.sharedMaterial;        curMat = GetComponent<Renderer>().sharedMaterial;                if(curMat)        {            curCube = CheckProbeDistance();            curMat.SetTexture("_Cubemap", curCube);        }            }}
复制代码
         保存着色器后,返回到 Unity 编辑器中等待着色器编译好后。点击 Play 并来回移动球体。您应该会看到雷同于下 图6.2 的结果:

图6.2


6.3、实现原理...

        我们只需通过声明类的 [ExecuteInEditMode] 属性来启动此脚本。这会告诉 Unity,我们希望在编辑器中运行脚原来切换立方体贴图,而不光仅是在点击 Play 时运行。这将答应我们举行测试立方体贴图的切换,而无需点击 Play — 这样会将工作流程变得更快捷。
        然后,该脚本包含一些变量,我们使用这些变量答应使用者输入两个立方体贴图和两个位置信息,我们使用它们来比较距离。最后,我们有两个私有变量,当程序运行时,我们可以使用它们来跟踪当前材质球和立方体贴图。
        有了变量,我们就可以使用 OnDrawGizmos() 内置函数来现实显示我们让用户输入的转换位置的信息。这些位置将下令脚本何时切换我们的立方体贴图。
        然后我们来相识这个程序的真正内容。我们声明自己的函数/方法,该函数/方法将使用 Vector3.Distance() 计算球体与两个变换中任何一个的距离。然后,它会查抄哪个距离更小,并返回该位置的 Cubemap。
        最后,在 Update() 函数中,我们从球体获取当前的材质球,大概此脚本附加到的对象,然后简朴地分配从自界说函数返回的当前选定的立方体贴图。
这只是一个非常简朴的脚原来论述这个概念,但它可以扩展成一个完备的体系,每个房间都有多个立方体贴图。该体系可以在运行时为我们自动天生全部的立方体贴图,这对于无法负担完全实时反射体系的游戏来说非常有用。
6.4、另请参阅

        您还可以尝试创建实时反射体系,此中 Cubemap 会针对游戏中的每一帧举行更新。这绝对是一个视觉上更吸引人的体系,但确实会以性能为代价:
Unity - Scripting API: Camera.RenderToCubemap
http://docs.unity3d.com/Documentation/ScriptReference/Camera.RenderToCubemap.html



​这本书可以让你学习到怎样使用着色器和屏幕殊效让你的Unity工程拥有震撼的渲染画面。

作者:Kenny Lammers




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4