• 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);
    	}
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    完整的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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ColorTexture)
    	SHADER_PARAMETER_SAMPLER(SamplerState, ColorSampler)
    
    • 1
    • 2

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    可以看出,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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    这个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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246

    这部分代码比较长,但是非常整洁,自解释性强,主要处理的是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];
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    它的实例挂在在FScene类下:

    class FScene : public FSceneInterface
    {
    public:
        ...
    	FPixelInspectorData PixelInspectorData;
        ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    在这个函数只有一个值得关注的点,就是它规定了每个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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这个函数的调用堆栈如下图所示:
    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
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    也就是说,只有开启了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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

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

    bool FScene::AddPixelInspectorRequest(FPixelInspectorRequest *PixelInspectorRequest)
    {
    	return PixelInspectorData.AddPixelInspectorRequest(PixelInspectorRequest);
    }
    
    • 1
    • 2
    • 3
    • 4

    布尔返回值的意思是,定义在基类的虚函数被子类实现了,见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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里还有一个细节,就是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;
    
    • 1

    同时,如果自己写了一个继承自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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    这段代码主要做了两件事情,一件是在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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    可以看出,在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);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    各个函数的功能从名字即可推断,不再赘述。值得注意的一点是,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));
    
    • 1
    • 2

    SPixelInspector

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

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

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

    FPixelInspectorSceneViewExtension

    SPixelInspector有这样一个成员变量:

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

    它是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;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    其实这里的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;
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    这些成员变量即真正保存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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157

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

    PixelInspectorSceneViewExtension->GetPixelFormat()
    
    • 1

    根据不同的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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167

    这个函数将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"));
    
    • 1

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

    总结

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

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

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

  • 相关阅读:
    基层管理者的思考方式
    核心交换机、汇聚交换机、接入交换机的概念
    QT+SQLite数据库配置和使用
    Vue中的$attrs和inheritAttrs
    打不过就加入 | 动植物泛基因组研究(一)
    JDBC基本概念
    【云原生之Docker实战】使用Docker部署Owncloud开源个人云盘系统
    复杂环境下多移动机器人路径规划研究(Matlab代码实现)
    mysql 默认时间写入时差问题
    低代码平台对于开发者来说可以解决哪些问题?
  • 原文地址:https://blog.csdn.net/u010281174/article/details/125416543