第四章:反射-Reflecting Your World《Unity Shaders and Effets Cookbook ...

打印 上一主题 下一主题

主题 1027|帖子 1027|积分 3081



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张纹理。这将答应我们去捕获附近环境并将其烘焙到纹理中。
        我们将会相识到怎样从我们自己的环境天生我们自己的立方体贴图,也会去研究使用这些天生的立方体贴图在着色器中来创建差异的反射效果。这非常恰当模拟金属、汽车上的漆甚至还有塑料。因此,在本章中,我们将学习以下内容:


  • 在 Unity3D 中创建立方体贴图
  • Unity3D 中简朴的立方体贴图反射
  • Unity3D 中的遮罩反射
  • Unity3D 中的法线贴图和反射
  • Unity3D 中的菲涅耳反射
  • 在 Unity3D 中创建简朴的动态立方体贴图体系

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


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

1.1、准备工作

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


  • 我们需要为场景创建一些元素,这些元素将充当反射立方体贴图中的灯光。因此,我们需要在场景中创建一些多少平面。你可以在建模软件(如 Maya 或 Max)中制作这些模子,也可以使用默认的 Unity 平面。无论哪种方式,这都可有可无。你的场景只要雷同于 图1.1 中展示的即可:

 图1.1



  • 现在,我们需要制作一些纹理,用来模拟差异范例光源的效果以及模拟环境中光线的衰减和强度。请参考以下 图1.2 中显示的效果:

图1.2



  • 接下来,我们需要使用 Unity 提供给我们的内置着色器,以便我们可以将平面多少体和纹理用作立方体贴图中的光源。建议我们使用 unlit/transparent 着色器,用它可以来模拟灯光的纹理的完备强度。完成后,您的场景应雷同于如下 图1.3 所示:

图1.3


1.2、怎样实现...

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


  • 步调1. 我们首先需要创建一个新脚本,但由于这将是一个弹出的编辑器窗口,因此我们必须将此脚本放在一个名为 Editor 的文件夹中。现在,在 Project 面板中创建该文件夹,然后在该文件夹中创建一个名为 GenerateStaticCubemap 的 C# 脚本,如下 图1.2 所示。创建后,双击新脚本启动它。

图1.2



  • 步调2. 打开脚本后,我们开始编辑脚原来实现我们需要的功能。首先,我们需要创建一个新的 using 指令,以便使用 UnityEditor 命名空间。
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections;
    复制代码
  • 步调3. 为了将此脚本视为弹出式编辑器窗口,我们需要使 GenerateStaticCubemap 脚本继续自 ScriptableWizard 类。这个类会为我们提供一些不错的低级函数,我们可以在脚本中使用它们。
    1. public class GenerateStaticCubemap : ScriptableWizard
    2. {
    复制代码


  • 步调4. 然后,我们需要添加一些公开的变量,以便我们可以存储新的 Cubemap 和 Cubemap 的位置。将以下代码添加到类的开始位置:
    1. public Transform renderPosition;
    2. public Cubemap cubemap;
    复制代码
  • 步调5. 此脚本中的第一个函数是名为 OnWizardUpdate()的一个内置函数。首次打开 wizard 或更改 GUI 时,就会调用此函数。因此,这是一个很好的查抄方式,并且还是一个用户向 wizard 提供一些资源的好地方。如果我们在变量中没有 Cubemap 或 transform,我们需要将 isValid 布尔值设置为 false,并且不答应 future 函数。
    1. void OnWizardUpdate ()
    2. {
    3.     helpString = "Select transform to render" + "from and cubemap to render into";
    4.     if(renderPosition != null && cubemap != null)
    5.     {
    6.         isValid = true;
    7.     }
    8.     else
    9.     {
    10.         isValid = false;
    11.     }
    12. }
    复制代码
  • 步调6. 如果 isValid 布尔值便是 true,则 wizard 将调用 OnWizardCreate() 函数。这会为我们创建一台新的摄像机;我们提供的 transform 用来作为它的位置信息,并使用 RenderToCubemap() 函数来返回 Cubemap。
    1. void OnWizardCreate ()
    2. {
    3.     // 创建一个临时摄像机
    4.     GameObject go = new GameObject("CubeCam", typeof(Camra));
    5.     // 把它放在我们渲染的位置上
    6.     go.transform.position = renderPosition.position;
    7.     go.transform.rotation = Quaternion.identity;
    8.     // 渲染立方体贴图
    9.     go.camera.RenderToCubemap(cubemap);
    10.     // 销毁临时摄像机
    11.     DestroyImmediate(go);
    12. }
    复制代码
  • 步调7. 最后,我们需要调用 wizard 以从 Unity 编辑器菜单中的菜单选项激活它。在 GenerateStaticCubemap 类中输入以下代码:
    1. [MenuItem("CookBook/Render Cubemap")]
    2. static void RenderCubemap()
    3. {
    4.     ScriptableWizard.DisplayWizard("Render CubeMap", typeof(GenerateStaticCubemap), "Render!");
    5. }
    复制代码

  • 完备代码:
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections;//using System.Collections.Generic;public class GenerateStaticCubemap : ScriptableWizard
    4. {    public Transform renderPosition;    public Cubemap cubemap;    void OnWizardUpdate ()    {        helpString = "Select transform to render" + "from and cubemap to render into";        if(renderPosition != null && cubemap != null)        {            isValid = true;        }        else        {            isValid = false;        }    }    void OnWizardCreate ()    {        // 创建一个临时摄像机        GameObject go = new GameObject("CubeCam", typeof(Camera));        // 把它放在我们渲染的位置上        go.transform.position = renderPosition.position;        go.transform.rotation = Quaternion.identity;        // 渲染立方体贴图        // go.camera.RenderToCubemap(cubemap); // API已被更换        go.GetComponent<Camera>().RenderToCubemap(cubemap);                // 烧毁临时摄像机        DestroyImmediate(go);    }    [MenuItem("CookBook/Render Cubemap")]    static void RenderCubemap()    {        ScriptableWizard.DisplayWizard("Render CubeMap", typeof(GenerateStaticCubemap), "Render!");    } }/*using UnityEngine;
    5. using UnityEditor;
    6. using System.Collections;//using System.Collections.Generic;public class GenerateStaticCubemap : ScriptableWizard
    7. {    public Transform renderPosition;    public Cubemap cubemap;    void OnWizardUpdate ()    {        helpString = "Select transform to render" + "from and cubemap to render into";        if(renderPosition != null && cubemap != null)        {            isValid = true;        }        else        {            isValid = false;        }    }    void OnWizardCreate ()    {        // 创建一个临时摄像机        GameObject go = new GameObject("CubeCam", typeof(Camera));        // 把它放在我们渲染的位置上        go.transform.position = renderPosition.position;        go.transform.rotation = Quaternion.identity;        // 渲染立方体贴图        go.camera.RenderToCubemap(cubemap);        // 烧毁临时摄像机        DestroyImmediate(go);    }    [MenuItem("CookBook/Render Cubemap")]    static void RenderCubemap()    {        ScriptableWizard.DisplayWizard("Render CubeMap", typeof(GenerateStaticCubemap), "Render!");    } }*/
    复制代码

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:


  •  ATI CubeMapGen: Developer Centralhttps://www.amd.com/en/developer.htmlhttps://www.amd.com/en/developer.htmlhttps://www.amd.com/en/developer.htmlhttps://www.amd.com/en/developer.htmlhttps://www.amd.com/en/developer.htmlhttps://www.amd.com/en/developer.htmlhttps://www.amd.com/en/developer.html
    https://www.amd.com/en/developer.html


  •  HDR Light Studio Pro: HDR Light Studio - Make and edit image-based lighting in your 3D softwarehttps://www.lightmap.co.uk/https://www.lightmap.co.uk/https://www.lightmap.co.uk/https://www.lightmap.co.uk/https://www.lightmap.co.uk/https://www.lightmap.co.uk/https://www.lightmap.co.uk/
    https://www.lightmap.co.uk/
  • 附加参考链接:HDR Light Studio(3D渲染软件 )https://blog.csdn.net/m0_56734636/article/details/144347511?spm=1001.2014.3001.5502https://blog.csdn.net/m0_56734636/article/details/144347511?spm=1001.2014.3001.5502https://blog.csdn.net/m0_56734636/article/details/144347511?spm=1001.2014.3001.5502https://blog.csdn.net/m0_56734636/article/details/144347511?spm=1001.2014.3001.5502https://blog.csdn.net/m0_56734636/article/details/144347511?spm=1001.2014.3001.5502https://blog.csdn.net/m0_56734636/article/details/144347511?spm=1001.2014.3001.5502https://blog.csdn.net/m0_56734636/article/details/144347511?spm=1001.2014.3001.5502
    https://blog.csdn.net/m0_56734636/article/details/144347511?spm=1001.2014.3001.5502


第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、准备工作

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


  • 1. 我们需要为着色器准备好一个立方体贴图,您可以新天生一个,也可以只使用上一小节中的立方体贴图。本小节使用的 Cubemap 在本书的代码示例中,如 图3.1 所示:

图3.1



  • 2. 我们还需要一张纹理,用于表现物体的表面哪些区域有反射效果,哪些区域没有反射效果。请记住,下边纹理中的黑色区域表现完全没有射率(0%反射率),白色表现完全反射率(100%反射率),而介于两者之间的全部灰度值是提供了肯定量的反射率(0%<反射率<100%)。下 图3.2 展示我们在本小节中使用的纹理:

图3.2



  • 3. 最后,创建一个新场景,里面放置一个物体对象、地面宁静行光,方便我们去观察着色器的反射效果!

3.2、怎样实现...

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


  • 步调1. 将以下属性添加到着色器中。这会让我们把自己的自界说立方体贴图和反射遮罩赋予给我们的着色器:
  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. }
复制代码


  • 步调2. 然后,我们需要在 SubShader 块中声明这些属性。这将答应我们从 Properties 块中访问到这些属性的数据。
  1. // 将属性链接到CG程序
  2. sampler2D _MainTex;
  3. sampler2D _ReflMask;
  4. samplerCUBE _Cubemap;
  5. half4 _MainTint;
  6. float _ReflAmount;
复制代码


  • 步调3. 为了让我们正确地模拟Cubemap的反射,我们需要在 Input 结构体中声明 worldRefl 属性。我们可以使用此数据查找 texCUBE() 函数中的参数。
  1. struct Input
  2. {
  3.    float2 uv_MainTex;
  4.    float3 worldRefl;
  5. };
复制代码


  • 步调4. 最后,我们在 surf() 函数中增加以下代码。将在下一个知识点中来表明它们的含义:
  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、准备工作

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


  • 1. 同样,我们需要一个立方体贴图来制作反射效果。因此,你可以去使用之前小节中的 Cubemap,也可以去新天生一个 Cubemap。在本小节中,我们使用的 Cubemap 在本书的示例代码中可以找到,如下 图4.1 所示:

图4.1



  • 2. 我们还需要一张法线贴图来天生法线贴图的反射。如下 图4.2 所示:

图4.2



  • 3. 最后,创建一个新的场景,里面包罗物体对象、地面宁静行光,并创建一个新的 Shader 和材质球。这会方便我们来查看 Shader的效果以及验证它是否有报错。

4.2、怎样实现...

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


  • 步调1. 让我们添加所需的属性,以便我们能够在 Shader 中计算自界说的立方体贴图和法线贴图。这个步调我们现在已经非常熟悉了。将以下代码添加到新建 Shader 中的 Properties 块中:
  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. }
复制代码


  • 步调2. 然后,我们需要在 SubShader 块中声明这些属性,这样我们就可以访问 Properties 块中的属性数据。
  1. // 将属性链接到CG程序
  2. samplerCUBE _Cubemap;
  3. sampler2D _MainTex;
  4. sampler2D _NormalMap;
  5. half4 _MainTint;
  6. float _ReflAmount;
复制代码


  • 步调3. 接下来,在 Input 结构体中添加以下代码。这就是法线贴图反射的真正魔力所在。通过使用 INTERNAL_DATA 语句,修改之后的法线贴图就可以传输到表面法线:
  1. struct Input
  2. {
  3.     float2 uv_MainTex;
  4.     float2 uv_NormalMap;
  5.     float3 worldRefl;
  6.     INTERNAL_DATA
  7. };
复制代码


  • 步调4. 最后,我们在 surf() 函数中添加以下代码,来得到法线贴图反射:
  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。


  • 1. 我们需要一个立方体贴图来产生我们的菲涅耳效果。因此,我们可以天生一个新的,也可以使用之前的立方体贴图。以下屏幕截图显示的是我们本小节中使用的立方体贴图,它包含在了本书的支持页面 www.packtpub.com/support 网址中。

    图5.1

  • 2. 创建一个新的场景、一个物体对象(球体大概立方体皆可)、地面、着色器和新的材质球。
  • 3. 最后,创建一个平行光,这样可以提供场景中需要的一些光源信息。

5.2、怎样实现...

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


  • 步调1. 首先,我们需要在 Properties 块中设置我们的属性。这次我们将使用内置的 BlinnPhong 光照模子,因此我们需要声明一些属性才气使用光照模子的 Specular 组件。
  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. }
复制代码


  • 步调2. 对于这个 Shader,我们需要使用 Shader 3.0模式,以便我们有充足的寄存器将全部数据导入到 surf() 函数中。因此,我们需要将 #pragma 语句添加到 SubShader 块中的界说中。
  1. CGPROGRAM
  2. #pragma surface surf BlinnPhong
  3. #pragma target 3.0
复制代码


  • 步调3. 然后,我们需要确保在新属性和 SubShader 块之间创建连接,因此我们需要按如下方式声明变量:
  1. // 将属性链接到CG程序
  2. samplerCUBE _Cubemap;
  3. sampler2D _MainTex;
  4. half4 _MainTint;
  5. float _ReflAmount;
  6. float _RimPower;
  7. float _SpecPower;
复制代码


  • 步调4. 要使反射正常工作,我们需要在 Input 结构体中声明 worldRefl 参数以及 viewDir 参数
  1. struct Input
  2. {
  3.     float2 uv_MainTex;
  4.     float3 worldRefl;
  5.     float3 viewDir;
  6. };
复制代码


  • 步调5. 然后,我们需要在 surf() 函数中计算边缘效果,用它往复创建简朴的菲涅耳反射效果。
  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、准备工作



  • 1. 我们需要创建一个新场景,并在天下中放置一个地平面和一个球体。别的,添加一个定向光源,为我们的着色器获取一些光照。
  • 2. 继续向场景添加两个空的 GameObject 构造函数,并分别将它们命名为 pos001 和 pos002。
  • 3. 然后,让我们为球体赋予一个新的材质球,并将我们刚刚在上一小节中创建的菲涅尔着色器赋予到我们新材质球上。你的场景现在应雷同于如 图6.1 。
  • 4. 最后,让我们创建一个脚本并将其命名为 SwapCubemaps.cs。
        以下屏幕截图显示了我们准备好的场景的结果,该场景已准备好用于我们的动态反射体系:

图6.1


6.2、怎样实现...

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


  • 步调1. 让我们在声明类之前添加 [ExecuteInEditMode]。
  1. [ExecuteInEditMode]
  2. public class SwapCubemaps : MonoBehaviour
  3. {
复制代码


  • 步调2. 然后,我们需要声明一些变量来存储我们体系中的全部数据。我们将在本小节中的下一部分表明这些。
  1. public Cubemap cubeA;
  2. public Cubemap cubeB;
  3. public Transform posA;
  4. public Transform posB;
  5. private Material curMat;
  6. private Cubemap curCube;
复制代码


  • 步调3. 为了直观地看到立方体贴图位置在空间中的位置,我们需要利用 Unity3D 为我们提供的超棒的 Gizmos 功能。因此,让我们将以下代码添加到脚本的底部:
  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. }
复制代码


  •  步调4. 现在,我们需要创建一个新函数,该函数将根据我们设置的每个位置之间的距离来确定我们应该使用哪个立方体贴图:
  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. }
复制代码


  •  步调5. 最后,我们只需要查抄每一帧,看看环境中每个位置之间的距离是多少,并在我们的材质中合适的时候举行交换立方体贴图:
  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企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

傲渊山岳

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表