立方体纹理简介
- 立方体纹理的形式:包含了6张图像,这些图像对应了一个立方体的6个面,立方体纹理的名称也由此而来。立方体的每个面表示沿着世界空间下的轴向(上、下、左、右、前、后)观察所得的图像。
- 对立方体纹理采样:需要提供一个三维的纹理坐标,这个三维纹理坐标表示了我们在世界空间下的一个3D方向。这个方向矢量从立方体的中心出发,当它向外部延伸时就会和立方体的6个纹理之一发生相交,而采样得到的结果就是由该交点计算而来的。
- 优缺点:
- 好处:它的实现简单快速,而且得到的效果也比较好。
- 缺点:(1)当场景中引入了新的物体、光源,或者物体发生移动时,需要重新生成立方体纹理。(2)立方体纹理也仅可以反射环境,但不能反射使用了该立方体纹理的物体本身。由于这样的原因,我们应该尽量对凸面体使用立方体纹理(因为凹面体会反射自身)。
立方体纹理应用
1.天空盒子
是什么?
天空盒子(Skybox)是游戏中用于模拟背景的一种方法。当我们在场景中使用了天空盒子时,整个场景就被包围在一个立方体内。在Unity中,天空盒子是在所有不透明物体之后渲染的,而其背后使用的网格是一个立方体或一个细分后的球体。
怎么用?
- 制作材质:
- 在Unity中,只需要创建一个Skybox材质,再把它赋给该场景的相关设置即可。
- 在SkyboxMat的Unity Shader下拉菜单中选择Unity自带的Skybox/6 Sided,该材质需要6张纹理。
- 注意这6张纹理的正确位置(如posz纹理对应了Front [+Z] 属性)。
- 我们还需要把这6张纹理的Wrap Mode设置为Clamp,以防止在接缝处出现不匹配的现象。
- 上面的材质中,除了6张纹理属性外还有3个属性:Tint Color,用于控制该材质的整体颜色;Exposure,用于调整天空盒子的亮度;Rotation,用于调整天空盒子沿+y轴方向的旋转角度。
- Window → Lighting菜单中,把SkyboxMat赋给Skybox选项。
- 设置摄像机:
- 为了让摄像机正常显示天空盒子,我们还需要保证渲染场景的摄像机的Camera组件中的ClearFlags被设置为Skybox。
- 在Window → Lighting → Skybox中设置的天空盒子会应用于该场景中的所有摄像机。如果我们希望某些摄像机可以使用不同的天空盒子,可以通过向该摄像机添加Skybox组件来覆盖掉之前的设置。
2.环境映射
如何创建用于环境映射的立方体纹理?
- 直接由一些特殊布局的纹理创建:
- 我们需要提供一张具有特殊布局的纹理,例如类似立方体展开图的交叉布局、全景布局等。然后,我们只需要把该纹理的Texture Type设置为Cubemap即可,Unity会为我们做好剩下的事情。
- 在Unity 5中,官方推荐这种方法创建立方体纹理,因为这种方法可以对纹理数据进行压缩,而且可以支持边缘修正、光滑反射(glossy reflection)和HDR等功能。
- 手动创建一个Cubemap资源,再把6张图赋给它。 在项目资源中创建一个Cubemap,然后把6张纹理拖曳到它的面板中。
- 由脚本生成(理想情况下,我们希望根据物体在场景中位置的不同,生成它们各自不同的立方体纹理)。
- Camera.RenderToCubemap函数可以把从任意位置观察到的场景图像存储到6张图像中,从而创建出该位置上对应的立方体纹理。
- 新建一个用于存储的立方体纹理(在Project视图下单击右键,选择Create → Legacy→Cubemap来创建)。为了让脚本可以顺利将图像渲染到该立方体纹理中,我们需要在它的面板中勾选Readable选项。
创建立方体纹理的脚本:
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 rendering
GameObject go = new GameObject( "CubemapCamera");
go.AddComponent<Camera>();
// place it on the object
go.transform.position = renderFromPosition.position;
// render into cubemap
go.GetComponent<Camera>().RenderToCubemap(cubemap);
// destroy temporary camera
DestroyImmediate( go );
}
[MenuItem("GameObject/Render into Cubemap")]
static void RenderCubemap () {
ScriptableWizard.DisplayWizard<RenderCubemapWizard>(
"Render cubemap", "Render!");
}
}
(1)反射
Shader关键代码:
Shader "Unity Shaders Book/Chapter 10/Reflection" {
Properties {
......
//用于控制反射颜色
_ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1)
//用于控制这个材质的反射程度
_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1
//用于模拟反射的环境映射纹理
_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {}
}
SubShader {
......
Pass {
......
CGPROGRAM
......
fixed4 _ReflectColor;
fixed _ReflectAmount;
samplerCUBE _Cubemap;
struct v2f {
......
fixed3 worldRefl : TEXCOORD3;
};
v2f vert(a2v v) {
......
//使用 CG的 reflect函数计算了该顶点处的反射方向
//物体反射到摄像机中的光线方向,可以由光路可逆的原则来反向求得
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);
......
}
fixed4 frag(v2f i) : SV_Target {
......
//利用反射方向来对立方体纹理采样
//对立方体纹理的采样需要使用CG的texCUBE函数
//采样的参数仅仅是作为方向变量传递给texCUBE函数的,因此没必要进行归一化
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectColor.rgb;
//使用_ReflectAmount来混合漫反射颜色和反射颜色
fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}
(2)折射
当光线从一种介质(例如空气)斜射入另一种介质(例如玻璃)时,传播方向一般会发生改变。当给定入射角时,我们可以使用斯涅尔定律(Snell’s Law)来计算反射角。当光从介质1沿着和表面法线夹角为θ1的方向斜射入介质2时,我们可以使用如下公式计算折射光线与法线的夹角θ2:
其中,η1和η2分别是两个介质的折射率(index of refraction)。折射率是一项重要的物理常数,例如真空的折射率是1,而玻璃的折射率一般是1.5。如果光是从空气射到玻璃表面,那么这个参数应该是空气的折射率和玻璃的折射率之间的比值,即1/1.5。
Shader关键代码:
Shader "ShaderBook/Chapter10/Refract" {
Properties {
......
_RefractColor ("Refraction Color", Color) = (1, 1, 1, 1)
_RefractAmount ("Refraction Amount", Range(0, 1)) = 1
//使用该属性得到不同介质的透射比,以此来计算折射方向
_RefractRatio ("Refraction Ratio", Range(0.1, 1)) = 0.5
_Cubemap ("Refraction Cubemap", Cube) = "_Skybox" {}
}
SubShader {
......
Pass {
......
CGPROGRAM
......
fixed4 _RefractColor;
float _RefractAmount;
fixed _RefractRatio;
samplerCUBE _Cubemap;
......
struct v2f {
......
fixed3 worldRefr : TEXCOORD3;
};
v2f vert(a2v v) {
......
//使用了 CG的 refract函数来计算折射方向
//第一个参数即为入射光线的方向,它必须是归一化后的矢量
//第二个参数是表面法线,法线方向同样需要是归一化后的
//第三个参数是入射光线所在介质的折射率和折射光线所在介质的折射率之间的比值
o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);
return o;
}
fixed4 frag(v2f i) : SV_Target {
......
//使用折射方向对立方体纹理进行采样
fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb;
......
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}
(3)菲涅尔反射
菲涅耳反射描述了一种光学现象,即当光线照射到物体表面上时,一部分发生反射,一部分进入物体内部,发生折射或散射。被反射的光和入射光之间存在一定的比率关系,这个比率关系可以通过菲涅耳等式进行计算。
一个经常使用的例子是,当你站在湖边,直接低头看脚边的水面时,你会发现水几乎是透明的,你可以直接看到水底的小鱼和石子;但是,当你抬头看远处的水面时,会发现几乎看不到水下的情景,而只能看到水面反射的环境。这就是所谓的菲涅耳效果。事实上,不仅仅是水、玻璃这样的反光物体具有菲涅耳效果,几乎任何物体都或多或少包含了菲涅耳效果。
真实世界的菲涅耳等式是非常复杂的,但在实时渲染中,我们通常会使用一些近似公式来计算。其中一个著名的近似公式就是Schlick菲涅耳近似等式:其中,F0是一个反射系数,用于控制菲涅耳反射的强度,v是视角方向,n是表面法线。
Shader关键代码:
Shader "ShaderBook/Chapter10/Fresnel" {
Properties {
......
//用于调整菲涅耳反射的属性
_FresnelScale ("Fresnel Scale", Range(0, 1)) = 0.5
//反射使用的Cubemap
_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {}
}
SubShader {
......
Pass {
......
CGPROGRAM
......
fixed _FresnelScale;
samplerCUBE _Cubemap;
......
struct v2f {
......
fixed3 worldRefl : TEXCOORD3;
};
v2f vert(a2v v) {
......
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);
return o;
}
fixed4 frag(v2f i) : SV_Target {
......
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb;
//使用 Schlick菲涅耳近似等式来计算 fresnel变量
fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5);
......
//我们使用它来混合漫反射光照和反射光照。
//一些实现也会直接把fresnel和反射光照相乘后叠加到漫反射光照上,模拟边缘光照的效果。
fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)) * atten;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}
把_FresnelScale调节到1时,物体将完全反射Cubemap中的图像;当_FresnelScale为0时,则是一个具有边缘光照效果的漫反射物体。
注意图中在模型边界处的反射现象。