面積光是具有形體的一類光源,不像點光源和平行光這類完全抽象化,沒有形體的光源。面積光適用於表達柔和的自然光,如通過燈發出的光具有明顯的幾何形體光源。unity實現了面積光,但僅用於燈光烘焙,而在實時渲染中仍然無法使用面積光。下面將通過着色器計算來實現面積光。
一、線光源
1.1點、線、面
兩個點可以確定一條直線,而兩個相互垂直的線段可以確定一個矩形,6個面可以構成一個立方體。而這裏,對面光源的構造也將從一條線段開始,也就是所謂的線光源。
1.2如何理解一個線光源
如何看待一個線光源,決定了如何在着色器代碼中實現對線光源的計算。線光源的形體特徵有兩點:一個是長度,另一個是方向,即兩端點所確定的直線的方向。線光源的光源照明特性如下:
線光源照明的物體其實處於從線光源的形體劃分出來的3個幾何空間內,處於兩端點之外的點受到的照明是點光源的照明,而處於線段之內的空間的物體所受到的照明,則和物體到線段的垂直距離,以及垂足上的光源強度相關。
因此在實現中,也要分兩部分來做,首先通過腳本傳遞幾個數據到着色器中,分別是線段的長度,位置和方向。其次,在着色器計算即將被着色器的點處於線段所劃分出的幾何空間的哪一部分內。
1.3通過腳本傳遞線光源的幾何信息
public class LineLit_1 : MonoBehaviour {
public Transform lit;
public Material mat;
void Update () {
mat.SetVector("litP", lit.position);
// mat.SetVector("litN", lit.forward);
// mat.SetVector("litR", lit.right);
mat.SetVector("litT", lit.up);
}
}
這個腳本只傳遞了物體的位置和方向到着色器中,而光源的長度則是着色器的屬性提供的。
1.4計算光源的照明
在腳本中已經得到了光源的關鍵幾何數據,接下來將在着色器中計算線光源對某一點的照明。代碼如下:
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Tut/Shader/Area Lit/LineLit_1" {
Properties{
lh("Height of Line light",range(0,4))=1 //線光源長度
li("Intensity of Light",range(0,20))=1 //線光源強度(寬度)
}
SubShader {
pass{
Tags{ "LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#pragma target 3.0
#include "UnityCG.cginc"
struct v2f{
float4 pos:SV_POSITION;
float3 wN:TEXCOORD0; //世界座標的法線
//float3 litDir:TEXCOORD1;
float4 wP:TEXCOORD1; //點在世界座標的位置
};
float4 litP; //線光源的幾何中心位置
float4 litT; //線光源的方向
float lh; //線光源的長度
float li; //線光源亮度
v2f vert(appdata_base v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.wN=mul(unity_ObjectToWorld,float4(SCALED_NORMAL,0)).xyz;
o.wP=mul(unity_ObjectToWorld,v.vertex);
//float4 wP=mul(_Object2World,v.vertex);
//o.litDir=litP.xyz-wP.xyz/wP.w;
return o;
}
float4 frag(v2f i):COLOR
{
float3 litDir=litP.xyz-i.wP.xyz/i.wP.w;//點到線光源中心的矢量
float3 litDir1=litP.xyz+litT.xyz*lh-i.wP.xyz/i.wP.w;//點到線光源一個端點的矢量
float3 litDir2=litP.xyz-litT.xyz*lh-i.wP.xyz/i.wP.w;//點到線光源另一個端點的矢量
float len1=length(litDir1); //到一個端點的距離
float len2=length(litDir2); //到另一個端點的距離
//假設點到線光源兩端點的矢量分別是一個直角三角形的斜邊和直角邊
float len=abs(len1*len1-len2*len2)-4*lh*lh;//(2*lw)*(2*lw)
//
float diff=0;
float att=1;
float3 dr=0;
//如果len小於0,可以推知當前點位於線段內側的哪個幾何空間內
if(len<0)//判斷 是否處於 線段光源的 內側
{
//下面計算垂線段 向量
//計算當前點到其中一個端點的矢量和線光源方向之間夾角的餘弦
float dt=abs(dot(normalize(litDir1),litT.xyz));
//使用這個餘弦和這兩個矢量計算到線光源的垂直矢量
float3 horT=dt*length(litDir1)*litT.xyz;
float3 Ldir=litDir1-horT;//垂直向量
//這個垂直矢量將會被當做光源的方向
dr=Ldir;
diff=dot(normalize(i.wN),normalize(dr)); // diffuse version 1
diff=(diff+0.7)/1.7;// diffuse version 2
att=1/(1+length(dr)); //使用這個垂直距離實現光源和幾何形體的衰減
att=att*att;
}
else//不然點就處於 線段光源的 外側
//假設線光源是從中心向兩端衰減的,因此取光源線段上最近的點
{
if(len1<len2)
dr=litDir1;
else
dr=litDir2;
//
att=1/(1+length(dr));
att=att*att;
//diff=max(0,dot(normalize(i.wN),normalize(dr))); // diffuse version 1
diff=dot(normalize(i.wN),normalize(dr));// diffuse version 2
diff=(diff+0.7)/1.7;
//diff=diff*abs(dot(litR.xyz,i.wN));
}
float c= li*diff*att;
//
return c;
}
ENDCG
}//end pass
}
}
在這個着色器計算中,首先根據三角形3條邊的大小關係,快速判斷出點處於線光源所分割出的哪一類空間中,然後根據兩種不同的情況分別計算出有效的光照衰減,下圖展示了這種光源的照明效果:
1.5線光源的輻射方向
在上面的情形中,假設線光源是向四周均勻輻射能量的,但是這裏將會增加一個條件,即線光源輻射的能量是帶有方向的。首先需要在腳本中設定這個輻射的方向,然後在着色器中計算照明時的方向,腳本如下:
public class LineLit_2 : MonoBehaviour {
public Transform lit;
public Material mat;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
mat.SetVector("litP", lit.position);
mat.SetVector("litN", lit.forward);
// mat.SetVector("litR", lit.right);
mat.SetVector("litT", lit.up);
}
}
這個腳本只比上節腳本多了一行代碼,即對光源輻射方向的設定,然後在着色器中計算這個方向,和上面的着色器相比,主要多出來下面的代碼:
dr=Ldir; //燈光方向
float diffN=abs(dot(litN,normalize(dr))); //燈光方向和輻射方向的點積
diff=dot(normalize(i.wN),normalize(dr)); // diffuse version 1
diff=(diff+0.7)/1.7;// diffuse version 2
diff=diff*diffN;
對線光源輻射度方向的的設置主要是通過燈光方向和輻射度方向的一個點積來實現的,下面是帶輻射方向的線光源效果:
1.6線光源的衰減
我們還可以進一步計算基於線光源中心的衰減,着色器裏主要改進是當點處於線光源內側的幾何空間內時,對基於線光源的中心所做的衰減,相關代碼如下:
if(len<0)//判斷 是否處於 線段光源的 內側
{
//下面計算垂線段 向量
float dt=abs(dot(normalize(litDir1),litT.xyz));
float3 horT=dt*length(litDir1)*litT.xyz;//LitDir的投影
float hdensity=1-abs(length(horT)/lh-1);//計算能量分佈位置
hdensity=smoothstep(0,1,hdensity);
li=li*(1+hdensity);
float3 Ldir=litDir1-horT;//垂直向量
dr=Ldir;
float diffN=abs(dot(litN,normalize(dr)));
diff=dot(normalize(i.wN),normalize(dr)); // diffuse version 1
diff=(diff+0.7)/1.7;// diffuse version 2
diff=diff*diffN;
att=1/(1+length(dr));
att=att*att;
}
線光源衰減效果圖如下: