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:

  • URP - RendererFeature :ScreenSpaceOutline
  • 平滑法线处理 - 八面体映射
  • Lv.3 Unity主线:一个简单的PBRShader
  • 理论支线:直接光漫反射与GGX高光的混合问题
  • 理论支线:PBR - 基于图像的照明( image based lighting-IBL)
  • # #