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

一、面积光源

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

推荐阅读