UP | HOME

D3DRaytracing

Table of Contents

D3DRaytracing note.

<!– more –>

D3D

Conception

CBV constant buffer view
SRV shader resource view
UAV unordered access view

D3DRaytracing

hello world

Acceleration Structures

加速结构是不透明的数据结构,其表示了场景几何。渲染时,该结构用于射线相交检测。有两类加速结构:Bottom-Level Acceleration Structure, Top-Level Acceleration Structure。BLAS 表示 local-space mesh, 其不包含顶点的世界空间位置信息或实例信息。TLAS 表示整个场景,TLAS 会引用 BLAS,并存储了每个引用的 BLAS 对应的 local-to-world 变换矩阵。

Raytracing Pipeline State

和光栅化一样,Raytracing 也需要创建一个 pipleline state 来控制 fixed-function units,以及描述将会使用哪些 shader。

Shader-Libraries

dxcompiler 是新的 SM6.x 的编译器,其引入了 Shader-libraries 这个新的概念。Libraries 允许我们编译一个包含多个 shaders 的文件,并且不指定一个 entry point。

Ray-Tracing Shaders

DXR 引入了 5 种新的 shader 类型:ray-generation, miss, closet-hit, any-hit, intersection。

ray-generation shader
ray-generation shader 是 ray-tracing pipline 的第一个阶段。每一个 work item 会执行一次 ray-generation shader。在该 shader 中,用户可以生成 primary-rays,发布 ray-query calls。

miss-shader
当 raytrace query 没有 hit 到 TLAS 中的任何对象时,就会调用 miss-shader

hit-group
一个 hit group 是 Closest-Hit,Any-Hit,Intersection Shaders 的集合。其描述了如何进行相交检测,以及当检测到射线相交后的行为。

  • any hit shader :在遍历过程中,发现一个 intersection 时,就会调用该 shader。其主要用途是用于确定一个 intersection 是否应该被接受。例如,对于 alpha-tested 几何体,如果 alpha test 失败,则我们应该忽略该 intersection。
    Tips 1: 当创建加速结构时使用了 D3D12_RAYTRACING_GEOMETRY_FLAG_OPAQUE 标记,会忽略 any-hit shader。
    Tips 2: 当找到多个 intersection 时,any-hit shaders 的执行顺序是不定的。即 第一个调用的可能不是离原点最近的 intersection。而且对于指定的 ray,该 shader 调用的次数也可能会变化。
  • closest hit shader : 每次遍历中,只会为最近的 intersection 调用一次该 shader.
  • intersection shader : 当 primitive 类型为轴对齐包围盒时,才会调用该 shader。对于 triangles,无论是否指定了一个 intersection shader 都只会使用一个内部的 triangle-intersection shader。
Creating RT Pipeline State Object

创建 RT Pipeline State Object 和 Pipeline State Object 不同,ID3D12StateObjectPtr 对象是新的 DXR 的 state interface。没有使用类似 D3D12_GRAPHICS_PIPELINE_STATE_DESC 这样的结构体,来构建 RTPSO,我们需要构建一个 D3D12_STATE_SUBOBJECT 数组。每个 sub-object 描述了单个元素的状态。

  • DxilLibray
    对 D3D12_STATE_SUBOBJECT_TYPE_DXIL_LIBRARY 类型 sub-object 的抽象
  • HitProgram
    对 D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP 类型 sub-object 的抽象
  • LocalRootSignature
    DXR 引入了新的概念 Local Root Signature. 在 graphics 和 compute pipelines, 我们有单个全局的 root-signatrue 被所有 programs 所使用。对于 ray-tracing, 除此之外,我们可以创建 local root-signatures 并将他们绑定到特定 shaders。root-signature 的 size 会影响 Shader Binding Table 的 size,Local Root Signature 允许我们优化 Shader Binding Table.
  • ExportAssociation
    对 D3D12_STATE_SUBOBJECT_TYPE_SUBOBJECT_TO_EXPORTS_ASSOCIATION 类型 sub-object 的抽象。将一个 sub-object 绑定到 shaders 和 hit-groups。
  • ShaderConfig
    对 D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_SHADER_CONFIG 类型 sub-object 的抽象
  • PipelineConfig
    对 D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_PIPELINE_CONFIG 类型 sub-object 的抽象
  • GlobalRootSignature
    对 D3D12_STATE_SUBOBJECT_TYPE_GLOBAL_ROOT_SIGNATURE 类型 sub-object 的抽象
  • CreateStateObject
    创建 ID3D12StateObject
D3D12_STATE_OBJECT_DESC desc;
desc.NumSubobjects = index;
desc.pSubobjects = subobjects.data();
desc.Type = D3D12_STATE_OBJECT_TYPE_RAYTRACING_PIPELINE;
d3d_call(mpDevice->CreateStateObject(&desc, IID_PPV_ARGS(&mpPipelineState)));

ShaderTable

Shader-Table 是 GPU 可见的 buffer,该 buffer 由 application 所拥有和管理(由 application 进行分配、数据上传等)。shader-table 是 records 的数组,其有两个职责:

  1. 描述场景几何体和被执行的程序之间的关系。我们有多个 hit 和 miss 程序关联到 state object,当射线碰到一个几何体后我们需要知道执行哪个 shader 程序。
  2. 绑定资源到 pipeline。我们可以使用不同的 local root signature 来创建每一个 shader 程序。每个几何体可能需要不同的资源(vertex-buffer, textures, 等等) 集合

Tips: 当个 DispatchRays() 调用中,可以使用多个 shader-tables。

Shader Table Records

每个 shader table record 有两段。一个不透明的 shader program identifier,加一个 root table,root table 中包含了该 shader 的资源绑定。
这里的 root table 和 光栅化 pipeline 中的 root table 非常类似。不同点在于,我们直接设置 root table 中的条目,而不是使用 setter 方法。
root table 中不同条目的尺寸限制如下:

  • Root Constants 4 Bytes
  • Root Descriptors 8 Bytes
  • Descriptor Tables 8 Bytes (这和正常的 root signature 所需的尺寸不同)

Tips: root-descriptors 必须被存储到 8 字节对其的地址中。

Shader Table Layout

shader-table 是 shader-table records 的数组。没有规则限定 records 应该如何排布。有一系列参数可以决定 indexing 如何进行。

shader-table 中所有的 shader-table records 必须有相同的尺寸, 因此我们需要按照最大的 record 条目来确定 shader-table 的尺寸。

Raytrace

Shader Resources

BasicShaders

A Very Simplified Execution Description

在 ray-generation shader 中,创建 rays。每条 ray 会和 acceleration structure 做相交测试。如果 ray 没有碰到任何物体,miss shader 将会被调用。如果 ray 碰到了物体,则会为最近的 intersection 调用 closest hit shader。遍历和相交测试发生在 fixed-function unit。

Tracing Rays

DXR 引入了新的 HLSL 结构体-RayDesc, 以及新的内置函数 TraceRay(),该函数用于初始化一个 ray-tracing query.

struct RayDesc
{
    float3 Origin;    // 世界空间中ray 的原点
    float  TMin;
    float3 Direction; // 世界空间中ray的方向
    float  TMax;
};

TraceRay 参数解释:
1 Top Level Acceleration structure
2 ray flags. 这些 flags 可用于控制遍历行为,如 开启背面剔除
3 ray masks. 可用于整体剔除物体
4 RayContributionToHitGroupIndex 5 MultiplierForGeometryContributionToHitGroupIndex 用于索引 shader-table
6 miss-shader index。index 相对于调用 DispatchRays() 传递的 miss-shader index。我们只有 1 个 miss-shader, 因此为 0
7 RayDesc
8 用于 raytracing shader 各个阶段之间传递数据, 因此需要保证各个阶段所使用的定义一致

TraceRay 其实是一个模板函数,其会根据不同 RayPayload 生成不同函数。Payload 牵涉到 c++ 代码。当创建 ray-tracing 程序时,我们需要设置 payload 的最大尺寸到 D3D12_RAYTRACING_SHADER_CONFIG 中, 我们需要为单个 pipeline-state 中的所有程序使用相同的 maximum payload size。

下面为整个 raytracing 的 shader:

// TLAS 
RaytracingAccelerationStructure gRtScene : register(t0);
// UAV
RWTexture2D<float4> gOutput : register(u0);

float3 linearToSrgb(float3 c)
{
    // Based on http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
    float3 sq1 = sqrt(c);
    float3 sq2 = sqrt(sq1);
    float3 sq3 = sqrt(sq2);
    float3 srgb = 0.662002687 * sq1 + 0.684122060 * sq2 - 0.323583601 * sq3 - 0.0225411470 * c;
    return srgb;
}

struct RayPayload
{
    float3 color;
};

[shader("raygeneration")]
void rayGen()
{
    // 发射 Rays 的索引
    uint3 launchIndex = DispatchRaysIndex();
    // 发射 Rays 的范围(width,height,depth)
    uint3 launchDim = DispatchRaysDimensions();

    float2 crd = float2(launchIndex.xy);
    float2 dims = float2(launchDim.xy);

    float2 d = ((crd/dims) * 2.f - 1.f);
    float aspectRatio = dims.x / dims.y;

    RayDesc ray;
    ray.Origin = float3(0, 0, -2);
    ray.Direction = normalize(float3(d.x * aspectRatio, -d.y, 1));

    ray.TMin = 0;
    ray.TMax = 100000;

    RayPayload payload;
    
    TraceRay( gRtScene, 0 /*rayFlags*/, 0xFF /*rayMasks*/, 0 /* ray index*/, 0, 0 /* miss-shader idx */, ray, payload );
    float3 col = linearToSrgb(payload.color);
    gOutput[launchIndex.xy] = float4(col, 1);
}

[shader("miss")]
void miss(inout RayPayload payload)
{
    payload.color = float3(0.4, 0.6, 0.2);
}

[shader("closesthit")]
void chs(inout RayPayload payload, in BuiltInTriangleIntersectionAttributes attribs)
{
    float3 barycentrics = float3(1.0 - attribs.barycentrics.x - attribs.barycentrics.y, attribs.barycentrics.x, attribs.barycentrics.y);

    const float3 A = float3(1, 0, 0);
    const float3 B = float3(0, 1, 0);
    const float3 C = float3(0, 0, 1);

    payload.color = A * barycentrics.x + B * barycentrics.y + C * barycentrics.z;
}

Instancing

ConstantBuffer

PerInstanceConstantBuffer

SecondGeometry

PerGeometryHitShader

SecondRayType

Refit

参考资料