BlueRoseNote/03-UnrealEngine/Rendering/RayTracing/UE5RayTracing渲染管线笔记——(1).md
2023-06-29 11:55:02 +08:00

17 KiB
Raw Permalink Blame History

title, date, tags, rating
title date tags rating
UE5RayTracing渲染管线笔记——(1) 2022-08-09 13:55:15 RayTracing

任务

  • 查看这个如何针对各个View构建场景

渲染事件

RayTracingScene位于LumenSceneUpdate之后。

收集场景信息

场景信息

在FScene中定了这2个变量来存储RayTracing专用的场景信息

FRayTracingScene RayTracingScene;
TArray<FLightSceneInfo*, TInlineAllocator<4>> RayTracedLights;

FRayTracingScene还存储着FRayTracingGeometryInstance数组、TArray<const FRayTracingGeometry*> GeometriesToBuild、RayTracingSceneBuffer、RayTracingSceneSRV。

收集过程

主要的逻辑位于GatherRayTracingWorldInstancesForView()中通过RayTracingCollector来收集场景中的图元。在FSceneRenderer定义了MeshCollector与RayTracingCollector其中MeshCollector的GatherDynamicMeshElements()在计算可见性阶段被调用()。

// Gather mesh instances, shaders, resources, parameters, etc. and build ray tracing acceleration structure
FRayTracingScene& RayTracingScene = Scene->RayTracingScene;
RayTracingScene.Reset(); // Resets the internal arrays, but does not release any resources.

const int32 ReferenceViewIndex = 0;
FViewInfo& ReferenceView = Views[ReferenceViewIndex];

// Prepare the scene for rendering this frame.
GatherRayTracingWorldInstancesForView(GraphBuilder, ReferenceView, RayTracingScene);
  • GatherRayTracingWorldInstancesForView()
    • FGPUScenePrimitiveCollector DummyDynamicPrimitiveCollector;
    • 给RayTracingCollector的内部变量赋值RayTracingCollector.AddViewMeshArrays(&View,&View.RayTracedDynamicMeshElements,&View.SimpleElementCollector,&DummyDynamicPrimitiveCollector,ViewFamily.GetFeatureLevel(),&DynamicIndexBufferForInitViews,&DynamicVertexBufferForInitViews,&DynamicReadBufferForInitViews);
    • 创建Mesh资源收集器View.RayTracingMeshResourceCollector = MakeUnique<FRayTracingMeshResourceCollector>(...);
    • 初始化Rtx裁剪变量View.RayTracingCullingParameters.Init(View);
    • 创建FRayTracingMaterialGatheringContext MaterialGatheringContext{Scene,&View,ViewFamily,GraphBuilder,*View.RayTracingMeshResourceCollector};
    • 声明FRelevantPrimitive结构体实现InstancingKey()用于返回图元类型掩码。并定义FRelevantPrimitive数组长度为场景图元总数。
    • 遍历所有图元,

加速结构构建

RayTracingGem中有提到了加速结构的Rebuild与Refit概念。

该步骤会在BasePass()之前调用。DispatchRayTracingWorldUpdates()的注释说:

异步构建可能会与BasePass重合。 Async AS builds can potentially overlap with BasePass

GeometriesToBuild在GatherRayTracingWorldInstancesForView()被填充之后在DispatchRayTracingWorldUpdates()中通过**GRayTracingGeometryManager.ForceBuildIfPending(GraphBuilder.RHICmdList, RayTracingScene.GeometriesToBuild);**更新。

  • FRayTracingGeometryManager GRayTracingGeometryManager全局的场景管理类。
    • ForceBuildIfPending():添加需要强制构建的多边形。
    • ProcessBuildRequests():在排序请求后,调用**InCmdList.BuildAccelerationStructures(BuildParams);**构建加速结构。Render() 2634 =>DispatchRayTracingWorldUpdates()=>ProcessBuildRequests()=>InCmdList.BuildAccelerationStructures(BuildParams);

加速结构存在一个UAV上以FRayTracingGeometryBuildParams为单位。里面存储了FRayTracingGeometryRHIRef Geometry、BuildMode、以及TArrayView<const FRayTracingGeometrySegment> Segments;

FRayTracingScene

使用这个类来管理Rtx场景。

RayTracingCommon.h

UE使用宏来简化RayTracingShader的编写。 比如RayTracingShader入口函数

#ifndef RAY_TRACING_ENTRY_RAYGEN
#define RAY_TRACING_ENTRY_RAYGEN(name)\
[shader("raygeneration")] void name()
#endif // RAY_TRACING_ENTRY_RAYGEN

#ifndef RAY_TRACING_ENTRY_INTERSECTION
#define RAY_TRACING_ENTRY_INTERSECTION(name)\
[shader("intersection")] void name()
#endif //RAY_TRACING_ENTRY_INTERSECTION

#ifndef RAY_TRACING_ENTRY_CLOSEST_HIT
#define RAY_TRACING_ENTRY_CLOSEST_HIT(name, payload_type, payload_name, attributes_type, attributes_name)\
[shader("closesthit")] void name(inout payload_type payload_name, in attributes_type attributes_name)
#endif //RAY_TRACING_ENTRY_CLOSEST_HIT

#ifndef RAY_TRACING_ENTRY_ANY_HIT
#define RAY_TRACING_ENTRY_ANY_HIT(name, payload_type, payload_name, attributes_type, attributes_name)\
[shader("anyhit")] void name(inout payload_type payload_name, in attributes_type attributes_name)
#endif // RAY_TRACING_ENTRY_ANY_HIT

#ifndef RAY_TRACING_ENTRY_MISS
#define RAY_TRACING_ENTRY_MISS(name, payload_type, payload_name)\
[shader("miss")] void name(inout payload_type payload_name)
#endif //RAY_TRACING_ENTRY_MISS

所以Name需要与IMPLEMENT_GLOBAL_SHADER中定义的Shader入口函数名相同。

RayTracing函数

  • FMinimalPayload Payload=TraceVisibilityRay()
  • FMaterialClosestHitPayload Payload = TraceMaterialRay()

以及其他工具函数:

  • DispatchRaysIndex()
  • GetPixelCoord()

RenderRayTracingReflections

  • SortedDeferred

FRayTracingDeferredReflectionsRGS

RenderDiffuseIndirectAndAmbientOcclusion

RenderRayTracingAmbientOcclusion()

  • 遍历每个View
    • 计算使用对应方式计算GI。将结果传递给FDiffuseIndirectInputs对象。
    • 通过AmbientOcclusionRGS()RayTracing降噪得到AmbientOcclusionMask并传递给FDiffuseIndirectInputs.AmbientOcclusionMask。
    • 如果有头发会多渲染头发的AO。
    • 调用FDiffuseIndirectCompositePS将之前的渲染结果与GI、AO效果合成在一起。

FDiffuseIndirectCompositePS()

AmbientOcclusionRGS

RayTracingAmbientOcclusionRGS.usf

  • 计算UV、当前像素的FGBufferData以及WorldPosition与CameraDirection
  • 对于非SHADINGMODELID_TWOSIDED_FOLIAGE并且开启CONFIG_SHOOT_WITH_GEOMETRIC_NORMAL则重新计算法线
    • 通过HalfFOV * WorldDepth * ViewInvSize.z计算像素半径
    • 计算通过DDX与DDY计算几何法线。
    • 计算3个球形高斯分布没看懂
    • 初始化RayTracing相关变量开始RayTracing。如果不开启追踪Visibility = 1.0;RayCount = SamplesPerPixel;SamplesPerPixelLocal = 0.0;
      • 使用RandomSequence生成随机样本。之后调用GenerateCosineNormalRay()生成Ray。
        1. 调用RandomSequence_GenerateSample2D()取得2维随机样本。默认使用Sobol低差异序列其他还有Halton与Hash随机(https://github.com/skeeto/hash-prospector)
        2. 进行余弦-半球采样并转换局部坐标为世界空间。
        3. 完成Ray的初始化。
      • 调用ApplyCameraRelativeDepthBias()对Ray的起点进行摄像机->像素坐标方向的偏移一个ε,以解决浮点数不精确的问题。
      • 计算光线采样权重, max(dot(WorldNormal, Ray.Direction), 0.05) / max(RayPDF, 0.05);
      • 调用TraceVisibilityRay()进行RayTracing。
      • 累加采样结果。Tracing范围内没有遮挡物Visibility就是1否则就是1-IntensityLocal。该值为后处理空间里设定的AO亮度值RayTracingAOIntensity。如果Ray Hit还会设置新的ClosestRayHitDistance值。
      • 输出结果。 OcclusionMask=ShadingDotGeometric * (Visibility / RayCount);HitDistance=ClosestRayHitDistance;这两个UAV都是屏幕空间降噪器的贴图变量Shader处理完之后会进行降噪处理
  • 否则直接使用世界法线ShadingDotGeometric=1.0

RenderRayTracingSkyLight

  • 初始化FPathTracingSkylight SkylightParameters与FSkyLightData SkyLightData如果天光功能未开启则返回黑色OutSkyLightTexture与OutHitDistanceTexture。

  • 使用CVarRayTracingSkyLightScreenPercentage计算ResolutionFraction。

  • 创建RDG Texture资源RayTracingSkylight与RayTracingSkyLightHitDistance。

  • 调用GenerateSkyLightVisibilityRays()生成Ray样本集256*256格式为RWStructuredBuffer<SkyLightVisibilityRays>SkyLightVisibilityRays为方向与PDF值float4 DirectionAndPdf;

    • FGenerateSkyLightVisibilityRaysCS的流程
      1. 计算坐标UAV坐标与SkyLightSamplingStrategyPdf会使用SkylightPdf
        • 大概率是在PrepareSkyTexture()中进行了资源绑定SkylightParameters->SkylightPdf = GraphBuilder.RegisterExternalTexture(Scene->PathTracingSkylightPdf, TEXT("PathTracer.SkylightPdf"));
      2. 计算每个像素数据。
      3. 生成随机序列使用Hilbert curve算法: https://github.com/hcs0/Hackers-Delight/blob/master/hilbert/hil_s_from_xy.c.txt
      4. 使用Sobol算子采样来得到样本。
      5. 使用样本来计算天光采样结果,这一步会根据上下半球进行区分。
        • FSkyLightSample {float3 Direction;float3 Radiance;float Pdf;};
      6. 计算最终的半球混合PDFfloat MisWeightOverPdf = 1.0 / lerp(UniformPdf, SkyLightPdf, SkyLightSamplingStrategyPdf);
      7. 计算Ray的Index并将结果写入。
    • 创建用于输出结果的UAV对象OutSkyLightTexture、OutHitDistanceTexture并且取得SceneTextures。
    • 遍历所有View计算天光结果。
      • 填充FRayTracingSkyLightRGS::FParameters。如果视口内有头发将会额外绑定HairStrandsVoxelUniformParameters。
      • 设置FRayTracingSkyLightRGS变体。
      • 计算FIntPoint RayTracingResolution = View.ViewRect.Size() / UpscaleFactor;
      • RayTraceDispatch()。
      • Denoising
      • 如果SceneViewState有效返回SkyLightVisibilityRaysDimensions。
  • 合成SkyLight

FRayTracingSkyLightRGS

FRayTracingSkyLightRGS是一个GlobalShader但因为是一个RayTracing Shader所以宏的类型为

IMPLEMENT_GLOBAL_SHADER(FRayTracingSkyLightRGS, "/Engine/Private/Raytracing/RaytracingSkylightRGS.usf", "SkyLightRGS", SF_RayGen);

TLAS数据位于通过Scene->RayTracingScene->RayTracingSceneSRV。

PassParameters->TLAS = View.GetRayTracingSceneViewChecked();

FRHIShaderResourceView* FViewInfo::GetRayTracingSceneViewChecked() const
{
	FRHIShaderResourceView* Result = nullptr;
	check(Family);
	if (Family->Scene)
	{
		if (FScene* Scene = Family->Scene->GetRenderScene())
		{
			Result = Scene->RayTracingScene.GetShaderResourceViewChecked();
		}
	}
	checkf(Result, TEXT("Ray tracing scene SRV is expected to be created at this point."));
	return Result;
}

FRHIShaderResourceView* FRayTracingScene::GetShaderResourceViewChecked() const
{
	checkf(RayTracingSceneSRV.IsValid(), TEXT("Ray tracing scene SRV was not created. Perhaps BeginCreate() was not called."));
	return RayTracingSceneSRV.GetReference();
}

AddPass

RayGem的AddPass()标记为ERDGPassFlags::Compute。RHICmdList.RayTraceDispatch()需要RayGem管线状态、Shader、RayTracingSceneRHI、RayTracing资源与分辨率。 FRayTracingPipelineStateInitializer管线状态需要:

  • MaxPayloadSizeInBytes
  • RayGenShaderTable
  • HitGroupTable
  • bAllowHitGroupIndexing
FIntPoint RayTracingResolution = View.ViewRect.Size() / UpscaleFactor;
GraphBuilder.AddPass(
	RDG_EVENT_NAME("SkyLightRayTracing %dx%d", RayTracingResolution.X, RayTracingResolution.Y),
	PassParameters,
	ERDGPassFlags::Compute,
	[PassParameters, this, &View, RayGenerationShader, RayTracingResolution](FRHIRayTracingCommandList& RHICmdList)
{
	//资源绑定将Texture、UniformStruct绑定的工具函数
	FRayTracingShaderBindingsWriter GlobalResources;
	SetShaderParameters(GlobalResources, RayGenerationShader, *PassParameters);

	//取得RayTracing管线状态
	FRayTracingPipelineState* Pipeline = View.RayTracingMaterialPipeline;

	//如果没开启RayTracing天光材质则重新创建一个RayTracing管线状态。看得出主要需求RayGemShader与HitGroupTable
	if (CVarRayTracingSkyLightEnableMaterials.GetValueOnRenderThread() == 0)
	{
		// Declare default pipeline
		FRayTracingPipelineStateInitializer Initializer;
		Initializer.MaxPayloadSizeInBytes = RAY_TRACING_MAX_ALLOWED_PAYLOAD_SIZE; // sizeof(FPackedMaterialClosestHitPayload)
		FRHIRayTracingShader* RayGenShaderTable[] = { RayGenerationShader.GetRayTracingShader() };
		Initializer.SetRayGenShaderTable(RayGenShaderTable);

		FRHIRayTracingShader* HitGroupTable[] = { View.ShaderMap->GetShader<FOpaqueShadowHitGroup>().GetRayTracingShader() };
		Initializer.SetHitGroupTable(HitGroupTable);
		Initializer.bAllowHitGroupIndexing = false; // Use the same hit shader for all geometry in the scene by disabling SBT indexing.

		Pipeline = PipelineStateCache::GetAndOrCreateRayTracingPipelineState(RHICmdList, Initializer);
	}

	FRHIRayTracingScene* RayTracingSceneRHI = View.GetRayTracingSceneChecked();
	RHICmdList.RayTraceDispatch(Pipeline, RayGenerationShader.GetRayTracingShader(), RayTracingSceneRHI, GlobalResources, RayTracingResolution.X, RayTracingResolution.Y);
});

Shader

  • 计算DispatchThreadId以及对应的屏幕UV。并且取得对应的FGBufferData。
  • 计算出WorldPosition以及CameraDirection。
  • 判断是否需要追踪光线: 是否是有限深度 && 当前像素的ShaderModel不是Unlit。如果需要追踪采样数为传入Shader的 SkyLight.SamplesPerPixel否则为0。
  • 调用SkyLightEvaluate(),进行光追计算。
    • 初始化相关函数。
    • 计算天光采样PDF。
    • 采样循环
      • 根据bDecoupleSampleGeneration()选择执行使用SkyLightVisibilityRays的样本 或者使用随机序列生成样本。
      • 如果当前像素的ShadingModel是Hair需要重新计算CurrentWorldNormal。
      • 偏移当前光线的深度并计算NoL。
      • 设置RayFlags并且调用TraceVisibilityRay()进行光线追踪。返回FMinimalPayload存光线命中距离信息
      • 如果命中累加RayDistance与HitCount。如没命中累加BentNormal并且计算FDirectLighting光照计算Hair会用另一套计算方式最后累加ExitantRadiance、DiffuseThroughput、DiffuseExitantRadiance。
      • ExitantRadiance、DiffuseThroughput、DiffuseExitantRadiance除以样本数目HitDistance = RayDistance / HitCount
      • 如果当前像素的ShadingModel是Hair增加头发多重散射贡献值。
  • 合成估算结果。DiffuseExitantRadiance.r = Albedo.r > 0.0 ? DiffuseExitantRadiance.r / Albedo.r : DiffuseExitantRadiance.r;
  • 乘以曝光值。
  • 返回RWSkyOcclusionMaskUAV[DispatchThreadId]=float4(ClampToHalfFloatRange(DiffuseExitantRadiance.rgb), AmbientOcclusion);RWSkyOcclusionRayDistanceUAV[DispatchThreadId] = float2(HitDistance, SamplesPerPixel);

降噪Denoising

  • 调用IScreenSpaceDenoiser接口取得默认降噪器
  • 设置IScreenSpaceDenoiser::FDiffuseIndirectInputs的Color与RayHitDistance(OutSkyLightTexture、OutHitDistanceTexture)
  • 设置IScreenSpaceDenoiser::FAmbientOcclusionRayTracingConfig的ResolutionFraction与RayCountPerPixel(ResolutionFraction、GetSkyLightSamplesPerPixel(SkyLight))
  • 调用DenoiseSkyLight()输出降噪后的结果覆盖OutSkyLightTexture。
if (GRayTracingSkyLightDenoiser != 0)
{
	//取得默认降噪器
	const IScreenSpaceDenoiser* DefaultDenoiser = IScreenSpaceDenoiser::GetDefaultDenoiser();
	const IScreenSpaceDenoiser* DenoiserToUse = DefaultDenoiser;// GRayTracingGlobalIlluminationDenoiser == 1 ? DefaultDenoiser : GScreenSpaceDenoiser;

	//降噪器变量结构体需要使用之前的渲染结果以及Hit距离结果
	IScreenSpaceDenoiser::FDiffuseIndirectInputs DenoiserInputs;
	DenoiserInputs.Color = OutSkyLightTexture;
	DenoiserInputs.RayHitDistance = OutHitDistanceTexture;

	{
		//初始化RayTracingConfig
		IScreenSpaceDenoiser::FAmbientOcclusionRayTracingConfig RayTracingConfig;
		RayTracingConfig.ResolutionFraction = ResolutionFraction;
		RayTracingConfig.RayCountPerPixel = GetSkyLightSamplesPerPixel(SkyLight);

		RDG_EVENT_SCOPE(GraphBuilder, "%s%s(SkyLight) %dx%d",
			DenoiserToUse != DefaultDenoiser ? TEXT("ThirdParty ") : TEXT(""),
			DenoiserToUse->GetDebugName(),
			View.ViewRect.Width(), View.ViewRect.Height());

		//降噪
		IScreenSpaceDenoiser::FDiffuseIndirectOutputs DenoiserOutputs = DenoiserToUse->DenoiseSkyLight(
			GraphBuilder,
			View,
			&View.PrevViewInfo,
			SceneTextures,
			DenoiserInputs,
			RayTracingConfig);

		//输出结果
		OutSkyLightTexture = DenoiserOutputs.Color;
	}
}

RenderRayTracingDebug

位于渲染Fog与Translucency、VirtualTextureFeedbackEnd()之后

#if RHI_RAYTRACING
	if (IsRayTracingEnabled())
	{
		// Path tracer requires the full ray tracing pipeline support, as well as specialized extra shaders.
		// Most of the ray tracing debug visualizations also require the full pipeline, but some support inline mode.
		
		if (ViewFamily.EngineShowFlags.PathTracing 
			&& FDataDrivenShaderPlatformInfo::GetSupportsPathTracing(Scene->GetShaderPlatform()))
		{
			for (const FViewInfo& View : Views)
			{
				RenderPathTracing(GraphBuilder, View, SceneTextures.UniformBuffer, SceneTextures.Color.Target);
			}
		}
		else if (ViewFamily.EngineShowFlags.RayTracingDebug)
		{
			for (const FViewInfo& View : Views)
			{
				RenderRayTracingDebug(GraphBuilder, View, SceneTextures.Color.Target);
			}
		}
	}
#endif

DebugVisualizationMode具有3中模式

  • TRAVERSAL使用ComputeShader
  • RAY_TRACING_DEBUG_VIZ_PRIMARY_RAYS使用SF_RayGenFRayTracingPrimaryRaysRGS
  • DebugVisualizationMode == RAY_TRACING_DEBUG_VIZ_INSTANCES || DebugVisualizationMode == RAY_TRACING_DEBUG_VIZ_TRIANGLES;使用SF_RayGenFRayTracingDebugRGS