Lv.2 Unity主线:添加环境光
环境光照
在实时渲染中,环境光照有几种方式:
| 方法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 常量环境光 | ambient = color | 简单 | 不真实,没有方向性 |
| 环境贴图采样 | 直接从 Cubemap 采样 | 精确 | 太慢(实时性差) |
| 球谐光照 | 用数学函数近似环境光 | 快速 + 较准确 | 需要预计算 |
环境光漫反射
两种计算方式选其一
Unity内置颜色
仅天光,不支持天空盒Skybox颜色,漫反射环境光
1
2
3
// 环境光(Unity 自动处理)
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo.rgb;
// ^^^^^^^^^^^^^^^^^^^^^^^ Unity 内置的环境光颜色
球谐函数
支持 Skybox/Gradient/Color,漫反射环境光
1
2
3
4
5
// 环境光(支持 Skybox/Gradient/Color)
fixed3 ambient = ShadeSH9(half4(i.normalWS, 1.0)) * albedo.rgb;
// ^^^^^^^ Unity 的球谐函数,根据法线方向采样环境光
ambient *= albedo.rgb; // 乘以基础颜色
环境光镜面反射
环境光镜面反射其实就是采样环境贴图,比如说HDR、EXR等等格式的图片,两种计算方式选其一
采样反射探针
DirectX 11 / OpenGL ES 3.0+ Fragment Shader最多支持 16个纹理采样器 但Cubemap算6个面,实际占用看平台实现 Unity同时最多混合 2个反射探针 场景中拖入反射探针进行烘焙然后再采样的
如果想把某个物体烘焙到CubeMap中,需要进行设置标签
场景默认会有一个全局反射探针,在Lighting设置中进行生成即可,就不用手动添加了,但是全局烘焙的不会含有物体,因为需要轻量化和统一,只包含天空盒SkyBox,需要烘焙物体的话还是得手动加反射探针 
用于金属类的,镜面反射环境光
1
2
3
// 采样反射探针(Reflection Probe)
half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, reflectDir);
half3 skyColor = DecodeHDR(skyData, unity_SpecCube0_HDR);
采样CubeMap图
和反射探针类似
1
2
3
4
5
6
7
8
9
10
11
12
13
14
_Cubemap ("Environment Cubemap", Cube) = "white" {}
------------------------------
samplerCUBE _CubeMap;
float4 _CubeMap_HDR; //此参数声明后,Unity会自动配置
------------------------------
// 计算反射方向
float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
float3 reflDir = reflect(-viewDir, normalize(i.worldNormal));
// 采样Cubemap
half4 reflColor = texCUBE(_Cubemap, reflDir);
//如果采样发现模糊,那么就需要指定lod0
half4 reflColor = texCUBElod(_CubeMap, float4(reflectDir, 0));
//同时也需要进行DecodeHDR
half3 reflRGB = DecodeHDR(reflColor, _CubeMap_HDR);
只要 Cubemap 是 HDR 格式(EXR / HDR / RGBM),采样后就一定要 DecodeHDR
只有 LDR(PNG / JPG)才可以直接用!!! 如果发现环境光镜面反射叠加效果阴影中也有的话,这其实是正确的。阴影区本来就有镜面反射,但是它的强度应该由AO(环境光遮蔽)控制,也就是AO贴图。
如果没有AO,可以随意整个假的AO,例如下面这样
1
2
3
//AO控制
float fakeAO = saturate(dot(normalWS, float3(0,1,0)));
reflRGB *= fakeAO;
环境光组成
非金属和金属的都必须包含高光,但是环境光镜面反射是只有光滑物体或者金属才有,然后镜面反射会和粗糙度关联控制,这个后面会补充
环境光 = 环境光SH漫反射 + 环境光镜面反射
参考性能
| 方法 | 性能 | 效果 | 适用场景 |
|---|---|---|---|
UNITY_LIGHTMODEL_AMBIENT | 最快 | 最简单 | 移动端/性能优先 |
ShadeSH9 | 快 | 好 | 推荐,通用 |
ShadeSH9 + 反射探针 | 中等 | 很好 | PC/主机高质量 |
| 实时 GI | 慢 | 最好 | 高端项目 |
最终片元代码参考
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
float4 frag(v2f i) : SV_Target
{
//vectors
float3 positionWS = i.positionWS;
float3 normalWS = normalize(i.normalWS);
float3 tangentWS = normalize(i.tangentWS);
float3 bitangentWS = normalize(i.bitangentWS);
float3x3 TBN = float3x3(tangentWS, bitangentWS, normalWS);
half3 lightWS = normalize(_WorldSpaceLightPos0.xyz);
float3 viewDir = normalize(_WorldSpaceCameraPos - positionWS);
//tex
float3 lightColor = _LightColor0.xyz;
float4 baseCol = tex2D(_MainTex, i.texcoord);
float3 normalMapData = UnpackNormal(tex2D(_NormalMap, i.texcoord)).xyz;
normalMapData.xy *= _NormalMapScale;
//halfLambet
float3 pixelNormal = normalize(mul(normalMapData, TBN));
float noL = max(0, dot(pixelNormal, lightWS));
float halfLambet = noL*0.5 + 0.5;
//Specular
//phong
half3 reflectDir = reflect(-lightWS, pixelNormal);
half phongItem = max(0, dot(reflectDir, viewDir));
half3 phongSpecular = _SpecularColor.xyz * _SpecularIntensity * pow(phongItem, _SpecularShininess) * lightColor;
//Blinn-phong
half3 halfDir = normalize(lightWS + viewDir);
half blinnPhongItem = max(0, dot(pixelNormal, halfDir));
half3 blinnPhongSpecular = _SpecularColor.xyz * _SpecularIntensity * pow(blinnPhongItem, _SpecularShininess) * lightColor;
/******************漫反射环境光*********************/
//EnvironmentColor
//1.环境光(Unity 自动处理)
fixed3 envColor = UNITY_LIGHTMODEL_AMBIENT.rgb * baseCol.rgb;
// ^^^^^^^^^^^^^^^^^^^^^^^ Unity 内置的环境光颜色
//2.SH环境光(支持 Skybox/Gradient/Color)
fixed3 envColorSH = ShadeSH9(half4(normalWS, 1.0)) * baseCol.rgb;
// ^^^^^^^ Unity 的球谐函数,根据法线方向采样环境光
/******************镜面反射环境光*********************/
//1.采样反射探针(Reflection Probe)
half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, reflectDir);
half3 skyColor = DecodeHDR(skyData, unity_SpecCube0_HDR);
//2. 采样Cubemap
// 计算反射方向
float3 reflviewDir = reflect(-viewDir, normalize(positionWS));
//half4 reflColor = texCUBE(_CubeMap, reflviewDir);
half4 reflColor = texCUBElod(_CubeMap, float4(reflectDir, 0));
half3 reflRGB = DecodeHDR(reflColor, _CubeMap_HDR);
//AO控制
float fakeAO = saturate(dot(normalWS, float3(0,1,0)));
reflRGB *= fakeAO;
float3 finalEnvColor = envColorSH + reflRGB;
//Shadow
float shadow = SHADOW_ATTENUATION(i);
//output
float3 finalColor = baseCol * halfLambet * lightColor * shadow + blinnPhongSpecular* shadow + finalEnvColor;
return float4(finalColor, 1);
}
Enjoy Reading This Article?
Here are some more articles you might like to read next: