技术美术TA之全局光照全攻略(GI)

本文是视频课程《Unity技术美术TA:Shader篇》,算是对自己学习的总结,也希望分享下所学知识~~

什么是全局光照?
Global Illumination,简称GI
既考虑来自光源的直接光照,又考虑经过场景中其他物体反射的光照

就是没有GI,阴影处很暗,但是现实情况确实也能看清
多了间接光照的处理


Unity 的 GI 实现方式
1.预计算实时GI
实时显示之前需要把数据进行烘焙好,运行时读取一遍实时和光源互动
(计算GI有延迟,不适合快速变化的灯光,适合昼夜变化缓慢变化的)

2.烘焙GI(常用)
把光源对场景中静态物体的光照效果提前烘焙到一张张光照纹理
把光照纹理采样到物体表面以产生全局光照的效果
(固定的光源方向,不能有变化)


Unity 烘焙系统
1.Enlighten,旧版烘焙技术,已经被2021中淘汰
2.Progressive CPU Lightmapper,渐进式烘焙
基于路径追踪的烘焙系统
会先烘焙当前scene预览的画面,方便查看
3.Progressive GPU Lightmapper(在GPU计算)

烘焙设置:
Baked Indirect:只烘焙间接光
Subtractive:直接光+间接光
Shadowmask:阴影相关
(在Baked Indirect的基础上, 多烘焙了一张Shadowmask贴图)

烘焙设置界面:
Windows->Rendering->Lighting
在这里插入图片描述

其中设置项:
Realtime Lighting:实时 GI (Realtime Global Illumination,标识是否开启)
Mixed Lighting:烘焙GI (Baked Global Illumination,标识是否开启)
Lightmap Compression:贴图压缩设置
Ambient Occlusion:环境光遮挡(阻挡光区域处更黑)

Generate Lighting 按钮:开始烘焙

注意:
场景物体需要标记为 static(或者单独设置 Contribute GI)

场景灯光要设置为 Mixed 或者Baked

Shader 代码如下

Shader "Custom/TA/GlobalIllumination"
{
    Properties {}
    SubShader
    {
        Tags
        {
            "LightMode"="ForwardBase"
        }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            #include "CgIncludes/MyGlobalIllumination.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;

                #if defined(LIGHTMAP_ON)
                    //一般光照贴图都是第二套UV
                    float4 texcoord1 : TEXCOORD1;
                #endif
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;

                #if defined(LIGHTMAP_ON)
                    //一般光照贴图都是第二套UV
                    float4 lightmapUV : TEXCOORD2;
                #endif
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                //BakeGI
                #if defined(LIGHTMAP_ON)
                o.lightmapUV.xy = v.texcoord1 * unity_LightmapST.xy + unity_LightmapST.zw;
                #endif

                //RealTimeGI
                #if defined(DYNAMICLIGHTMAP_ON)
                o.lightmapUV.zw = v.texcoord1 * unity_LightmapST.xy + unity_LightmapST.zw;
                #endif
                
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                //表面信息
                SurfaceOutput o;
                UNITY_INITIALIZE_OUTPUT(SurfaceOutput, o);
                o.Albedo = 1;
                o.Normal = i.worldNormal;
                
                //GI 输入信息(要什么给什么)
                UnityGIInput giInput = (UnityGIInput)0;
                // UNITY_INITIALIZE_OUTPUT(UnityGIInput, giInput);
                giInput.light.color = _LightColor0;
                giInput.light.dir = _WorldSpaceLightPos0;
                giInput.worldPos = i.worldPos;
                giInput.worldViewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
                giInput.atten = 1;
                giInput.ambient = unity_AmbientSky;
                #if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
                giInput.lightmapUV = i.lightmapUV;
                #endif

                //GI 输出信息
                //间接光照计算存储在这里
                UnityGI gi;
                UNITY_INITIALIZE_OUTPUT(UnityGI, gi);

                CUSTOM_LightingLambert_GI(o, giInput, gi);

                //直接光照计算
                fixed4 c = CUSTOM_LightingLambert(o, gi);
                return c;
            }
            ENDCG
        }
    }
}

其中 #include "CgIncludes/MyGlobalIllumination.cginc" 是自定义 cginc 文件:
作用是计算直接光照+简介光照
(就是 Unity 内置的GI shader,拷贝出来的,方法前加 CUSTOM_ )

代码如下:

#ifndef MYGLOBALILLUMINATION_CGINCLUDE
#define MYGLOBALILLUMINATION_CGINCLUDE

//兰伯特光照模型
inline fixed4 CUSTOM_UnityLambertLight(SurfaceOutput s, UnityLight light)
{
    fixed diff = max(0, dot(s.Normal, light.dir));

    fixed4 c;
    c.rgb = s.Albedo * light.color * diff;
    c.a = s.Alpha;
    return c;
}

//计算直接光照+间接光照的影响
inline fixed4 CUSTOM_LightingLambert(SurfaceOutput s, UnityGI gi)
{
    fixed4 c;
    c = CUSTOM_UnityLambertLight(s, gi.light);

    //  LIGHTMAP_ON          =>     BakeGI
    //  DYNAMICLIGHTMAP_ON   =>     RealtimeGI

    // #if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
    // #define UNITY_LIGHT_FUNCTION_APPLY_INDIRECT
    // #endif

    //如果是BakeGI 或者 RealTimeGI 条件下
    #ifdef UNITY_LIGHT_FUNCTION_APPLY_INDIRECT
    c.rgb += s.Albedo * gi.indirect.diffuse;
    #endif

    return c;
}

//GI的核心
inline UnityGI CUSTOM_UnityGI_Base(UnityGIInput data, half occlusion, half3 normalWorld)
{
    // 储存结果,最后返回
    UnityGI o_gi;
    ResetUnityGI(o_gi);

    //计算 shadow相关的, DistanceShadow 实时阴影和烘焙阴影的过度
    // Base pass with Lightmap support is responsible for handling ShadowMask / blending here for performance reason
    #if defined(HANDLE_SHADOWS_BLENDING_IN_GI)
        half bakedAtten = UnitySampleBakedOcclusion(data.lightmapUV.xy, data.worldPos);
        float zDist = dot(_WorldSpaceCameraPos - data.worldPos, UNITY_MATRIX_V[2].xyz);
        float fadeDist = UnityComputeShadowFadeDistance(data.worldPos, zDist);
        data.atten = UnityMixRealtimeAndBakedShadows(data.atten, bakedAtten, UnityComputeShadowFade(fadeDist));
    #endif

    //主平行灯传入 gi
    o_gi.light = data.light;
    //作用衰减值
    o_gi.light.color *= data.atten;

    //是否进行球谐光计算
    #if UNITY_SHOULD_SAMPLE_SH
        o_gi.indirect.diffuse = ShadeSHPerPixel(normalWorld, data.ambient, data.worldPos);
    #endif

    //如果使用烘焙GI
    #if defined(LIGHTMAP_ON)

        //光照贴图的采样 xy是烘焙GI的 uv
        // Baked lightmaps
        half4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, data.lightmapUV.xy);
        half3 bakedColor = DecodeLightmap(bakedColorTex);

    //Direction Mode 如果为 Direction,会计算相关光照方向,增加纹理凹凸感
    #ifdef DIRLIGHTMAP_COMBINED
            fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER (unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy);
            o_gi.indirect.diffuse += DecodeDirectionalLightmap (bakedColor, bakedDirTex, normalWorld);

    #if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
                ResetUnityLight(o_gi.light);
                o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap (o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
    #endif

    // 如果为 non-Directional 就是直接取bakeColor
    #else // not directional lightmap
            o_gi.indirect.diffuse += bakedColor;

    #if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
                ResetUnityLight(o_gi.light);
                o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap(o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
    #endif

    #endif
    #endif

    //如果使用实时GI
    #ifdef DYNAMICLIGHTMAP_ON

        //光照贴图的采样 zw是烘焙GI的 uv
        // Dynamic lightmaps
        fixed4 realtimeColorTex = UNITY_SAMPLE_TEX2D(unity_DynamicLightmap, data.lightmapUV.zw);
        half3 realtimeColor = DecodeRealtimeLightmap (realtimeColorTex);

    #ifdef DIRLIGHTMAP_COMBINED
            half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw);
            o_gi.indirect.diffuse += DecodeDirectionalLightmap (realtimeColor, realtimeDirTex, normalWorld);
    #else
            o_gi.indirect.diffuse += realtimeColor;
    #endif
    #endif

    //diffuse 就是最后得出的间接光颜色 作用于环境光遮蔽
    o_gi.indirect.diffuse *= occlusion;
    return o_gi;
}

inline UnityGI CUSTOM_UnityGlobalIllumination(UnityGIInput data, half occlusion, half3 normalWorld)
{
    return CUSTOM_UnityGI_Base(data, occlusion, normalWorld);
}

inline void CUSTOM_LightingLambert_GI(SurfaceOutput s, UnityGIInput data, inout UnityGI gi)
{
    gi = CUSTOM_UnityGlobalIllumination(data, 1.0, s.Normal);
}


#endif


对于阴影有两种
1.实时阴影(随着光变化)
2.烘焙阴影(固定的)

对于 Distance Shadowmask,可以设置实时阴影和烘焙阴影的切换距离
在这里插入图片描述
过渡效果如下:
在这里插入图片描述

支持shadow的代码


Shader "Custom/TA/GlobalIllumination"
{
    Properties {}
    SubShader
    {
        Pass
        {
            struct v2f
            {
            	...
            
                //定义 shadow
                //unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
                //unityShadowCoord4 _LightCoord : TEXCOORD##idx;
                UNITY_LIGHTING_COORDS(3, 4)
            };

            v2f vert(appdata v)
            {
            	...

                //计算 shadow
                //a._LightCoord = mul(unity_WorldToLight, mul(unity_ObjectToWorld, v.vertex)).xy;
                //a._ShadowCoord.xy = coord.xy * unity_LightmapST.xy + unity_LightmapST.zw;
                UNITY_TRANSFER_LIGHTING(o, v.texcoord1.xy)
                
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
            	...

                //灯光的衰减效果 + 阴影纹理采样
                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
                
                ...
                
                //赋值
                giInput.atten = atten;
	
                
                #endif
                
            }
            ENDCG
        }
		
		//需要 shadowcaster
		UsePass "XXX/SHADOW_CASTER"
    }
}

光照探针
烘焙是静态的,但是场景中的物体是动态的
光照探针就是为了让物体也受到烘焙效果

对比光照探针的效果
在这里插入图片描述
在这里插入图片描述

代码支持如下

Shader "Custom/TA/GlobalIllumination"
{
    SubShader
    {
        Pass
        {
            struct v2f
            {
            	...

                #if UNITY_SHOULD_SAMPLE_SH
                    half3 sh : TEXCOORD3;
                #endif
            };

            v2f vert(appdata v)
            {
            	...

                //球谐光照和顶点光照的计算
                // SH/ambient and vertex lights
                #ifndef LIGHTMAP_ON //如果没有开启烘焙
                //是否启用球谐 并且 不是逐片断的执行SH采样
                #if UNITY_SHOULD_SAMPLE_SH && !UNITY_SAMPLE_FULL_SH_PER_PIXEL
                    o.sh = 0;
                //非重要级别的点光在逐顶点的光照效果
                #ifdef VERTEXLIGHT_ON
                    //逐顶点光照(只计算4个灯)
                    o.sh += Shade4PointLights (
                      unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
                      unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
                      unity_4LightAtten0, o.worldPos, o.worldNormal);
                #endif
                    //球谐,光照探针效果
                    o.sh = ShadeSHPerVertex (o.worldNormal, o.sh);
                #endif
                #endif


                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
            	...

                //光照计算(包括反射探针)
                #if UNITY_SHOULD_SAMPLE_SH && !UNITY_SAMPLE_FULL_SH_PER_PIXEL
                giInput.ambient = i.sh;
                #else
                giInput.ambient.rgb = 0;
                #endif
            }
            ENDCG
        }

    }
}

间接光的 Meta Pass
自发光颜色烘焙时会对其他物体产生影响
在这里插入图片描述

只会在烘焙执行,运行游戏不会执行

注意:需要设置 Flags为2
在这里插入图片描述

代码如下

Shader "Custom/TA/GlobalIllumination"
{
    Properties
    {
        _Color("Color",color)=(1,1,1,1)
    }
    SubShader
    {
    	...
    
        //计算光照的间接光反弹
        Pass
        {
            Name "META"
            Tags
            {
                "LightMode" ="Meta"
            }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0
            #include "UnityCG.cginc"
            #include "UnityMetaPass.cginc"

            fixed4 _Color;

            struct v2f
            {
                float4 pos : SV_POSITION;
            };

            v2f vert(appdata_full v)
            {
                v2f o;
                    UNITY_INITIALIZE_OUTPUT(v2f, o);
                o.pos = UnityMetaVertexPosition(v.vertex,
                    v.texcoord1.xy, v.texcoord2.xy,
                    unity_LightmapST, unity_DynamicLightmapST);
                return o;
            }

            half4 frag(v2f i) : SV_Target
            {
                UnityMetaInput metaIN;
                    UNITY_INITIALIZE_OUTPUT(UnityMetaInput, metaIN);
                metaIN.Albedo = 1;
                metaIN.Emission = _Color;
                return UnityMetaFragment(metaIN);
            }
            ENDCG
        }
    }
    
}

代码优化
1.烘焙的shader 可以和运行 shader作区分
(烘焙的复杂,运行的简单,删除一些没用的功能)
2.确定不要的分支可以删掉,精简代码
(比如只用 BakeGI)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值