光源类型
Unity 一共支持4种光源类型:平行光、点光源、聚光灯和面光源 (area light)。面光源仅在烘焙时才可发挥作用,因此不在本节讨论范围内。由于每种光源的几何定义不同,因此它们对应的光源属性也就各不相同。这就要求我们要区别对待它们。幸运的是,Unity 提供了很多内置函数来帮我们处理这些光源,在本章的最后我们会介绍这些函数,但首先我们需要了解它们背后的原理。
平行光:
平行光之所以简单,是因为它没有~个唯一的位置,也就是说,它可以放在场景中的任意位置(回忆一下,我们小时候是不是总感觉太阳跟着我们一起移动)。它的几何属性只有方向,我们可以调整平行光的 Transform组件中的 Rotation 属性来改变它的光源方向,而且平行光到场景中所有点的方向都是一样的,这也是平行光名字的由来。除此之外,由于平行光没有一个具体的位置,因此也没有衰减的概念,也就是说,光照强度不会随着距离而发生改变。
点光源
点光源的照亮空间则是有限的,它是由空间中的一个球体定义的。点光源可以表示由一个点发出的、向所有方向延伸的光。图9.6给出了Uity 中点光源在Scene 视图中的表示以及 Light组件的面板。
球体的半径可以由面板中的 Range 属性来调整,也可以在 Scene 视图中直接拖拉点光源的线框(如球体上的黄色控制点)来修改它的属性。点光源是有位置属性的,它是由点光源的 Transform组件中的 Position 属性定义的。对于方向属性,我们需要用点光源的位置减去某点的位置来得到它到该点的方向。而点光源的颜色和强度可以在 Light 组件面板中调整。同时,点光源也是会衰减的,随着物体逐渐远离点光源,它接收到的光照强度也会逐渐减小。点光源球心处的光照强度最强,球体边界处的最弱,值为0。其中间的衰减值可以由一个函数定义。
聚光灯
这块锥形区域的半径由面板中的 Range 属性决定,而锥体的张开角度由 SpotAngle 属性决定。我们同样也可以在 Scene 视图中直接拖拉聚光灯的线框(如中间的黄色控制点以及四周的黄色控制点)来修改它的属性。聚光灯的位置同样是由 Transform组件中的Position属性定义的。对于方向属性,我们需要用聚光灯的位置减去某点的位置来得到它到该点的方向。聚光灯的衰减也是随着物体逐渐远离点光源而逐渐减小,在锥形的顶点处光照强度最强,在锥形的边界处强度为 0。其中间的衰减值可以由一个函数定义,这个函数相对于点光源衰减计算公式要更加复杂,因为我们需要判断一个点是否在锥体的范围内。
在前向渲染中处理不同的光源类型
在了解了3种光源的几何定义后,我们来看一下如何在Unity Shader 中访问它们的5个属性位置、方向、颜色、强度以及衰减。
Shader "MyShader/9-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" }
Pass {
// 前向渲染标签
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
// Shader 中使用光照衰减等光照变量可以被正确赋值
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//反射光
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
//反射光的衰减可以认为是没有的
fixed atten = 1.0;
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
ENDCG
}
Pass {
// 额外渲染的标签
Tags { "LightMode"="ForwardAdd" }
Blend One One
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdadd
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
//当前处理的逐像素光源的类型
#ifdef USING_DIRECTIONAL_LIGHT
//平行光
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
//非平行光计算
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
#ifdef USING_DIRECTIONAL_LIGHT
//平行光衰减
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.0);
}
ENDCG
}
}
FallBack "Specular"
}
步骤:
1 BasePass用来计算场景平行光照,阴影,环境光等,所以需要在Pass中计算这部分,选用的是场景中最亮的平行光,默认无衰减。
2 AdditionPass用来计算在基础光照之外叠加的其他光源光照,#pragma multi_compile_fwdadd 定义后,判断光源的类型,平行光依然按照上述方法计算,根据不同的光源类型做不同的计算,光源的方向和衰减。
从图9.12可以看出,Unity 是如何一步步将不同光照染到物体上的:在第一个渲染事件中Unity 首先清除颜色、深度和模板缓冲,为后面的渲染做准备;在第二个染事件中,Unity利Chapter9-ForwardRendering 的第一个Pass,即Base Pass,将平行光的光照染到缓存中:在面的4个染事件中,Unity使用Chapter9-ForwardRendering的第二个Pass,即AdditionalPass依次将4个点光源的光照应用到物体上,得到最后的渲染结果。
我们知道,如果逐像素光源的数目很多的话,该物体的Additional Pass 就会被调用多次,影响性能。我们可以通过把光源的 Render Mode设为 Not Important来告诉 Unity,我们不希望把亥光源当成逐像素处理。在本例中我们可以把4个点光源的Render Mode都设为NotImportant,可以得到图9.14中的结果。