1. 立方体纹理原理
立方体纹理由6张图片组成,每张图片分别对应立方体的一个面。这6张图片代表沿世界空间下的轴线(上下左右前后)观察所得的图像
立方体的应用主要分为两类:
- 单纯利用6张图片的展示功能,为我们提供一个环境背景,比如天空盒
- 对立方体纹理进行采样,显示在物体表面,反映物体对周围环境的反射、折射等效果,也就是用于环境映射
与2D纹理采样不同,立方体纹理采样时,需要提供一个3维的uv坐标,该坐标提供的是一个世界空间的方向,从立方体的中心出发,沿这个方向前进,会与6张图片中某一张相交在一点,这一点的颜色值即为采样结果。
2. 用于天空盒
创建天空盒
- 先创建一个材质,本次使用的材质命名为Chapter_10_SkyBox_Mat
- 为材质选择天空盒Shader,如下图有四种类型的天空盒Shader
- 6-Sided
- 使用6张纹理作为天空盒的6个面
- TintColor——调整整体颜色
- Exposure——调整亮度
- Rotation——调整Y轴旋转
(没有找到合适的资源,不试了)
-
Cubemap
- 通过一张Cube纹理生成天空盒
- Cube纹理如下
- 通过一张Cube纹理生成天空盒
-
Panoramic
- 由美术提供的一张特殊制作的高清纹理生成天空盒
- 由美术提供的一张特殊制作的高清纹理生成天空盒
设置天空盒
- 通过Window → Rendering → Lighting 打开光照设置面板
- 在Environment标签页下设置当前场景天空盒
- 摄像机的Clear Flags 选项选择为 Skybox
- 也可以为摄像机添加skybox组件,为组件选择其他的天空盒,此时当前摄像机在渲染时就会使用组件指定的天空盒替换掉光照面板中设置的场景天空盒
渲染顺序
从上面的截图中可以看到,天空盒的渲染序列为1000(Background)
在【Unity Shader入门精要 第8章】透明效果(一)中曾经说过,渲染队列为Background的物体用于远处的背景,但实际天空盒这些背景并不会真的最先渲染,而是在所有不透明的物体之后再进行渲染
这样在经过不透明物体的深度写入后,天空盒中的大量片元都不会通过深度测试,也就不需要进入片元着色器,可以有效降低 Over Draw
3. 用于环境映射
3.1 创建
除了用于天空盒,立方体纹理的另一个主要作用是用于环境映射。换句话说就是,以场景中的某一点为中心,通过立方体纹理反映从这一点看到的上下前后左右的环境,并可以通过对立方体纹理采样表现这一点的反射折射等现象。
可见,与天空盒不同,用于环境映射的立方体纹理的中心点通常是不确定的,其位置由使用立方体纹理的物体决定,需要表现哪个物体的反射等现象,就需要以该物体的位置为中心创建立方体纹理,这样才能正确表现该物体的周边环境信息。
在创建时,既可以提前准备好用于表示环境的纹理资源,也可以通过摄像机实时创建,常见有以下三种做法:
- 先有图片,后生成资源——将美术提供提前绘制好的图片导入工程,将类型设置为Cube
- 先有资源,后提供图片——直接在资源面板里右键创建一个Cubemap资源,然后为该资源提供所需的6张图片
- 脚本调用Camera的RenderToCubemap接口动态创建
GameObject _go = new GameObject();
_go.transform.position = TargetTransform.position;
Camera _tmpCam = _go.AddComponent<Camera>();
_tmpCam.RenderToCubemap(TargetCubemap);
DestroyImmediate(_tmpCam);
3.2 反射和折射
反射原理
光线与物体表面交互后改变传播方向进入我们的眼睛(摄像机),这个过程只改变光线的传播路径,不改变光线的颜色,因此我们就看到在物体表面倒映出周边环境的样子。
要模拟反射现象,只需要在渲染Pass中,计算出环境中的哪一点的光线在与当前片元交互后会传入摄像机,并根据反射的强度将这一点的颜色与物体本身颜色做融合即可。
因此,模拟反射的重点即是找到光线的来源。基于光路可逆的原理,我们可以从视线方向出发,经过与片元交互反向求出光线方向,然后用该方向对表示环境信息的立方体纹理进行采样。Unity中内置了 reflect 方法来快速计算模拟反射的光线来源方向。
o.worldRefl = reflect(-_worldView, o.worldNormal);
其中:
- 第一个参数为视线方向,并且是由摄像机指向物体的方向,而Unity中内置变量或内置方法返回的观察方向都是从物体指向摄像机的,因此在使用时需要先取反
- 第二个参数为当前处理的点的法线
- 该方法中的参数都不需要归一化
折射原理
折射现象的原理为光线与物体交互后进入物体内部并发生方向改变,最终进入摄像机,光线改变的角度与光线前后所处介质的折射率有关:
可见折射的角度与入射光线的角度成一定比例,我们可以用一个折射率系数 RefractRatio 来概括表示这个比例关系。跟反射一样,在模拟折射现象的时候,也可以从视线方向出发,根据折射率系数逆向求出入射光线方向,然后对表示环境信息的立方体纹理采样。
Unity同样提供了用于计算折射的方法
o.worldRefr = refract(-normalize(_worldView), normalize(o.worldNormal), _RefrcatRatio);
其中:
- 第一个参数为视线方向,同样需要从摄像机指向交互点
- 第二个参数为交互点的法线方向
- 第三个参数为折射率系数
- 与反射不同,在该方法中,传入的视线方向和法线方向都需要经过归一化处理
一个综合了反射和折射的测试Shader:
Shader "MyShader/Chapter_10/Chapter_10_ReflectAndRefract_Shader"
{
Properties
{
_CubeMap ("Cubemap", Cube) = "_Skybox" {}
_Color("Color", Color) = (1, 1, 1, 1)
_RefrcatRatio("RefractRatio", Range(0.1, 1)) = 0.5
_ReflToRefr("ReflToRefr", Range(0, 1)) = 0
_Amount("Amount", Range(0, 1)) = 0
}
SubShader
{
Pass
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float3 worldRefl : TEXCOORD2;
float3 worldRefr : TEXCOORD3;
SHADOW_COORDS(4)
};
samplerCUBE _CubeMap;
fixed4 _Color;
fixed _RefrcatRatio;
fixed _ReflToRefr;
fixed _Amount;
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
float3 _worldView = WorldSpaceViewDir(v.vertex);
o.worldRefl = reflect(-_worldView, o.worldNormal);
o.worldRefr = refract(-normalize(_worldView), normalize(o.worldNormal), _RefrcatRatio);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 _ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
float3 _worldNomal = normalize(i.worldNormal);
float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 _diffuse = _LightColor0.rgb * _Color.xyz * saturate(dot(_worldNomal, _worldLight));
fixed3 _reflColor = texCUBE(_CubeMap, i.worldRefl).rgb;
fixed3 _refrColor = texCUBE(_CubeMap, i.worldRefr).rgb;
fixed3 _sampler = lerp(_reflColor, _refrColor, _ReflToRefr);
UNITY_LIGHT_ATTENUATION(_atten, i, i.worldPos);
fixed3 _finalColor = _ambient + lerp(_diffuse, _sampler, _Amount) * _atten;
return fixed4(_finalColor, 1);
}
ENDCG
}
}
}
效果如下:
3.3 菲涅尔反射
菲涅尔反射描述了一种光学现象——当光线照射到物体上时,一部分被反射,一部分进入物体内部,而被反射的光线与入射光存在一定的比率关系。
一个现实中的例子就是当我们看水面时会发现,近处的部分可以透过水面看到水底,而远处的部分就只能看到反射。
在菲涅尔反射现象中,反射光线与入射光线的比率可以通过菲涅尔等式获得。一个描述真实世界菲涅尔反射的等式是非常复杂的,在渲染中往往通过一些公式进行近似模拟,Schlick菲涅尔近似等式就是其中常用的一个等式:
FSchlick(v, n) = F0 + (1 - F0)(1 - v · n)5
其中 v为视线方向,n为法线方向,F0为反射系数,用于控制菲涅尔反射的强度。
一个加入了菲涅尔反射的测试Shader:
Shader "MyShader/Chapter_10/Chapter_10_Fresnel_Shader"
{
Properties
{
_CubeMap ("Cubemap", Cube) = "_Skybox" {}
_Color("Color", Color) = (1, 1, 1, 1)
_FresnelScale("FresnelToRefl", Range(0, 1)) = 0
_Pow("Pow", Range(1, 50)) = 5
}
SubShader
{
Pass
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float3 worldRefl : TEXCOORD2;
float3 worldView : TEXCOORD3;
SHADOW_COORDS(4)
};
samplerCUBE _CubeMap;
fixed4 _Color;
fixed _FresnelScale;
half _Pow;
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldView = WorldSpaceViewDir(v.vertex);
o.worldRefl = reflect(-o.worldView, o.worldNormal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 _ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
float3 _worldNomal = normalize(i.worldNormal);
float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 _diffuse = _LightColor0.rgb * _Color.xyz * saturate(dot(_worldNomal, _worldLight));
fixed3 _reflColor = texCUBE(_CubeMap, i.worldRefl).rgb;
float3 _worldView = normalize(i.worldView);
fixed _fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(_worldView, _worldNomal), _Pow);
UNITY_LIGHT_ATTENUATION(_atten, i, i.worldPos);
fixed3 _finalColor = _ambient + lerp(_diffuse, _reflColor, saturate(_fresnel)) * _atten;
return fixed4(_finalColor, 1);
}
ENDCG
}
}
}
效果如下:
另外,也可以利用这个现象做简单的外轮廓显示: