Pixel Inspector是UE的一个原生Debug工具,本文剖析它的实现逻辑。
工具介绍
Pixel Inspector可以从Window-Developer Tools-Pixel Inspector打开:
点击左上角的Inspecting按钮,可以看到鼠标当前指向像素的信息:
其中的信息包括:
- 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工具需要包含以下这些模块或功能:
- 信息获取,采集当前Viewport的像素信息
- 窗口展示,将采集到的信息整理输出到窗口上
窗口展示部分没啥特别可以说的,本文主要关注信息获取部分,剖析Pixel信息的获取、传递问题:
其中的实线表示函数调用,虚线表示数据传递。
存储数据的类,主要涉及到三个:PixelInspectorResult
、SPixelInspector
、FPixelInspectorData
。
传递数据的过程,涉及到一个后处理Pass,以及一系列DecodeXXX解析函数。
接下来按照数据流向介绍各个过程和数据结构。
PixelInspector Pass
Pixel Inspector最关键的一个Pass是位于后处理的PixelInspector Pass,用于获取SceneTexture信息。这个Pass是集成在Renderer模块的。在Engine\Source\Runtime\Renderer\Private\PostProcess\PostProcessing.cpp
的AddPostProcessingPasses
函数可以找到注册代码:
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::CopySrc
,ERHIAccess::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拷贝请求,后面会详细介绍。需要补充几个点:
- 在这几个拷贝操作中,
FRHICopyTextureInfo
的Size
除了一处其他全部是(1, 1, 1)
。Size
为1可以理解,即只需要一个像素的信息;唯一一处不同是FinalColor,溯源可以知道它的大小定义在Engine\Source\Editor\PixelInspector\Private\PixelInspectorView.h
,是一个宏#define FinalColorContextGridSize 7
。这是因为在PixelInspector窗口里,FinalColor一栏会展示当前像素点及其周围邻域像素点的颜色:
最后,OutlineCornerMax
和OutlineCornerMin
的作用是防止拷贝区间溢出。 - 另外一个比较异常的是,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
还有一个名为Requests
的TMap
。这个Requests
保存的是从视口UV坐标到FPixelInspectorRequest
的映射,设计成TMap
的原因应该是防止同一个UV坐标(可以理解为同一个像素点)被重复执行。这个变量在前述的ProcessPixelInspectorRequests
函数用到。
FPixelInspectorData
一共只有两个成员函数,InitializeBuffers
和AddPixelInspectorRequest
,分别处理成员变量RenderTargetBufferXXX
和Requests
。
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::InitializeBuffers
被FScene::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;
}
这个函数的调用堆栈如下图所示:
调用的源头是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是绑定在一起的,如下图红框部分所示。
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;
}
这个函数在FScene
的AddPixelInspectorRequest
函数被调用:
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;
}
这里还有一个细节,就是FScene
的InitializePixelInspector
和AddPixelInspectorRequest
函数虽然都是继承自FSceneInterface
的虚函数,但是FPixelInspectorData PixelInspectorData
并不是FSceneInterface
的成员变量而是FScene
的。这个细节可以理解为,虚函数的设置是方便调用,很多地方只有FSceneInterface*
类型的指针而不是FScene*
。但是FSceneInterface
作为接口(虚类),没有保存FPixelInspectorData
数据的必要。这带来的一个不变就是,如果上下文里只有FSceneInterface*
,却想访问PixelInspectorData
,必须采用强制类型转换,例如在Engine\Source\Runtime\Renderer\Private\PostProcess\PostProcessBufferInspector.cpp
的ProcessPixelInspectorRequests
函数有这样一行:
FPixelInspectorData& PixelInspectorData = static_cast<FScene*>(ViewFamily.Scene)->PixelInspectorData;
同时,如果自己写了一个继承自FSceneInterface
的自定义场景类FMyScene
,也可以不采用FPixelInspectorData
而采用自定义的PixelInspectorData
。
回到FScene
的AddPixelInspectorRequest
函数。继续追踪它的调用堆栈,可以发现整个PixelInspector的调用链:
整个调用链的源头在FEditorViewportClient
的SetupViewForRendering
函数,在这里FSceneViewFamily
和FSceneView
会被设置好,之后会检测如果PixelInspectorModule
模块是Enable的(满足两个条件,1.PixelInspector窗口被打开,2.Inspecting按钮被激活),就会读取当前鼠标所处位置的UV坐标,并以此生成一个Inspector Request,保存起来。
EditorViewportClient
运行时,Pixel Inspector的一些数据是通过EditorViewportClient
传递过来的,参考Engine\Source\Editor\UnrealEd\Private\EditorViewportClient.cpp
的FEditorViewportClient::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
从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>
,这种格式也刚好是从FTextureRenderTargetResource
中ReadPixels
或ReadLinearColorPixels
返回的格式,见Engine\Source\Runtime\Engine\Public\UnrealClient.h
中FRenderTarget
的接口:
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
会对他们进行初始化,并调用FSceneInterface
的InitializePixelInspector
接口:
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(GBufferFormat
,PixelInspectorSceneViewExtension->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的不同,用到了前面提到的ReadLinearColorPixels
和ReadPixels
函数。
UPixelInspectorView
UPixelInspectorView
类的主要作用是保存展示在窗口的信息,它除了构造函数只有一个成员函数,SetFromResult
,然后就是一堆数据。SetFromResult
被SPixelInspector::ReadBackRequestData()
函数调用,将解析好的数据设置到UPixelInspectorView
里。
这个类比较方便查看Pixel Inspector都展示了哪些信息,这些信息又是什么含义。
FPixelInspectorModule
FPixelInspectorModule
类是Pixel Inspector模块唯一一个public的类,继承自IModuleInterface
,可以在模块外访问,例如在EditorViewPortClient里:
FPixelInspectorModule& PixelInspectorModule = FModuleManager::LoadModuleChecked<FPixelInspectorModule>(TEXT("PixelInspectorModule"));
这个类包含了关于Pixel Inspector的公开接口,也有很多创建窗口相关的接口,具体的就不再展开了。
总结
总的来说,Pixel Inspector是一个非常实用的工具,可以直接将GBuffer在内的像素信息展示出来,对美术来说很方便。从程序侧来说,这个工具的实现上有很多可以借鉴的地方,包括
- 从
SceneTextures
里获取信息 -
FSceneViewExtensionBase
接口的使用 - 从GPU往CPU传递信息的解决方案
唯一的一点遗憾是,这个工具与引擎的部分核心模块集成在一起,没有独立成单独的插件。