跳转到内容

Cg 编程/Unity/反射表面

来自维基教科书,开放的书籍,开放的世界
肥皂泡中的反射。

本教程介绍了反射贴图(以及立方体贴图以实现它)。

这是关于在 Unity 中使用立方体贴图进行环境贴图的一系列小教程中的第一个。本教程基于 “平滑镜面高光” 部分中描述的逐像素光照,以及在 “纹理球体” 部分中介绍的纹理贴图概念。

天空盒是一个围绕整个场景的(无限)大的盒子。这里一个反射的相机射线(即视点射线)击中了天空盒的纹理面之一。

使用天空盒的反射贴图

[编辑 | 编辑源代码]

左侧的图示描述了使用静态天空盒的反射贴图的概念:一个视点射线在一个物体表面的一个点反射,反射射线与天空盒相交以确定相应像素的颜色。天空盒只是一个大的立方体,它具有纹理的面,围绕着整个场景。需要注意的是,天空盒通常是静态的,不包含场景中的任何动态物体。然而,用于反射贴图的“天空盒”通常被渲染为包含来自特定视点的场景。然而,这超出了本教程的范围。

此外,本教程仅涵盖反射的计算,不涵盖天空盒的渲染,这是在 “天空盒” 部分中讨论的。对于物体中天空盒的反射,我们必须渲染物体并将来自相机的射线反射到表面法线矢量的表面点。这种反射的数学原理与光线在表面法线矢量上的反射相同,这是在 “镜面高光” 部分中讨论的。

一旦我们有了反射射线,就必须计算它与一个大的天空盒的交点。如果天空盒无限大,这种计算实际上变得更容易:在这种情况下,表面点的坐标位置根本不重要,因为它的距离从坐标系原点的距离与天空盒的大小相比无限小;因此,只有反射射线的方向很重要,而不是它的位置。因此,我们实际上也可以考虑一条从一个小天空盒的中心开始的射线,而不是一条从无限大的天空盒中的某个地方开始的射线。(如果你不熟悉这个想法,你可能需要一些时间来接受它。)根据反射射线的方向,它将与纹理天空盒的六个面之一相交。我们可以计算哪个面被相交,以及哪个面被相交,然后在特定面的纹理图像中执行纹理查找(参见 “纹理球体”)。但是,Cg 提供了立方体贴图,它使用方向矢量完全支持这种在立方体的六个面上的纹理查找。因此,我们所需要做的就是为环境提供一个立方体贴图作为着色器属性,并使用texCUBE指令和反射方向来获得立方体贴图中相应位置的颜色。

立方体贴图

[编辑 | 编辑源代码]

一个名为_Cube的立方体贴图着色器属性可以在 Unity 着色器中以这种方式定义

   Properties {
      _Cube ("Reflection Map", Cube) = "" {}
   }

相应的统一变量在 Cg 着色器中以这种方式定义

            uniform samplerCUBE _Cube;

要创建立方体贴图,请在项目窗口中选择创建 > Legacy > 立方体贴图。然后,您必须在检查器窗口中为立方体的面指定六个纹理图像。此类纹理的免费示例可以在 Unity 的 Asset Store 中找到(选择Windows > Asset Store并搜索“天空盒”)。此外,您应该为立方体贴图在检查器窗口中选中MipMaps;这应该为反射贴图产生明显更好的视觉效果。或者,您可以通过在检查器窗口中将纹理类型设置为立方体贴图来导入立方体贴图纹理(其中包括六个面的图像在一个图像文件中)。

顶点着色器必须计算视点方向viewDir和法线方向normalDir,如 “镜面高光” 部分所述。为了在片段着色器中反射视点方向,我们可以使用 Cg 函数reflect,如 “镜面高光” 部分所述

            float3 reflectedDir = 
               reflect(input.viewDir, normalize(input.normalDir));

为了在立方体贴图中执行纹理查找并将结果颜色存储在片段颜色中,我们只需使用

            return texCUBE(_Cube, reflectedDir);

就是这样。

完整着色器代码

[编辑 | 编辑源代码]

然后着色器代码变为

Shader "Cg shader with reflection map" {
   Properties {
      _Cube("Reflection Map", Cube) = "" {}
   }
   SubShader {
      Pass {   
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag

         #include "UnityCG.cginc"

         // User-specified uniforms
         uniform samplerCUBE _Cube;

         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float3 normalDir : TEXCOORD0;
            float3 viewDir : TEXCOORD1;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = unity_ObjectToWorld;
            float4x4 modelMatrixInverse = unity_WorldToObject; 
 
            output.viewDir = mul(modelMatrix, input.vertex).xyz 
               - _WorldSpaceCameraPos;
            output.normalDir = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            float3 reflectedDir = 
               reflect(input.viewDir, normalize(input.normalDir));
            return texCUBE(_Cube, reflectedDir);
         }
 
         ENDCG
      }
   }
}

恭喜!您已经完成了关于环境贴图的第一个教程。我们已经看到了

  • 如何在物体中计算天空盒的反射。
  • 如何在 Unity 中生成立方体贴图,以及如何将它们用于反射贴图。

进一步阅读

[编辑 | 编辑源代码]

如果您仍然想知道更多

< Cg 编程/Unity

除非另有说明,否则本页上的所有示例源代码均授予公共领域。
华夏公益教科书