之前博客中所写的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及光源的距离两个得到一个值(具体方法不得而知),按照这个值得大小先后渲染不同的光。