一、面积光源
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