目标:使用 Ray Query 在片段着色器中添加一个简单的阴影测试。我们将从每个片段点向光源投射一条光线,如果命中某些物体则使片段变暗(硬阴影)。
恭喜:你已为场景构建了有效的 TLAS/BLAS!现在,让我们用它来投射一些光线。
在片段着色器中,我们将使用 Ray Query 从片段位置向光源投射阴影光线。如果光线在到达光源之前命中任何几何体,我们将使片段颜色变暗:
// TASK05:实现 Ray Query 阴影
bool in_shadow(float3 P)
{
bool hit = false;
return hit;
}
[shader("fragment")]
float4 fragMain(VSOutput vertIn) : SV_TARGET {
float4 baseColor = textures[pc.materialIndex].Sample(textureSampler, vertIn.fragTexCoord);
float3 P = vertIn.worldPos;
bool inShadow = in_shadow(P);
// 如果在阴影中则变暗
if (inShadow) {
baseColor.rgb *= 0.2;
}
return baseColor;
}
为此,你将实现一个辅助函数 in_shadow(),它执行 Ray Query。首先定义光线描述,并用片段位置和光线方向初始化它:
bool in_shadow(float3 P)
{
// 从世界位置向光源构建阴影光线
RayDesc shadowRayDesc;
shadowRayDesc.Origin = P;
shadowRayDesc.Direction = normalize(lightDir);
shadowRayDesc.TMin = EPSILON;
shadowRayDesc.TMax = 1e4;
TMin 和 TMax 定义了光线从其起点行进的最小和最大距离。EPSILON 是一个小值,用于避免自相交,1e4 是一个大值,以确保我们能够击中远处的物体。
接下来,我们将初始化一个 RayQuery 对象,用于执行光线遍历。注意我们选择的标志,以使其更快:
RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES,因为这是一个仅包含三角形的简单场景。
RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH,以便在找到第一个不透明交点时立即结束遍历,这对于阴影测试已经足够,因为我们只需要知道是否有物体阻挡光线。
// 初始化用于阴影的 RayQuery
RayQuery<RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES |
RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH> sq;
let rayFlags = RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES |
RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH;
然后我们将开始光线追踪操作,它结合了光线描述、RayQuery 对象和加速结构:
sq.TraceRayInline(accelerationStructure, rayFlags, 0xFF, shadowRayDesc);
sq.Proceed();
Proceed() 将 RayQuery 对象的状态推进到光线上的下一个交点“候选”。每次调用 Proceed() 都会检查是否还有另一个交点需要处理。如果有,它会更新查询的内部状态,以便你可以访问当前候选交点的信息。这允许你实现自定义的交点处理逻辑,例如跳过透明表面(我们将在本实验后面重新讨论)或在第一个不透明命中点停止。它通常在一个循环中调用,以遍历所有可能的交点,但对于阴影,我们只需要第一个命中点:
// 如果阴影光线命中不透明三角形,我们认为该像素处于阴影中
bool hit = (sq.CommittedStatus() == COMMITTED_TRIANGLE_HIT);
return hit;
}
就是这样!你已经使用 Ray Query 实现了一个基本的阴影测试。如果光线在到达光源之前命中任何几何体,in_shadow() 函数将返回 true,表示该片段处于阴影中。
使用以下设置重新构建并运行:
#define LAB_TASK_LEVEL 5
你会注意到有些不对劲:
物体在旋转,但阴影是静态的。这是因为我们还没有更新 TLAS 来考虑物体的动画。每当物体移动或动画时,TLAS 都需要重新构建,所以接下来让我们实现这一点。
Vulkan 是 Khronos Group Inc. 的注册商标
教程内容版权归原作者,遵循 CC BY-SA 4.0;本站独立代码及设计除外。