《Unity Shader 入门精要》高级纹理
立方体纹理
图形学中,立方体纹理(Cubemap)是环境映射(Environment Mapping)的一种实现方法。环境映射可以模拟物体周围的环境,而使用了环境映射的物体可以看起来像镀了层金属一样反射出周围的环境。
对立方体纹理采样我们需要提供一个三维的纹理坐标,这个三维纹理坐标表示了我们在世界空间下的一个3D方向。这个方向矢量从立方体的中心出发,当它向外部延伸时就会和立方体的6个纹理之一发生相交,而采样得到的结果就是由该交点计算而来的
- 优点
简单快速,效果较好
- 缺点
场景发生变化(有新物体、光源,物体位移)时,需要重新生成立方体纹理。
立方体纹理在实时渲染中常用于天空盒以及环境映射。
天空盒子
天空盒子(Skybox)是游戏中用于模拟背景的一种方法,当我们在场景中使用了天空盒子时,整个场景就被包围在一个立方体内。这个立方体的每个面使用的技术就是立方体纹理映射技术。
为了让天空盒子正常渲染,我们需要把这6张纹理的Wrap Mode设置为Clamp,以防止在接缝处出现不匹配的现象
Tint Color,用于控制该材质的整体颜色;
Exposure,用于调整天空盒子的亮度;
Rotation,用于调整天空盒子沿+y轴方向的旋转角度。
有一点需要注意:
Windows-Rendering-Lighting Settings里设置的Skybox会应用于当前场景的所有摄像机。若有相机需要使用不同的天空盒,可给相机添加Skybox组件,覆盖之前的设置
创建用于环境映射的立方体纹理
除了天空盒子,立方体纹理最常见的用处是用于环境映射。通过这种方法,我们可以模拟出金属质感的材质。
Unity中,创建用于环境映射的立方体纹理方法有3种:
- 使用Texture Type设置为
Cubemap
的纹理; - 创建Cubemap材质,设置6张纹理;
- 由脚本生成。
我们主要关注第三种方法,主要通过Unity提供的Camera.RenderToCubemap
函数实现,思路是基于Camera所在的位置,将周围的环境渲染到一个Cubemap里。
using UnityEngine;
using UnityEditor;
using System.Collections;public class RenderCubemapWizard : ScriptableWizard {public Transform renderFromPosition;public Cubemap cubemap;void OnWizardUpdate () {helpString = "Select transform to render from and cubemap to render into";isValid = (renderFromPosition != null) && (cubemap != null);}void OnWizardCreate () {// create temporary camera for renderingGameObject go = new GameObject( "CubemapCamera");go.AddComponent<Camera>();// place it on the objectgo.transform.position = renderFromPosition.position;// render into cubemap go.GetComponent<Camera>().RenderToCubemap(cubemap);// destroy temporary cameraDestroyImmediate( go );}[MenuItem("GameObject/Render into Cubemap")]static void RenderCubemap () {ScriptableWizard.DisplayWizard<RenderCubemapWizard>("Render cubemap", "Render!");}
}
在上面的代码中,我们在renderFromPosition(由用户指定)位置处动态创建一个摄像机,并调用Camera.RenderToCubemap函数把从当前位置观察到的图像渲染到用户指定的立方体纹理cubemap中,完成后再销毁临时摄像机。由于该代码需要添加菜单栏条目,因此我们需要把它放在Editor文件夹下才能正确执行。
打开脚本实现的窗口,将一个物体(用于获取位置)拖进去,再拖进去想要绘制的Cubemap(Create -> Legacy -> Cubemap 创建,需要勾选 Readable 选项),之后点击 Render!
反射
使用了反射效果的物体通常看起来就像镀了层金属。想要模拟反射效果很简单,我们只需要通过入射光线的方向和表面法线方向来计算反射方向,再利用反射方向对立方体纹理采样即可。
Properties {_Color ("Color Tint", Color) = (1, 1, 1, 1)_ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1)_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {}}
_ReflectColor用于控制反射颜色,_ReflectAmount用于控制这个材质的反射程度,而_Cubemap就是用于模拟反射的环境映射纹理。
我们在顶点着色器中计算了该顶点处的反射方向,这是通过使用CG的reflect函数来实现的:
v2f vert(a2v v) {v2f o;o.pos = mul(UNITY_MATRIX_MVP, v.vertex);o.worldNormal = UnityObjectToWorldNormal(v.normal);o.worldPos = mul(_Object2World, v.vertex).xyz;o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);// Compute the reflect dir in world spaceo.worldRefl = reflect(-o.worldViewDir, o.worldNormal);TRANSFER_SHADOW(o);return o;}
物体反射到摄像机中的光线方向,可以由光路可逆的原则来反向求得。也就是说,我们可以计算视角方向关于顶点法线的反射方向来求得入射光线的方向。
在片元着色器中,利用反射方向来对立方体纹理采样:
fixed4 frag(v2f i) : SV_Target {fixed3 worldNormal = normalize(i.worldNormal);fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));fixed3 worldViewDir = normalize(i.worldViewDir);fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal,worldLightDir));// Use the reflect dir in world space to access the cubemapfixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectColor.rgb;UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);// Mix the diffuse color with the reflected colorfixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten;return fixed4(color, 1.0);}