在SRP中,首先我们需要使用CBUFFER来存储光照相关的信息:
CBUFFER_START(_LightBuffer)
float4 _VisibleLightColors[MAX_VISIBLE_LIGHTS];
float4 _VisibleLightDirections[MAX_VISIBLE_LIGHTS];
CBUFFER_END
当然,这些数据是从C#中塞过去的,首先需要在C#中定义shader中用到的cbuffer属性,以及存放对应数据的数组:
static int visibleLightColorsId =
Shader.PropertyToID("_VisibleLightColors");
static int visibleLightDirectionsId =
Shader.PropertyToID("_VisibleLightDirections");
Vector4[] visibleLightColors = new Vector4[maxVisibleLights];
Vector4[] visibleLightDirections = new Vector4[maxVisibleLights];
CommandBuffer
类中提供了传递数据的接口:
cameraBuffer.SetGlobalVectorArray(
visibleLightColorsId, visibleLightColors
);
cameraBuffer.SetGlobalVectorArray(
visibleLightDirectionsId, visibleLightDirections
);
light相关的数据可以从CullingResults
中获取:
for (int i = 0; i < cull.visibleLights.Count; i++) {
VisibleLight light = cull.visibleLights[i];
visibleLightColors[i] = light.finalColor;
Vector4 v = light.localToWorld.GetColumn(2);
v.x = -v.x;
v.y = -v.y;
v.z = -v.z;
visibleLightDirections[i] = v;
}
对于平行光来说,它的光照方向取决于它的local z轴的方向,因此我们只要取出这个向量即可,由于Unity的向量是列向量,所以矩阵的第2列(下标从0开始)就是这个local z轴。
对于点光源来说,它并不存在某一个具体的光照方向,而是需要具体的位置信息。因此,在C#层需要对数据结构稍微修改:
static int visibleLightDirectionsOrPositionsId =
Shader.PropertyToID("_VisibleLightDirectionsOrPositions");
Vector4[] visibleLightDirectionsOrPositions = new Vector4[maxVisibleLights];
传递数据的地方也要修改:
cameraBuffer.SetGlobalVectorArray(
visibleLightDirectionsOrPositionsId, visibleLightDirectionsOrPositions
);
在塞数据的时候,需要对光源的类型进行区分:
if (light.lightType == LightType.Directional)
{
Vector4 v = light.localToWorld.GetColumn(2);
v.x = -v.x;
v.y = -v.y;
v.z = -v.z;
visibleLightDirectionsOrPositions[i] = v;
}
else
{
visibleLightDirectionsOrPositions[i] =
light.localToWorld.GetColumn(3);
}
也很好理解,点光源的位置保存在矩阵的最后一列中。
同样地,在shader中也要对CBUFFER里的定义进行修改:
CBUFFER_START(_LightBuffer)
float4 _VisibleLightColors[MAX_VISIBLE_LIGHTS];
float4 _VisibleLightDirectionsOrPositions[MAX_VISIBLE_LIGHTS];
CBUFFER_END
那么,shader中要如何区分平行光源和点光源呢?平行光源传入的是向量,用float4来表示,最后一位分量w是0,而点光源传入的是位置,用float4表示的话最后一位分量w是1。在计算光源方向时,可以统一用下面一行代码计算:
float3 lightVector = lightPositionOrDirection.xyz - worldPos * lightPositionOrDirection.w;
点光源的range信息也可以从VisibleLight
数据结构中获取:
attenuation.x = 1f /
Mathf.Max(light.range * light.range, 0.00001f);
然后我们就可以传到shader中,去实现我们自定义的衰减逻辑了:
float rangeFade = dot(lightVector, lightVector) * lightAttenuation.x;
rangeFade = saturate(1.0 - rangeFade * rangeFade);
rangeFade *= rangeFade;
float distanceSqr = max(dot(lightVector, lightVector), 0.00001);
diffuse *= rangeFade / distanceSqr;
最后是聚光灯,它更复杂一些,既需要方向信息,也需要位置信息。除此之外,它还有个能被照亮的最大角度,超出该角度范围里的物体是不会被照亮的,这个衰减是过渡的,我们假设聚光灯方向与聚光灯到物体的方向夹角为 θ \theta θ,聚光灯开始衰减的角度为 θ 0 \theta_0 θ0,彻底衰减时的角度为 θ 1 \theta_1 θ1,那么衰减如下计算:
F = c o s θ − c o s θ 1 c o s θ 0 − c o s θ 1 F = \dfrac{cos\theta - cos\theta_1}{cos\theta_0 - cos\theta_1} F=cosθ0−cosθ1cosθ−cosθ1
c o s θ cos\theta cosθ的值是在shader中计算的,其余部分是常量,可以在C#层预先计算好传进去:
float outerRad = Mathf.Deg2Rad * 0.5f * light.spotAngle;
float outerCos = Mathf.Cos(outerRad);
float outerTan = Mathf.Tan(outerRad);
float innerCos =
Mathf.Cos(Mathf.Atan(((46f / 64f) * outerTan)));
float angleRange = Mathf.Max(innerCos - outerCos, 0.001f);
attenuation.z = 1f / angleRange;
attenuation.w = -outerCos * attenuation.z;
如果你觉得我的文章有帮助,欢迎关注我的微信公众号 我是真的想做游戏啊
Reference
[1] Lights