摄像机
在光线生成着色器中,我们需要根据摄像机所处的坐标和朝向来发出射线。在光栅化中,我们会计算出观察矩阵和投影矩阵,这两个矩阵也可以用于推算出射线信息,就不再需要存储额外的相机信息。
首先计算出屏幕的像素坐标相关信息:
1 2 3 4 5 6
|
float2 pixelCenter = DispatchRaysIndex().xy + (float2) 0.5f; float2 screenPos = pixelCenter / DispatchRaysDimensions().xy; screenPos = screenPos * 2.0f - 1.0f;
|
根据求得的像素坐标,应用观察矩阵和投影矩阵的逆即可获得射线的原点和方向,这个过程实际上是在将视锥体逆推回世界空间中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
#include "raycommon.hlsl"
struct PassConstants { float4x4 viewMatrix; float4x4 projMatrix; float4x4 invViewMatrix; float4x4 invProjMatrix; float tMin; float tMax; ... };
RaytracingAccelerationStructure rs : register(t0); RWTexture2D<float4> renderTarget : register(u1);
ConstantBuffer<PassConstants> passConstants : register(b0, space1);
[shader("raygeneration")] void main() { ...
float4 rayOrigin = mul(passConstants.invViewMatrix, float4(0.0f, 0.0f, 0.0f, 1.0f)); float4 rayTarget = mul(passConstants.invProjMatrix, float4(screenPos.x, screenPos.y, 1.0f, 1.0f)); float4 rayDirection = mul(passConstants.invViewMatrix, float4(normalize(rayTarget.xyz), 0.0f));
RayDesc rayDesc = { rayOrigin, passConstants.tMin, rayDirection, passConstants.tMax }; Payload rayPayload; TraceRay(rs, 0x0u, 0xffu, 0u, 1u, 0u, rayDesc, rayPayload); renderTarget[DispatchRaysIndex().xy] = rayPayload.color; }
|
我们的光线负载结构体定义在 raycommon.hlsl 中,所有光线追踪着色器都需要包含这个头文件:
1 2 3 4 5
|
struct Payload { float4 color; };
|
顶点属性插值
加速结构对于我们来说是一个黑盒,它的唯一作用就是让 GPU 去自动执行相交检测,我们无法从加速结构中得到想要的数据,所以无论是顶点缓冲、索引缓冲还是物体常量,都需要将其当成由数组构成的 Uniform Buffer,通过描述符在着色器中引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| struct VertexConstants { float3 localPos; float3 normal; float3 tangent; float2 texCoord; };
struct ObjectContants { float4x4 worldMatrix; uint vertexOffset; uint indexOffset; }
cbuffer VertexBuffer : register(b0, space2) { VertexConstants vertexConstantsArray[]; };
cbuffer IndexBuffer : register(b1, space2) { uint indices[]; };
cbuffer ObjectBuffer : register(b2, space2) { ObjectContants objectConstantsArray[]; };
|
HLSL 提供了以下函数,以便查询到当前所命中几何体的实例索引和图元索引:
InstanceIndex
:当前实例在顶层加速结构中自动生成的索引。
InstanceID
:用户在创建顶层加速结构时,为所引用的底层加速结构提供的标识,对应于VkAccelerationStructureInstanceKHR
中的instanceCustomIndex
。
PrimitiveIndex
:底层加速结构实例内部几何体中图元自动生成的索引。
我们首先通过InstanceIndex
或InstanceID
访问当前物体常量数据:
1
| ObjectConstants objectConstants = objectConstantsArray[InstanceID()];
|
接着根据实例对应的顶点和索引偏移以及PrimitiveIndex
,访问当前图元的顶点与索引数据:
1 2 3 4 5 6 7 8 9
| uint3 triangleIndices = uint3(indices[objectConstants.indexOffset + PrimitiveIndex() * 3], indices[objectConstants.indexOffset + PrimitiveIndex() * 3 + 1], indices[objectConstants.indexOffset + PrimitiveIndex() * 3 + 2]);
VertexConstants v0 = vertexConstantsArray[objectConstants.vertexOffset + triangleIndices.x]; VertexConstants v1 = vertexConstantsArray[objectConstants.vertexOffset + triangleIndices.y]; VertexConstants v2 = vertexConstantsArray[objectConstants.vertexOffset + triangleIndices.z];
|
和我们在顶点着色器里所做的一样,我们需要将顶点的坐标、法线、切线从局部坐标系变换到世界坐标系中:
1 2 3 4 5 6 7 8 9 10 11
| float3 worldPos0 = mul(objectConstants.worldMatrix, float4(v0.localPos, 1.0f)).xyz; float3 worldPos1 = mul(objectConstants.worldMatrix, float4(v0.localPos, 1.0f)).xyz; float3 worldPos2 = mul(objectConstants.worldMatrix, float4(v0.localPos, 1.0f)).xyz;
float3 normal0 = mul(objectConstants.worldMatrix, float4(v0.normal, 0.0f)).xyz; float3 normal1 = mul(objectConstants.worldMatrix, float4(v0.normal, 0.0f)).xyz; float3 normal2 = mul(objectConstants.worldMatrix, float4(v0.normal, 0.0f)).xyz;
float3 tangent0 = mul(objectConstants.worldMatrix, float4(v0.tangent, 0.0f)).xyz; float3 tangent1 = mul(objectConstants.worldMatrix, float4(v0.tangent, 0.0f)).xyz; float3 tangent2 = mul(objectConstants.worldMatrix, float4(v0.tangent, 0.0f)).xyz;
|
接下来我们要面对的问题便是是顶点属性插值,和光栅化管线不同,光线追踪管线很明显不会帮我们自动进行插值。对于三角形图元而言,我们可以在最近命中着色器中得到命中点的三角形重心坐标,使用重心坐标可以简单地完成插值操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
struct Attribute { float2 bary; };
[shader("closesthit")] void main(inout Payload output, in Attribute attribute) { ...
const float3 bary = float3(1.0f - attribute.bary.x - attribute.bary.y, attribute.bary.x, attribute.bary.y);
float3 worldPos = bary.x * worldPos0 + bary.y * worldPos1 + bary.z * worldPos2; float3 normal = bary.x * normal0 + bary.y * normal1 + bary.z * normal2; float3 tangent = bary.x * tangent0 + bary.y * tangent1 + bary.z * tangent2; float2 texCoord = bary.x * v0.texCoord + bary.y * v1.texCoord + bary.z * v2.texCoord;
... }
|
材质
一般我们会通过一系列材质常量和纹理贴图来定义一个材质,
散射
散射要求我们在光线与几何体相交后,发射出次生光线,
蒙特卡洛积分
降噪