光源类型
- 分类:平行光、点光源、聚光灯、面光源
- 属性:位置、方向、颜色、强度、衰减
- 面光源仅在烘培时才可发挥作用
PS:需要在Scene视图中开启光照才能看到预览光源是如何影响场景中的物体
在前向渲染中处理不同的光源类型
先判断光源类型,获得光源方向,之后处理光源的衰减
- 先定义Base Pass
- 设置Pass的渲染路径标签Tags{“LightMode”=“ForwardBase”}
- 注意要使用编译指令 #pragma multi_compile_fwdbase才可以让光照变量被正确赋值
- _WorldSpaceLightPos0 获得平行光的方向
- _LightColor0已经是颜色和强度相乘之后的结果
- 定义衰减值(平行光可认为是没有衰减的)
- 为场景中其他逐像素光源定义Additional Pass
- 设置Pass的渲染路径标签Tags{“LightMode”=“ForwardAdd”}
- 使用编译指令 #pragma multi_compile_fwdadd 保证我们在Additional Pass 中访问到正确的光照变量
- 使用Blend(混合)命令,让Additional Pass计算得到的光照结果可以在帧缓存中与之前的光照结果进行叠合,不然会直接覆盖掉之前的光照结果
- 可以直接把Base Pass的顶点和片元着色器代码复制到Additional Pass中,去掉Base Pass中环境光、自发光、逐顶点光照、SH光照的部分,再添加一些对不同光源类型的支持
注意
- 先判断当前处理的逐像素光源的类型
- 使用#ifdef指令判断是否定义了USING_DIRECTIONAL_LIGHT,如果当前前向渲染Pass处理的光源是平行光,那么Unity的底层渲染引擎就会定义USING_DIRECTIONAL_LIGHT
- 如果是平行光的话,光源方向直接由 _WorldSpaceLightPos0.xyz 得到
- 如果是点光源或者聚光灯,那么 _WorldSpaceLightPos0.xyz 表示的是世界空间下的光源位置
- 光源方向需要用这个位置 - 世界空间下的顶点位置
处理不同光源的衰减
- 同样判断是否定义了USING_DIRECTIONAL_LIGHT
- 如果是平行光,衰减值为1.0
- 若是其他类型光源,Unity使用一张纹理作为查找表(Lookup Table,LUT),以再片元着色器中得到光源的衰减:首先得到光源空间下的坐标,然后使用该坐标对衰减纹理进行采样得到衰减值
// Upgrade NOTE: replaced ‘_LightMatrix0’ with ‘unity_WorldToLight’ 报错
在对应的Pass中添加 #include “AutoLight.cginc”。
效果:
- Base Pass和Additional Pass的调用(光源的渲染顺序)
-
前向渲染中Unity决定了哪些光源是逐像素光,哪些是逐顶点或SH光
-
默认情况下,新创建一个光源,它的Render 设置为Auto(Unity在背后判断处理方式:逐像素、逐顶点或SH方式)
-
Edit -> Project Setting ->Quality ->Pixel Light Count 中的数值,代表默认情况下一个物体可以接受最亮的平行光外的n个逐像素光照。每个光源会调用一次Additional Pass。
-
帧调用器(Frame Debugger)可以来查看场景的绘制过程 (Frame Debugger 在Unity的Window->Analysis里可以找到)。例如下面:
- 在第一个渲染事件中,Unity首先清除颜色、深度和模板缓冲
- 第二个渲染事件中,Unity利用Base Pass,将平行光的光照渲染到帧缓存中
- 后面三个渲染事件中,利用Additional Pass依次将3个点光源的光照应用到物体上,得到最后的渲染结果
- 处理点光源顺序:按照重要程度排序,即取决于点光源的颜色、强度、距离物体远近
- 如果物体不在光源的光照范围内,Unity不会为这个物体调用Pass来处理这个光源(调用相关的渲染事件)
- 把光源的Render Mode设为Not Important,Unity不会把该光源当成逐像素光来处理
PS:当把所有的光源设置为Not Important时(包括平行光),物体仅会显示环境光的光照结果,如下图:
Unity的光照衰减
- Unity使用一张纹理作为查找表来在片元着色器中计算逐像素光照的衰减
- 缺点:
- 需要预处理得到采样纹理,而且纹理的大小也会影响衰减的精度
- 数据一旦存储到查找表中,就无法使用其他数学公式来计算衰减
- 用于光照衰减的纹理
- Unity内部使用了一张_LightTexture0的纹理来计算光源衰减(如果我们对该光源使用了cookie,那么衰减查找纹理是_LightTextureB0)
- _LightTexture0的(0,0)明了于光源位置重合的点的衰减值,(1,1)表明在光源空间中距离最远的点的衰减
- _LightTexture0可以把顶点从世界空间变换到光源空间
(通过_LightTexture0变换矩阵得到的,P188_LightTexture0本身即为世界空间到光源空间的变换矩阵,可以用于采样cookie和光强衰减纹理),补充:
— 只需_LightTexture0和世界空间中的顶点相乘即可得到光源空间中的相应位置
float3 lightCoord = mul(_LightMatrix0,float4(i.worldPos,1)).xyz;
— 使用这个坐标的模的平方对衰减纹理进行采样,得到衰减值:
fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
(通过dot函数来得到光源空间中顶点距离的平方)
(宏UNITY_ATTEN_CHANNEL得到衰减纹理中衰减值所在分量,以得到最终的衰减值)
- 使用数学公式计算衰减
线性衰减:
float distance = length(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);
atten = 1.0/distance;
Shader "Custom/ForwardRendering"
{
Properties
{
_Diffuse("Diffuse",Color)=(1,1,1,1)
_Specular("Specular",Color)=(1,1,1,1)
_Gloss("Gloss",Range(8.0,256))=20
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass //Base Pass
{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
//编译指令 #pragma multi_compile_fwdbase
//保证我们在shader中使用光照衰减等光照变量可以被正确赋值
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal:NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;//裁剪空间下的顶点坐标
float3 worldNormal : TEXCOORD0;
float3 worldVertex : TEXCOORD1;
};
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
v2f vert (a2v v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP,v.vertex);
o.worldNormal= UnityObjectToWorldNormal(v.normal);//法线 模型空间-世界空间
o.worldVertex = mul(unity_ObjectToWorld,v.vertex).xyz;//顶点坐标 模型空间-世界空间
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 normalDir = normalize(i.worldNormal);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 lightDir = normalize(_WorldSpaceLightPos0);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir (i.worldVertex) );
fixed3 halfDir = normalize(lightDir + viewDir);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max( 0, dot(normalDir,lightDir) );
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow ( max ( 0,dot(normalDir,halfDir)) ,_Gloss);
//_LightColor0 已经是颜色和强度相乘之后的结果
fixed atten=1.0; //由于平行光可以认为是没有衰减的,因此这里直接令衰减值为1.0
return fixed4(ambient + (diffuse + specular) * atten ,1.0);
}
ENDCG
}
Pass //Additional Pass
{
Tags{"LightMode"="ForwardAdd"}
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc" //不加_LightMatrix0会报错
#pragma multi_compile_fwdadd
//编译指令 #pragma multi_compile_fwdadd
//保证我们在Additional Pass 中访问到正确的光照变量
struct a2v
{
float4 vertex : POSITION;
float3 normal:NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;//裁剪空间下的顶点坐标
float3 worldNormal : TEXCOORD0;
float3 worldVertex : TEXCOORD1;
};
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
v2f vert (a2v v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP,v.vertex);
o.worldNormal= UnityObjectToWorldNormal(v.normal);//法线 模型空间-世界空间
o.worldVertex = mul(unity_ObjectToWorld,v.vertex).xyz;//顶点坐标 模型空间-世界空间
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//先判断当前处理的逐像素光源的类型
//使用#ifdef指令判断是否定义了USING_DIRECTIONAL_LIGHT
//如果当前前向渲染Pass处理的光源是平行光,那么Unity的底层渲染引擎就会定义USING_DIRECTIONAL_LIGHT
//如果是平行光的话,光源方向直接由_WorldSpaceLightPos0.xyz得到
//如果是点光源或者聚光灯,那么_WorldSpaceLightPos0.xyz表示的是世界空间下的光源位置
//光源方向需要用这个位置 - 世界空间下的顶点位置
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldVertex.xyz);
#endif
fixed3 normalDir = normalize(i.worldNormal);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir (i.worldVertex) );
fixed3 halfDir = normalize(lightDir + viewDir);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max( 0, dot(normalDir,lightDir) );
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow ( max ( 0,dot(normalDir,halfDir)) ,_Gloss);
//_LightColor0 已经是颜色和强度相乘之后的结果
//处理不同光源的衰减
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
#if defined (POINT)
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldVertex, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT)
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldVertex, 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.0);
}
ENDCG
}
}
FallBack"Specular"
}