是什么?
天空盒子(Skybox)是游戏中用于模拟背景的一种方法。当我们在场景中使用了天空盒子时,整个场景就被包围在一个立方体内。在Unity中,天空盒子是在所有不透明物体之后渲染的,而其背后使用的网格是一个立方体或一个细分后的球体。
怎么用?
如何创建用于环境映射的立方体纹理?
创建立方体纹理的脚本:
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!");
}
}
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"
}
当光线从一种介质(例如空气)斜射入另一种介质(例如玻璃)时,传播方向一般会发生改变。当给定入射角时,我们可以使用斯涅尔定律(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"
}
菲涅耳反射描述了一种光学现象,即当光线照射到物体表面上时,一部分发生反射,一部分进入物体内部,发生折射或散射。被反射的光和入射光之间存在一定的比率关系,这个比率关系可以通过菲涅耳等式进行计算。
一个经常使用的例子是,当你站在湖边,直接低头看脚边的水面时,你会发现水几乎是透明的,你可以直接看到水底的小鱼和石子;但是,当你抬头看远处的水面时,会发现几乎看不到水下的情景,而只能看到水面反射的环境。这就是所谓的菲涅耳效果。事实上,不仅仅是水、玻璃这样的反光物体具有菲涅耳效果,几乎任何物体都或多或少包含了菲涅耳效果。
真实世界的菲涅耳等式是非常复杂的,但在实时渲染中,我们通常会使用一些近似公式来计算。其中一个著名的近似公式就是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时,则是一个具有边缘光照效果的漫反射物体。
注意图中在模型边界处的反射现象。