跳转到内容

GLSL 编程/Unity/反射表面

来自维基教科书,开放世界中的开放书籍
文件:Cloud Gate Chicago.jpg
反射表面的例子:芝加哥的“云门”雕塑。

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

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

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

使用天空盒的反射映射

[编辑 | 编辑源代码]

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

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

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

立方体贴图

[编辑 | 编辑源代码]

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

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

相应的 uniform 变量在 GLSL 着色器中这样定义

            uniform samplerCube _Cube;

要创建立方体贴图,请在项目视图中选择创建 > 立方体贴图。然后,您必须在检查器视图中为立方体的面指定六个纹理图像。此类纹理的示例可以在标准资源 > 天空盒 > 纹理中找到。此外,您应该为立方体贴图在检查器视图中选中MipMaps;这应该为反射映射产生明显更好的视觉效果。

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

            vec3 reflectedDirection = 
               reflect(viewDirection, normalize(normalDirection));

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

            gl_FragColor = textureCube(_Cube, reflectedDirection);

就是这样。

完整的着色器代码

[编辑 | 编辑源代码]

然后着色器代码变为

Shader "GLSL shader with reflection map" {
   Properties {
      _Cube("Reflection Map", Cube) = "" {}
   }
   SubShader {
      Pass {   
         GLSLPROGRAM

         // User-specified uniforms
         uniform samplerCube _Cube;   

         // The following built-in uniforms
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform vec3 _WorldSpaceCameraPos; 
            // camera position in world space
         uniform mat4 _Object2World; // model matrix
         uniform mat4 _World2Object; // inverse model matrix

         // Varyings         
         varying vec3 normalDirection;
         varying vec3 viewDirection;
         
         #ifdef VERTEX
         
         void main()
         {            
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
               // is unnecessary because we normalize vectors
            
            normalDirection = normalize(vec3(
               vec4(gl_Normal, 0.0) * modelMatrixInverse));
            viewDirection = vec3(modelMatrix * gl_Vertex 
               - vec4(_WorldSpaceCameraPos, 1.0));
            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif


         #ifdef FRAGMENT
         
         void main()
         {
            vec3 reflectedDirection = 
               reflect(viewDirection, normalize(normalDirection));
            gl_FragColor = textureCube(_Cube, reflectedDirection);
         }

         #endif

         ENDGLSL
      }
   }
}


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

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

进一步阅读

[编辑 | 编辑源代码]

如果您仍然想了解更多


< GLSL 编程/Unity

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