效果如下:
Unity中的聚光灯SpotLight,可以用作手电筒,射灯等类似的效果,比如这样的
但是如果想把光束的效果做出来,就超出了SpotLight的能力范围了,本篇就为了记录一下一种简单的实现光束效果的办法。(不会用到体积光复杂的计算,当然局限性也很大,适合于要求不是很高的场景)
原理:
准备好一个锥体模型,半透明着色,并处理好边缘的虚化。
1.椎体网格,就是光束的样子,用来模拟光束的体积感
2.开始编写shader,简单的输出颜色即可,不需要纹理也不需要计算UV,因为是模拟光,就普通的半透明材质,采用blend SrcAlpha OneMinusSrcAlpha的混合模式。vert和frag代码如下:
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = _TintColor;
return col;
}
基本上已经是最简洁的情况了,大概调个颜色则可以达到下面的效果,右下角就利用模型的穿模从而达到一种感觉光是打在了这个立方体上面一样的感觉。
3.那么问题来了,这个光也太假了点吧,边缘居然可以这么硬,一般看到的光束应该是像这样的感觉才对呢,比如:
所以接下来就要处理灯光的边缘,让他虚化一些。查找边缘,就和模型描边,边缘泛光Rim等思路是差不多的,只要找到边缘,将边缘部分透明度降低一些即可达到虚化边缘的效果。这里就采用法线normal和视线的夹角来识别边缘,椎体侧面的法线是刚好垂直朝向,越往中间走,法线与视线方向的夹角越来越小,dot值越来越大(尴尬的手绘图没有画出3D的感觉,所以中间部分的法线就靠想象了)
Frag中的关键代码:
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 worldNormal = normalize(i.worldNormal);
col.a *= abs(dot(viewDir, worldNormal));
修改后的效果如下:
好很多了有一些朦胧的感觉了。
4.如果这个光束没有投射到物体上,而是这样的,那么尾部的效果有略显尴尬了,就是这里:
因为这里法线的过度和边缘是不一样的,所以需要换个思路来处理。这里采用通过模型坐标来做文章(因为我用的这个模型是这样的,尾部是Z为负的那部分)
所以可以在Z小于一定值的时候,透明度做一个渐变的效果来达到尾部虚化的需求,关键代码:
if (i.modelPosZ < _FadeRange) {
col.a = lerp(col.a, 0, saturate(abs(i.modelPosZ - _FadeRange) * _FadeFactor));
}
最终虚化后的效果:
完整代码如下:
Shader "Custom/Light"
{
Properties{
[HDR]_TintColor("Color", Color) = (1,1,1,1)
_FadeRange("range", range(-1,0)) = -0.5
_FadeFactor("factor", range(0,10)) = 1
}
SubShader
{
Tags {
"IgnoreProjector" = "True"
"Queue" = "Transparent"
"RenderType" = "Transparent"
}
Pass
{
blend SrcAlpha OneMinusSrcAlpha
Cull Off
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal:NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float modelPosZ : TEXCOORD2;
};
float4 _TintColor;
float _FadeRange;
float _FadeFactor;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.modelPosZ = v.vertex.z;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = _TintColor;
//边缘虚化
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 worldNormal = normalize(i.worldNormal);
col.a *= abs(dot(viewDir, worldNormal));
//底部虚化(这里的坐标相关得参考具体的模型坐标来给一个合适的值)
if (i.modelPosZ < _FadeRange) {
col.a = lerp(col.a, 0, saturate(abs(i.modelPosZ - _FadeRange) * _FadeFactor));
}
return col;
}
ENDCG
}
}
}