unity shader之——面积光:上篇(线光源)

面积光是具有形体的一类光源,不像点光源和平行光这类完全抽象化,没有形体的光源。面积光适用于表达柔和的自然光,如通过灯发出的光具有明显的几何形体光源。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;
			}

线光源衰减效果图如下:


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

最新文章