UnityShader前向渲染之三种不同光源的叠加应用

之前博客中所写的shader全都只能接受场景中的强度( 光源脚本上的Intensity )最高的平行光,那其他两种光源①点光源②聚光灯怎么接收呢? 先上代码

// Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight'

Shader "ShaderPath/ForwardAddShader"//shader的选择路径
{
	Properties//该Shader可控的属性
	{
		_DiffuseColor ("DiffuseColor",Color) = (1,1,1,1)//漫反射的主色调
		_SpecularColor ("SpecularColor",Color) = (1,1,1,1)//高光反射的主色调
		_Gloss ("Gloss",Range(1,100)) = 2 //光泽度(反光度) 控制高光区域的大小
	}
	SubShader//子着色器
	{
		Tags{"Queue"="Transparent" "IgnoreProject"="True" "RenderType"="Transparent"}

		Pass //第一个Pass 光照模型是ForwardBase 渲染那些只需要执行一次的光 比如平行光、环境光
		{
			// !!!!!特别注意 这里要关闭深度写入
			Tags{"LightMode" = "ForwardBase"}
			//与ENDCG相照应,将CG代码包裹
			CGPROGRAM
			//确保我们在使用光照衰减等光照变量的时候能够获得正确的值
			#pragma multi_compile_fwdbase 
			//顶点函数定义
			#pragma vertex diffusevert  
			//片元函数定义
			#pragma fragment diffusefrag
			//引入必要的Unity库 如下面的UnityObjectToClipPos 就是库中函数
			#include "UnityCG.cginc"
			//引入光照库 _LightColor0需要用
			#include "Lighting.cginc"
			struct appdata
			{
				float4 vertex : POSITION;//每个顶点结构体必须有的
				float3 normal : NORMAL;//定义法线
			};

			struct v2f
			{
				fixed3 worldNormal : TEXCOORD0; 
				float3 worldPos : TEXCOORD1;
				float4 pos : SV_POSITION;//每个片元结构体必须有的
			};
			
			fixed4 _DiffuseColor;
			fixed4 _SpecularColor;
			float _Gloss;


			v2f diffusevert (appdata v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);//把顶点从模型空间转换到剪裁空间
				o.worldNormal = normalize(UnityObjectToWorldNormal(v.normal));//把法线从模型空间转换到世界空间
				o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;//模型坐标转到世界坐标
				return o;
			}
			
			fixed4 diffusefrag (v2f i) : SV_Target//返回一个RGBA到模型上
			{
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; //环境光
				fixed3 lightDir = UnityWorldSpaceLightDir(i.worldPos);//获取光源在世界空间下的方向(光源发射出来的方向)
				fixed3 diffuse = _LightColor0.rgb * _DiffuseColor * (1+dot(lightDir,i.worldNormal))/2;
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);//计算眼睛的方向 相机位置-模型的世界坐标 向量
				//Blinn-Phong模型高光 
				fixed3 halfView = normalize(lightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _SpecularColor * pow(saturate(dot(i.worldNormal,halfView)),_Gloss);
				return fixed4(ambient + diffuse +specular,1); //a通道返回原图片纹理的a通道值+_AlphaScale
			}
			ENDCG
		}
		//第二个Pass的光照模型为 ForwardAdd, 该Pass可以被多个光源调用,因此该部分主要渲染的就是点光源和聚光灯
		//大部分代码与第一个Pass相同,比较有区别的主要还是在片元着色器中光照方向的计算
		Pass
		{
			Tags{"LightMode" = "ForwardAdd"}
			Blend One One //使计算得到的光照结果和之前的光照结果叠加
			CGPROGRAM
			//确保得到正确的光照变量信息
			#pragma multi_compile_fwdadd
			//顶点函数定义
			#pragma vertex vert  
			//片元函数定义
			#pragma fragment frag
			//引入必要的Unity库 如下面的UnityObjectToClipPos 就是库中函数
			#include "UnityCG.cginc"
			//引入光照库 _LightColor0需要用
			#include "Lighting.cginc"
			//引入自动光照库 unity_WorldToLight要用
			#include "AutoLight.cginc"
			struct appdata
			{
				float4 vertex : POSITION;//每个顶点结构体必须有的
				float3 normal : NORMAL;//定义法线
			};

			struct v2f
			{
				fixed3 worldNormal : TEXCOORD0; 
				float3 worldPos : TEXCOORD1;
				float4 pos : SV_POSITION;//每个片元结构体必须有的
			};
			
			fixed4 _DiffuseColor;
			fixed4 _SpecularColor;
			float _Gloss;


			v2f vert (appdata v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);//把顶点从模型空间转换到剪裁空间
				o.worldNormal = normalize(UnityObjectToWorldNormal(v.normal));//把法线从模型空间转换到世界空间
				o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;//模型坐标转到世界坐标
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target//返回一个RGBA到模型上
			{
				#ifdef USING_DIRECTIONAL_LIGHT 
					fixed3 lightDir = UnityWorldSpaceLightDir(i.worldPos);//获取光源在世界空间下的方向(光源发射出来的方向)
				#else
					fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz-i.worldPos);//获取光源在世界空间下的方向(光源发射出来的方向)
				#endif
				fixed3 diffuse = _LightColor0.rgb * _DiffuseColor * (1+dot(lightDir,i.worldNormal))/2;
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);//计算眼睛的方向 相机位置-模型的世界坐标 向量
				//Blinn-Phong模型高光 
				fixed3 halfView = normalize(lightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _SpecularColor * pow(saturate(dot(i.worldNormal,halfView)),_Gloss);
				#ifdef USING_DIRECTIONAL_LIGHT //如果是直射光,光照强度是不会衰减的,因此是1
					fixed atten = 1.0;
				#else
					#if defined (POINT) //对于点光源
				        float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
				        fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #elif defined (SPOT) //对于聚光灯
				        float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
				        fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #else
				        fixed atten = 1.0;
				    #endif
				#endif
				return fixed4((diffuse +specular)*atten,1); //颜色值*光照衰减
			}
			ENDCG
		}
	}
	Fallback "Transparent/VertexLit"
}

知识点补充

					#if defined (POINT) //对于点光源
				        float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
				        fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #elif defined (SPOT) //对于聚光灯
				        float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
				        fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #else
				        fixed atten = 1.0;
				    #endif

代码分析:
点光源: “_LightTexture0” 是Unity的一张内置光照衰减表,尽管我们可以通过计算得到更为准确的衰减值,但是计算复杂并且较为耗性能。
首先使用矩阵 “unity_WorldToLight” 把模型的点从世界坐标转到光源空间坐标,接着通过光源空间下点的位置信息_LightTexture0 采集到对应的光照衰减值。

//详解
//dot(lightCoord, lightCoord) 将转化后的光源坐标点成得到模的平方,在这里其实就是距离的平方,通常用距离的平方不使用距离,因为开放复杂
//dot(lightCoord, lightCoord).rr 为什么是rr? 首先我们平时经常用到.xyz  .rgb 等等,这里其实是一样的
//举个例子: n = (1,2,3) 那n.rr = (1,1)  n.gg = (2,2)  并且  n.xx = (1,1) n.yy = (2,2) ,上面相当于是用距离的平方构建了一个二维标量   注:因为dot后是一个一维标量,所以只能使用rr 或者 xx 有一个顺序关系
//为什么是采样对角线 (.rr明显就是斜线) 因为“通常我们只关心_LightTexture0对角线上的纹理颜色纸,这些值表明了光源空间中不同位置的点的衰减值,如(0,0)表示与光源重合的点,(1,1)表示距离光源最远的点”
//最后取采集到的数据的衰减通道的值,UNITY_ATTEN_CHANNEL是Unity封装过的,会根据不同的平台选择不同的通道
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

聚光灯: “_LightTextureB0” 是Unity的一张内置光照衰减表

//详解
//(lightCoord.z > 0) 聚光灯只对位于前面的物体产生效果
//tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL 同上面的点光源
//最难理解的部分是tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w,这句话大体可以概括为,基于点的张角计算衰减,如果和光源空间的中心轴(+z轴)刚好重合,那么这个衰减值是1,而在聚光灯的张角边缘处,这个值是0。
//详见下方链接
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

关于聚光灯计算的解释链接

各个光源的位置如下
在这里插入图片描述

用Frame Debug查看的渲染顺序如下图: 在渲染时候先渲染平行光,接着按照光源Intensity及光源的距离两个得到一个值(具体方法不得而知),按照这个值得大小先后渲染不同的光。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值