Geometry Light :《Lara Croft GO》里的球形点光源

Last modified date

Comments: 0

Unity 3D shader 实现几何体点光源,模仿《Lara Croft GO》里适合lowpoly风格的点光源。

原理

用一个球形几何体当作光源,只渲染背面,而且只有在背面被其他物件遮挡的时候才显示。

  • 实现这一步只需踢除正面(Cull Front),然后深度测试的时候通过远的(即显示被遮挡的部分),并且不写入深度缓存。(ZTest Greater, Zwrite Off)

以上步骤还未完全解决,因为灯光球体如果在物体背面的话,就会等于全被遮挡,就会全显示出来。所以还需要用Stencil的遮罩来解决这个问题。取巧的做法是直接利用球体的正面来当作遮罩,当球体逐渐远离其他物体时,前方的球体会利用其球形的特征表现出逐渐远离物体的效果。具体做法就是利用Stencil:

  • 第一个Pass先渲染背面(当被遮挡时才渲染),Stencil里全通过,并且标记为1,不显示任何内容
  • 第二个Pass渲染正面,Stencil里标记=1时通过(等于用正面当mask,把第一步的内容抠出来)。然后渲染成additive,就有了灯光照亮的效果。
  • 上图每个pass直接输出了实色填充,我们最终要的是Additive的发光效果,所以把黑色的部分做成Additive的即可。
  • 注意这个shader需要在所有非透明物体渲染完以后再画,所以渲染排序写成”Queue” = “Geometry+1″。

好了,现在我们做好了一层灯光的效果。如果需要多层灯光,就需要多个材质球,对应不同的Stencil Ref,每个材质球的Render queue要后移一位以避免渲染时相互遮挡。

好了,原理到此为止。我现在用的3个Material的做法感觉有点费劲,最好是能共用一个材质球搞定,但是我目前还没想到好办法。

shader参考:
Shader "WalkingFat/Toon2/GeometryPointLight"
{
    Properties
    {
        _LightColor ("Light Color", Color) = (1.0, 1.0, 1.0, 1) // 最终输出的灯光颜色,会配合Additive效果
        _Stencil ("Stencil", Int) = 1 // 预设的“Stencil Ref”参考值
    }

    SubShader
    {
        Tags
        { 
            "Queue" = "Geometry+1" // 这里默认是在Geometry上加1,如果要多个灯,就用多个Material,每个的Queue后移一位
            "RenderType"="Opaque"
        }
        LOD 100

        Pass // 第一个Pass只获取背后的轮廓信息
        {
            Blend OneMinusDstColor One // Additive,配合后面的黑色,让效果等于透明(这里不用ColorMask因为它不稳定)

            Stencil
            {
                Ref [_Stencil]     // 设置参考值
                Comp Always        // stencil完全通过
                Pass Replace       // 通过的部分标记为参考值
            }

            ZTest Greater    // 深度测试 大于等于当前最小【深度缓存】中的值时,就会显示。即被物体挡住的部分就会显示 
            ZWrite Off       // 不写入到【深度缓存】

            Cull Front       // 剔除正面,只渲染背面颜色

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

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : POSITION;
            };
            
            v2f vert (appdata v)
            {
                v2f o;  
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return fixed4 (0,0,0,1); // 填充黑色,配合Additive等于透明,因为这里不需要显示任何东西。
            }
            ENDCG
        }

        Pass // 第二个Pass用正面当蒙版,并画出Additive当亮色
        {
            Blend OneMinusDstColor One // Additive,这里是要配合最后的灯光颜色,显示出发亮的光效

            Stencil
            {
                Ref [_Stencil]  // 设置参考值
                Comp Equal      // 标记为参考值的部分就显示,就是把上个Pass里的被物体挡住的背面的部分显示出来
            }

            ZWrite Off       // 不写入到【深度缓存】
            Cull Back        // 只显示前面,相当于一个mask把上述的内容抠出来

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


            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : POSITION;
            };

            float4 _LightColor;
            
            v2f vert (appdata v)
            {
                v2f o;  
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return _LightColor; // 最终输出灯光颜色
            }
            ENDCG
        }
    }
    Fallback "Unlit/Color"
}

Leave a Reply

Your email address will not be published. Required fields are marked *

Post comment