UE源码剖析 - Pixel Inspector

Pixel Inspector是UE的一个原生Debug工具,本文剖析它的实现逻辑。

工具介绍

Pixel Inspector可以从Window-Developer Tools-Pixel Inspector打开:
Menu
点击左上角的Inspecting按钮,可以看到鼠标当前指向像素的信息:
window
其中的信息包括:

  • Viewport信息,包括id、鼠标的坐标
  • Final color,指屏幕向显示的最终像素的信息
  • Scene color
  • HDR
  • GBuffer A
    • Normal
    • Per object data
  • GBuffer B, PBR工作流相关信息
    • Metallic
    • Specular
    • Roughness
    • Shading model
    • Selective mask
  • GBuffer C
    • BaseColor
    • Indirect irradiance
    • AO
  • GBuffer D,特殊shading model的相关信息,例如ClearCoat、Subsurface等。

源码剖析

Pixel Inspector相关的代码都在Engine\Source\Editor\PixelInspector\下,另外还有在一些PostProcessing和FScene类的代码。

一个完整的Pixel Inspector工具需要包含以下这些模块或功能:

  1. 信息获取,采集当前Viewport的像素信息
  2. 窗口展示,将采集到的信息整理输出到窗口上

窗口展示部分没啥特别可以说的,本文主要关注信息获取部分,剖析Pixel信息的获取、传递问题:
dataflow
其中的实线表示函数调用,虚线表示数据传递。

存储数据的类,主要涉及到三个:PixelInspectorResultSPixelInspectorFPixelInspectorData

传递数据的过程,涉及到一个后处理Pass,以及一系列DecodeXXX解析函数。

接下来按照数据流向介绍各个过程和数据结构。

PixelInspector Pass

1
Pixel Inspector最关键的一个Pass是位于后处理的PixelInspector Pass,用于获取SceneTexture信息。这个Pass是集成在Renderer模块的。在Engine\Source\Runtime\Renderer\Private\PostProcess\PostProcessing.cppAddPostProcessingPasses函数可以找到注册代码:

void AddPostProcessingPasses(FRDGBuilder& GraphBuilder, const FViewInfo& View, int32 ViewIndex, const FPostProcessingInputs& Inputs)
{
    ...
	if (PassSequence.IsEnabled(EPass::PixelInspector))
	{
		FPixelInspectorInputs PassInputs;
		PassSequence.AcceptOverrideIfLastPass(EPass::PixelInspector, PassInputs.OverrideOutput);
		PassInputs.SceneColor = SceneColor;
		PassInputs.SceneColorBeforeTonemap = SceneColorBeforeTonemap;
		PassInputs.OriginalSceneColor = OriginalSceneColor;

		SceneColor = AddPixelInspectorPass(GraphBuilder, View, PassInputs);
	}
    ...
}

完整的AddPixelInspectorPass函数实现在Engine\Source\Runtime\Renderer\Private\PostProcess\PostProcessBufferInspector.cpp下,完整的代码比较长,分开介绍。

首先是Pass的输入参数Parameter:

BEGIN_SHADER_PARAMETER_STRUCT(FPixelInspectorParameters, )
	RDG_TEXTURE_ACCESS(GBufferA, ERHIAccess::CopySrc)
	RDG_TEXTURE_ACCESS(GBufferB, ERHIAccess::CopySrc)
	RDG_TEXTURE_ACCESS(GBufferC, ERHIAccess::CopySrc)
	RDG_TEXTURE_ACCESS(GBufferD, ERHIAccess::CopySrc)
	RDG_TEXTURE_ACCESS(GBufferE, ERHIAccess::CopySrc)
	RDG_TEXTURE_ACCESS(GBufferF, ERHIAccess::CopySrc)
	RDG_TEXTURE_ACCESS(SceneColor, ERHIAccess::CopySrc)
	RDG_TEXTURE_ACCESS(SceneColorBeforeTonemap, ERHIAccess::CopySrc)
	RDG_TEXTURE_ACCESS(SceneDepth, ERHIAccess::CopySrc)
	RDG_TEXTURE_ACCESS(OriginalSceneColor, ERHIAccess::CopySrc)
END_SHADER_PARAMETER_STRUCT()

主要是GBuffer Texture和一些SceneColor、SceneDepth信息,需要注意的是这里RHIAccess全部都是用宏RDG_TEXTURE_ACCESS注册的ERHIAccess::CopySrcERHIAccess::CopySrc的意思是,这些纹理会作为拷贝的源,而不是在shader里进行采样。作为对比,shader里用到的采样纹理会这样注册:

	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ColorTexture)
	SHADER_PARAMETER_SAMPLER(SamplerState, ColorSampler)

PixelInspector Pass并没有shader绑定,而只进行Texture的copy操作。

AddPixelInspectorPass函数主要作用是添加PixelInspector Pass:

FScreenPassTexture AddPixelInspectorPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FPixelInspectorInputs& Inputs)
{
    ...
	RDG_EVENT_SCOPE(GraphBuilder, "PixelInspector");

	// Perform copies of scene textures data into staging resources for visualization.
	{
		FSceneTextureParameters SceneTextures = GetSceneTextureParameters(GraphBuilder);

		// GBufferF is optional, so it may be a dummy texture. Revert it to null if so.
		if (SceneTextures.GBufferFTexture->Desc.Extent != Inputs.OriginalSceneColor.Texture->Desc.Extent)
		{
			SceneTextures.GBufferFTexture = nullptr;
		}

		FPixelInspectorParameters* PassParameters = GraphBuilder.AllocParameters<FPixelInspectorParameters>();
		PassParameters->GBufferA = SceneTextures.GBufferATexture;
		PassParameters->GBufferB = SceneTextures.GBufferBTexture;
		PassParameters->GBufferC = SceneTextures.GBufferCTexture;
		PassParameters->GBufferD = SceneTextures.GBufferDTexture;
		PassParameters->GBufferE = SceneTextures.GBufferETexture;
		PassParameters->GBufferF = SceneTextures.GBufferFTexture;
		PassParameters->SceneColor = Inputs.SceneColor.Texture;
		PassParameters->SceneColorBeforeTonemap = Inputs.SceneColorBeforeTonemap.Texture;
		PassParameters->SceneDepth = SceneTextures.SceneDepthTexture;
		PassParameters->OriginalSceneColor = Inputs.OriginalSceneColor.Texture;

		const FIntRect SceneColorViewRect(Inputs.SceneColor.ViewRect);

		GraphBuilder.AddPass(
			RDG_EVENT_NAME("Copy"),
			PassParameters,
			ERDGPassFlags::Copy | ERDGPassFlags::NeverCull,
			[PassParameters, &View, SceneColorViewRect](FRHICommandListImmediate& RHICmdList)
		{
			ProcessPixelInspectorRequests(RHICmdList, View, *PassParameters, SceneColorViewRect);
		});
	}

	FScreenPassRenderTarget Output = Inputs.OverrideOutput;

    ...

	return MoveTemp(Output);
}

可以看出,PassParameter的纹理都是来自于SceneTextures的,UE里的SceneTextures结构包含了许多在各个Pass传递的公共纹理信息,包括GBuffer、DepthTexture等,定义在Engine\Source\Runtime\Renderer\Public\SceneRenderTargetParameters.h里:

/** A uniform buffer containing common scene textures used by materials or global shaders. */
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FSceneTextureUniformParameters, RENDERER_API)
	// Scene Color / Depth
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneColorTexture)
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneDepthTexture)

	// GBuffer
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferATexture)
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferBTexture)
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferCTexture)
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferDTexture)
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferETexture)
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferFTexture)
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D<uint>, GBufferGTexture) // SpeedEngine: new GBufferG parameter
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferVelocityTexture)

	// SSAO
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ScreenSpaceAOTexture)

	// Custom Depth / Stencil
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, CustomDepthTexture)
	SHADER_PARAMETER_SRV(Texture2D<uint2>, CustomStencilTexture)

	// Misc
	SHADER_PARAMETER_SAMPLER(SamplerState, PointClampSampler)
END_GLOBAL_SHADER_PARAMETER_STRUCT()

这个SceneTextures可以很方便的在各个Pass获取信息,很好用。

执行拷贝的具体代码在ProcessPixelInspectorRequests函数里:

void ProcessPixelInspectorRequests(
	FRHICommandListImmediate& RHICmdList,
	const FViewInfo& View,
	const FPixelInspectorParameters& Parameters,
	const FIntRect SceneColorViewRect)
{
	const FSceneViewFamily& ViewFamily = *(View.Family);
	const int32 ViewUniqueId = View.State->GetViewKey();

	FPixelInspectorData& PixelInspectorData = static_cast<FScene*>(ViewFamily.Scene)->PixelInspectorData;
	TArray<FVector2D> ProcessRequests;

	// Process all request for this view.
	for (auto KeyValue : PixelInspectorData.Requests)
	{
		FPixelInspectorRequest *PixelInspectorRequest = KeyValue.Value;
		if (PixelInspectorRequest->RequestComplete == true)
		{
			PixelInspectorRequest->RenderingCommandSend = true;
			ProcessRequests.Add(KeyValue.Key);
		}
		else if (PixelInspectorRequest->RenderingCommandSend == false && PixelInspectorRequest->ViewId == ViewUniqueId)
		{
			FVector2D SourceViewportUV = PixelInspectorRequest->SourceViewportUV;
			FVector2D ExtendSize(1.0f, 1.0f);

			//
			// Pixel Depth
			if (PixelInspectorData.RenderTargetBufferDepth[PixelInspectorRequest->BufferIndex] != nullptr)
			{
				const FIntVector SourcePoint(
					FMath::FloorToInt(SourceViewportUV.X * View.ViewRect.Width()),
					FMath::FloorToInt(SourceViewportUV.Y * View.ViewRect.Height()),
					0
				);

				const FTexture2DRHIRef &DestinationBufferDepth = PixelInspectorData.RenderTargetBufferDepth[PixelInspectorRequest->BufferIndex]->GetRenderTargetTexture();
				if (DestinationBufferDepth.IsValid())
				{
					FRHITexture* SourceBufferSceneDepth = Parameters.SceneDepth->GetRHI();
					if (DestinationBufferDepth->GetFormat() == SourceBufferSceneDepth->GetFormat())
					{
						FRHICopyTextureInfo CopyInfo;
						CopyInfo.SourcePosition = SourcePoint;
						CopyInfo.Size = FIntVector(1);
						RHICmdList.CopyTexture(SourceBufferSceneDepth, DestinationBufferDepth, CopyInfo);
					}
				}
			}

			//
			// FINAL COLOR
			const FTexture2DRHIRef &DestinationBufferFinalColor = PixelInspectorData.RenderTargetBufferFinalColor[PixelInspectorRequest->BufferIndex]->GetRenderTargetTexture();
			if (DestinationBufferFinalColor.IsValid())
			{
				const FIntVector SourcePoint(
					FMath::FloorToInt(SourceViewportUV.X * SceneColorViewRect.Width()),
					FMath::FloorToInt(SourceViewportUV.Y * SceneColorViewRect.Height()),
					0
				);

				FRHITexture* SourceBufferFinalColor = Parameters.SceneColor->GetRHI();
				if (DestinationBufferFinalColor->GetFormat() == SourceBufferFinalColor->GetFormat())
				{
					FRHICopyTextureInfo CopyInfo;
					CopyInfo.Size = DestinationBufferFinalColor->GetSizeXYZ();
					CopyInfo.SourcePosition = SourcePoint - CopyInfo.Size / 2;

					const FIntVector OutlineCornerMin(
						FMath::Min(CopyInfo.SourcePosition.X - SceneColorViewRect.Min.X, 0),
						FMath::Min(CopyInfo.SourcePosition.Y - SceneColorViewRect.Min.Y, 0),
						0
					);
					CopyInfo.SourcePosition -= OutlineCornerMin;
					CopyInfo.DestPosition -= OutlineCornerMin;
					CopyInfo.Size += OutlineCornerMin;

					const FIntVector OutlineCornerMax(
						FMath::Max(0, CopyInfo.SourcePosition.X + CopyInfo.Size.X - SceneColorViewRect.Max.X),
						FMath::Max(0, CopyInfo.SourcePosition.Y + CopyInfo.Size.Y - SceneColorViewRect.Max.Y),
						0
					);
					CopyInfo.Size -= OutlineCornerMax;

					if (CopyInfo.Size.X > 0 && CopyInfo.Size.Y > 0)
					{
						RHICmdList.CopyTexture(SourceBufferFinalColor, DestinationBufferFinalColor, CopyInfo);
					}
				}
			}

			//
			// ORIGINAL SCENE COLOR
			const FTexture2DRHIRef& DestinationBufferSceneColor = PixelInspectorData.RenderTargetBufferSceneColor[PixelInspectorRequest->BufferIndex]->GetRenderTargetTexture();
			if (DestinationBufferSceneColor.IsValid())
			{
				const FIntVector SourcePoint(
					FMath::FloorToInt(SourceViewportUV.X * View.ViewRect.Width()),
					FMath::FloorToInt(SourceViewportUV.Y * View.ViewRect.Height()),
					0
				);

				FRHITexture* SourceBufferSceneColor = Parameters.OriginalSceneColor->GetRHI();
				if (DestinationBufferSceneColor->GetFormat() == SourceBufferSceneColor->GetFormat())
				{
					FRHICopyTextureInfo CopyInfo;
					CopyInfo.SourcePosition = SourcePoint;
					CopyInfo.Size = FIntVector(1, 1, 1);
					RHICmdList.CopyTexture(SourceBufferSceneColor, DestinationBufferSceneColor, CopyInfo);
				}
			}

			//
			// HDR
			const FTexture2DRHIRef &DestinationBufferHDR = PixelInspectorData.RenderTargetBufferHDR[PixelInspectorRequest->BufferIndex]->GetRenderTargetTexture();
			if (DestinationBufferHDR.IsValid())
			{
				const FIntVector SourcePoint(
					FMath::FloorToInt(SourceViewportUV.X * SceneColorViewRect.Width()),
					FMath::FloorToInt(SourceViewportUV.Y * SceneColorViewRect.Height()),
					0
				);

				if (Parameters.SceneColorBeforeTonemap)
				{
					FRHITexture* SourceBufferHDR = Parameters.SceneColorBeforeTonemap->GetRHI();
					if (DestinationBufferHDR->GetFormat() == SourceBufferHDR->GetFormat())
					{
						FRHICopyTextureInfo CopyInfo;
						CopyInfo.SourcePosition = SourcePoint;
						CopyInfo.Size = FIntVector(1, 1, 1);
						RHICmdList.CopyTexture(SourceBufferHDR, DestinationBufferHDR, CopyInfo);
					}
				}
			}

			//
			// GBuffer A
			if (PixelInspectorData.RenderTargetBufferA[PixelInspectorRequest->BufferIndex] != nullptr)
			{
				const FIntVector SourcePoint(
					FMath::FloorToInt(SourceViewportUV.X * View.ViewRect.Width()),
					FMath::FloorToInt(SourceViewportUV.Y * View.ViewRect.Height()),
					0
				);

				const FTexture2DRHIRef &DestinationBufferA = PixelInspectorData.RenderTargetBufferA[PixelInspectorRequest->BufferIndex]->GetRenderTargetTexture();
				if (DestinationBufferA.IsValid() && Parameters.GBufferA)
				{
					FRHITexture* SourceBufferA = Parameters.GBufferA->GetRHI();
					if (DestinationBufferA->GetFormat() == SourceBufferA->GetFormat())
					{
						FRHICopyTextureInfo CopyInfo;
						CopyInfo.SourcePosition = SourcePoint;
						CopyInfo.Size = FIntVector(1, 1, 1);
						RHICmdList.CopyTexture(SourceBufferA, DestinationBufferA, CopyInfo);
					}
				}
			}

			//
			// GBuffer BCDEF
			const FTexture2DRHIRef &DestinationBufferBCDEF = PixelInspectorData.RenderTargetBufferBCDEF[PixelInspectorRequest->BufferIndex]->GetRenderTargetTexture();
			if (DestinationBufferBCDEF.IsValid())
			{
				const FIntVector SourcePoint(
					FMath::FloorToInt(SourceViewportUV.X * View.ViewRect.Width()),
					FMath::FloorToInt(SourceViewportUV.Y * View.ViewRect.Height()),
					0
				);

				if (Parameters.GBufferB)
				{
					FRHITexture* SourceBufferB = Parameters.GBufferB->GetRHI();
					if (DestinationBufferBCDEF->GetFormat() == SourceBufferB->GetFormat())
					{
						FRHICopyTextureInfo CopyInfo;
						CopyInfo.SourcePosition = SourcePoint;
						CopyInfo.Size = FIntVector(1, 1, 1);
						RHICmdList.CopyTexture(SourceBufferB, DestinationBufferBCDEF, CopyInfo);
					}
				}

				if (Parameters.GBufferC)
				{
					FRHITexture* SourceBufferC = Parameters.GBufferC->GetRHI();
					if (DestinationBufferBCDEF->GetFormat() == SourceBufferC->GetFormat())
					{
						FRHICopyTextureInfo CopyInfo;
						CopyInfo.SourcePosition = SourcePoint;
						CopyInfo.DestPosition = FIntVector(1, 0, 0);
						CopyInfo.Size = FIntVector(1, 1, 1);
						RHICmdList.CopyTexture(SourceBufferC, DestinationBufferBCDEF, CopyInfo);
					}
				}

				if (Parameters.GBufferD)
				{
					FRHITexture* SourceBufferD = Parameters.GBufferD->GetRHI();
					if (DestinationBufferBCDEF->GetFormat() == SourceBufferD->GetFormat())
					{
						FRHICopyTextureInfo CopyInfo;
						CopyInfo.SourcePosition = SourcePoint;
						CopyInfo.DestPosition = FIntVector(2, 0, 0);
						CopyInfo.Size = FIntVector(1, 1, 1);
						RHICmdList.CopyTexture(SourceBufferD, DestinationBufferBCDEF, CopyInfo);
					}
				}

				if (Parameters.GBufferE)
				{
					FRHITexture* SourceBufferE = Parameters.GBufferE->GetRHI();
					if (DestinationBufferBCDEF->GetFormat() == SourceBufferE->GetFormat())
					{
						FRHICopyTextureInfo CopyInfo;
						CopyInfo.SourcePosition = SourcePoint;
						CopyInfo.DestPosition = FIntVector(3, 0, 0);
						CopyInfo.Size = FIntVector(1, 1, 1);
						RHICmdList.CopyTexture(SourceBufferE, DestinationBufferBCDEF, CopyInfo);
					}
				}

				if (Parameters.GBufferF)
				{
					FRHITexture* SourceBufferF = Parameters.GBufferF->GetRHI();
					if (DestinationBufferBCDEF->GetFormat() == SourceBufferF->GetFormat())
					{
						FRHICopyTextureInfo CopyInfo;
						CopyInfo.SourcePosition = SourcePoint;
						CopyInfo.Size = FIntVector(1, 1, 1);
						RHICmdList.CopyTexture(SourceBufferF, DestinationBufferBCDEF, CopyInfo);
					}
				}
			}

			PixelInspectorRequest->RenderingCommandSend = true;
			ProcessRequests.Add(KeyValue.Key);
		}
	}

	// Remove request we just processed.
	for (FVector2D RequestKey : ProcessRequests)
	{
		PixelInspectorData.Requests.Remove(RequestKey);
	}
}

这部分代码比较长,但是非常整洁,自解释性强,主要处理的是PixelInspectorData的Requests,将SceneTexture里的数据拷贝到FPixelInspectorData的RenderTarget里。Requests由FScene::AddPixelInspectorRequest函数添加,记录的是需要拷贝的像素的坐标,一个Request可以理解为一个Pixel拷贝请求,后面会详细介绍。需要补充几个点:

  1. 在这几个拷贝操作中,FRHICopyTextureInfoSize除了一处其他全部是(1, 1, 1)Size为1可以理解,即只需要一个像素的信息;唯一一处不同是FinalColor,溯源可以知道它的大小定义在Engine\Source\Editor\PixelInspector\Private\PixelInspectorView.h,是一个宏#define FinalColorContextGridSize 7。这是因为在PixelInspector窗口里,FinalColor一栏会展示当前像素点及其周围邻域像素点的颜色:
    finalcolor
    最后,OutlineCornerMaxOutlineCornerMin的作用是防止拷贝区间溢出。
  2. 另外一个比较异常的是,GBuffer BCDEF部分。这几张GBuffer信息被保存在了一个DestinationBuffer里,通过CopyInfo.DestPosition调整拷贝目标位置。但是仔细观察,会发现GBuffer B和GBuffer F的目标坐标是一样的(均为默认0),这两者不会同时存在,实际上GBufferBCDEF的大小被限定为4x1,如FPixelInspectorData::InitializeBuffers函数规定的。

FPixelInspectorData

FPixelInspectorData是一个保存信息的结构体,它定义在Engine\Source\Runtime\Renderer\Private\ScenePrivate.h下面,也属于Renderer模块:

class FPixelInspectorData
{
public:
	FPixelInspectorData();

	void InitializeBuffers(FRenderTarget* BufferFinalColor, FRenderTarget* BufferSceneColor, FRenderTarget* BufferDepth, FRenderTarget* BufferHDR, FRenderTarget* BufferA, FRenderTarget* BufferBCDEF, int32 bufferIndex);

	bool AddPixelInspectorRequest(FPixelInspectorRequest *PixelInspectorRequest);

	//Hold the buffer array
	TMap<FVector2D, FPixelInspectorRequest *> Requests;

	FRenderTarget* RenderTargetBufferDepth[2];
	FRenderTarget* RenderTargetBufferFinalColor[2];
	FRenderTarget* RenderTargetBufferHDR[2];
	FRenderTarget* RenderTargetBufferSceneColor[2];
	FRenderTarget* RenderTargetBufferA[2];
	FRenderTarget* RenderTargetBufferBCDEF[2];
};

它的实例挂在在FScene类下:

class FScene : public FSceneInterface
{
public:
    ...
	FPixelInspectorData PixelInspectorData;
    ...
};

FPixelInspectorData的数据来源于后处理Pass(如前一节所述),存储在FRenderTarget里。这里有一个细节是,所有的FRenderTarget都是长度为2的数组,猜测这个设定类似于双缓冲区,Pipeline可以交替往这两套FRenderTarget里写入数据,当前使用哪一个Buffer由InitializeBuffers函数的bufferIndex指定,后面会遇到很多BufferIndex,都是一回事。

FPixelInspectorData还有一个名为RequestsTMap。这个Requests保存的是从视口UV坐标到FPixelInspectorRequest的映射,设计成TMap的原因应该是防止同一个UV坐标(可以理解为同一个像素点)被重复执行。这个变量在前述的ProcessPixelInspectorRequests函数用到。

FPixelInspectorData一共只有两个成员函数,InitializeBuffersAddPixelInspectorRequest,分别处理成员变量RenderTargetBufferXXXRequests

Buffers

FPixelInspectorData::InitializeBuffers用于根据输入的RenderTarget初始化RenderTargetBufferXXX

void FPixelInspectorData::InitializeBuffers(FRenderTarget* BufferFinalColor, FRenderTarget* BufferSceneColor, FRenderTarget* BufferDepth, FRenderTarget* BufferHDR, FRenderTarget* BufferA, FRenderTarget* BufferBCDEF, int32 BufferIndex)
{
	RenderTargetBufferFinalColor[BufferIndex] = BufferFinalColor;
	RenderTargetBufferDepth[BufferIndex] = BufferDepth;
	RenderTargetBufferSceneColor[BufferIndex] = BufferSceneColor;
	RenderTargetBufferHDR[BufferIndex] = BufferHDR;
	RenderTargetBufferA[BufferIndex] = BufferA;
	RenderTargetBufferBCDEF[BufferIndex] = BufferBCDEF;

	check(RenderTargetBufferBCDEF[BufferIndex] != nullptr);
	
	FIntPoint BufferSize = RenderTargetBufferBCDEF[BufferIndex]->GetSizeXY();
	check(BufferSize.X == 4 && BufferSize.Y == 1);

	if (RenderTargetBufferA[BufferIndex] != nullptr)
	{
		BufferSize = RenderTargetBufferA[BufferIndex]->GetSizeXY();
		check(BufferSize.X == 1 && BufferSize.Y == 1);
	}
	
	if (RenderTargetBufferFinalColor[BufferIndex] != nullptr)
	{
		BufferSize = RenderTargetBufferFinalColor[BufferIndex]->GetSizeXY();
		//The Final color grab an area and can change depending on the setup
		//It should at least contain 1 pixel but can be 3x3 or more
		check(BufferSize.X > 0 && BufferSize.Y > 0);
	}

	if (RenderTargetBufferDepth[BufferIndex] != nullptr)
	{
		BufferSize = RenderTargetBufferDepth[BufferIndex]->GetSizeXY();
		check(BufferSize.X == 1 && BufferSize.Y == 1);
	}

	if (RenderTargetBufferSceneColor[BufferIndex] != nullptr)
	{
		BufferSize = RenderTargetBufferSceneColor[BufferIndex]->GetSizeXY();
		check(BufferSize.X == 1 && BufferSize.Y == 1);
	}

	if (RenderTargetBufferHDR[BufferIndex] != nullptr)
	{
		BufferSize = RenderTargetBufferHDR[BufferIndex]->GetSizeXY();
		check(BufferSize.X == 1 && BufferSize.Y == 1);
	}
}

在这个函数只有一个值得关注的点,就是它规定了每个FRenderTarget的大小。

FPixelInspectorData::InitializeBuffersFScene::InitializePixelInspector函数调用:

bool FScene::InitializePixelInspector(FRenderTarget* BufferFinalColor, FRenderTarget* BufferSceneColor, FRenderTarget* BufferDepth, FRenderTarget* BufferHDR, FRenderTarget* BufferA, FRenderTarget* BufferBCDEF, int32 BufferIndex)
{
	//Initialize the buffers
	PixelInspectorData.InitializeBuffers(BufferFinalColor, BufferSceneColor, BufferDepth, BufferHDR, BufferA, BufferBCDEF, BufferIndex);
	//return true when the interface is implemented
	return true;
}

这个函数的调用堆栈如下图所示:
2
调用的源头是FEditorViewportClient::SetupViewForRendering,位于Engine\Source\Editor\UnrealEd\Private\EditorViewportClient.cpp

void FEditorViewportClient::SetupViewForRendering(FSceneViewFamily& ViewFamily, FSceneView& View)
{
    ...
	if (IsInspectorActive)
	{
		// Ready to send a request
		FSceneInterface *SceneInterface = GetScene();

		FVector2D InspectViewportUV(
			(InspectViewportPos.X + 0.5f) / float(View.UnscaledViewRect.Width()),
			(InspectViewportPos.Y + 0.5f) / float(View.UnscaledViewRect.Height()));

		PixelInspectorModule.CreatePixelInspectorRequest(InspectViewportUV, View.State->GetViewKey(), SceneInterface, bInGameViewMode, View.State->GetPreExposure());
	}
    else
    ...
}

也就是说,只有开启了PixelInspector模块启用时,才会执行到PixelInspectorModule.CreatePixelInspectorRequest,挂载在FScene下面的FPixelInspectorData才会被用到。另外,从这个调用堆栈可以看出,FPixelInspectorData::RenderTargetBufferXXX其实就是SPixelInspector::Buffer_XXX,这两组Buffer是绑定在一起的,如下图红框部分所示。
3

Requests

另一个FPixelInspectorData::AddPixelInspectorRequest函数用于添加Requests:

bool FPixelInspectorData::AddPixelInspectorRequest(FPixelInspectorRequest *PixelInspectorRequest)
{
	if (PixelInspectorRequest == nullptr)
		return false;
	FVector2D ViewportUV = PixelInspectorRequest->SourceViewportUV;
	if (Requests.Contains(ViewportUV))
		return false;
	
	//Remove the oldest request since the new request use the buffer
	if (Requests.Num() > 1)
	{
		FVector2D FirstKey(-1, -1);
		for (auto kvp : Requests)
		{
			FirstKey = kvp.Key;
			break;
		}
		if (Requests.Contains(FirstKey))
		{
			Requests.Remove(FirstKey);
		}
	}
	Requests.Add(ViewportUV, PixelInspectorRequest);
	return true;
}

这个函数在FSceneAddPixelInspectorRequest函数被调用:

bool FScene::AddPixelInspectorRequest(FPixelInspectorRequest *PixelInspectorRequest)
{
	return PixelInspectorData.AddPixelInspectorRequest(PixelInspectorRequest);
}

布尔返回值的意思是,定义在基类的虚函数被子类实现了,见Engine\Source\Runtime\Renderer\Private\ScenePrivate.h

	/**
	 * Add a pixel Inspector request.
	 * @return True if implemented false otherwise.
	 */
	virtual bool AddPixelInspectorRequest(class FPixelInspectorRequest *PixelInspectorRequest)
	{
		return false;
	}

这里还有一个细节,就是FSceneInitializePixelInspectorAddPixelInspectorRequest函数虽然都是继承自FSceneInterface的虚函数,但是FPixelInspectorData PixelInspectorData并不是FSceneInterface的成员变量而是FScene的。这个细节可以理解为,虚函数的设置是方便调用,很多地方只有FSceneInterface*类型的指针而不是FScene*。但是FSceneInterface作为接口(虚类),没有保存FPixelInspectorData数据的必要。这带来的一个不变就是,如果上下文里只有FSceneInterface*,却想访问PixelInspectorData,必须采用强制类型转换,例如在Engine\Source\Runtime\Renderer\Private\PostProcess\PostProcessBufferInspector.cppProcessPixelInspectorRequests函数有这样一行:

	FPixelInspectorData& PixelInspectorData = static_cast<FScene*>(ViewFamily.Scene)->PixelInspectorData;

同时,如果自己写了一个继承自FSceneInterface的自定义场景类FMyScene,也可以不采用FPixelInspectorData而采用自定义的PixelInspectorData

回到FSceneAddPixelInspectorRequest函数。继续追踪它的调用堆栈,可以发现整个PixelInspector的调用链:
callstack
整个调用链的源头在FEditorViewportClientSetupViewForRendering函数,在这里FSceneViewFamilyFSceneView会被设置好,之后会检测如果PixelInspectorModule模块是Enable的(满足两个条件,1.PixelInspector窗口被打开,2.Inspecting按钮被激活),就会读取当前鼠标所处位置的UV坐标,并以此生成一个Inspector Request,保存起来。

EditorViewportClient

运行时,Pixel Inspector的一些数据是通过EditorViewportClient传递过来的,参考Engine\Source\Editor\UnrealEd\Private\EditorViewportClient.cppFEditorViewportClient::SetupViewForRendering函数:

void FEditorViewportClient::SetupViewForRendering(FSceneViewFamily& ViewFamily, FSceneView& View)
{
    ...
    //Look if the pixel Inspector tool is on
	View.bUsePixelInspector = false;
	FPixelInspectorModule& PixelInspectorModule = FModuleManager::LoadModuleChecked<FPixelInspectorModule>(TEXT("PixelInspectorModule"));
	bool IsInspectorActive = PixelInspectorModule.IsPixelInspectorEnable();
	View.bUsePixelInspector = IsInspectorActive;
	FIntPoint InspectViewportPos = FIntPoint(-1, -1);
	if (IsInspectorActive)
	{
		if (CurrentMousePos == FIntPoint(-1, -1))
		{
			uint32 CoordinateViewportId = 0;
			PixelInspectorModule.GetCoordinatePosition(InspectViewportPos, CoordinateViewportId);

			bool IsCoordinateInViewport = InspectViewportPos.X <= Viewport->GetSizeXY().X && InspectViewportPos.Y <= Viewport->GetSizeXY().Y;
			IsInspectorActive = IsCoordinateInViewport && (CoordinateViewportId == View.State->GetViewKey());
			if (IsInspectorActive)
			{
				PixelInspectorModule.SetViewportInformation(View.State->GetViewKey(), Viewport->GetSizeXY());
			}
		}
		else
		{
			InspectViewportPos = CurrentMousePos;
			PixelInspectorModule.SetViewportInformation(View.State->GetViewKey(), Viewport->GetSizeXY());
			PixelInspectorModule.SetCoordinatePosition(InspectViewportPos, false);
		}
	}

	if (IsInspectorActive)
	{
		// Ready to send a request
		FSceneInterface *SceneInterface = GetScene();

		FVector2D InspectViewportUV(
			(InspectViewportPos.X + 0.5f) / float(View.UnscaledViewRect.Width()),
			(InspectViewportPos.Y + 0.5f) / float(View.UnscaledViewRect.Height()));

		PixelInspectorModule.CreatePixelInspectorRequest(InspectViewportUV, View.State->GetViewKey(), SceneInterface, bInGameViewMode, View.State->GetPreExposure());
	}
	else if (!View.bUsePixelInspector && CurrentMousePos != FIntPoint(-1, -1))
	{
		//Track in case the user hit esc key to stop inspecting pixel
		PixelInspectorRealtimeManagement(this, true);
	}
}

这段代码主要做了两件事情,一件是在Pixel Inspector开启(active)的情况下,生成一个Request,如前面介绍的;另一件事情是将当前鼠标的位置传递给Pixel Inpector。这里用的是CurrentMousePos变量,它保存的是鼠标位于LevelEditor窗口的坐标,以左上角为原点,如果鼠标不在LevelEditor内则赋值(-1,-1)。但是这个变量属于protected类型,无法在类外使用。还有一个类似功能的变量也可以替换使用,那就是FSceneView::CursorPos,只要能拿到FSceneView的地方就可以获取到。

PixelInspectorResult

4
SceneTextures拷贝下来的信息保存在SPixelInspector::Buffer_XXX以后,下一步就轮到PixelInspectorResult类执行解码操作了,见Engine\Source\Editor\PixelInspector\Private\PixelInspectorResult.h

class PixelInspectorResult
{
public:
    // Data Identification
    int32 ViewUniqueId;
    FVector2D ViewportUV;

    //
    // PreExposure used to render this frame. See "r.UsePreExposure"
    float PreExposure;
    float OneOverPreExposure;

    //
    // Final color 3x3 grid
    TArray<FLinearColor> FinalColor;

    //
    // Scene color
    FLinearColor SceneColor;

    //
    // Depth and world position
    float Depth;
    FVector WorldPosition;

    //
    // HDR Values
    float HdrLuminance;
    FLinearColor HdrColor;

    //
    //Buffers value
    FVector Normal; //GBufferA RGB
    float PerObjectGBufferData; //GBufferA A
    float Metallic; //GBufferB R
    float Specular; //GBufferB G
    float Roughness; //GBufferB B
    EMaterialShadingModel ShadingModel; //GBufferB A encode
    int32 SelectiveOutputMask; //GBufferB A encode
    FLinearColor BaseColor; //GBufferC RGB
    
    //Irradiance and Ambient occlusion decoding
    float IndirectIrradiance; //GBufferC A encode only if static light is allow 1 otherwise
    float AmbientOcclusion; //GBufferC A if static light is not allow 1 otherwise

    //
    // Per shader model Data

    //MSM_Subsurface
    //MSM_PreintegratedSkin
    //MSM_TwoSidedFoliage
    FLinearColor SubSurfaceColor; // GBufferD RGB
    float Opacity; // GBufferD A

    //MSM_SubsurfaceProfile
    FVector SubsurfaceProfile; // GBufferD RGB

    //MSM_ClearCoat
    float ClearCoat; // GBufferD R
    float ClearCoatRoughness; // GBufferD G

    //MSM_Hair
    FVector WorldNormal;
    float BackLit;

    //MSM_Cloth
    float Cloth;

    //MSM_Eye
    FVector EyeTangent;
    float IrisMask;
    float IrisDistance;
}

可以看出,在PixelInspectorResult里,像素信息已经不是类似UTextureRenderTarget2D这种格式了,而是被Decode为方便读写的float、array等格式。这得益于PixelInspectorResult的Decode函数:

class PixelInspectorResult
{
public:
    void DecodeFinalColor(TArray<FColor>& BufferFinalColorValue);
    /** Decodes final color from HDR input. */
    void DecodeFinalColor(TArray<FLinearColor> &BufferFinalColorValue, float InGamma, bool bHasAlphaChannel);
    void DecodeSceneColor(TArray<FLinearColor> &BufferSceneColorValue);
    void DecodeDepth(TArray<FLinearColor> &BufferDepthValue);
    void DecodeHDR(TArray<FLinearColor> &BufferHDRValue);

    void DecodeBufferData(TArray<FColor> &BufferAValue, TArray<FColor> &BufferBCDEValue, bool AllowStaticLighting);
    void DecodeBufferData(TArray<FLinearColor> &BufferAValue, TArray<FColor> &BufferBCDEValue, bool AllowStaticLighting);
    void DecodeBufferData(TArray<FFloat16Color> &BufferAValue, TArray<FFloat16Color> &BufferBCDEValue, bool AllowStaticLighting);

private:
    void DecodeBufferA(TArray<FColor> &BufferAValue);
    void DecodeBufferA(TArray<FLinearColor> &BufferAValue);
    void DecodeBufferA(TArray<FFloat16Color> &BufferAValue);

    void DecodeBufferBCDE(TArray<FColor> &BufferBCDEValue, bool AllowStaticLighting);
    void DecodeBufferBCDE(TArray<FFloat16Color> &BufferBCDEValue, bool AllowStaticLighting);

    FVector4 ConvertLinearRGBAToFloat(FColor LinearRGBColor);
    FVector ConvertLinearRGBToFloat(FColor LinearRGBColor);
    FVector ConvertLinearRGBToFloat(uint8 Red, uint8 Green, uint8 Blue);
    FLinearColor DecodeSubSurfaceColor(FVector EncodeColor);
    FVector DecodeNormalFromBuffer(FVector NormalEncoded);
    EMaterialShadingModel DecodeShadingModel(float InPackedChannel);
    uint32 DecodeSelectiveOutputMask(float InPackedChannel);
    float DecodeIndirectIrradiance(float IndirectIrradiance);
    FVector OctahedronToUnitVector(FVector2D Oct);
    void DecodeCustomData(FVector4 InCustomData);
};

各个函数的功能从名字即可推断,不再赘述。值得注意的一点是,Decode函数的输入大部分是TArray<FColor>TArray<FLinearColor>,这种格式也刚好是从FTextureRenderTargetResourceReadPixelsReadLinearColorPixels返回的格式,见Engine\Source\Runtime\Engine\Public\UnrealClient.hFRenderTarget的接口:

ENGINE_API bool ReadPixels(TArray<FColor>& OutImageData,FReadSurfaceDataFlags InFlags = FReadSurfaceDataFlags(RCM_UNorm, CubeFace_MAX), FIntRect InRect = FIntRect(0, 0, 0, 0) );
ENGINE_API bool ReadLinearColorPixels(TArray<FLinearColor>& OutputBuffer, FReadSurfaceDataFlags InFlags = FReadSurfaceDataFlags(RCM_MinMax, CubeFace_MAX), FIntRect InRect = FIntRect(0, 0, 0, 0));

SPixelInspector

到目前为止,我们已经了解到数据从SceneTextures一路传递到PixelInspectorResult的全过程。

但是这个过程里有一个关键的类还没有介绍到,那就是SPixelInspector。完整的定义在Engine\Source\Editor\PixelInspector\Private\PixelInspector.h

SPixelInspector是一个比较综合的类,串起了完整的流程,承载了很多功能。整个类的成员、函数分为几大类,这里仅介绍数据相关的部分。

FPixelInspectorSceneViewExtension

SPixelInspector有这样一个成员变量:

TSharedPtr< class FPixelInspectorSceneViewExtension, ESPMode::ThreadSafe > PixelInspectorSceneViewExtension;

它是FPixelInspectorSceneViewExtension类型的,这个跟SPixelInspector定义在同一个文件,继承自FSceneViewExtensionBase,利用了UE的SceneViewExtension接口,从FSceneViewFamily中获取Gamma值,从SceneColor中获取Pixel Format。

SceneViewExtension的介绍见另一篇文章

直接看它具体做事的函数:

void FPixelInspectorSceneViewExtension::BeginRenderViewFamily(FSceneViewFamily& InViewFamily)
{
    const float DisplayGamma = InViewFamily.RenderTarget->GetDisplayGamma();
    // We need to apply gamma to the final color if the output is in HDR.
    FinalColorGamma = (InViewFamily.EngineShowFlags.Tonemapper == 0) ? DEFAULT_DISPLAY_GAMMA : DisplayGamma;
}

void FPixelInspectorSceneViewExtension::SubscribeToPostProcessingPass(EPostProcessingPass PassId, FAfterPassCallbackDelegateArray& InOutPassCallbacks, bool bIsPassEnabled)
{
    if (PassId == EPostProcessingPass::FXAA)
    {
        InOutPassCallbacks.Add(FAfterPassCallbackDelegate::CreateRaw(this, &FPixelInspectorSceneViewExtension::PostProcessPassAfterFxaa_RenderThread));
    }
}

FScreenPassTexture FPixelInspectorSceneViewExtension::PostProcessPassAfterFxaa_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& View, const FPostProcessMaterialInputs& InOutInputs)
{
    FinalColorPixelFormat = InOutInputs.Textures[(uint32)EPostProcessMaterialInput::SceneColor].Texture->Desc.Format;

    if (InOutInputs.OverrideOutput.IsValid())
    {
        return InOutInputs.OverrideOutput;
    }
    else
    {
        /** We don't want to modify scene texture in any way. We just want it to be passed back onto the next stage. */
        FScreenPassTexture SceneTexture = const_cast<FScreenPassTexture&>(InOutInputs.Textures[(uint32)EPostProcessMaterialInput::SceneColor]);
        return SceneTexture;
    }

}

其实这里的AfterPass function无所谓在哪一个pass后执行,只要是一个确定的Pass即可,因为我们需要获取的内容跟具体的后处理Pass无关。这个Format将用于解析SceneTexture。

BufferXXX相关

前面介绍FPixelInspectorData时提到过,FPixelInspectorData类自己并不初始化Buffer,而是使用FPixelInspectorData::InitializeBuffers函数传入的Buffer。也就是说,FPixelInspectorData只是一个搬运工。而真正维护这些buffer的,恰恰是SPixelInspector。在定义里有这样一段数据:

class SPixelInspector : public SCompoundWidget, public FNotifyHook
{
    ...
    //
    //Buffer management we can do only one pixel inspection per frame
    //We have two buffer of each type to not halt the render thread when we do the read back from the GPU
    //FinalColor Buffer
    UTextureRenderTarget2D* Buffer_FinalColor_AnyFormat[2];
    //Depth Buffer
    UTextureRenderTarget2D* Buffer_Depth_Float[2];
    //SceneColor Buffer
    UTextureRenderTarget2D* Buffer_SceneColor_Float[2];
    //HDR Buffer
    UTextureRenderTarget2D* Buffer_HDR_Float[2];
    //GBufferA RenderTarget
    UTextureRenderTarget2D* Buffer_A_Float[2];
    UTextureRenderTarget2D* Buffer_A_RGB8[2];
    UTextureRenderTarget2D* Buffer_A_RGB10[2];
    //GBuffer BCDE RenderTarget
    UTextureRenderTarget2D* Buffer_BCDEF_Float[2];
    UTextureRenderTarget2D* Buffer_BCDEF_RGB8[2];
    //Which index we are at for the current Request
    int32 LastBufferIndex;
    ...
}

这些成员变量即真正保存Pixel信息的地方。在函数SPixelInspector::CreateRequestBuffer会对他们进行初始化,并调用FSceneInterfaceInitializePixelInspector接口:

void SPixelInspector::CreatePixelInspectorRequest(FVector2D InspectViewportUV, int32 viewportUniqueId, FSceneInterface *SceneInterface, bool bInGameViewMode, float InPreExposure)
{
    if (TickSinceLastCreateRequest < MINIMUM_TICK_BETWEEN_CREATE_REQUEST)
        return;

    //Make sure we dont get value outside the viewport size
    if ( InspectViewportUV.X >= 1.0f || InspectViewportUV.Y >= 1.0f || InspectViewportUV.X <= 0.0f || InspectViewportUV.Y <= 0.0f )
    {
        return;
    }

    TickSinceLastCreateRequest = 0;
    // We need to know if the GBuffer is in low, default or high precision buffer
    const auto CVarGBufferFormat = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.GBufferFormat"));
    //0: lower precision (8bit per component, for profiling)
    //1: low precision (default)
    //5: high precision
    const int32 GBufferFormat = CVarGBufferFormat != nullptr ? CVarGBufferFormat->GetValueOnGameThread() : 1;

    // We need to know the static lighting mode to decode properly the buffers
    const auto CVarAllowStaticLighting = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
    //0: false
    //1: true
    //default: true
    const bool AllowStaticLighting = CVarAllowStaticLighting != nullptr ? CVarAllowStaticLighting->GetValueOnGameThread() == 1 : true;
    
    //Try to create the request buffer
    int32 BufferIndex = CreateRequestBuffer(SceneInterface, GBufferFormat, bInGameViewMode);
    if (BufferIndex == -1)
        return;
    
    Requests[BufferIndex].SetRequestData(InspectViewportUV, BufferIndex, viewportUniqueId, GBufferFormat, AllowStaticLighting, InPreExposure);
    SceneInterface->AddPixelInspectorRequest(&(Requests[BufferIndex]));
}

int32 SPixelInspector::CreateRequestBuffer(FSceneInterface *SceneInterface, const int32 GBufferFormat, bool bInGameViewMode)
{
    //Toggle the last buffer Index
    LastBufferIndex = (LastBufferIndex + 1) % 2;
    
    //Check if we have an available request
    if (Requests[LastBufferIndex].RequestComplete == false)
    {
        //Put back the last buffer position
        LastBufferIndex = (LastBufferIndex - 1) % 2;
        return -1;
    }
    
    //Release the old buffer
    ReleaseBuffers(LastBufferIndex);

    FTextureRenderTargetResource* FinalColorRenderTargetResource = nullptr;
    FTextureRenderTargetResource* SceneColorRenderTargetResource = nullptr;
    FTextureRenderTargetResource* HDRRenderTargetResource = nullptr;
    FTextureRenderTargetResource* DepthRenderTargetResource = nullptr;
    FTextureRenderTargetResource* BufferARenderTargetResource = nullptr;
    FTextureRenderTargetResource* BufferBCDEFRenderTargetResource = nullptr;
    
    //Final color can be in HDR (FloatRGBA) or RGB8 formats so we should rely on scene view extension to tell us which format is being used.
    Buffer_FinalColor_AnyFormat[LastBufferIndex] = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), TEXT("PixelInspectorBufferFinalColorTarget"), RF_Standalone);
    Buffer_FinalColor_AnyFormat[LastBufferIndex]->AddToRoot();
    Buffer_FinalColor_AnyFormat[LastBufferIndex]->InitCustomFormat(FinalColorContextGridSize, FinalColorContextGridSize, PixelInspectorSceneViewExtension->GetPixelFormat(), true);
    Buffer_FinalColor_AnyFormat[LastBufferIndex]->ClearColor = FLinearColor::Black;
    Buffer_FinalColor_AnyFormat[LastBufferIndex]->UpdateResourceImmediate(true);
    FinalColorRenderTargetResource = Buffer_FinalColor_AnyFormat[LastBufferIndex]->GameThread_GetRenderTargetResource();

    //Scene color is in RGB8 format
    {
        Buffer_SceneColor_Float[LastBufferIndex] = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), TEXT("PixelInspectorBufferSceneColorTarget"), RF_Standalone);
        Buffer_SceneColor_Float[LastBufferIndex]->AddToRoot();
        Buffer_SceneColor_Float[LastBufferIndex]->InitCustomFormat(1, 1, BufferFormat, true);
        Buffer_SceneColor_Float[LastBufferIndex]->ClearColor = FLinearColor::Black;
        Buffer_SceneColor_Float[LastBufferIndex]->UpdateResourceImmediate(true);
        SceneColorRenderTargetResource = Buffer_SceneColor_Float[LastBufferIndex]->GameThread_GetRenderTargetResource();
    }

    //HDR is in float RGB format
    Buffer_HDR_Float[LastBufferIndex] = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), TEXT("PixelInspectorBufferHDRTarget"), RF_Standalone);
    Buffer_HDR_Float[LastBufferIndex]->AddToRoot();

    {
        if (!bInGameViewMode)
        {
            Buffer_HDR_Float[LastBufferIndex]->InitCustomFormat(1, 1, PF_FloatRGBA, true);
        }
        else
        {
            Buffer_HDR_Float[LastBufferIndex]->InitCustomFormat(1, 1, PF_FloatRGB, true);
        }
    }

    Buffer_HDR_Float[LastBufferIndex]->ClearColor = FLinearColor::Black;
    Buffer_HDR_Float[LastBufferIndex]->UpdateResourceImmediate(true);
    HDRRenderTargetResource = Buffer_HDR_Float[LastBufferIndex]->GameThread_GetRenderTargetResource();
    
    //Low precision GBuffer
    if (GBufferFormat == EGBufferFormat::Force8BitsPerChannel)
    {
        //All buffer are PF_B8G8R8A8
        Buffer_A_RGB8[LastBufferIndex] = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), TEXT("PixelInspectorBufferATarget"), RF_Standalone );
        Buffer_A_RGB8[LastBufferIndex]->AddToRoot();
        Buffer_A_RGB8[LastBufferIndex]->InitCustomFormat(1, 1, PF_B8G8R8A8, true);
        Buffer_A_RGB8[LastBufferIndex]->ClearColor = FLinearColor::Black;
        Buffer_A_RGB8[LastBufferIndex]->UpdateResourceImmediate(true);
        BufferARenderTargetResource = Buffer_A_RGB8[LastBufferIndex]->GameThread_GetRenderTargetResource();

        Buffer_BCDEF_RGB8[LastBufferIndex] = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), TEXT("PixelInspectorBufferBTarget"), RF_Standalone );
        Buffer_BCDEF_RGB8[LastBufferIndex]->AddToRoot();
        Buffer_BCDEF_RGB8[LastBufferIndex]->InitCustomFormat(4, 1, PF_B8G8R8A8, true);
        Buffer_BCDEF_RGB8[LastBufferIndex]->ClearColor = FLinearColor::Black;
        Buffer_BCDEF_RGB8[LastBufferIndex]->UpdateResourceImmediate(true);
        BufferBCDEFRenderTargetResource = Buffer_BCDEF_RGB8[LastBufferIndex]->GameThread_GetRenderTargetResource();
    }
    else if(GBufferFormat == EGBufferFormat::Default)
    {
        //Default is PF_A2B10G10R10
        Buffer_A_RGB10[LastBufferIndex] = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), TEXT("PixelInspectorBufferATarget"), RF_Standalone );
        Buffer_A_RGB10[LastBufferIndex]->AddToRoot();
        Buffer_A_RGB10[LastBufferIndex]->InitCustomFormat(1, 1, PF_A2B10G10R10, true);
        Buffer_A_RGB10[LastBufferIndex]->ClearColor = FLinearColor::Black;
        Buffer_A_RGB10[LastBufferIndex]->UpdateResourceImmediate(true);
        BufferARenderTargetResource = Buffer_A_RGB10[LastBufferIndex]->GameThread_GetRenderTargetResource();

        //Default is PF_B8G8R8A8
        Buffer_BCDEF_RGB8[LastBufferIndex] = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), TEXT("PixelInspectorBufferBTarget"), RF_Standalone );
        Buffer_BCDEF_RGB8[LastBufferIndex]->AddToRoot();
        Buffer_BCDEF_RGB8[LastBufferIndex]->InitCustomFormat(4, 1, PF_B8G8R8A8, true);
        Buffer_BCDEF_RGB8[LastBufferIndex]->ClearColor = FLinearColor::Black;
        Buffer_BCDEF_RGB8[LastBufferIndex]->UpdateResourceImmediate(true);
        BufferBCDEFRenderTargetResource = Buffer_BCDEF_RGB8[LastBufferIndex]->GameThread_GetRenderTargetResource();
    }
    else if (GBufferFormat == EGBufferFormat::HighPrecisionNormals || GBufferFormat == EGBufferFormat::Force16BitsPerChannel)
    {
        //All buffer are PF_FloatRGBA
        Buffer_A_Float[LastBufferIndex] = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), TEXT("PixelInspectorBufferATarget"), RF_Standalone );
        Buffer_A_Float[LastBufferIndex]->AddToRoot();
        Buffer_A_Float[LastBufferIndex]->InitCustomFormat(1, 1, PF_FloatRGBA, true);
        Buffer_A_Float[LastBufferIndex]->ClearColor = FLinearColor::Black;
        Buffer_A_Float[LastBufferIndex]->UpdateResourceImmediate(true);
        BufferARenderTargetResource = Buffer_A_Float[LastBufferIndex]->GameThread_GetRenderTargetResource();

        Buffer_BCDEF_Float[LastBufferIndex] = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), TEXT("PixelInspectorBufferBTarget"), RF_Standalone );
        Buffer_BCDEF_Float[LastBufferIndex]->AddToRoot();
        Buffer_BCDEF_Float[LastBufferIndex]->InitCustomFormat(4, 1, PF_FloatRGBA, true);
        Buffer_BCDEF_Float[LastBufferIndex]->ClearColor = FLinearColor::Black;
        Buffer_BCDEF_Float[LastBufferIndex]->UpdateResourceImmediate(true);
        BufferBCDEFRenderTargetResource = Buffer_BCDEF_Float[LastBufferIndex]->GameThread_GetRenderTargetResource();
    }
    else
    {
        checkf(0, TEXT("Unhandled gbuffer format (%i) during pixel Inspector initializtion."), GBufferFormat);
    }	
    
    SceneInterface->InitializePixelInspector(FinalColorRenderTargetResource, SceneColorRenderTargetResource, DepthRenderTargetResource, HDRRenderTargetResource, BufferARenderTargetResource, BufferBCDEFRenderTargetResource, LastBufferIndex);

    return LastBufferIndex;
}

同样是在这里,FPixelInspectorSceneViewExtension获取的信息被利用上了:

PixelInspectorSceneViewExtension->GetPixelFormat()

根据不同的Format(GBufferFormatPixelInspectorSceneViewExtension->GetPixelFormat())来初始化Buffer。

另外CreatePixelInspectorRequest函数用到了两个CVar变量,具体细节注释里有详细的阐述,不再赘述。

ReadBackRequestData

Pixel信息保存在Buffer_xxxx之后,下一步需要将这些信息解析出来,传递给PixelInspectorResult。这个过程是ReadBackRequestData函数触发的:

void SPixelInspector::ReadBackRequestData()
{
    for (int RequestIndex = 0; RequestIndex < UE_ARRAY_COUNT(Requests); ++RequestIndex)
    {
        FPixelInspectorRequest& Request = Requests[RequestIndex];
        if (Request.RequestComplete == false && Request.RenderingCommandSend == true)
        {
            if (Request.FrameCountAfterRenderingCommandSend >= WAIT_FRAMENUMBER_BEFOREREADING)
            {
                if (Request.SourceViewportUV == FVector2D(-1, -1))
                {
                    continue;
                }

                PixelInspectorResult PixelResult;
                PixelResult.ViewportUV = Request.SourceViewportUV;
                PixelResult.ViewUniqueId = Request.ViewId;
                PixelResult.PreExposure = Request.PreExposure;
                PixelResult.OneOverPreExposure = Request.PreExposure > 0.f ? (1.f / Request.PreExposure) : 1.f;;

                FTextureRenderTargetResource* RTResourceFinalColor = Buffer_FinalColor_AnyFormat[Request.BufferIndex]->GameThread_GetRenderTargetResource();
                const EPixelFormat FinalColorPixelFormat = PixelInspectorSceneViewExtension->GetPixelFormat();
                if (FinalColorPixelFormat == PF_B8G8R8A8)
                {
                    TArray<FColor> BufferFinalColorValue;
                    if (RTResourceFinalColor->ReadPixels(BufferFinalColorValue) == false)
                    {
                        BufferFinalColorValue.Empty();
                    }
                    PixelResult.DecodeFinalColor(BufferFinalColorValue);
                }
                else if (FinalColorPixelFormat == PF_FloatRGBA || FinalColorPixelFormat == PF_FloatRGB)
                {
                    TArray<FLinearColor> BufferFinalColorValueLinear;
                    if (RTResourceFinalColor->ReadLinearColorPixels(BufferFinalColorValueLinear) == false)
                    {
                        BufferFinalColorValueLinear.Empty();
                    }
                    PixelResult.DecodeFinalColor(BufferFinalColorValueLinear, PixelInspectorSceneViewExtension->GetGamma(), FinalColorPixelFormat == PF_FloatRGBA);
                }

                TArray<FLinearColor> BufferSceneColorValue;
                FTextureRenderTargetResource* RTResourceSceneColor = Buffer_SceneColor_Float[Request.BufferIndex]->GameThread_GetRenderTargetResource();
                if (RTResourceSceneColor->ReadLinearColorPixels(BufferSceneColorValue) == false)
                {
                    BufferSceneColorValue.Empty();
                }
                PixelResult.DecodeSceneColor(BufferSceneColorValue);

                if (Buffer_Depth_Float[Request.BufferIndex] != nullptr)
                {
                    TArray<FLinearColor> BufferDepthValue;
                    FTextureRenderTargetResource* RTResourceDepth = Buffer_Depth_Float[Request.BufferIndex]->GameThread_GetRenderTargetResource();
                    if (RTResourceDepth->ReadLinearColorPixels(BufferDepthValue) == false)
                    {
                        BufferDepthValue.Empty();
                    }
                    PixelResult.DecodeDepth(BufferDepthValue);
                }

                TArray<FLinearColor> BufferHDRValue;
                FTextureRenderTargetResource* RTResourceHDR = Buffer_HDR_Float[Request.BufferIndex]->GameThread_GetRenderTargetResource();
                if (RTResourceHDR->ReadLinearColorPixels(BufferHDRValue) == false)
                {
                    BufferHDRValue.Empty();
                }
                PixelResult.DecodeHDR(BufferHDRValue);

                if (Request.GBufferPrecision == EGBufferFormat::Force8BitsPerChannel)
                {
                    TArray<FColor> BufferAValue;
                    FTextureRenderTargetResource* RTResourceA = Buffer_A_RGB8[Request.BufferIndex]->GameThread_GetRenderTargetResource();
                    if (RTResourceA->ReadPixels(BufferAValue) == false)
                    {
                        BufferAValue.Empty();
                    }

                    TArray<FColor> BufferBCDEFValue;
                    FTextureRenderTargetResource* RTResourceBCDEF = Buffer_BCDEF_RGB8[Request.BufferIndex]->GameThread_GetRenderTargetResource();
                    if (RTResourceA->ReadPixels(BufferBCDEFValue) == false)
                    {
                        BufferBCDEFValue.Empty();
                    }

                    PixelResult.DecodeBufferData(BufferAValue, BufferBCDEFValue, Request.AllowStaticLighting);
                }
                else if (Request.GBufferPrecision == EGBufferFormat::Default)
                {
                    //PF_A2B10G10R10 format is not support yet
                    TArray<FLinearColor> BufferAValue;
                    FTextureRenderTargetResource* RTResourceA = Buffer_A_RGB10[Request.BufferIndex]->GameThread_GetRenderTargetResource();
                    if (RTResourceA->ReadLinearColorPixels(BufferAValue) == false)
                    {
                        BufferAValue.Empty();
                    }

                    TArray<FColor> BufferBCDEFValue;
                    FTextureRenderTargetResource* RTResourceBCDEF = Buffer_BCDEF_RGB8[Request.BufferIndex]->GameThread_GetRenderTargetResource();
                    if (RTResourceBCDEF->ReadPixels(BufferBCDEFValue) == false)
                    {
                        BufferBCDEFValue.Empty();
                    }
                    PixelResult.DecodeBufferData(BufferAValue, BufferBCDEFValue, Request.AllowStaticLighting);
                }
                else if (Request.GBufferPrecision == EGBufferFormat::HighPrecisionNormals || Request.GBufferPrecision == EGBufferFormat::Force16BitsPerChannel)
                {
                    //PF_A2B10G10R10 format is not support yet
                    TArray<FFloat16Color> BufferAValue;
                    FTextureRenderTargetResource* RTResourceA = Buffer_A_Float[Request.BufferIndex]->GameThread_GetRenderTargetResource();
                    if (RTResourceA->ReadFloat16Pixels(BufferAValue) == false)
                    {
                        BufferAValue.Empty();
                    }

                    TArray<FFloat16Color> BufferBCDEFValue;
                    FTextureRenderTargetResource* RTResourceBCDEF = Buffer_BCDEF_Float[Request.BufferIndex]->GameThread_GetRenderTargetResource();
                    if (RTResourceA->ReadFloat16Pixels(BufferBCDEFValue) == false)
                    {
                        BufferBCDEFValue.Empty();
                    }
                    PixelResult.DecodeBufferData(BufferAValue, BufferBCDEFValue, Request.AllowStaticLighting);
                }
                else
                {
                    checkf(0, TEXT("Unhandled gbuffer format (%i) during pixel Inspector readback."), Request.GBufferPrecision);
                }

                AccumulationResult.Add(PixelResult);
                ReleaseBuffers(RequestIndex);
                Request.RequestComplete = true;
                Request.RenderingCommandSend = true;
                Request.FrameCountAfterRenderingCommandSend = 0;
                Request.RequestTickSinceCreation = 0;
            }
            else
            {
                Requests[RequestIndex].FrameCountAfterRenderingCommandSend++;
            }
        }
        else if (Requests[RequestIndex].RequestComplete == false)
        {
            Requests[RequestIndex].RequestTickSinceCreation++;
            if (Requests[RequestIndex].RequestTickSinceCreation > PIXEL_INSPECTOR_REQUEST_TIMEOUT)
            {
                ReleaseBuffers(RequestIndex);
                Requests[RequestIndex].RequestComplete = true;
                Requests[RequestIndex].RenderingCommandSend = true;
                Requests[RequestIndex].FrameCountAfterRenderingCommandSend = 0;
                Requests[RequestIndex].RequestTickSinceCreation = 0;
            }
        }
    }
    if (AccumulationResult.Num() > 0)
    {
        if (DisplayResult == nullptr)
        {
            DisplayResult = NewObject<UPixelInspectorView>(GetTransientPackage(), FName(TEXT("PixelInspectorDisplay")), RF_Standalone);
            DisplayResult->AddToRoot();
        }
        DisplayResult->SetFromResult(AccumulationResult[0]);
        DisplayDetailsView->SetObject(DisplayResult, true);
        LastViewportInspectionPosition.X = AccumulationResult[0].ViewportUV.X * LastViewportInspectionSize.X;
        LastViewportInspectionPosition.Y = AccumulationResult[0].ViewportUV.Y * LastViewportInspectionSize.Y;
        LastViewportId = AccumulationResult[0].ViewUniqueId;
        AccumulationResult.RemoveAt(0);
    }
}

这个函数将Buffer_xxx里的信息读出来,调用PixelInspectorResult类的Decode函数解析,然后保存在成员变量TArray<PixelInspectorResult> AccumulationResult;里。这里根据PixelInspectorSceneViewExtension->GetPixelFormat()Format的不同,用到了前面提到的ReadLinearColorPixelsReadPixels函数。
5

UPixelInspectorView

UPixelInspectorView类的主要作用是保存展示在窗口的信息,它除了构造函数只有一个成员函数,SetFromResult,然后就是一堆数据。SetFromResultSPixelInspector::ReadBackRequestData()函数调用,将解析好的数据设置到UPixelInspectorView里。
6
这个类比较方便查看Pixel Inspector都展示了哪些信息,这些信息又是什么含义。

FPixelInspectorModule

FPixelInspectorModule类是Pixel Inspector模块唯一一个public的类,继承自IModuleInterface,可以在模块外访问,例如在EditorViewPortClient里:

FPixelInspectorModule& PixelInspectorModule = FModuleManager::LoadModuleChecked<FPixelInspectorModule>(TEXT("PixelInspectorModule"));

这个类包含了关于Pixel Inspector的公开接口,也有很多创建窗口相关的接口,具体的就不再展开了。

总结

总的来说,Pixel Inspector是一个非常实用的工具,可以直接将GBuffer在内的像素信息展示出来,对美术来说很方便。从程序侧来说,这个工具的实现上有很多可以借鉴的地方,包括

  1. SceneTextures里获取信息
  2. FSceneViewExtensionBase接口的使用
  3. 从GPU往CPU传递信息的解决方案

唯一的一点遗憾是,这个工具与引擎的部分核心模块集成在一起,没有独立成单独的插件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值