一、面积光源
1.1 面积光和线光源的不同
面积光其实就是一个长方形光线,是所有几何形体中最易计算的一类面积,这个例子中,面积光和线光源最大的不同是,面积光把空间分成了更多的区域。如果不考虑光源法线,一个长方形的面积光会把空间分成9个区域,共4种类型的照明条件。因此,对于面积光的计算,主要就是确定点落在了这9个区域内的哪一个区域,并使用相应的照明条件计算点的光照。
1.2 通过脚本设定面积光的几何特性
和线光源类似,需要通过脚本来设定面积光的一些几何特性,代码如下:
public class RectLit_1 : MonoBehaviour {
public Transform lit;
public Material mat;
// 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);
}
}这里设定了面积光的中心位置,以及它的朝向以及上、下、左、右的方向。
1.3 计算面积光
代码如下:
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Tut/Shader/Area Lighting/RectLit_1" {
Properties{
lh("Height of Line light",range(0,4))=1
lw("Width 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;
float4 wP:TEXCOORD1;
};
float4 litP;//面积光的几何中心位置
float4 litN;//面积光的法线方向
float4 litT;//几何体的Y方向
float4 litR;//几何体的X方向
float lh;//面积光的长度
float lw;//面积光的宽度
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);
return o;
}
//该函数用于判断一个点处于长方形4个顶点指向的哪一个区域内,是根据距离来判断的
//判定结束后,返回该点到此长方形中顶点的矢量方向
float3 GetNearest(float d1,float d2,float d3,float d4,float3 dr1,float3 dr2,float3 dr3,float3 dr4)
{
float3 dr=float3(0,0,0);
if(d1<d2)
{
if(d1<d4)
{dr=dr1;
//dr=float3(0,0,1);
}
else
{dr=dr4;
//dr=float3(0,1,1);
}
}
else
{
if(d2<d3)
{dr=dr2;
//dr=float3(1,1,0);
}
else
{dr=dr3;
//dr=float3(0.5,0.5,0.5);
}
}
return dr;
}
float4 frag(v2f i):COLOR
{
float3 litDir=litP.xyz-i.wP.xyz/i.wP.w;//点到光源几何中心的矢量
//下面是到面积光源的4个顶点的矢量
float3 litDir1=litDir-litT.xyz*lh-litR.xyz*lw;//1
float3 litDir2=litDir-litT.xyz*lh+litR.xyz*lw;//2
float3 litDir3=litDir+litT.xyz*lh+litR.xyz*lw;//3
float3 litDir4=litDir+litT.xyz*lh-litR.xyz*lw;//4
float diff=0;//漫反射
float att=1;//衰减
float3 dr=0;//最终用于计算的灯光方向
float diffN=0;//和光源辐射方向相关的衰减
float len1=length(litDir1);
float len2=length(litDir2);
float len3=length(litDir3);
float len4=length(litDir4);
//和线光源的计算类似,使用三角形3条边的关系来确定顶点处于哪一个区域
float lenw=abs(len1*len1-len2*len2)-4*lw*lw;//用于判断 是否处于 光源的水平 内侧
float lenh=abs(len2*len2-len3*len3)-4*lh*lh;//用于判断 是否处于 光源的垂直 内侧
if(lenw<0&&lenh<0)//处于面积光源的正对面(在长方形内部)
{
//float dist=abs(dot(litDir,litN));
float dist=dot(litDir,litN);
dr=dist*litN;
}else if(lenw<0)//处于面积光源的水平正对面
{
if(len2<len3)
dr=litDir2;
else
dr=litDir3;
float dt=abs(dot(normalize(dr),litR.xyz));
float3 hor=dt*length(dr)*litR.xyz;
float3 Rdir=dr-hor;//垂直向量
dr=Rdir;
}else if(lenh<0)//处于面积光源的垂直正对面
{
if(len3<len4)
dr=litDir3;
else
dr=litDir4;
float dt=abs(dot(normalize(dr),litT.xyz));
float3 hor=dt*length(dr)*litT.xyz;
float3 Tdir=dr-hor;//垂直向量
dr=Tdir;
}else //处于面积光源的角落内
{
dr=GetNearest(len1,len2,len3,len4,litDir1,litDir2,litDir3,litDir4);//判断灯光处于4个角落的哪个角落
}
diffN=abs(dot(litN,normalize(dr))); //计算光源方向相关的衰减
//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*diffN; //计算最终的照明
att=1/(1+length(dr));
att=att*att;
float c= li*diff*att;
//
return c;
}
ENDCG
}//end pass
}
}
这个代码计算思路和线光源类似,先计算处于长方形几何体在所划分出来的哪一个区域内,然后分别计算出有效的、用于最终照明的光源方向,这个面积光的照明效果如下图:

1.4 和默认照明的整合
我们可以把这种面积光和Unity默认的照明系统整合到一起,一种方法是为每一个材质添加计算面积光的代码,不过这样有点复杂,而且没有可伸缩性,我们可以在OnPostRender消息中完成这件事:
public class RectLit_PostRender_1 : MonoBehaviour {
public Transform lit;
public Material mat;
//public int pass;
public MeshFilter[] objs;
public Material[] mats;
void OnPostRender()
{
if(!enabled) return;
//设置面积光源的参数
mat.SetVector("litP", lit.position);
mat.SetVector("litN", lit.forward);
mat.SetVector("litR", lit.right);
mat.SetVector("litT", lit.up);
//
for (int i = 0; i < objs.Length;i++ )
{
RectLighting(objs[i], mats[i]);
}
}
void RectLighting(MeshFilter obj,Material exMat)
{
Mesh ms = obj.sharedMesh;
Transform tr = obj.transform;
exMat.SetPass(0);
Graphics.DrawMeshNow(ms, tr.localToWorldMatrix);
}
}在OnPostRender中,对每一个希望受面积光影响的物体进行一次渲染,这个渲染用的着色器和面积光的着色器非常类似,但为了能整合物体已有的照明还是稍有不同,这个着色器使用了Blend One One的模式,因此能够和已有的照明相结合。此外,这个着色器使用了偏移量来对物体的Z(深度)进行略微的调整,从而使Blend操作能够成功完成。因为这里的方法实际上在同一位置对物体又进行了一次渲染,肯定会出现深度冲突的现象,所以要使用偏移量来调整Z。
SubShader {
pass{
//Tags{ "LightMode"="ForwardBase"}
ZWrite Off
ZTest LEqual
//Offset 0,-1可以使Blend One One操作成功完成
//因为此对面积光的计算,实际上是在场景物体的同一个位置处又重新计算的
Offset 0,-1
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//#pragma multi_compile_fwdbase
#pragma target 3.0
#include "UnityCG.cginc"面积光和默认照明整合的效果如下图:

版权声明:本文为CSDN博主「小橙子0」的原创文章,
遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cgy56191948/article/details/103542129





