← 返回项目列表

DXR 路径追踪器

本项目采用DirectX 光线追踪管线模拟全局光照,结合基于物理的渲染 (PBR) 与路径追踪,实现照片级真实感的渲染效果。

DXRHLSLPBRIOR

路径追踪管线

对于每个像素,发射32条随机射线,每条射线最多会反弹6次,用俄罗斯轮盘赌提前终止一些射线。路径追踪结束后,得到累积的Radiance并取平均值来计算最终结果。

Cornell Box中进行路径追踪

Sponza中进行路径追踪

float3 PathTracing(in float3 startPos, in float3 startDir, inout uint seed)
{
	float3 radiance = 0.0f;
	float3 attenuation = 1.0f;

	RayDesc ray = Ray(startPos, startDir, rayTmin, rayTmax);
	RayPayload prd;
	prd.seed = seed;
	prd.rayDepth = 0;

	while(prd.rayDepth < maxPathLength)
	{
		TraceRay(scene, 0, ~0, 0, 1, 0, ray, prd);
	
		//Russian roulette
		float probContinue = max(prd.attenuation.r, max(prd.attenuation.g, prd.attenuation.b));
		if (prd.rayDepth > 3)  
		{
		    if (rnd(seed) > probContinue)
		        break; 

		    prd.attenuation /= probContinue;
		}

		radiance += attenuation * prd.radiance;
		attenuation *= prd.attenuation;
	
		ray.Origin = prd.hitPos;
		ray.Direction = prd.bounceDir;
		++prd.rayDepth;
	}
	
	seed = prd.seed;

	return radiance;
}

路径追踪的 HLSL 代码

PBR

对于 PBR,通过 Cook-Torrance BRDF 来计算 BRDF 的镜面部分。

对于BRDF中的DFG项,D为Trowbridge-Reitz,G为Smith,F为Fresnel-Schlick。

场景中的 PBR 物体

场景中的 PBR 物体

我实现了Fresnel-Schlick 反射、基于折射率 (IOR) 的折射和全内反射 (TIR)。通过菲涅尔系数以概率方式确定光线是反射还是折射,从而确保真实的光线相互作用。

场景中的玻璃材质

场景中的玻璃材质

DXR 管线

我将 DirectX 光线追踪管线集成到我的引擎中。在我的光追管线中,

  • Build加速结构
  • 设置RayGen、Miss、HitGroup
  • 设置根签名
  • 设置SBT
  • DispatchRays

DirectX 光线追踪管线

void DXRPathTracer::BuildRaytracingPipeline()
{
	m_rtPipeline.setDXRLib(&m_dxrLib);
	m_rtPipeline.setGlobalRootSignature(&m_globalRS);
	m_rtPipeline.addHitGroup(HitGroup(L"hitGp", L"closestHit", nullptr));
	m_rtPipeline.addHitGroup(HitGroup(L"hitGpGlass", L"closestHitGlass", nullptr));
	m_rtPipeline.addLocalRootSignature(LocalRootSignature(&m_hitGroupRS, { L"hitGp", L"hitGpGlass" }));
	m_rtPipeline.setMaxPayloadSize(sizeof(float) * 16);
	m_rtPipeline.setMaxRayDepth(2);
	m_rtPipeline.build();
}
void DXRPathTracer::SetupShaderTable()
{
	ShaderIdentifier* rayGenID = m_rtPipeline.getIdentifier(L"rayGen");
	ShaderIdentifier* missRayID = m_rtPipeline.getIdentifier(L"missRay");
	ShaderIdentifier* missShadowID = m_rtPipeline.getIdentifier(L"missShadow");
	ShaderIdentifier* hitGpID = m_rtPipeline.getIdentifier(L"hitGp");
	ShaderIdentifier* hitGpGlassID = m_rtPipeline.getIdentifier(L"hitGpGlass");

	uint numObjs = m_scene->GetObjectsNum();
	mShaderTable.create(recordSize, numObjs + 3);

	HitGroupRecord* table = (HitGroupRecord*) mShaderTable.map();
	table[0].data.shaderIdentifier = *rayGenID;
	table[1].data.shaderIdentifier = *missRayID;
	table[2].data.shaderIdentifier = *missShadowID;

	auto& mtlArr = m_scene->GetMaterialArray();
	for (uint i = 0; i < numObjs; ++i)
	{
		if(mtlArr[ m_scene->GetObject(i).m_materialIdx ].type == Glass)
			table[3 + i].data.shaderIdentifier = *hitGpGlassID;
		else
			table[3 + i].data.shaderIdentifier = *hitGpID;

		table[3 + i].data.objConsts.objectIdx = i;
	}

	mShaderTable.uploadData(m_cmdList);
}

void DXRPathTracer::BuildAccelerationStructure()
{
	uint numObjs = m_scene->GetObjectsNum();
	Array gpuMeshArr(numObjs);
	Array transformArr(numObjs);

	D3D12_GPU_VIRTUAL_ADDRESS vtxAddr = m_vertexBuffer.getGpuAddress();
	D3D12_GPU_VIRTUAL_ADDRESS tdxAddr = m_tridexBuffer.getGpuAddress();
	for (uint objIdx = 0; objIdx < numObjs; ++objIdx)
	{
		const SceneObject& obj = m_scene->GetObject(objIdx);
		
		gpuMeshArr[objIdx].numVertices = obj.m_numVertices;
		gpuMeshArr[objIdx].vertexBufferVA = vtxAddr + obj.m_vertexOffset * sizeof(VertexPNU);
		gpuMeshArr[objIdx].numTridices = obj.m_numTridices;
		gpuMeshArr[objIdx].tridexBufferVA = tdxAddr + obj.m_tridexOffset * sizeof(Tridex);
		
		transformArr[objIdx] = obj.m_modelMatrix;
	}
	
	m_accelerationStructure.build(m_cmdList, gpuMeshArr, transformArr, 
		sizeof(VertexPNU), 1, m_buildMode, m_buildFlags);

	ThrowFailedHR(m_cmdList->Close());
	ID3D12CommandList* cmdLists[] = { m_cmdList };
	m_cmdQueue->ExecuteCommandLists(1, cmdLists);
	m_fence.waitCommandQueue(m_cmdQueue);
	ThrowFailedHR(m_cmdAllocator->Reset());
	ThrowFailedHR(m_cmdList->Reset(m_cmdAllocator, nullptr));
}

DXR 初始化引擎代码

截图