UE4 SkyLight 记录
背景:
想了解一下,UE4如何使用HDR图,作为环境光,官方有个插件HDRIBackdrop,主要就是使用SkyLight进行,因此重点看了一下SkyLight,记录一下。
现在主要是记录过程,后面整理一个框架。
流程:
从Recapture入手,刚好转换的流程执行一遍。
D:\UnrealEngine426\Engine\Source\Runtime\Engine\Private\Components\SkyLightComponent.cpp
- FAutoConsoleCommandWithWorld CaptureConsoleCommand(
TEXT("r.SkylightRecapture"),
TEXT("Updates all stationary and movable skylights, useful for debugging the capture pipeline"),
FConsoleCommandWithWorldDelegate::CreateStatic(OnUpdateSkylights)
);
- void OnUpdateSkylights(UWorld* InWorld)
{
}for (TObjectIterator<USkyLightComponent> It; It; ++It)
{
USkyLightComponent* SkylightComponent = *It;
if (InWorld->ContainsActor(SkylightComponent->GetOwner()) && !SkylightComponent->IsPendingKill())
{
SkylightComponent->SetCaptureIsDirty();
}
}
USkyLightComponent::UpdateSkyCaptureContents(InWorld);
void USkyLightComponent::UpdateSkyCaptureContents(UWorld* WorldToUpdate)
{if (WorldToUpdate->Scene)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_SkylightCaptures);
if (GUpdateSkylightsEveryFrame)
{
for (TObjectIterator<USkyLightComponent> It; It; ++It)
{
USkyLightComponent* SkylightComponent = *It;
if (WorldToUpdate->ContainsActor(SkylightComponent->GetOwner()) && !SkylightComponent->IsPendingKill())
{
SkylightComponent->SetCaptureIsDirty();
}
}
}
if (SkyCapturesToUpdate.Num() > 0)
{
FScopeLock Lock(&SkyCapturesToUpdateLock);
UpdateSkyCaptureContentsArray(WorldToUpdate, SkyCapturesToUpdate, true);
}
if (SkyCapturesToUpdateBlendDestinations.Num() > 0)
{
UpdateSkyCaptureContentsArray(WorldToUpdate, SkyCapturesToUpdateBlendDestinations, false);
}
}
}
void USkyLightComponent::UpdateSkyCaptureContentsArray(UWorld WorldToUpdate, TArray<USkyLightComponent>& ComponentArray, bool bOperateOnBlendSource)
{const bool bIsCompilingShaders = GShaderCompilingManager != NULL && GShaderCompilingManager->IsCompiling();
// Iterate backwards so we can remove elements without changing the index
for (int32 CaptureIndex = ComponentArray.Num() - 1; CaptureIndex >= 0; CaptureIndex--)
{
USkyLightComponent* CaptureComponent = ComponentArray[CaptureIndex];
AActor* Owner = CaptureComponent->GetOwner();
if (((!Owner || !Owner->GetLevel() || Owner->GetLevel()->bIsVisible) && CaptureComponent->GetWorld() == WorldToUpdate)
// Only process sky capture requests once async shader compiling completes, otherwise we will capture the scene with temporary shaders
&& (!bIsCompilingShaders || CaptureComponent->SourceType == SLS_SpecifiedCubemap))
{
// Only capture valid sky light components
if (CaptureComponent->SourceType != SLS_SpecifiedCubemap || CaptureComponent->Cubemap)
{
if WITH_EDITOR
FStaticLightingSystemInterface::OnLightComponentUnregistered.Broadcast(CaptureComponent);
endif
if (bOperateOnBlendSource)
{
ensure(!CaptureComponent->ProcessedSkyTexture || CaptureComponent->ProcessedSkyTexture->GetSizeX() == CaptureComponent->ProcessedSkyTexture->GetSizeY());
// Allocate the needed texture on first capture
if (!CaptureComponent->ProcessedSkyTexture || CaptureComponent->ProcessedSkyTexture->GetSizeX() != CaptureComponent->CubemapResolution)
{
CaptureComponent->ProcessedSkyTexture = new FSkyTextureCubeResource();
CaptureComponent->ProcessedSkyTexture->SetupParameters(CaptureComponent->CubemapResolution, FMath::CeilLogTwo(CaptureComponent->CubemapResolution) + 1, SKYLIGHT_CUBEMAP_FORMAT);
BeginInitResource(CaptureComponent->ProcessedSkyTexture);
CaptureComponent->MarkRenderStateDirty();
}
WorldToUpdate->Scene->UpdateSkyCaptureContents(CaptureComponent, CaptureComponent->bCaptureEmissiveOnly, CaptureComponent->Cubemap, CaptureComponent->ProcessedSkyTexture, CaptureComponent->AverageBrightness, CaptureComponent->IrradianceEnvironmentMap, NULL);
CaptureComponent->UpdateImportanceSamplingData();
}
else
{
ensure(!CaptureComponent->BlendDestinationProcessedSkyTexture || CaptureComponent->BlendDestinationProcessedSkyTexture->GetSizeX() == CaptureComponent->BlendDestinationProcessedSkyTexture->GetSizeY());
// Allocate the needed texture on first capture
if (!CaptureComponent->BlendDestinationProcessedSkyTexture || CaptureComponent->BlendDestinationProcessedSkyTexture->GetSizeX() != CaptureComponent->CubemapResolution)
{
CaptureComponent->BlendDestinationProcessedSkyTexture = new FSkyTextureCubeResource();
CaptureComponent->BlendDestinationProcessedSkyTexture->SetupParameters(CaptureComponent->CubemapResolution, FMath::CeilLogTwo(CaptureComponent->CubemapResolution) + 1, SKYLIGHT_CUBEMAP_FORMAT);
BeginInitResource(CaptureComponent->BlendDestinationProcessedSkyTexture);
CaptureComponent->MarkRenderStateDirty();
}
WorldToUpdate->Scene->UpdateSkyCaptureContents(CaptureComponent, CaptureComponent->bCaptureEmissiveOnly, CaptureComponent->BlendDestinationCubemap, CaptureComponent->BlendDestinationProcessedSkyTexture, CaptureComponent->BlendDestinationAverageBrightness, CaptureComponent->BlendDestinationIrradianceEnvironmentMap, NULL);
CaptureComponent->UpdateImportanceSamplingData();
}
CaptureComponent->IrradianceMapFence.BeginFence();
CaptureComponent->bHasEverCaptured = true;
CaptureComponent->MarkRenderStateDirty();
if WITH_EDITOR
FStaticLightingSystemInterface::OnLightComponentRegistered.Broadcast(CaptureComponent);
endif
}
// Only remove queued update requests if we processed it for the right world
ComponentArray.RemoveAt(CaptureIndex);
}
}
}
- FAutoConsoleCommandWithWorld CaptureConsoleCommand(
D:\UnrealEngine426\Engine\Source\Runtime\Renderer\Private\ReflectionEnvironmentCapture.cpp
// Warning: returns before writes to OutIrradianceEnvironmentMap have completed, as they are queued on the rendering thread
void FScene::UpdateSkyCaptureContents(const USkyLightComponent* CaptureComponent,
bool bCaptureEmissiveOnly,
UTextureCube* SourceCubemap,
FTexture* OutProcessedTexture,
float& OutAverageBrightness,
FSHVectorRGB3& OutIrradianceEnvironmentMap,
TArray<FFloat16Color>* OutRadianceMap)
{
if (GSupportsRenderTargetFormat_PF_FloatRGBA || GetFeatureLevel() >= ERHIFeatureLevel::SM5)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_UpdateSkyCaptureContents);
{
World = GetWorld();
if (World)
{
//guarantee that all render proxies are up to date before kicking off this render
World->SendAllEndOfFrameUpdates();
}
}
{
int32 CubemapSize = CaptureComponent->CubemapResolution;
ENQUEUE_RENDER_COMMAND(ClearCommand)(
[CubemapSize](FRHICommandListImmediate& RHICmdList)
{
ClearScratchCubemaps(RHICmdList, CubemapSize);
});
}
if (CaptureComponent->SourceType == SLS_CapturedScene)
{
bool bStaticSceneOnly = CaptureComponent->Mobility == EComponentMobility::Static;
bool bCapturingForMobile = false;
CaptureSceneIntoScratchCubemap(this, CaptureComponent->GetComponentLocation(), CaptureComponent->CubemapResolution, true, bStaticSceneOnly, CaptureComponent->SkyDistanceThreshold, CaptureComponent->bLowerHemisphereIsBlack, bCaptureEmissiveOnly, CaptureComponent->LowerHemisphereColor, bCapturingForMobile);
}
else if (CaptureComponent->SourceType == SLS_SpecifiedCubemap)
{
int32 CubemapSize = CaptureComponent->CubemapResolution;
bool bLowerHemisphereIsBlack = CaptureComponent->bLowerHemisphereIsBlack;
float SourceCubemapRotation = CaptureComponent->SourceCubemapAngle * (PI / 180.f);
ERHIFeatureLevel::Type InnerFeatureLevel = FeatureLevel;
FLinearColor LowerHemisphereColor = CaptureComponent->LowerHemisphereColor;
ENQUEUE_RENDER_COMMAND(CopyCubemapCommand)(
[SourceCubemap, CubemapSize, bLowerHemisphereIsBlack, SourceCubemapRotation, InnerFeatureLevel, LowerHemisphereColor](FRHICommandListImmediate& RHICmdList)
{
CopyCubemapToScratchCubemap(RHICmdList, InnerFeatureLevel, SourceCubemap, CubemapSize, true, bLowerHemisphereIsBlack, SourceCubemapRotation, LowerHemisphereColor);
});
}
else if (CaptureComponent->IsRealTimeCaptureEnabled())
{
ensureMsgf(false, TEXT("A sky light with RealTimeCapture enabled cannot be scheduled for a cubemap update. This will be done dynamically each frame by the renderer."));
return;
}
else
{
check(0);
}
if (OutRadianceMap)
{
int32 CubemapSize = CaptureComponent->CubemapResolution;
ENQUEUE_RENDER_COMMAND(ReadbackCommand)(
[CubemapSize, OutRadianceMap](FRHICommandListImmediate& RHICmdList)
{
ReadbackRadianceMap(RHICmdList, CubemapSize, *OutRadianceMap);
});
}
{
int32 CubemapSize = CaptureComponent->CubemapResolution;
float* AverageBrightness = &OutAverageBrightness;
FSHVectorRGB3* IrradianceEnvironmentMap = &OutIrradianceEnvironmentMap;
ERHIFeatureLevel::Type InFeatureLevel = GetFeatureLevel();
ENQUEUE_RENDER_COMMAND(FilterCommand)(
[CubemapSize, AverageBrightness, IrradianceEnvironmentMap, InFeatureLevel](FRHICommandListImmediate& RHICmdList)
{
if (InFeatureLevel <= ERHIFeatureLevel::ES3_1)
{
MobileReflectionEnvironmentCapture::ComputeAverageBrightness(RHICmdList, InFeatureLevel, CubemapSize, *AverageBrightness);
MobileReflectionEnvironmentCapture::FilterReflectionEnvironment(RHICmdList, InFeatureLevel, CubemapSize, IrradianceEnvironmentMap);
}
else
{
ComputeAverageBrightness(RHICmdList, InFeatureLevel, CubemapSize, *AverageBrightness);
FilterReflectionEnvironment(RHICmdList, InFeatureLevel, CubemapSize, IrradianceEnvironmentMap);
}
});
}
// Optionally copy the filtered mip chain to the output texture
if (OutProcessedTexture)
{
FScene* Scene = this;
ERHIFeatureLevel::Type InFeatureLevel = GetFeatureLevel();
ENQUEUE_RENDER_COMMAND(CopyCommand)(
[Scene, OutProcessedTexture, InFeatureLevel](FRHICommandListImmediate& RHICmdList)
{
if (InFeatureLevel <= ERHIFeatureLevel::ES3_1)
{
MobileReflectionEnvironmentCapture::CopyToSkyTexture(RHICmdList, Scene, OutProcessedTexture);
}
else
{
CopyToSkyTexture(RHICmdList, Scene, OutProcessedTexture);
}
});
}
if (!!GFreeReflectionScratchAfterUse)
{
ENQUEUE_RENDER_COMMAND(FreeReflectionScratch)(
[](FRHICommandListImmediate& RHICmdList)
{
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
SceneContext.FreeReflectionScratchRenderTargets();
GRenderTargetPool.FreeUnusedResources();
});
}
}
}
void CopyCubemapToScratchCubemap(FRHICommandListImmediate& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, UTextureCube* SourceCubemap, int32 CubemapSize, bool bIsSkyLight, bool bLowerHemisphereIsBlack, float SourceCubemapRotation, const FLinearColor& LowerHemisphereColorValue)
{SCOPED_DRAW_EVENT(RHICmdList, CopyCubemapToScratchCubemap);
check(SourceCubemap);
const FTexture* SourceCubemapResource = SourceCubemap->Resource;
if (SourceCubemapResource == nullptr)
{
UE_LOG(LogEngine, Warning, TEXT("Unable to copy from cubemap %s, it's RHI resource is null"), *SourceCubemap->GetPathName());
return;
}
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
FMemMark Mark(FMemStack::Get());
FRDGBuilder GraphBuilder(RHICmdList);
FRDGTextureRef OutputTexture = GraphBuilder.RegisterExternalTexture(SceneContext.ReflectionColorScratchCubemap[0], TEXT("ReflectionColorScratchCubemap"));
for (uint32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
{
auto* PassParameters = GraphBuilder.AllocParameters<FCopyCubemapToCubeFacePS::FParameters>();
PassParameters->RenderTargets[0] = FRenderTargetBinding(OutputTexture, ERenderTargetLoadAction::ENoAction, 0, CubeFace);
PassParameters->LowerHemisphereColor = LowerHemisphereColorValue;
PassParameters->SkyLightCaptureParameters = FVector(bIsSkyLight ? 1.0f : 0.0f, 0.0f, bLowerHemisphereIsBlack ? 1.0f : 0.0f);
PassParameters->SourceCubemapSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->SourceCubemapTexture = SourceCubemapResource->TextureRHI;
PassParameters->SinCosSourceCubemapRotation = FVector2D(FMath::Sin(SourceCubemapRotation), FMath::Cos(SourceCubemapRotation));
PassParameters->CubeFace = CubeFace;
const int32 EffectiveSize = CubemapSize;
GraphBuilder.AddPass(
RDG_EVENT_NAME("CopyCubemapToCubeFace"),
PassParameters,
ERDGPassFlags::Raster,
[EffectiveSize, &SceneContext, SourceCubemapResource, PassParameters, FeatureLevel](FRHICommandList& InRHICmdList)
{
const FIntPoint SourceDimensions(SourceCubemapResource->GetSizeX(), SourceCubemapResource->GetSizeY());
const FIntRect ViewRect(0, 0, EffectiveSize, EffectiveSize);
InRHICmdList.SetViewport(0.0f, 0.0f, 0.0f, (float)EffectiveSize, (float)EffectiveSize, 1.0f);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
InRHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
TShaderMapRef<FScreenVS> VertexShader(GetGlobalShaderMap(FeatureLevel));
TShaderMapRef<FCopyCubemapToCubeFacePS> PixelShader(GetGlobalShaderMap(FeatureLevel));
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(InRHICmdList, GraphicsPSOInit);
SetShaderParameters(InRHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters);
DrawRectangle(
InRHICmdList,
ViewRect.Min.X, ViewRect.Min.Y,
ViewRect.Width(), ViewRect.Height(),
0, 0,
SourceDimensions.X, SourceDimensions.Y,
FIntPoint(ViewRect.Width(), ViewRect.Height()),
SourceDimensions,
VertexShader);
});
}
GraphBuilder.Execute();
}
class FCopyToCubeFaceShader : public FGlobalShader
{
public:static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return true;
}
FCopyToCubeFaceShader() = default;
FCopyToCubeFaceShader(const CompiledShaderInitializerType& Initializer)
: FGlobalShader(Initializer)
{}
};
/* Pixel shader used when copying a cubemap into a face of a reflection capture cubemap. /
class FCopyCubemapToCubeFacePS : public FCopyToCubeFaceShader
{
public:DECLARE_GLOBAL_SHADER(FCopyCubemapToCubeFacePS);
SHADER_USE_PARAMETER_STRUCT(FCopyCubemapToCubeFacePS, FCopyToCubeFaceShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_TEXTURE(TextureCube, SourceCubemapTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, SourceCubemapSampler)
SHADER_PARAMETER(FVector, SkyLightCaptureParameters)
SHADER_PARAMETER(int32, CubeFace)
SHADER_PARAMETER(FVector4, LowerHemisphereColor)
SHADER_PARAMETER(FVector2D, SinCosSourceCubemapRotation)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_GLOBAL_SHADER(FCopyCubemapToCubeFacePS, “/Engine/Private/ReflectionEnvironmentShaders.usf”, “CopyCubemapToCubeFaceColorPS”, SF_Pixel);
D:\UnrealEngine426\Engine\Shaders\Private\ReflectionEnvironmentShaders.usf
int CubeFace;
float3 GetCubemapVector(float2 ScaledUVs, int InCubeFace)
{float3 CubeCoordinates;
//@todo - this could be a 3x3 matrix multiply
if (InCubeFace == 0)
{
CubeCoordinates = float3(1, -ScaledUVs.y, -ScaledUVs.x);
}
else if (InCubeFace == 1)
{
CubeCoordinates = float3(-1, -ScaledUVs.y, ScaledUVs.x);
}
else if (InCubeFace == 2)
{
CubeCoordinates = float3(ScaledUVs.x, 1, ScaledUVs.y);
}
else if (InCubeFace == 3)
{
CubeCoordinates = float3(ScaledUVs.x, -1, -ScaledUVs.y);
}
else if (InCubeFace == 4)
{
CubeCoordinates = float3(ScaledUVs.x, -ScaledUVs.y, 1);
}
else
{
CubeCoordinates = float3(-ScaledUVs.x, -ScaledUVs.y, -1);
}
return CubeCoordinates;
}
TextureCube SourceCubemapTexture;
SamplerState SourceCubemapSampler;
float2 SinCosSourceCubemapRotation;
void CopyCubemapToCubeFaceColorPS(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
float2 ScaledUVs = Input.UV * 2 - 1;
float3 CubeCoordinates = GetCubemapVector(ScaledUVs, CubeFace);
// Rotate around Z axis
CubeCoordinates.xy = float2(dot(CubeCoordinates.xy, float2(SinCosSourceCubemapRotation.y, -SinCosSourceCubemapRotation.x)), dot(CubeCoordinates.xy, SinCosSourceCubemapRotation));
OutColor = TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, CubeCoordinates, 0);
if (SkyLightCaptureParameters.x > 0)
{
// Assuming we're on a planet and no sky lighting is coming from below the horizon
// This is important to avoid leaking from below since we are integrating incoming lighting and shadowing separately
if (CubeCoordinates.z < 0 && SkyLightCaptureParameters.z >= 1)
{
OutColor.rgb = lerp(OutColor.rgb, LowerHemisphereColor.rgb, LowerHemisphereColor.a);
}
}
OutColor.a = 1;
}
4. D:\\UnrealEngine426\\Engine\\Source\\Runtime\\Engine\\Public\\ScreenRendering.h
1. /**
* A vertex shader for rendering a textured screen element.
*/
class FScreenVS : public FGlobalShader
{
DECLARE_EXPORTED_SHADER_TYPE(FScreenVS,Global,ENGINE_API);
public:
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return true; }
FScreenVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer):
FGlobalShader(Initializer)
{
}
FScreenVS() {}
void SetParameters(FRHICommandList& RHICmdList, FRHIUniformBuffer* ViewUniformBuffer)
{
FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, RHICmdList.GetBoundVertexShader(), ViewUniformBuffer);
}
};
2. D:\\UnrealEngine426\\Engine\\Source\\Runtime\\Engine\\Private\\ScreenRendering.cpp
1. // Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
ScreenRendering.cpp: Screen rendering implementation.
=============================================================================*/
#include "ScreenRendering.h"
/** Vertex declaration for screen-space rendering. */
TGlobalResource<FScreenVertexDeclaration> GScreenVertexDeclaration;
// Shader implementations.
IMPLEMENT_SHADER_TYPE(, FScreenPS, TEXT("/Engine/Private/ScreenPixelShader.usf"), TEXT("Main"), SF_Pixel);
IMPLEMENT_SHADER_TYPE(,FScreenPSInvertAlpha,TEXT("/Engine/Private/ScreenPixelShader.usf"),TEXT("MainInvertAlpha"),SF_Pixel);
IMPLEMENT_SHADER_TYPE(,FScreenPSsRGBSource, TEXT("/Engine/Private/ScreenPixelShader.usf"), TEXT("MainsRGBSource"), SF_Pixel);
IMPLEMENT_SHADER_TYPE(,FScreenPSMipLevel, TEXT("/Engine/Private/ScreenPixelShader.usf"), TEXT("MainMipLevel"), SF_Pixel);
IMPLEMENT_SHADER_TYPE(,FScreenPSsRGBSourceMipLevel, TEXT("/Engine/Private/ScreenPixelShader.usf"), TEXT("MainsRGBSourceMipLevel"), SF_Pixel);
IMPLEMENT_SHADER_TYPE(,FScreenVS,TEXT("/Engine/Private/ScreenVertexShader.usf"),TEXT("Main"),SF_Vertex);
IMPLEMENT_SHADER_TYPE(,FScreenPS_OSE,TEXT("/Engine/Private/ScreenPixelShaderOES.usf"),TEXT("Main"),SF_Pixel);
2. /Private/ScreenVertexShader.usf
1. void Main(
float2 InPosition : ATTRIBUTE0,
float2 InUV : ATTRIBUTE1,
out FScreenVertexOutput Output
)
{
DrawRectangle( float4( InPosition, 0, 1 ), InUV, Output.Position, Output.UV);
}
2. /Private/Common.ush
1. /** Used for calculating vertex positions and UVs when drawing with DrawRectangle */
void DrawRectangle(
in float4 InPosition,
in float2 InTexCoord,
out float4 OutPosition,
out float2 OutTexCoord)
{
OutPosition = InPosition;
OutPosition.xy = -1.0f + 2.0f * (DrawRectangleParameters.PosScaleBias.zw + (InPosition.xy * DrawRectangleParameters.PosScaleBias.xy)) * DrawRectangleParameters.InvTargetSizeAndTextureSize.xy;
OutPosition.xy *= float2( 1, -1 );
OutTexCoord.xy = (DrawRectangleParameters.UVScaleBias.zw + (InTexCoord.xy * DrawRectangleParameters.UVScaleBias.xy)) * DrawRectangleParameters.InvTargetSizeAndTextureSize.zw;
}
5. D:\\UnrealEngine426\\Engine\\Source\\Runtime\\Renderer\\Private\\PostProcess\\SceneFilterRendering.h
1. /**
* Draws a quad with the given vertex positions and UVs in denormalized pixel/texel coordinates.
* The platform-dependent mapping from pixels to texels is done automatically.
* Note that the positions are affected by the current viewport.
* NOTE: DrawRectangle should be used in the vertex shader to calculate the correct position and uv for vertices.
* NOTE2: Assumes previously set PSO has PrimitiveType = PT_TriangleList
*
* X, Y Position in screen pixels of the top left corner of the quad
* SizeX, SizeY Size in screen pixels of the quad
* U, V Position in texels of the top left corner of the quad's UV's
* SizeU, SizeV Size in texels of the quad's UV's
* TargetSizeX, TargetSizeY Size in screen pixels of the target surface
* TextureSize Size in texels of the source texture
* VertexShader The vertex shader used for rendering
* Flags see EDrawRectangleFlags
* InstanceCount Number of instances of rectangle
*/
extern RENDERER_API void DrawRectangle(
FRHICommandList& RHICmdList,
float X,
float Y,
float SizeX,
float SizeY,
float U,
float V,
float SizeU,
float SizeV,
FIntPoint TargetSize,
FIntPoint TextureSize,
const TShaderRef<FShader>& VertexShader,
EDrawRectangleFlags Flags = EDRF_Default,
uint32 InstanceCount = 1
);
/** Uniform buffer for computing the vertex positional and UV adjustments in the vertex shader. */
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT( FDrawRectangleParameters, RENDERER_API)
SHADER_PARAMETER( FVector4, PosScaleBias )
SHADER_PARAMETER( FVector4, UVScaleBias )
SHADER_PARAMETER( FVector4, InvTargetSizeAndTextureSize )
END_GLOBAL_SHADER_PARAMETER_STRUCT()
2. D:\\UnrealEngine426\\Engine\\Source\\Runtime\\Renderer\\Private\\PostProcess\\SceneFilterRendering.cpp
1. void DrawRectangle(
FRHICommandList& RHICmdList,
float X,
float Y,
float SizeX,
float SizeY,
float U,
float V,
float SizeU,
float SizeV,
FIntPoint TargetSize,
FIntPoint TextureSize,
const TShaderRef<FShader>& VertexShader,
EDrawRectangleFlags Flags,
uint32 InstanceCount
)
{
InternalDrawRectangle(RHICmdList, X, Y, SizeX, SizeY, U, V, SizeU, SizeV, TargetSize, TextureSize, VertexShader, Flags, InstanceCount);
}
template <typename TRHICommandList>
static inline void InternalDrawRectangle(
TRHICommandList& RHICmdList,
float X,
float Y,
float SizeX,
float SizeY,
float U,
float V,
float SizeU,
float SizeV,
FIntPoint TargetSize,
FIntPoint TextureSize,
const TShaderRef<FShader>& VertexShader,
EDrawRectangleFlags Flags,
uint32 InstanceCount
)
{
float ClipSpaceQuadZ = 0.0f;
DoDrawRectangleFlagOverride(Flags);
// triangle if extending to left and top of the given rectangle, if it's not left top of the viewport it can cause artifacts
if(X > 0.0f || Y > 0.0f)
{
// don't use triangle optimization
Flags = EDRF_Default;
}
// Set up vertex uniform parameters for scaling and biasing the rectangle.
// Note: Use DrawRectangle in the vertex shader to calculate the correct vertex position and uv.
FDrawRectangleParameters Parameters;
Parameters.PosScaleBias = FVector4(SizeX, SizeY, X, Y);
Parameters.UVScaleBias = FVector4(SizeU, SizeV, U, V);
Parameters.InvTargetSizeAndTextureSize = FVector4(
1.0f / TargetSize.X, 1.0f / TargetSize.Y,
1.0f / TextureSize.X, 1.0f / TextureSize.Y);
SetUniformBufferParameterImmediate(RHICmdList, VertexShader.GetVertexShader(), VertexShader->GetUniformBufferParameter<FDrawRectangleParameters>(), Parameters);
if(Flags == EDRF_UseTesselatedIndexBuffer)
{
// no vertex buffer needed as we compute it in VS
RHICmdList.SetStreamSource(0, NULL, 0);
RHICmdList.DrawIndexedPrimitive(
GTesselatedScreenRectangleIndexBuffer.IndexBufferRHI,
/*BaseVertexIndex=*/ 0,
/*MinIndex=*/ 0,
/*NumVertices=*/ GTesselatedScreenRectangleIndexBuffer.NumVertices(),
/*StartIndex=*/ 0,
/*NumPrimitives=*/ GTesselatedScreenRectangleIndexBuffer.NumPrimitives(),
/*NumInstances=*/ InstanceCount
);
}
else
{
if (Flags == EDRF_UseTriangleOptimization)
{
FPixelShaderUtils::DrawFullscreenTriangle(RHICmdList, InstanceCount);
}
else
{
FPixelShaderUtils::DrawFullscreenQuad(RHICmdList, InstanceCount);
}
}
}
2. D:\\UnrealEngine426\\Engine\\Source\\Runtime\\RenderCore\\Private\\PixelShaderUtils.cpp
1. // static
void FPixelShaderUtils::DrawFullscreenQuad(FRHICommandList& RHICmdList, uint32 InstanceCount)
{
RHICmdList.SetStreamSource(0, GScreenRectangleVertexBuffer.VertexBufferRHI, 0);
RHICmdList.DrawIndexedPrimitive(
GScreenRectangleIndexBuffer.IndexBufferRHI,
/*BaseVertexIndex=*/ 0,
/*MinIndex=*/ 0,
/*NumVertices=*/ 4,
/*StartIndex=*/ 0,
/*NumPrimitives=*/ 2,
/*NumInstances=*/ InstanceCount);
}
6. ComputeAverageBrightness()
1. void ComputeAverageBrightness(FRHICommandListImmediate& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, int32 CubmapSize, float& OutAverageBrightness)
{
SCOPED_DRAW_EVENT(RHICmdList, ComputeAverageBrightness);
const int32 EffectiveTopMipSize = CubmapSize;
const int32 NumMips = FMath::CeilLogTwo(EffectiveTopMipSize) + 1;
// necessary to resolve the clears which touched all the mips. scene rendering only resolves mip 0.
FullyResolveReflectionScratchCubes(RHICmdList);
FSceneRenderTargetItem& DownSampledCube = FSceneRenderTargets::Get(RHICmdList).ReflectionColorScratchCubemap[0]->GetRenderTargetItem();
CreateCubeMips( RHICmdList, FeatureLevel, NumMips, DownSampledCube );
OutAverageBrightness = ComputeSingleAverageBrightnessFromCubemap(RHICmdList, FeatureLevel, CubmapSize, DownSampledCube);
}
2. void CreateCubeMips( FRHICommandListImmediate& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, int32 NumMips, FSceneRenderTargetItem& Cubemap )
{
SCOPED_DRAW_EVENT(RHICmdList, CreateCubeMips);
FRHITexture* CubeRef = Cubemap.TargetableTexture.GetReference();
auto* ShaderMap = GetGlobalShaderMap(FeatureLevel);
TArray<TPair<FRHITextureSRVCreateInfo, TRefCountPtr<FRHIShaderResourceView>>> SRVs;
SRVs.Empty(NumMips);
for (int32 MipIndex = 0; MipIndex < NumMips; MipIndex++)
{
FRHITextureSRVCreateInfo SRVDesc;
SRVDesc.MipLevel = MipIndex;
SRVs.Emplace(SRVDesc, RHICreateShaderResourceView(Cubemap.ShaderResourceTexture, SRVDesc));
}
FGraphicsPipelineStateInitializer GraphicsPSOInit;
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
// Downsample all the mips, each one reads from the mip above it
for (int32 MipIndex = 1; MipIndex < NumMips; MipIndex++)
{
// For the first iteration, we don't know what the previous state
// of the source mip was, but we *do* for all the other iterations...
ERHIAccess Previous = MipIndex == 1
? ERHIAccess::Unknown
: ERHIAccess::RTV;
FRHITransitionInfo Transitions[] =
{
// Make the source mip readable (SRVGraphics)
FRHITransitionInfo(CubeRef, Previous, ERHIAccess::SRVGraphics, EResourceTransitionFlags::None, uint32(MipIndex - 1)),
// Make the destination mip writable (RTV)
FRHITransitionInfo(CubeRef, ERHIAccess::Unknown, ERHIAccess::RTV, EResourceTransitionFlags::None, uint32(MipIndex))
};
RHICmdList.Transition(Transitions);
const int32 MipSize = 1 << (NumMips - MipIndex - 1);
SCOPED_DRAW_EVENT(RHICmdList, CreateCubeMipsPerFace);
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
{
FRHIRenderPassInfo RPInfo(Cubemap.TargetableTexture, ERenderTargetActions::DontLoad_Store, nullptr, MipIndex, CubeFace);
RHICmdList.BeginRenderPass(RPInfo, TEXT("CreateCubeMips"));
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
const FIntRect ViewRect(0, 0, MipSize, MipSize);
RHICmdList.SetViewport(0.0f, 0.0f, 0.0f, (float)MipSize, (float)MipSize, 1.0f);
TShaderMapRef<FScreenVS> VertexShader(ShaderMap);
TShaderMapRef<FCubeFilterPS> PixelShader(ShaderMap);
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
{
FRHIPixelShader* ShaderRHI = PixelShader.GetPixelShader();
SetShaderValue(RHICmdList, ShaderRHI, PixelShader->CubeFace, CubeFace);
SetShaderValue(RHICmdList, ShaderRHI, PixelShader->MipIndex, MipIndex);
SetShaderValue(RHICmdList, ShaderRHI, PixelShader->NumMips, NumMips);
check(SRVs.IsValidIndex(MipIndex - 1) && SRVs[MipIndex - 1].Key.MipLevel == MipIndex - 1);
SetSRVParameter(RHICmdList, ShaderRHI, PixelShader->SourceCubemapTexture, SRVs[MipIndex - 1].Value);
SetSamplerParameter(RHICmdList, ShaderRHI, PixelShader->SourceCubemapSampler, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
}
DrawRectangle(
RHICmdList,
ViewRect.Min.X, ViewRect.Min.Y,
ViewRect.Width(), ViewRect.Height(),
ViewRect.Min.X, ViewRect.Min.Y,
ViewRect.Width(), ViewRect.Height(),
FIntPoint(ViewRect.Width(), ViewRect.Height()),
FIntPoint(MipSize, MipSize),
VertexShader);
RHICmdList.EndRenderPass();
}
}
RHICmdList.Transition(FRHITransitionInfo(CubeRef, ERHIAccess::Unknown, ERHIAccess::SRVMask));
SRVs.Empty();
}
1. void DownsamplePS(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
float2 ScaledUVs = Input.UV * 2 - 1;
const int SelectedCubeFace = CubeFace;
#endif // USE_COMPUTE
float3 CubeCoordinates = GetCubemapVector(ScaledUVs, SelectedCubeFace);
uint MipSize = 1 << ( NumMips - MipIndex - 1 );
float3 TangentZ = normalize( CubeCoordinates );
float3 TangentX = normalize( cross( GetCubemapVector( ScaledUVs + float2(0,1), SelectedCubeFace), TangentZ ) );
float3 TangentY = cross( TangentZ, TangentX );
const float SampleOffset = 2.0 * 2 / MipSize;
float2 Offsets[] =
{
float2(-1, -1) * 0.7,
float2( 1, -1) * 0.7,
float2(-1, 1) * 0.7,
float2( 1, 1) * 0.7,
float2( 0, -1),
float2(-1, 0),
float2( 1, 0),
float2( 0, 1),
};
OutColor = SourceCubemapTexture.SampleLevel(SourceCubemapSampler, CubeCoordinates, 0 );
UNROLL
for( uint i = 0; i < 8; i++ )
{
float Weight = 0.375;
float3 SampleDir = CubeCoordinates;
SampleDir += TangentX * ( Offsets[i].x * SampleOffset );
SampleDir += TangentY * ( Offsets[i].y * SampleOffset );
OutColor += SourceCubemapTexture.SampleLevel(SourceCubemapSampler, SampleDir, 0 ) * Weight;
}
OutColor *= rcp( 1.0 + 1.0 + 2.0 );
#ifdef USE_COMPUTE
OutTextureMipColor[uint3(FaceCoord, SelectedCubeFace)] = OutColor;
#endif
}
3. /** Computes the average brightness of the given reflection capture and stores it in the scene. */
float ComputeSingleAverageBrightnessFromCubemap(FRHICommandListImmediate& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, int32 TargetSize, FSceneRenderTargetItem& Cubemap)
{
SCOPED_DRAW_EVENT(RHICmdList, ComputeSingleAverageBrightnessFromCubemap);
TRefCountPtr<IPooledRenderTarget> ReflectionBrightnessTarget;
FPooledRenderTargetDesc Desc(FPooledRenderTargetDesc::Create2DDesc(FIntPoint(1, 1), PF_FloatRGBA, FClearValueBinding::None, TexCreate_None, TexCreate_RenderTargetable, false));
GRenderTargetPool.FindFreeElement(RHICmdList, Desc, ReflectionBrightnessTarget, TEXT("ReflectionBrightness"));
FTextureRHIRef& BrightnessTarget = ReflectionBrightnessTarget->GetRenderTargetItem().TargetableTexture;
FRHIRenderPassInfo RPInfo(BrightnessTarget, ERenderTargetActions::Load_Store);
TransitionRenderPassTargets(RHICmdList, RPInfo);
RHICmdList.BeginRenderPass(RPInfo, TEXT("ReflectionBrightness"));
{
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
auto ShaderMap = GetGlobalShaderMap(FeatureLevel);
TShaderMapRef<FPostProcessVS> VertexShader(ShaderMap);
TShaderMapRef<FComputeBrightnessPS> PixelShader(ShaderMap);
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
PixelShader->SetParameters(RHICmdList, TargetSize, Cubemap);
DrawRectangle(
RHICmdList,
0, 0,
1, 1,
0, 0,
1, 1,
FIntPoint(1, 1),
FIntPoint(1, 1),
VertexShader);
}
RHICmdList.EndRenderPass();
RHICmdList.CopyToResolveTarget(BrightnessTarget, BrightnessTarget, FResolveParams());
FSceneRenderTargetItem& EffectiveRT = ReflectionBrightnessTarget->GetRenderTargetItem();
check(EffectiveRT.ShaderResourceTexture->GetFormat() == PF_FloatRGBA);
TArray<FFloat16Color> SurfaceData;
RHICmdList.ReadSurfaceFloatData(EffectiveRT.ShaderResourceTexture, FIntRect(0, 0, 1, 1), SurfaceData, CubeFace_PosX, 0, 0);
// Shader outputs luminance to R
float AverageBrightness = SurfaceData[0].R.GetFloat();
return AverageBrightness;
}
1. int NumCaptureArrayMips;
/** Cube map array of reflection captures. */
TextureCube ReflectionEnvironmentColorTexture;
SamplerState ReflectionEnvironmentColorSampler;
#if COMPUTEBRIGHTNESS_PIXELSHADER
void ComputeBrightnessMain(
in float4 UVAndScreenPos : TEXCOORD0,
out float4 OutColor : SV_Target0
)
{
// Sample the 6 1x1 cube faces and average
float3 AverageColor = TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(1, 0, 0), NumCaptureArrayMips - 1).rgb;
AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(-1, 0, 0), NumCaptureArrayMips - 1).rgb;
AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(0, 1, 0), NumCaptureArrayMips - 1).rgb;
AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(0, -1, 0), NumCaptureArrayMips - 1).rgb;
AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(0, 0, 1), NumCaptureArrayMips - 1).rgb;
AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(0, 0, -1), NumCaptureArrayMips - 1).rgb;
OutColor = dot(AverageColor / 6, .3333f);
}
#endif
7. FilterReflectionEnvironment()
1. /** Generates mips for glossiness and filters the cubemap for a given reflection. */
void FilterReflectionEnvironment(FRHICommandListImmediate& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, int32 CubmapSize, FSHVectorRGB3* OutIrradianceEnvironmentMap)
{
SCOPED_DRAW_EVENT(RHICmdList, FilterReflectionEnvironment);
const int32 EffectiveTopMipSize = CubmapSize;
const int32 NumMips = FMath::CeilLogTwo(EffectiveTopMipSize) + 1;
FSceneRenderTargetItem& EffectiveColorRT = FSceneRenderTargets::Get(RHICmdList).ReflectionColorScratchCubemap[0]->GetRenderTargetItem();
FGraphicsPipelineStateInitializer GraphicsPSOInit;
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_Zero, BF_DestAlpha, BO_Add, BF_Zero, BF_One>::GetRHI();
RHICmdList.Transition(FRHITransitionInfo(EffectiveColorRT.TargetableTexture, ERHIAccess::Unknown, ERHIAccess::RTV));
// Premultiply alpha in-place using alpha blending
for (uint32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
{
FRHIRenderPassInfo RPInfo(EffectiveColorRT.TargetableTexture, ERenderTargetActions::Load_Store, nullptr, 0, CubeFace);
RHICmdList.BeginRenderPass(RPInfo, TEXT("FilterReflectionEnvironmentRP"));
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
const FIntPoint SourceDimensions(CubmapSize, CubmapSize);
const FIntRect ViewRect(0, 0, EffectiveTopMipSize, EffectiveTopMipSize);
RHICmdList.SetViewport(0, 0, 0.0f, EffectiveTopMipSize, EffectiveTopMipSize, 1.0f);
TShaderMapRef<FScreenVS> VertexShader(GetGlobalShaderMap(FeatureLevel));
TShaderMapRef<FOneColorPS> PixelShader(GetGlobalShaderMap(FeatureLevel));
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
FLinearColor UnusedColors[1] = { FLinearColor::Black };
PixelShader->SetColors(RHICmdList, UnusedColors, UE_ARRAY_COUNT(UnusedColors));
DrawRectangle(
RHICmdList,
ViewRect.Min.X, ViewRect.Min.Y,
ViewRect.Width(), ViewRect.Height(),
0, 0,
SourceDimensions.X, SourceDimensions.Y,
FIntPoint(ViewRect.Width(), ViewRect.Height()),
SourceDimensions,
VertexShader);
RHICmdList.EndRenderPass();
}
RHICmdList.Transition(FRHITransitionInfo(EffectiveColorRT.TargetableTexture, ERHIAccess::Unknown, ERHIAccess::SRVMask));
auto ShaderMap = GetGlobalShaderMap(FeatureLevel);
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
FSceneRenderTargetItem& DownSampledCube = FSceneRenderTargets::Get(RHICmdList).ReflectionColorScratchCubemap[0]->GetRenderTargetItem();
FSceneRenderTargetItem& FilteredCube = FSceneRenderTargets::Get(RHICmdList).ReflectionColorScratchCubemap[1]->GetRenderTargetItem();
CreateCubeMips( RHICmdList, FeatureLevel, NumMips, DownSampledCube );
if (OutIrradianceEnvironmentMap)
{
SCOPED_DRAW_EVENT(RHICmdList, ComputeDiffuseIrradiance);
const int32 NumDiffuseMips = FMath::CeilLogTwo( GDiffuseIrradianceCubemapSize ) + 1;
const int32 DiffuseConvolutionSourceMip = FMath::Max(0, NumMips - NumDiffuseMips);
ComputeDiffuseIrradiance(RHICmdList, FeatureLevel, DownSampledCube.ShaderResourceTexture, DiffuseConvolutionSourceMip, OutIrradianceEnvironmentMap);
}
FilterCubeMap(RHICmdList, FeatureLevel, NumMips, DownSampledCube, FilteredCube);
RHICmdList.CopyToResolveTarget(FilteredCube.TargetableTexture, FilteredCube.ShaderResourceTexture, FResolveParams());
}
1. ComputeDiffuseIrradiance() D:\\UnrealEngine426\\Engine\\Source\\Runtime\\Renderer\\Private\\ReflectionEnvironmentDiffuseIrradiance.cpp
1. void ComputeDiffuseIrradiance(FRHICommandListImmediate& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, FTextureRHIRef LightingSource, int32 LightingSourceMipIndex, FSHVectorRGB3* OutIrradianceEnvironmentMap)
{
auto ShaderMap = GetGlobalShaderMap(FeatureLevel);
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
for (int32 CoefficientIndex = 0; CoefficientIndex < FSHVector3::MaxSHBasis; CoefficientIndex++)
{
// Copy the starting mip from the lighting texture, apply texel area weighting and appropriate SH coefficient
{
const int32 MipIndex = 0;
const int32 MipSize = GDiffuseIrradianceCubemapSize;
FSceneRenderTargetItem& EffectiveRT = GetEffectiveDiffuseIrradianceRenderTarget(SceneContext, MipIndex);
RHICmdList.Transition(FRHITransitionInfo(EffectiveRT.TargetableTexture, ERHIAccess::Unknown, ERHIAccess::RTV));
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
{
FRHIRenderPassInfo RPInfo(EffectiveRT.TargetableTexture, ERenderTargetActions::DontLoad_Store, nullptr, 0, CubeFace);
RHICmdList.BeginRenderPass(RPInfo, TEXT("CopyDiffuseIrradianceRP"));
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
const FIntRect ViewRect(0, 0, MipSize, MipSize);
RHICmdList.SetViewport(0.0f, 0.0f, 0.0f, (float)MipSize, (float)MipSize, 1.0f);
TShaderMapRef<FCopyDiffuseIrradiancePS> PixelShader(ShaderMap);
TShaderMapRef<FScreenVS> VertexShader(GetGlobalShaderMap(FeatureLevel));
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
PixelShader->SetParameters(RHICmdList, CubeFace, LightingSourceMipIndex, CoefficientIndex, MipSize, LightingSource);
DrawRectangle(
RHICmdList,
ViewRect.Min.X, ViewRect.Min.Y,
ViewRect.Width(), ViewRect.Height(),
ViewRect.Min.X, ViewRect.Min.Y,
ViewRect.Width(), ViewRect.Height(),
FIntPoint(ViewRect.Width(), ViewRect.Height()),
FIntPoint(MipSize, MipSize),
VertexShader);
RHICmdList.EndRenderPass();
}
RHICmdList.Transition(FRHITransitionInfo(EffectiveRT.TargetableTexture, ERHIAccess::RTV, ERHIAccess::SRVGraphics));
}
const int32 NumMips = FMath::CeilLogTwo(GDiffuseIrradianceCubemapSize) + 1;
{
// Accumulate all the texel values through downsampling to 1x1 mip
for (int32 MipIndex = 1; MipIndex < NumMips; MipIndex++)
{
const int32 SourceMipIndex = FMath::Max(MipIndex - 1, 0);
const int32 MipSize = 1 << (NumMips - MipIndex - 1);
FSceneRenderTargetItem& EffectiveRT = GetEffectiveDiffuseIrradianceRenderTarget(SceneContext, MipIndex);
FSceneRenderTargetItem& EffectiveSource = GetEffectiveDiffuseIrradianceSourceTexture(SceneContext, MipIndex);
check(EffectiveRT.TargetableTexture != EffectiveSource.ShaderResourceTexture);
RHICmdList.Transition(FRHITransitionInfo(EffectiveRT.TargetableTexture, ERHIAccess::Unknown, ERHIAccess::RTV));
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
{
FRHIRenderPassInfo RPInfo(EffectiveRT.TargetableTexture, ERenderTargetActions::Load_Store, nullptr, MipIndex, CubeFace);
RHICmdList.BeginRenderPass(RPInfo, TEXT("AccumulateDiffuseIrradianceRP"));
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
const FIntRect ViewRect(0, 0, MipSize, MipSize);
RHICmdList.SetViewport(0.0f, 0.0f, 0.0f, (float)MipSize, (float)MipSize, 1.0f);
TShaderMapRef<FAccumulateDiffuseIrradiancePS> PixelShader(ShaderMap);
TShaderMapRef<FScreenVS> VertexShader(GetGlobalShaderMap(FeatureLevel));
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
PixelShader->SetParameters(RHICmdList, CubeFace, NumMips, SourceMipIndex, CoefficientIndex, EffectiveSource.ShaderResourceTexture);
DrawRectangle(
RHICmdList,
ViewRect.Min.X, ViewRect.Min.Y,
ViewRect.Width(), ViewRect.Height(),
ViewRect.Min.X, ViewRect.Min.Y,
ViewRect.Width(), ViewRect.Height(),
FIntPoint(ViewRect.Width(), ViewRect.Height()),
FIntPoint(MipSize, MipSize),
VertexShader);
RHICmdList.EndRenderPass();
}
RHICmdList.Transition(FRHITransitionInfo(EffectiveRT.TargetableTexture, ERHIAccess::RTV, ERHIAccess::SRVGraphics));
}
}
{
// Gather the cubemap face results and normalize, copy this coefficient to FSceneRenderTargets::Get(RHICmdList).SkySHIrradianceMap
FSceneRenderTargetItem& EffectiveRT = FSceneRenderTargets::Get(RHICmdList).SkySHIrradianceMap->GetRenderTargetItem();
//load/store actions so we don't lose results as we render one pixel at a time on tile renderers.
RHICmdList.Transition(FRHITransitionInfo(EffectiveRT.TargetableTexture, ERHIAccess::Unknown, ERHIAccess::RTV));
FRHIRenderPassInfo RPInfo(EffectiveRT.TargetableTexture, ERenderTargetActions::Load_Store, nullptr);
RHICmdList.BeginRenderPass(RPInfo, TEXT("GatherCoeffRP"));
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
const FIntRect ViewRect(CoefficientIndex, 0, CoefficientIndex + 1, 1);
RHICmdList.SetViewport(0.0f, 0.0f, 0.0f, (float)FSHVector3::MaxSHBasis, 1.0f, 1.0f);
TShaderMapRef<FScreenVS> VertexShader(ShaderMap);
TShaderMapRef<FAccumulateCubeFacesPS> PixelShader(ShaderMap);
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
const int32 SourceMipIndex = NumMips - 1;
const int32 MipSize = 1;
FSceneRenderTargetItem& EffectiveSource = GetEffectiveDiffuseIrradianceRenderTarget(SceneContext, SourceMipIndex);
PixelShader->SetParameters(RHICmdList, SourceMipIndex, EffectiveSource.ShaderResourceTexture);
DrawRectangle(
RHICmdList,
ViewRect.Min.X, ViewRect.Min.Y,
ViewRect.Width(), ViewRect.Height(),
0, 0,
MipSize, MipSize,
FIntPoint(FSHVector3::MaxSHBasis, 1),
FIntPoint(MipSize, MipSize),
VertexShader);
RHICmdList.EndRenderPass();
RHICmdList.Transition(FRHITransitionInfo(EffectiveRT.TargetableTexture, ERHIAccess::RTV, ERHIAccess::SRVGraphics));
}
}
{
// Read back the completed SH environment map
FSceneRenderTargetItem& EffectiveRT = FSceneRenderTargets::Get(RHICmdList).SkySHIrradianceMap->GetRenderTargetItem();
check(EffectiveRT.ShaderResourceTexture->GetFormat() == PF_FloatRGBA);
TArray<FFloat16Color> SurfaceData;
RHICmdList.ReadSurfaceFloatData(EffectiveRT.ShaderResourceTexture, FIntRect(0, 0, FSHVector3::MaxSHBasis, 1), SurfaceData, CubeFace_PosX, 0, 0);
check(SurfaceData.Num() == FSHVector3::MaxSHBasis);
for (int32 CoefficientIndex = 0; CoefficientIndex < FSHVector3::MaxSHBasis; CoefficientIndex++)
{
const FLinearColor CoefficientValue(SurfaceData[CoefficientIndex]);
OutIrradianceEnvironmentMap->R.V[CoefficientIndex] = CoefficientValue.R;
OutIrradianceEnvironmentMap->G.V[CoefficientIndex] = CoefficientValue.G;
OutIrradianceEnvironmentMap->B.V[CoefficientIndex] = CoefficientValue.B;
}
}
}
D:\\UnrealEngine426\\Engine\\Shaders\\Private\\ReflectionEnvironmentShaders.usf
1. float4 CoefficientMask0;
float4 CoefficientMask1;
float CoefficientMask2;
int NumSamples;
void DiffuseIrradianceCopyPS(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
float2 ScaledUVs = Input.UV * 2 - 1;
float3 CubeCoordinates = normalize(GetCubemapVector(ScaledUVs, CubeFace));
float SquaredUVs = 1 + dot(ScaledUVs, ScaledUVs);
// Dividing by NumSamples here to keep the sum in the range of fp16, once we get down to the 1x1 mip
float TexelWeight = 4 / (sqrt(SquaredUVs) * SquaredUVs);
FThreeBandSHVector SHCoefficients = SHBasisFunction3(CubeCoordinates);
float CurrentSHCoefficient = dot(SHCoefficients.V0, CoefficientMask0) + dot(SHCoefficients.V1, CoefficientMask1) + SHCoefficients.V2 * CoefficientMask2;
float3 TexelLighting = SampleCubemap(CubeCoordinates).rgb;
OutColor = float4(TexelLighting * CurrentSHCoefficient * TexelWeight, TexelWeight);
}
float4 Sample01;
float4 Sample23;
void DiffuseIrradianceAccumulatePS(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
float4 AccumulatedValue = 0;
{
float2 ScaledUVs = saturate(Input.UV + Sample01.xy) * 2 - 1;
float3 CubeCoordinates = GetCubemapVector(ScaledUVs, CubeFace);
AccumulatedValue += SampleCubemap(CubeCoordinates);
}
{
float2 ScaledUVs = saturate(Input.UV + Sample01.zw) * 2 - 1;
float3 CubeCoordinates = GetCubemapVector(ScaledUVs, CubeFace);
AccumulatedValue += SampleCubemap(CubeCoordinates);
}
{
float2 ScaledUVs = saturate(Input.UV + Sample23.xy) * 2 - 1;
float3 CubeCoordinates = GetCubemapVector(ScaledUVs, CubeFace);
AccumulatedValue += SampleCubemap(CubeCoordinates);
}
{
float2 ScaledUVs = saturate(Input.UV + Sample23.zw) * 2 - 1;
float3 CubeCoordinates = GetCubemapVector(ScaledUVs, CubeFace);
AccumulatedValue += SampleCubemap(CubeCoordinates);
}
OutColor = float4(AccumulatedValue.rgb / 4.0f, AccumulatedValue.a / 4.0f);
}
void AccumulateCubeFacesPS(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
float4 AccumulatedValue;
AccumulatedValue = TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3(1, 0, 0), SourceMipIndex);
AccumulatedValue += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3(-1, 0, 0), SourceMipIndex);
AccumulatedValue += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3(0, 1, 0), SourceMipIndex);
AccumulatedValue += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3(0, -1, 0), SourceMipIndex);
AccumulatedValue += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3(0, 0, 1), SourceMipIndex);
AccumulatedValue += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3(0, 0, -1), SourceMipIndex);
OutColor = float4(4 * PI * AccumulatedValue.rgb / ( max(AccumulatedValue.a, .00001f)), 0);
}
D:\\UnrealEngine426\\Engine\\Shaders\\Private\\SHCommon.ush
2. FThreeBandSHVector SHBasisFunction3(half3 InputVector)
{
FThreeBandSHVector Result;
// These are derived from simplifying SHBasisFunction in C++
Result.V0.x = 0.282095f;
Result.V0.y = -0.488603f * InputVector.y;
Result.V0.z = 0.488603f * InputVector.z;
Result.V0.w = -0.488603f * InputVector.x;
half3 VectorSquared = InputVector * InputVector;
Result.V1.x = 1.092548f * InputVector.x * InputVector.y;
Result.V1.y = -1.092548f * InputVector.y * InputVector.z;
Result.V1.z = 0.315392f * (3.0f * VectorSquared.z - 1.0f);
Result.V1.w = -1.092548f * InputVector.x * InputVector.z;
Result.V2 = 0.546274f * (VectorSquared.x - VectorSquared.y);
return Result;
}
2. FilterCubeMap()
1. void FilterCubeMap(FRHICommandListImmediate& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, int32 NumMips,
FSceneRenderTargetItem& DownSampledCube, FSceneRenderTargetItem& FilteredCube)
{
SCOPED_DRAW_EVENT(RHICmdList, FilterCubeMap);
auto ShaderMap = GetGlobalShaderMap(FeatureLevel);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
RHICmdList.Transition(FRHITransitionInfo(FilteredCube.TargetableTexture, ERHIAccess::Unknown, ERHIAccess::RTV));
// Filter all the mips
for (int32 MipIndex = 0; MipIndex < NumMips; MipIndex++)
{
const int32 MipSize = 1 << (NumMips - MipIndex - 1);
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
{
FRHIRenderPassInfo RPInfo(FilteredCube.TargetableTexture, ERenderTargetActions::DontLoad_Store, nullptr, MipIndex, CubeFace);
RHICmdList.Transition(FRHITransitionInfo(FilteredCube.TargetableTexture, ERHIAccess::Unknown, ERHIAccess::RTV));
RHICmdList.BeginRenderPass(RPInfo, TEXT("FilterMips"));
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
const FIntRect ViewRect(0, 0, MipSize, MipSize);
RHICmdList.SetViewport(0, 0, 0.0f, MipSize, MipSize, 1.0f);
//TShaderMapRef<TCubeFilterPS<1>> CaptureCubemapArrayPixelShader(GetGlobalShaderMap(FeatureLevel));
TShaderMapRef<FScreenVS> VertexShader(GetGlobalShaderMap(FeatureLevel));
TShaderMapRef<TCubeFilterPS<0>> PixelShader(GetGlobalShaderMap(FeatureLevel));
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
{
FRHIPixelShader* ShaderRHI = PixelShader.GetPixelShader();
SetShaderValue(RHICmdList, ShaderRHI, PixelShader->CubeFace, CubeFace);
SetShaderValue(RHICmdList, ShaderRHI, PixelShader->MipIndex, MipIndex);
SetShaderValue(RHICmdList, ShaderRHI, PixelShader->NumMips, NumMips);
SetTextureParameter(
RHICmdList,
ShaderRHI,
PixelShader->SourceCubemapTexture,
PixelShader->SourceCubemapSampler,
TStaticSamplerState<SF_Trilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI(),
DownSampledCube.ShaderResourceTexture);
}
DrawRectangle(
RHICmdList,
ViewRect.Min.X, ViewRect.Min.Y,
ViewRect.Width(), ViewRect.Height(),
ViewRect.Min.X, ViewRect.Min.Y,
ViewRect.Width(), ViewRect.Height(),
FIntPoint(ViewRect.Width(), ViewRect.Height()),
FIntPoint(MipSize, MipSize),
VertexShader);
RHICmdList.EndRenderPass();
}
}
}
D:\\UnrealEngine426\\Engine\\Shaders\\Private\\ReflectionEnvironmentShaders.usf
1. #ifdef USE_COMPUTE
int CubeFaceOffset;
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)]
void FilterCS(uint3 ThreadId : SV_DispatchThreadID)
{
const uint2 FaceCoord = uint2(ThreadId.x % uint(FaceThreadGroupSize), ThreadId.y);
if (any(FaceCoord >= uint2(ValidDispatchCoord)))
{
return;
}
const int SelectedCubeFace = CubeFaceOffset + int(ThreadId.x) / FaceThreadGroupSize;
float2 ScaledUVs = ((float2(FaceCoord) + 0.5f) / float2(ValidDispatchCoord)) * 2.0f - 1.0f;
float4 OutColor;
#else // USE_COMPUTE
void FilterPS(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
float2 ScaledUVs = Input.UV * 2 - 1;
const int SelectedCubeFace = CubeFace;
#endif // USE_COMPUTE
float3 CubeCoordinates = GetCubemapVector(ScaledUVs, SelectedCubeFace);
float3 N = normalize(CubeCoordinates);
float3x3 TangentToWorld = GetTangentBasis( N );
float Roughness = ComputeReflectionCaptureRoughnessFromMip( MipIndex, NumMips - 1 );
if( Roughness < 0.01 )
{
OutColor = SourceCubemapTexture.SampleLevel(SourceCubemapSampler, CubeCoordinates, 0 );
#ifdef USE_COMPUTE
OutTextureMipColor[uint3(FaceCoord, SelectedCubeFace)] = OutColor;
#endif
return;
}
uint CubeSize = 1 << ( NumMips - 1 );
const float SolidAngleTexel = 4*PI / ( 6 * CubeSize * CubeSize ) * 2;
//const uint NumSamples = 1024;
const uint NumSamples = Roughness < 0.1 ? 32 : 64;
float4 FilteredColor = 0;
BRANCH
if( Roughness > 0.99 )
{
// Roughness=1, GGX is constant. Use cosine distribution instead
LOOP
for( uint i = 0; i < NumSamples; i++ )
{
float2 E = Hammersley( i, NumSamples, 0 );
float3 L = CosineSampleHemisphere( E ).xyz;
float NoL = L.z;
float PDF = NoL / PI;
float SolidAngleSample = 1.0 / ( NumSamples * PDF );
float Mip = 0.5 * log2( SolidAngleSample / SolidAngleTexel );
L = mul( L, TangentToWorld );
FilteredColor += SourceCubemapTexture.SampleLevel(SourceCubemapSampler, L, Mip );
}
OutColor = FilteredColor / NumSamples;
}
else
{
float Weight = 0;
LOOP
for( uint i = 0; i < NumSamples; i++ )
{
float2 E = Hammersley( i, NumSamples, 0 );
// 6x6 Offset rows. Forms uniform star pattern
//uint2 Index = uint2( i % 6, i / 6 );
//float2 E = ( Index + 0.5 ) / 5.8;
//E.x = frac( E.x + (Index.y & 1) * (0.5 / 6.0) );
E.y *= 0.995;
float3 H = ImportanceSampleGGX( E, Pow4(Roughness) ).xyz;
float3 L = 2 * H.z * H - float3(0,0,1);
float NoL = L.z;
float NoH = H.z;
if( NoL > 0 )
{
//float TexelWeight = CubeTexelWeight( L );
//float SolidAngleTexel = SolidAngleAvgTexel * TexelWeight;
//float PDF = D_GGX( Pow4(Roughness), NoH ) * NoH / (4 * VoH);
float PDF = D_GGX( Pow4(Roughness), NoH ) * 0.25;
float SolidAngleSample = 1.0 / ( NumSamples * PDF );
float Mip = 0.5 * log2( SolidAngleSample / SolidAngleTexel );
float ConeAngle = acos( 1 - SolidAngleSample / (2*PI) );
L = mul( L, TangentToWorld );
FilteredColor += SourceCubemapTexture.SampleLevel(SourceCubemapSampler, L, Mip ) * NoL;
Weight += NoL;
}
}
OutColor = FilteredColor / Weight;
}
#ifdef USE_COMPUTE
OutTextureMipColor[uint3(FaceCoord, SelectedCubeFace)] = OutColor;
#endif
}
D:\\UnrealEngine426\\Engine\\Shaders\\Private\\MonteCarlo.ush
1. float2 Hammersley( uint Index, uint NumSamples, uint2 Random )
{
float E1 = frac( (float)Index / NumSamples + float( Random.x & 0xffff ) / (1<<16) );
float E2 = float( ReverseBits32(Index) ^ Random.y ) * 2.3283064365386963e-10;
return float2( E1, E2 );
}
float4 CosineSampleHemisphere( float2 E )
{
float Phi = 2 * PI * E.x;
float CosTheta = sqrt( E.y );
float SinTheta = sqrt( 1 - CosTheta * CosTheta );
float3 H;
H.x = SinTheta * cos( Phi );
H.y = SinTheta * sin( Phi );
H.z = CosTheta;
float PDF = CosTheta * (1.0 / PI);
return float4( H, PDF );
}
float4 ImportanceSampleGGX( float2 E, float a2 )
{
float Phi = 2 * PI * E.x;
float CosTheta = sqrt( (1 - E.y) / ( 1 + (a2 - 1) * E.y ) );
float SinTheta = sqrt( 1 - CosTheta * CosTheta );
float3 H;
H.x = SinTheta * cos( Phi );
H.y = SinTheta * sin( Phi );
H.z = CosTheta;
float d = ( CosTheta * a2 - CosTheta ) * CosTheta + 1;
float D = a2 / ( PI*d*d );
float PDF = D * CosTheta;
return float4( H, PDF );
}
2. D:\\UnrealEngine426\\Engine\\Shaders\\Private\\BRDF.ush
1. // GGX / Trowbridge-Reitz
// [Walter et al. 2007, "Microfacet models for refraction through rough surfaces"]
float D_GGX( float a2, float NoH )
{
float d = ( NoH * a2 - NoH ) * NoH + 1; // 2 mad
return a2 / ( PI*d*d ); // 4 mul, 1 rcp
}
还没有评论,来说两句吧...