GLSL 编程/Unity/反射表面
本教程介绍了反射映射(以及用于实现它的立方体贴图)。
这是关于在 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 中生成立方体贴图,以及如何将它们用于反射映射。
如果您仍然想了解更多
- 关于向量的反射,您应该阅读 “镜面高光”部分。
- 关于 Unity 中的立方体贴图,您应该阅读 Unity 关于立方体贴图的文档。