SRP中的光照

SRP中的光照

在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轴。

SRP中的光照_第1张图片

SRP中的光照_第2张图片

对于点光源来说,它并不存在某一个具体的光照方向,而是需要具体的位置信息。因此,在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;

SRP中的光照_第3张图片

最后是聚光灯,它更复杂一些,既需要方向信息,也需要位置信息。除此之外,它还有个能被照亮的最大角度,超出该角度范围里的物体是不会被照亮的,这个衰减是过渡的,我们假设聚光灯方向与聚光灯到物体的方向夹角为 θ \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θ0cosθ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;

SRP中的光照_第4张图片

如果你觉得我的文章有帮助,欢迎关注我的微信公众号 我是真的想做游戏啊

Reference
[1] Lights

你可能感兴趣的:(unity,游戏引擎,shader)