Unity——UI光照(模拟光照)

前言
没错,这是UI光照不是2D光照。是Image不是SpriteRenderer。
本文的内容更偏向于花活和骚操作,为了不耽误读者的时间,请提前知晓!
不管是Buind-In管线还是URP管线下,可能都没有特别提供针对UI的光照功能,可能也确实没有必要,但是本着“玩”技术的心理,我在这里给出一个UI模拟光照的“魔道功法”。
其实就是Shader 实现经验型光照模型的衍生,因为本文属于Shader光照部分的衍生,所以本文并不会非常详细的讲解光照相关的知识,那部分知识可能会单独做一期,详细的讲解知识点以及计算过程。
老规矩!此方案是否具有实用性,各位读者请自行衡量。
作者也认为实用性较小,所以当做一个骚操作来写。
先来看效果:



如果部分读者不太了解Shader方面的知识也没有关系,因为是个花活,用的的东西也比较简单,阅读完本文也是可以实现的。
最后会给出完整的Shader代码。
思路
单纯让Image模拟光照的思路其实比较简单,就是将3D模型实现光照的那一套挪用到UI上即可,修改的地方相当少。比如我示例中使用的就是【兰伯特(Lambert)光照模型】。
至于为什么采用兰伯特光照模型,那是因为兰伯特光照模型比较简单。
实现
兰伯特光照模型是一个经验光照模型。他不符合真正的物理,只是看起来可能是对的,根据计算机图形学第一定律:(⊙o⊙)…。

兰伯特光照模型: Diffuse=Ambient+Kd*LightColor*Dot(N,L)
Diffuse:最终漫反射光强(颜色) Ambient:环境光强(环境色)
Kd:材质对光的反射系数 LightColor:灯光的颜色
N:顶点法线向量 L:顶点到光源位置的单位向量

如果对光照这部分不太了解的读者,可以先理解为L与N形成的夹角越小,那就表示更亮,反之就更暗,在90°及以上的时候我们认为当前点不接受光照。
知道了思路,实现起来就很简单了,我们直接动手开始写Shader。
我们这里只写最简的功能,把贴图渲染出来,支持光照。其他的功能比如什么调整Color,图片透明,还有因为引入cginc导致变体变多等问题因为与本文无关就不处理了。
我们要用到编辑器为我们提供的一些数据,需要引用 #include "Lighting.cginc"
环境色:fixed4 Ambient=unity_AmbientSky;
反射系数:这里用1,half Kd=1;
灯光颜色:fixed4 LightColor=_LightColor0;
N法线:这里只考虑最简单情况,让法线面向【前】fixed3 N=fixed3(0,0,-1);
L光源单位向量:fixed3 L=_WorldSpaceLightPos0;
然后只需要将以上数值带入公式,就可以了。
fixed4 Diffuse=Ambient+Kd*LightColor*max(0,dot(N,L));
为了避免一些因为负值导致的问题,我们使用Max()限制一下范围。
部分代码
Properties
{
//为了与UI的Sprite对接,图片的名字尽量不要动
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Transparent" }
Pass
{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
略 ......
#include "Lighting.cginc"
略......
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
fixed4 Ambient=unity_AmbientSky;
half Kd=1;
fixed4 LightColor=_LightColor0;
fixed3 N=fixed3(0,0,-1);
fixed3 L=_WorldSpaceLightPos0;
fixed4 Diffuse=Ambient+Kd*LightColor*max(0,dot(N,L));
col*=Diffuse;
return col;
}
ENDCG
}
}
现在平行光就已经实现了。
点光源与聚光灯
平行光和点光源与聚光灯不同,点光源与聚光灯发出的光会随着距离做出衰减,距离光源越远,光强越弱,这个衰减并不是一个线性的。这个衰减可以自己计算也可以使用Unity提供的方法UNITY_LIGHT_ATTENUATION(“返回的衰减值”,“顶点世界坐标”)
我们以点光源为例,看一下返回的衰减值是个什么样子的。


我们要做的就是把这个衰减值*灯光颜色附加到图像上。就可以得到最后的结果。
我们在第二Pass中实现效果。此处要用到#include "AutoLight.cginc"空间中的方法。

完整代码
Shader "UI/UILight"
{
Properties
{
[PerRendererData]_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Transparent" }
Pass
{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return 0;
fixed4 col = tex2D(_MainTex, i.uv);
clip(col.w-0.01);
fixed4 Ambient=unity_AmbientSky;
half Kd=1;
fixed4 LightColor=_LightColor0;
fixed3 N=fixed3(0,0,-1);
fixed3 L=_WorldSpaceLightPos0;
fixed4 Diffuse=Ambient+Kd*LightColor*max(0,dot(N,L));
col*=Diffuse;
return col;
}
ENDCG
}
Pass
{
Tags{"LightMode"="ForwardAdd"}
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal:NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 worldPos:TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.worldPos=mul(unity_ObjectToWorld,v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
UNITY_LIGHT_ATTENUATION(atten,0,i.worldPos)
fixed4 LightColor=_LightColor0*atten;
return col*LightColor;
}
ENDCG
}
}
}
现在就已经实现了我一开始那三张图的效果了。

最后可能会出现一些Scene和Game窗口显示不一样的情况。比如在平行光是正常的,但是点光源和聚光灯不正常。还有就是这种方式的开销相对来说会变大。


可以将Canvas的渲染模式(Render Mode)改为Screen Space-Camera。新建一个相机,让他只渲染UI层,为了让UI显示在模型之前,相机的Depth调高,并且将Clear Flags设置为Depth Only。



到这里文章的内容就全部结束。
本期的内容是个骚操作,目的是与各位读者分享一下有趣的操作。因为本期的定位不是[教程]系列,可能会有些不是仔细,希望各位读者理解。
如果本文对您有一丝丝的帮助请赏给作者一个赞好吗。
最后想说一点,以后我打算每期专栏配一个演示视频,可能会更好的帮助有需要的读者,能让有需要的读者更直观的看到我的操作过程。
我们下期再见。