本教程介绍了反射贴图(以及实现它的立方体贴图)。
这是Unity中使用立方体贴图的环境映射的一系列小教程中的第一篇。本章基于章节“平滑镜面高光”中介绍的逐像素光照以及章节“纹理球体”中介绍的纹理映射概念。
一个天空反射贴图
一个大型反射表面的例子
一个天空盒是一个(无限)大的盒子包围着整个场景。这里一条反射的摄像机射线跟天空盒其中的一个纹理表面相交。
上面插画描述了一个静态天空盒反射贴图的概念:一条观察射线会在一个物体表面上的一个点被反射,并且被反射的射线跟天空盒相交以决定相应像素的颜色。天空盒只是一个有纹理表面的大型立方体环绕着整个场景。应该注意到天空盒通常是静态的并且不包含场景的任何动态物体。但是,反射贴图的“天空盒”通常从某种角度说会渲染包括场景。但是,这已经超出了本教程的范围。
而且,本教程只涵盖了反射的计算,它并不包含章节“天空盒”中讨论的天空盒的渲染。对于一个物体中天空盒的反射,我们必须渲染这个物体并且把摄像机的射线反射到表面法向量所在的表面点上。这个反射的数学跟表面法向量处光线的反射是一样的,它在章节“镜面高光”中有讨论。
一旦我们得到了反射射线,它跟大型天空盒的交点就必须被计算。如果天空盒无限大的话这个计算实际就变得很简单了:在这种情况下表面点的位置就无关紧要了,因为它到坐标系原点的距离相对于天空盒的尺寸来说是无限小的;于是只有反射射线的方向是有关的而不是它的位置。因此,我们可以实际考虑一条从一个小型天空盒而不是从其它地方开始的射线。(如果你不熟悉这个想法,你最好花点时间了解一下。)取决于反射光线的方向,它会跟纹理天空盒的六个面中一个相交。我们会计算哪个面相交,在哪里相交以及对指定面的纹理贴图进行纹理查找(参考章节“纹理球体”)。于是,我们要做的就是为环境提供一个立方体纹理,把它作为一个着色器属性,以及使用有反射方向的texCUBE
指令来获取立方体纹理相应位置的颜色。
立方体贴图
在Unity着色器中,一个被叫做_Cube
的立方体贴图着色器属性可以这样定义:
Properties
{
_Cube ("Reflection Map", Cube) = "" {}
}
在Cg着色器中,相应的uniform变量可以这样定义:
uniform samplerCUBE _Cube;
为了创建一个立方体贴图,在Project Window中选择Create > Legacy > Cubemap。然后你必须在in the Inspector Window中为立方体的表面指定六张纹理贴图。你可以在Unity的资源商店里找到这种纹理的免费示例(选择Windows > Asset Store然后搜索“skyboxes”)。此外,你应该在Inspector Window中为立方体贴图选中MipMaps;这个可以为反射映射带来更好的视觉效果。或者,你可以在Inspector Window中导入立方体贴图(它在一个图像文件中包含了六个面的纹理),设置Texture Type为Cubemap。
顶点着色器必须计算观察方向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 = _Object2World;
float4x4 modelMatrixInverse = _World2Object;
output.viewDir = mul(modelMatrix, input.vertex).xyz
- _WorldSpaceCameraPos;
output.normalDir = normalize(
mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
float3 reflectedDir = reflect(input.viewDir, normalize(input.normalDir));
return texCUBE(_Cube, reflectedDir);
}
ENDCG
}
}
}
总结
恭喜!你完成了环境映射的第一篇教程。我们学到了:
- 如何计算物体内天空盒的反射。
- 如果在Unity生成方位体贴图以及如何把他们用到反射映射上。
扩展阅读
如果你还想了解得更多:
- 关于向量的反射,你可以阅读章节“镜面高光”。
- 关于Unity中的立方体贴图,你可以阅读文档。
- 关于在Unity中渲染到一个立方体贴图,你可以阅读Unity关于
Camera.RenderToCubemap
的文档。