Geometry Light :《Lara Croft GO》里的球形点光源
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"
}