目录
1.UE 延迟渲染的Gbuffer是什么样的?
2.怎么样处理多光源
3.怎么样处理同屏不同的光照模型
UE的渲染部分从GameEngine.h 和 GameEngine.cpp
voidUGameEngine::Tick( float DeltaSeconds, bool bIdleMode )
{
[... ...]
//检查设置,比如是否是专属服务器,只有命令行
if (!bIdleMode && !IsRunningDedicatedServer() && !IsRunningCommandlet() && FEmbeddedCommunication::IsAwakeForRendering())
{
//渲染窗口/所有物体
RedrawViewports();
// 渲染完之后的回调
GetRendererModule().PostRenderAllViewports();
}
[... ...]
}
//上述的RedrawViewports();
void UGameEngine::RedrawViewports( bool bShouldPresent /*= true*/ )
{
SCOPE_CYCLE_COUNTER(STAT_RedrawViewports);
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(ViewportMisc);
if ( GameViewport != NULL )
{
GameViewport->LayoutPlayers();
if ( GameViewport->Viewport != NULL )
{
GameViewport->Viewport->Draw(bShouldPresent);
}
}
}
//上述的GameViewport->Viewport->Draw(bShouldPresent);
void UGameViewportClient::Draw()
{
[... ...]
if (!bDisableWorldRendering && PlayerViewMap.Num() > 0 && FSlateApplication::Get().GetPlatformApplication()->IsAllowedToRender()) //-V560
{
//渲染玩家视窗,从游戏线程调用发送一个消息到渲染线程正在渲染这个视图家族。
GetRendererModule().BeginRenderingViewFamily(SceneCanvas,&ViewFamily);
}
[... ...]
}
void FRendererModule::BeginRenderingViewFamily(FCanvas* Canvas, FSceneViewFamily* ViewFamily)
{
[... ...]
// 创建场景渲染器,FSceneRenderer有两个子类,分别是FMobileSceneRenderer和FDeferredShadingSceneRenderer
FSceneRenderer* SceneRenderer = FSceneRenderer::CreateSceneRenderer(ViewFamily, Canvas->GetHitProxyConsumer());
// 向渲染线程发送绘制场景指令.
ENQUEUE_RENDER_COMMAND(FDrawSceneCommand)(
[SceneRenderer, DrawSceneEnqueue](FRHICommandListImmediate& RHICmdList)
{
const float StartDelayMillisec = FPlatformTime::ToMilliseconds(FPlatformTime::Cycles() - DrawSceneEnqueue);
CSV_CUSTOM_STAT_GLOBAL(DrawSceneCommand_StartDelay, StartDelayMillisec, ECsvCustomStatOp::Set);
[... ...]
//渲染线程实际干的活
RenderViewFamily_RenderThread(RHICmdList, SceneRenderer);
FlushPendingDeleteRHIResources_RenderThread();
});
[... ...]
}
//RenderViewFamily_RenderThread(RHICmdList, SceneRenderer);
static void RenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneRenderer* SceneRenderer)
{
[... ...]
//更新任何需要延迟更新的资源
FDeferredUpdateResource::UpdateResources(RHICmdList);
[... ...]
//渲染场景
if(SceneRenderer->ViewFamily.EngineShowFlags.HitProxies)
{
// Render the scene's hit proxies.
SceneRenderer->RenderHitProxies(RHICmdList);
}
else
{
// Render the scene.
SceneRenderer->Render(RHICmdList);
}
[... ...]
}
因为要看延迟渲染,所以接下来就直接看FDeferredShadingSceneRenderer.h和FDeferredShadingSceneRenderer.cpp
FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)这个函数一千三百多行,大概包含了准备大气散射的数据,Prepass 渲染深度, 光追,Base pass 延迟渲染的几何阶段,用于遮挡剔除的简化包围盒深度值渲染,渲染阴影和深度,渲染motion vector,渲染头发,渲染光照,渲染大气,渲染大气散射,渲染雾,渲染体积云,通知特效系统,渲染半透明物体,后处理
目前不会讲述关于cluster shading的处理
FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
[... ...]
bool bDoInitViewAftersPrepass = false;
{
SCOPED_GPU_STAT(RHICmdList, VisibilityCommands);
bDoInitViewAftersPrepass = InitViews(RHICmdList, BasePassDepthStencilAccess, ILCTaskData)
}
[... ...]
}
bool FDeferredShadingSceneRenderer::InitViews(FRHICommandListImmediate& RHICmdList, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, struct FILCUpdatePrimTaskData& ILCTaskData)
{
[... ...]
//平头体裁剪,包围盒计算,遮挡剔除,收集网格信息,创建光源信息
ComputeViewVisibility(RHICmdList, BasePassDepthStencilAccess, ViewCommandsPerView, DynamicIndexBufferForInitViews, DynamicVertexBufferForInitViews, DynamicReadBufferForInitViews);
[... ...]
//处理贴花排序,处理光源可见性
PostVisibilityFrameSetup(ILCTaskData);
[... ...]
}
void FSceneRenderer::PostVisibilityFrameSetup(FILCUpdatePrimTaskData& OutILCTaskData)
{
[... ...]
// 确定光源的可见性
for(TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights);LightIt;++LightIt)
{
const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
const FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;
// 利用摄像机的视锥体裁剪光源
for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
{
const FLightSceneProxy* Proxy = LightSceneInfo->Proxy;
FViewInfo& View = Views[ViewIndex];
FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightIt.GetIndex()];
// 直接光永远可见,点光/聚光灯/面光需要判断
if( Proxy->GetLightType() == LightType_Point ||
Proxy->GetLightType() == LightType_Spot ||
Proxy->GetLightType() == LightType_Rect )
{
FSphere const& BoundingSphere = Proxy->GetBoundingSphere();
if (View.ViewFrustum.IntersectSphere(BoundingSphere.Center, BoundingSphere.W))
{
//透视相机需要提出太远的光源
if (View.IsPerspectiveProjection())
{
FSphere Bounds = Proxy->GetBoundingSphere();
float DistanceSquared = (Bounds.Center - View.ViewMatrices.GetViewOrigin()).SizeSquared();
float MaxDistSquared = Proxy->GetMaxDrawDistance() * Proxy->GetMaxDrawDistance() * GLightMaxDrawDistanceScale * GLightMaxDrawDistanceScale;
//考虑了光源的半径、视图的LOD因子、最小光源屏幕半径等因素来决定最终光源是否需要绘制,以便剔除掉远距离屏幕占比很小的光源。
const bool bDrawLight = (FMath::Square(FMath::Min(0.0002f, GMinScreenRadiusForLights / Bounds.W) * View.LODDistanceFactor) * DistanceSquared < 1.0f)
&& (MaxDistSquared == 0 || DistanceSquared < MaxDistSquared);
VisibleLightViewInfo.bInViewFrustum = bDrawLight;
}
else
{
VisibleLightViewInfo.bInViewFrustum = true;
}
}
}
else
{
VisibleLightViewInfo.bInViewFrustum = true;
// 设置单个太阳轴从方向灯移动。
if (bSetupMobileLightShafts && LightSceneInfo->bEnableLightShaftBloom && ShouldRenderLightShaftsForLight(View, *LightSceneInfo->Proxy))
{
View.MobileLightShaft = GetMobileLightShaftInfo(View, *LightSceneInfo);
}
}
[... ...]
}
由上可知对于光源的处理为:使用光源的包围盒与视锥体求交,将不相交的剔除,然后对屏幕占比较小的光源进行提出,并将其可见性记录到VisibleLightViewInfo.bInViewFrustum
渲染几何阶段的代码应该在RenderBasePass()这个函数
void FDeferredShadingSceneRenderer::RenderBasePass(
FRDGBuilder& GraphBuilder,
FExclusiveDepthStencil::Type BasePassDepthStencilAccess,
FRDGTextureRef SceneColorTexture,
FRDGTextureRef SceneDepthTexture,
ERenderTargetLoadAction SceneDepthLoadAction,
FRDGTextureRef ForwardShadowMaskTexture)
{
const bool bEnableParallelBasePasses = GRHICommandList.UseParallelAlgorithms() && CVarParallelBasePass.GetValueOnRenderThread();
static const auto ClearMethodCVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.ClearSceneMethod"));
bool bRequiresRHIClear = true;
bool bRequiresFarZQuadClear = false;
if (ClearMethodCVar)
{
int32 ClearMethod = ClearMethodCVar->GetValueOnRenderThread();
if (ClearMethod == 0 && !ViewFamily.EngineShowFlags.Game)
{
// Do not clear the scene only if the view family is in game mode.
ClearMethod = 1;
}
switch (ClearMethod)
{
case 0: // No clear
bRequiresRHIClear = false;
bRequiresFarZQuadClear = false;
break;
case 1: // RHICmdList.Clear
bRequiresRHIClear = true;
bRequiresFarZQuadClear = false;
break;
case 2: // Clear using far-z quad
bRequiresFarZQuadClear = true;
bRequiresRHIClear = false;
break;
}
}
// Always perform a full buffer clear for wireframe, shader complexity view mode, and stationary light overlap viewmode.
if (ViewFamily.EngineShowFlags.Wireframe || ViewFamily.EngineShowFlags.ShaderComplexity || ViewFamily.EngineShowFlags.StationaryLightOverlap)
{
bRequiresRHIClear = true;
bRequiresFarZQuadClear = false;
}
const bool bIsWireframeRenderpass = ViewFamily.EngineShowFlags.Wireframe && FSceneRenderer::ShouldCompositeEditorPrimitives(Views[0]);
const bool bDebugViewMode = ViewFamily.UseDebugViewPS();
const bool bRenderLightmapDensity = ViewFamily.EngineShowFlags.LightMapDensity && AllowDebugViewmodes();
const bool bRenderSkyAtmosphereEditorNotifications = ShouldRenderSkyAtmosphereEditorNotifications();
const bool bDoParallelBasePass = bEnableParallelBasePasses && !bDebugViewMode && !bRenderLightmapDensity; // DebugView and LightmapDensity are non-parallel substitutions inside BasePass
const bool bNeedsBeginRender = AllowDebugViewmodes() &&
(ViewFamily.EngineShowFlags.RequiredTextureResolution ||
ViewFamily.EngineShowFlags.MaterialTextureScaleAccuracy ||
ViewFamily.EngineShowFlags.MeshUVDensityAccuracy ||
ViewFamily.EngineShowFlags.PrimitiveDistanceAccuracy ||
ViewFamily.EngineShowFlags.ShaderComplexity ||
ViewFamily.EngineShowFlags.LODColoration ||
ViewFamily.EngineShowFlags.HLODColoration);
const FExclusiveDepthStencil ExclusiveDepthStencil(BasePassDepthStencilAccess);
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(GraphBuilder.RHICmdList);
TStaticArray<FRDGTextureRef, MaxSimultaneousRenderTargets> BasePassTextures;
int32 GBufferDIndex = INDEX_NONE;
uint32 BasePassTextureCount = SceneContext.GetGBufferRenderTargets(GraphBuilder, BasePassTextures, GBufferDIndex);
TArrayView<FRDGTextureRef> BasePassTexturesView = MakeArrayView(BasePassTextures.GetData(), BasePassTextureCount);
FRDGTextureRef BasePassDepthTexture = SceneDepthTexture;
FLinearColor SceneColorClearValue;
if (bRequiresRHIClear)
{
if (ViewFamily.EngineShowFlags.ShaderComplexity)
{
SceneContext.ClearQuadOverdrawUAV(GraphBuilder);
}
if (ViewFamily.EngineShowFlags.ShaderComplexity || ViewFamily.EngineShowFlags.StationaryLightOverlap)
{
SceneColorClearValue = FLinearColor(0, 0, 0, kSceneColorClearAlpha);
}
else
{
SceneColorClearValue = FLinearColor(Views[0].BackgroundColor.R, Views[0].BackgroundColor.G, Views[0].BackgroundColor.B, kSceneColorClearAlpha);
}
ERenderTargetLoadAction ColorLoadAction = ERenderTargetLoadAction::ELoad;
if (SceneColorTexture->Desc.ClearValue.GetClearColor() == SceneColorClearValue)
{
ColorLoadAction = ERenderTargetLoadAction::EClear;
}
else
{
ColorLoadAction = ERenderTargetLoadAction::ENoAction;
}
auto* PassParameters = GraphBuilder.AllocParameters<FRenderTargetParameters>();
PassParameters->RenderTargets = GetRenderTargetBindings(ColorLoadAction, BasePassTexturesView);
static TConsoleVariableData<int32>* CVarNoGBufferDClear = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.NoGBufferDClear"));
if (CVarNoGBufferDClear && !!CVarNoGBufferDClear->GetValueOnRenderThread() && GBufferDIndex != INDEX_NONE)
{
PassParameters->RenderTargets[GBufferDIndex].SetLoadAction(ERenderTargetLoadAction::ENoAction);
}
if (SceneDepthLoadAction == ERenderTargetLoadAction::EClear)
{
PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(BasePassDepthTexture, SceneDepthLoadAction, SceneDepthLoadAction, ExclusiveDepthStencil);
}
GraphBuilder.AddPass(RDG_EVENT_NAME("GBufferClear"), PassParameters, ERDGPassFlags::Raster,
[PassParameters, ColorLoadAction, SceneColorClearValue](FRHICommandList& RHICmdList)
{
// If no fast-clear action was used, we need to do an MRT shader clear.
if (ColorLoadAction == ERenderTargetLoadAction::ENoAction)
{
const FRenderTargetBindingSlots& RenderTargets = PassParameters->RenderTargets;
FLinearColor ClearColors[MaxSimultaneousRenderTargets];
FRHITexture* Textures[MaxSimultaneousRenderTargets];
int32 TextureIndex = 0;
RenderTargets.Enumerate([&](const FRenderTargetBinding& RenderTarget)
{
FRHITexture* TextureRHI = RenderTarget.GetTexture()->GetRHI();
ClearColors[TextureIndex] = TextureIndex == 0 ? SceneColorClearValue : TextureRHI->GetClearColor();
Textures[TextureIndex] = TextureRHI;
++TextureIndex;
});
// Clear color only; depth-stencil is fast cleared.
DrawClearQuadMRT(RHICmdList, true, TextureIndex, ClearColors, false, 0, false, 0);
}
});
if (bRenderSkyAtmosphereEditorNotifications)
{
// We only render this warning text when bRequiresRHIClear==true to make sure the scene color buffer is allocated at this stage.
// When false, the option specifies that all pixels must be written to by a sky dome anyway.
RenderSkyAtmosphereEditorNotifications(GraphBuilder, SceneColorTexture);
}
}
if (ViewFamily.EngineShowFlags.Wireframe)
{
checkf(ExclusiveDepthStencil.IsDepthWrite(), TEXT("Wireframe base pass requires depth-write, but it is set to read-only."));
SceneContext.GetEditorPrimitivesColor(GraphBuilder.RHICmdList);
SceneContext.GetEditorPrimitivesDepth(GraphBuilder.RHICmdList);
BasePassTextureCount = 1;
BasePassTextures[0] = GraphBuilder.RegisterExternalTexture(SceneContext.EditorPrimitivesColor, ERenderTargetTexture::Targetable);
BasePassTexturesView = MakeArrayView(BasePassTextures.GetData(), BasePassTextureCount);
BasePassDepthTexture = GraphBuilder.RegisterExternalTexture(SceneContext.EditorPrimitivesDepth, ERenderTargetTexture::Targetable);
auto* PassParameters = GraphBuilder.AllocParameters<FRenderTargetParameters>();
PassParameters->RenderTargets = GetRenderTargetBindings(ERenderTargetLoadAction::EClear, BasePassTexturesView);
PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(BasePassDepthTexture, ERenderTargetLoadAction::EClear, ERenderTargetLoadAction::EClear, ExclusiveDepthStencil);
GraphBuilder.AddPass(RDG_EVENT_NAME("WireframeClear"), PassParameters, ERDGPassFlags::Raster, [](FRHICommandList&) {});
}
// Render targets bindings should remain constant at this point.
FRenderTargetBindingSlots BasePassRenderTargets = GetRenderTargetBindings(ERenderTargetLoadAction::ELoad, BasePassTexturesView);
BasePassRenderTargets.DepthStencil = FDepthStencilBinding(BasePassDepthTexture, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, ExclusiveDepthStencil);
BasePassRenderTargets.ShadingRateTexture = GVRSImageManager.GetVariableRateShadingImage(GraphBuilder, ViewFamily, nullptr, EVRSType::None);
AddSetCurrentStatPass(GraphBuilder, GET_STATID(STAT_CLM_BasePass));
RenderBasePassInternal(GraphBuilder, BasePassRenderTargets, BasePassDepthStencilAccess, ForwardShadowMaskTexture, bDoParallelBasePass, bRenderLightmapDensity);
AddSetCurrentStatPass(GraphBuilder, GET_STATID(STAT_CLM_AfterBasePass));
if (ViewFamily.ViewExtensions.Num() > 0)
{
SCOPE_CYCLE_COUNTER(STAT_FDeferredShadingSceneRenderer_ViewExtensionPostRenderBasePass);
RDG_EVENT_SCOPE(GraphBuilder, "BasePass_ViewExtensions");
auto* PassParameters = GraphBuilder.AllocParameters<FRenderTargetParameters>();
PassParameters->RenderTargets = BasePassRenderTargets;
for (auto& ViewExtension : ViewFamily.ViewExtensions)
{
for (FViewInfo& View : Views)
{
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
GraphBuilder.AddPass(
{},
PassParameters,
ERDGPassFlags::Raster,
[&ViewExtension, &View](FRHICommandListImmediate& RHICmdList)
{
ViewExtension->PostRenderBasePass_RenderThread(RHICmdList, View);
});
}
}
}
if (bRequiresFarZQuadClear)
{
ClearGBufferAtMaxZ(GraphBuilder, Views, BasePassRenderTargets, SceneColorClearValue);
}
if (ShouldRenderAnisotropyPass())
{
AddSetCurrentStatPass(GraphBuilder, GET_STATID(STAT_CLM_AnisotropyPass));
RenderAnisotropyPass(GraphBuilder, SceneDepthTexture, bEnableParallelBasePasses);
AddSetCurrentStatPass(GraphBuilder, GET_STATID(STAT_CLM_AfterAnisotropyPass));
}
if (SceneContext.GBufferA)
{
AddAsyncComputeSRVTransitionHackPass(GraphBuilder, GraphBuilder.RegisterExternalTexture(SceneContext.GBufferA));
}
}
首先我先确定他有多少个MRT,追踪其中的BasePassRenderTargets变量,一步步深入可以在

中找到int32 FSceneRenderTargets::GetGBufferRenderTargets()
int32 FSceneRenderTargets::GetGBufferRenderTargets(const TRefCountPtr<IPooledRenderTarget>* OutRenderTargets[MaxSimultaneousRenderTargets], int32& OutVelocityRTIndex, int32& OutGBufferDIndex) const
{
int32 MRTCount = 0;
//rt0为场景颜色
OutRenderTargets[MRTCount++] = &GetSceneColor();
const EShaderPlatform ShaderPlatform = GetFeatureLevelShaderPlatform(CurrentFeatureLevel);
const bool bUseGBuffer = IsUsingGBuffers(ShaderPlatform);
if (bUseGBuffer)
{
OutRenderTargets[MRTCount++] = &GBufferA;
OutRenderTargets[MRTCount++] = &GBufferB;
OutRenderTargets[MRTCount++] = &GBufferC;
}
//rtVelocity =速度缓冲rt
// The velocity buffer needs to be bound before other optionnal rendertargets (when UseSelectiveBasePassOutputs() is true).
// Otherwise there is an issue on some AMD hardware where the target does not get updated. Seems to be related to the velocity buffer format as it works fine with other targets.
if (bAllocateVelocityGBuffer && !IsSimpleForwardShadingEnabled(ShaderPlatform))
{
OutVelocityRTIndex = MRTCount;
check(OutVelocityRTIndex == 4 || (!bUseGBuffer && OutVelocityRTIndex == 1)); // As defined in BasePassPixelShader.usf
OutRenderTargets[MRTCount++] = &SceneVelocity;
}
else
{
OutVelocityRTIndex = -1;
}
OutGBufferDIndex = INDEX_NONE;
if (bUseGBuffer)
{
OutGBufferDIndex = MRTCount;
OutRenderTargets[MRTCount++] = &GBufferD;
if (bAllowStaticLighting)
{
check(MRTCount == (bAllocateVelocityGBuffer ? 6 : 5)); // As defined in BasePassPixelShader.usf
OutRenderTargets[MRTCount++] = &GBufferE;
}
}
check(MRTCount <= MaxSimultaneousRenderTargets);
return MRTCount;
}
其中MaxSimultaneousRenderTargets如下:

根据上述的代码可以知道,RT最多有8个(根据MaxSimultaneousRenderTargets可知,但是上面好像大概只用了七个?),分别为SceneColor,GBufferA,GBufferB,GBufferC,SceneVelocity,GBufferD,GBufferE,然后其相关的定义在BasePassPixelShader.usf这个像素着色器中
接下来看BasePassPixelShader.usf
BasePassPixelShader的主入口为void FPixelShaderInOut_MainPS()

可以从MRT的写入这里分析出各个RT到底存了什么
#if MATERIAL_DOMAIN_POSTPROCESS
#if MATERIAL_OUTPUT_OPACITY_AS_ALPHA
Out.MRT[0] = half4(Color, Opacity);
#else
Out.MRT[0] = half4(Color, 0);
#endif
Out.MRT[0] = RETURN_COLOR(Out.MRT[0]);
// MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT must come first because it also has MATERIALBLENDING_TRANSLUCENT defined
#elif MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT
// After thin translucency, the final color is going to be:
// FinalColor = DualBlendColorAdd + DualBlendColorMul * BackgroundColor;
// To apply fogging, we want the final equation to be:
// FinalColor = Fogging.rgb + Fogging.a * (DualBlendColorAdd + DualBlendColorMul * BackgroundColor);
// FinalColor = (Fogging.rgb + Fogging.a * DualBlendColorAdd) + Fogging.a * DualBlendColorMul * BackgroundColor;
// Or in other words:
// AdjustedDualBlendAdd = Fogging.rgb + Fogging.a * DualBlendColorAdd;
// AdjustedDualBlendMul = Fogging.a * DualBlendColorMul;
// FinalColor = AdjustedDualBlendAdd + AdjustedDualBlendMul * BackgroundColor;
float3 AdjustedDualBlendAdd = Fogging.rgb + Fogging.a * DualBlendColorAdd;
float3 AdjustedDualBlendMul = Fogging.a * DualBlendColorMul;
#if THIN_TRANSLUCENT_USE_DUAL_BLEND
// no RETURN_COLOR because these values are explicit multiplies and adds
Out.MRT[0] = half4(AdjustedDualBlendAdd,0.0);
Out.MRT[1] = half4(AdjustedDualBlendMul,1.0);
#else
// In the fallback case, we are blending with the mode
float AdjustedAlpha = saturate(1-dot(AdjustedDualBlendMul,float3(1.0f,1.0f,1.0f)/3.0f));
Out.MRT[0] = half4(AdjustedDualBlendAdd,AdjustedAlpha);
Out.MRT[0] = RETURN_COLOR(Out.MRT[0]);
#endif
#elif MATERIALBLENDING_ALPHAHOLDOUT
// not implemented for holdout
Out.MRT[0] = half4(Color * Fogging.a + Fogging.rgb * Opacity, Opacity);
Out.MRT[0] = RETURN_COLOR(Out.MRT[0]);
#elif MATERIALBLENDING_ALPHACOMPOSITE
Out.MRT[0] = half4(Color * Fogging.a + Fogging.rgb * Opacity, Opacity);
Out.MRT[0] = RETURN_COLOR(Out.MRT[0]);
#elif MATERIALBLENDING_TRANSLUCENT
Out.MRT[0] = half4(Color * Fogging.a + Fogging.rgb, Opacity);
Out.MRT[0] = RETURN_COLOR(Out.MRT[0]);
#elif MATERIALBLENDING_ADDITIVE
Out.MRT[0] = half4(Color * Fogging.a * Opacity, 0.0f);
Out.MRT[0] = RETURN_COLOR(Out.MRT[0]);
#elif MATERIALBLENDING_MODULATE
// RETURN_COLOR not needed with modulative blending
half3 FoggedColor = lerp(float3(1, 1, 1), Color, Fogging.aaa * Fogging.aaa);
Out.MRT[0] = half4(FoggedColor, Opacity);
#else
{
FLightAccumulator LightAccumulator = (FLightAccumulator)0;
// Apply vertex fog
Color = Color * Fogging.a + Fogging.rgb;
#if POST_PROCESS_SUBSURFACE
// Apply vertex fog to diffuse color
DiffuseColor = DiffuseColor * Fogging.a + Fogging.rgb;
if (UseSubsurfaceProfile(GBuffer.ShadingModelID) &&
View.bSubsurfacePostprocessEnabled > 0 && View.bCheckerboardSubsurfaceProfileRendering > 0 )
{
// Adjust for checkerboard. only apply non-diffuse lighting (including emissive)
// to the specular component, otherwise lighting is applied twice
Color *= !bChecker;
}
LightAccumulator_Add(LightAccumulator, Color + DiffuseColor, DiffuseColor, 1.0f, UseSubsurfaceProfile(GBuffer.ShadingModelID));
#else
LightAccumulator_Add(LightAccumulator, Color, 0, 1.0f, false);
#endif
Out.MRT[0] = RETURN_COLOR(LightAccumulator_GetResult(LightAccumulator));
#if !USES_GBUFFER
// Without deferred shading the SSS pass will not be run to reset scene color alpha for opaque / masked to 0
// Scene color alpha is used by scene captures and planar reflections
Out.MRT[0].a = 0;
#endif
}
#endif
#if USES_GBUFFER
GBuffer.IndirectIrradiance = IndirectIrradiance;
// -0.5 .. 0.5, could be optimzed as lower quality noise would be sufficient
float QuantizationBias = PseudoRandom( MaterialParameters.SvPosition.xy ) - 0.5f;
EncodeGBuffer(GBuffer, Out.MRT[1], Out.MRT[2], Out.MRT[3], OutGBufferD, OutGBufferE, OutVelocity, QuantizationBias);
#endif
if(bEditorWeightedZBuffering)
{
Out.MRT[0].a = 1;
#if MATERIALBLENDING_MASKED
// some material might have a opacity value
Out.MRT[0].a = GetMaterialMaskInputRaw(PixelMaterialInputs);
#endif
#if EDITOR_ALPHA2COVERAGE != 0
// per MSAA sample
if(View.NumSceneColorMSAASamples > 1)
{
Out.Coverage = In.Coverage & CustomAlpha2Coverage(Out.MRT[0]);
}
else
{
// no MSAA is handle like per pixel
clip(Out.MRT[0].a - GetMaterialOpacityMaskClipValue());
}
#else
// per pixel
clip(Out.MRT[0].a - GetMaterialOpacityMaskClipValue());
#endif
}
#if USES_GBUFFER
#if GBUFFER_HAS_VELOCITY
Out.MRT[4] = OutVelocity;
#endif
Out.MRT[GBUFFER_HAS_VELOCITY ? 5 : 4] = OutGBufferD;
#if GBUFFER_HAS_PRECSHADOWFACTOR
Out.MRT[GBUFFER_HAS_VELOCITY ? 6 : 5] = OutGBufferE;
#endif
#else
// If not using the full gbuffer (forward shading) the velocity buffer can still be written to in the basepass.
#if GBUFFER_HAS_VELOCITY
Out.MRT[1] = OutVelocity;
#endif
#endif
#if !MATERIALBLENDING_MODULATE && USE_PREEXPOSURE
#if MATERIAL_IS_SKY
// Dynamic capture exposure is 1 as of today.
const float ViewPreExposure = View.RealTimeReflectionCapture>0.0f ? View.RealTimeReflectionCapturePreExposure : View.PreExposure;
#else
const float ViewPreExposure = View.PreExposure;
#endif
// We need to multiply pre-exposure by all components including A, otherwise the ratio of
// diffuse to specular lighting will get messed up in the SSS pass.
// RGB: Full color (Diffuse + Specular)
// A: Diffuse Intensity, but only if we are not blending
#if MATERIAL_DOMAIN_POSTPROCESS || MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT || MATERIALBLENDING_ALPHAHOLDOUT || MATERIALBLENDING_ALPHACOMPOSITE || MATERIALBLENDING_TRANSLUCENT || MATERIALBLENDING_ADDITIVE
Out.MRT[0].rgb *= ViewPreExposure;
#else
Out.MRT[0].rgba *= ViewPreExposure;
#endif
#endif
#if MATERIAL_IS_SKY
// Sky materials can result in high luminance values, e.g. the sun disk.
// This is so we make sure to at least stay within the boundaries of fp10 and not cause NaN on some platforms.
// We also half that range to also make sure we have room for other additive elements such as bloom, clouds or particle visual effects.
Out.MRT[0].xyz = min(Out.MRT[0].xyz, Max10BitsFloat.xxx * 0.5f);
#endif
首先分析SceneColor ,由于ue为了兼容不同的光照模型,用了一堆的宏,基本是思路是调用实际的光照模型变体计算基础Diffuse色+自发光+叠加雾效【会根据着色模型的不同有变化】

然后上面有个EncodeGBuffer函数将GBuffer中的部分数据编码到MRT中,这个函数的定义在DeferredShadingCommon.ush
/** Populates OutGBufferA, B and C */
void EncodeGBuffer(
FGBufferData GBuffer,
out float4 OutGBufferA,
out float4 OutGBufferB,
out float4 OutGBufferC,
out float4 OutGBufferD,
out float4 OutGBufferE,
out float4 OutGBufferVelocity,
float QuantizationBias = 0 // -0.5 to 0.5 random float. Used to bias quantization.
)
{
if (GBuffer.ShadingModelID == SHADINGMODELID_UNLIT)
{
OutGBufferA = 0;
SetGBufferForUnlit(OutGBufferB);
OutGBufferC = 0;
OutGBufferD = 0;
OutGBufferE = 0;
}
else
{
#if MOBILE_DEFERRED_SHADING
OutGBufferA.rg = UnitVectorToOctahedron( normalize(GBuffer.WorldNormal) ) * 0.5f + 0.5f;
OutGBufferA.b = GBuffer.PrecomputedShadowFactors.x;
OutGBufferA.a = GBuffer.PerObjectGBufferData;
#elif 1
OutGBufferA.rgb = EncodeNormal( GBuffer.WorldNormal );
OutGBufferA.a = GBuffer.PerObjectGBufferData;
#else
float3 Normal = GBuffer.WorldNormal;
uint NormalFace = 0;
EncodeNormal( Normal, NormalFace );
OutGBufferA.rg = Normal.xy;
OutGBufferA.b = 0;
OutGBufferA.a = GBuffer.PerObjectGBufferData;
#endif
OutGBufferB.r = GBuffer.Metallic;
OutGBufferB.g = GBuffer.Specular;
OutGBufferB.b = GBuffer.Roughness;
OutGBufferB.a = EncodeShadingModelIdAndSelectiveOutputMask(GBuffer.ShadingModelID, GBuffer.SelectiveOutputMask);
OutGBufferC.rgb = EncodeBaseColor( GBuffer.BaseColor );
#if ALLOW_STATIC_LIGHTING
// No space for AO. Multiply IndirectIrradiance by AO instead of storing.
OutGBufferC.a = EncodeIndirectIrradiance(GBuffer.IndirectIrradiance * GBuffer.GBufferAO) + QuantizationBias * (1.0 / 255.0);
#else
OutGBufferC.a = GBuffer.GBufferAO;
#endif
OutGBufferD = GBuffer.CustomData;
OutGBufferE = GBuffer.PrecomputedShadowFactors;
}
#if WRITES_VELOCITY_TO_GBUFFER
OutGBufferVelocity = GBuffer.Velocity;
#else
OutGBufferVelocity = 0;
#endif
}
下面配上上面函数用到的一些函数:
①单位向量的压缩,八面体映射压缩:
// Octahedron Normal Vectors
// [Cigolle 2014, "A Survey of Efficient Representations for Independent Unit Vectors"]
// Mean Max
// oct 8:8 0.33709 0.94424
// snorm 8:8:8 0.17015 0.38588
// oct 10:10 0.08380 0.23467
// snorm 10:10:10 0.04228 0.09598
// oct 12:12 0.02091 0.05874
float2 UnitVectorToOctahedron( float3 N )
{
N.xy /= dot( 1, abs(N) );
if( N.z <= 0 )
{
N.xy = ( 1 - abs(N.yx) ) * ( N.xy >= 0 ? float2(1,1) : float2(-1,-1) );
}
return N.xy;
}
②压缩ShadingModelId和SelectiveOutputMask
loat EncodeShadingModelIdAndSelectiveOutputMask(uint ShadingModelId, uint SelectiveOutputMask)
{
uint Value = (ShadingModelId & SHADINGMODELID_MASK) | SelectiveOutputMask;
return (float)Value / (float)0xFF;
}
③编码IndirectIrradiance
float EncodeIndirectIrradiance(float IndirectIrradiance)
{
float L = IndirectIrradiance;
#if USE_PREEXPOSURE
L *= View.PreExposure; // Apply pre-exposure as a mean to prevent compression overflow.
#endif
const float LogBlackPoint = 0.00390625; // exp2(-8);
return log2( L + LogBlackPoint ) / 16 + 0.5;
}
然后根据这个编码函数我们可以知道GBuffer的布局,虽然根据不同光照模型/管线会有些许差异,
但是大致可以总结如下:
①GBufferA:rg/b存储法线/预计算的阴影因子,a存储预计算的物体数据
②GBufferB:r存储金属度,g存储高光值,b存储粗糙度,a存储编码的光照模型和SelectiveOutputMask
SelectiveOutputMask记录了绘制时以下宏的开启结果:
③GBufferC :rgb存储basecolor,a存储AO,如果允许静态光照则a存储IndirectIrradiance*Material AO
④GBufferD:存储自定义的数据
⑤GBufferE:存储预计算的阴影值
⑥GBufferVelocity:存储SceneVelocity
UE的shader采用的是Uber Shader的设计,通过再一个shader里面定义一堆的宏,然后在编译Shader的时候,传入不同的宏参数,编译出不同的shader代码(bgfx里面也是这么干的),
在编译的时候,不同的宏怎么传入编译器的呢,UE主要建立了一个Shader Permutation的概念,来存储一个唯一的哈希键值,通过不同的Shader Permutation传到HLSL,编译出对应的着色器代码,然后在通过ShaderMap获取到实例的shader
Shader Permutation的相关代码在ShaderPermutation.h中,
template <typename TDimension, typename... Ts>
struct TShaderPermutationDomain<TDimension, Ts...>
{
/** Setup the dimension's type in permutation domain as itself so that a permutation domain can be
* used as a dimension of another domain.
*/
using Type = TShaderPermutationDomain<TDimension, Ts...>;
/** Define a domain as a multidimensional dimension so that ModifyCompilationEnvironment() is used. */
static constexpr bool IsMultiDimensional = true;
/** Parent type in the variadic template to reduce code. */
using Super = TShaderPermutationDomain<Ts...>;
/** Total number of permutation within the domain. */
static constexpr int32 PermutationCount = Super::PermutationCount * TDimension::PermutationCount;
/** Constructors. */
TShaderPermutationDomain<TDimension, Ts...>()
: DimensionValue(TDimension::FromDimensionValueId(0))
{
}
explicit TShaderPermutationDomain<TDimension, Ts...>(int32 PermutationId)
: DimensionValue(TDimension::FromDimensionValueId(PermutationId % TDimension::PermutationCount))
, Tail(PermutationId / TDimension::PermutationCount)
{
checkf(PermutationId >= 0 && PermutationId < PermutationCount, TEXT("Invalid shader permutation id %i."), PermutationId);
}
/** Set dimension's value. */
template<class DimensionToSet>
void Set(typename DimensionToSet::Type Value)
{
return TShaderPermutationDomainSpetialization<TIsSame<TDimension, DimensionToSet>::Value>::template SetDimension<Type, DimensionToSet>(*this, Value);
}
/** Get dimension's value. */
template<class DimensionToGet>
const typename DimensionToGet::Type& Get() const
{
return TShaderPermutationDomainSpetialization<TIsSame<TDimension, DimensionToGet>::Value>::template GetDimension<Type, DimensionToGet>(*this);
}
/** Modify the shader's compilation environment. */
void ModifyCompilationEnvironment(FShaderCompilerEnvironment& OutEnvironment) const
{
TShaderPermutationDomainSpetialization<TDimension::IsMultiDimensional>::template ModifyCompilationEnvironment<Type, TDimension>(*this, OutEnvironment);
}
/** Converts domain permutation vector to domain's value id. */
static int32 ToDimensionValueId(const Type& PermutationVector)
{
return PermutationVector.ToDimensionValueId();
}
int32 ToDimensionValueId() const
{
return TDimension::ToDimensionValueId(DimensionValue) + TDimension::PermutationCount * Tail.ToDimensionValueId();
}
/** Returns the permutation domain from the unique ID. */
static Type FromDimensionValueId(const int32 PermutationId)
{
return Type(PermutationId);
}
/** Test if equal. */
bool operator==(const Type& Other) const
{
return DimensionValue == Other.DimensionValue && Tail == Other.Tail;
}
/** Test if not equal. */
bool operator!=(const Type& Other) const
{
return !(*this == Other);
}
private:
template<bool BooleanSpetialization>
friend class TShaderPermutationDomainSpetialization;
typename TDimension::Type DimensionValue;
Super Tail;
};
这个模板其实不太好理解,根据结合class FDeferredLightPS : public FGlobalShader中的代码简单理一下
首先把对应的阵列声明出来,其中的string就是shader中的宏
class FSourceShapeDim : SHADER_PERMUTATION_ENUM_CLASS("LIGHT_SOURCE_SHAPE", ELightSourceShape);
class FSourceTextureDim : SHADER_PERMUTATION_BOOL("USE_SOURCE_TEXTURE");
class FIESProfileDim : SHADER_PERMUTATION_BOOL("USE_IES_PROFILE");
class FInverseSquaredDim : SHADER_PERMUTATION_BOOL("INVERSE_SQUARED_FALLOFF");
class FVisualizeCullingDim : SHADER_PERMUTATION_BOOL("VISUALIZE_LIGHT_CULLING");
class FLightingChannelsDim : SHADER_PERMUTATION_BOOL("USE_LIGHTING_CHANNELS");
class FTransmissionDim : SHADER_PERMUTATION_BOOL("USE_TRANSMISSION");
class FHairLighting : SHADER_PERMUTATION_INT("USE_HAIR_LIGHTING", 2);
class FAtmosphereTransmittance : SHADER_PERMUTATION_BOOL("USE_ATMOSPHERE_TRANSMITTANCE");
class FCloudTransmittance : SHADER_PERMUTATION_BOOL("USE_CLOUD_TRANSMITTANCE");
class FAnistropicMaterials : SHADER_PERMUTATION_BOOL("SUPPORTS_ANISOTROPIC_MATERIALS");
然后在获取实例shader的时候如下,先声明一个Permutation域,并且设置他的值表明对应的编译选项是否开启,然后再利用这个Permutation域从ShaderMap获取到对应的实例shader,然后一个shader是否需要编译是通过一个静态函数static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
FDeferredLightPS::FPermutationDomain PermutationVector;
PermutationVector.Set< FDeferredLightPS::FSourceShapeDim >( ELightSourceShape::Directional );
PermutationVector.Set< FDeferredLightPS::FIESProfileDim >( false );
PermutationVector.Set< FDeferredLightPS::FInverseSquaredDim >( false );
PermutationVector.Set< FDeferredLightPS::FVisualizeCullingDim >( View.Family->EngineShowFlags.VisualizeLightCulling );
PermutationVector.Set< FDeferredLightPS::FLightingChannelsDim >( View.bUsesLightingChannels );
PermutationVector.Set< FDeferredLightPS::FAnistropicMaterials >(ShouldRenderAnisotropyPass());
PermutationVector.Set< FDeferredLightPS::FTransmissionDim >( bTransmission );
PermutationVector.Set< FDeferredLightPS::FHairLighting>(0);
// Only directional lights are rendered in this path, so we only need to check if it is use to light the atmosphere
PermutationVector.Set< FDeferredLightPS::FAtmosphereTransmittance >(bAtmospherePerPixelTransmittance);
PermutationVector.Set< FDeferredLightPS::FCloudTransmittance >(bLight0CloudPerPixelTransmittance || bLight1CloudPerPixelTransmittance);
TShaderMapRef< FDeferredLightPS > PixelShader( View.ShaderMap, PermutationVector );
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
PixelShader->SetParameters(RHICmdList, View, LightSceneInfo, ScreenShadowMaskTexture, LightingChannelsTexture, &RenderLightParams);
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po
尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr