From 18778aa51159a9758bd4a1389e9393346e01e49a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 14 Jan 2026 09:49:01 +0100 Subject: [PATCH] **Add Box Projection to Environment Probe** for better indoor areas --- .../Features/ForwardShading.hlsl | 6 +- .../Graphics/Materials/MaterialShader.h | 2 +- Source/Engine/Graphics/RenderTools.cpp | 10 +++ Source/Engine/Graphics/RenderTools.h | 1 + .../Engine/Level/Actors/EnvironmentProbe.cpp | 48 ++++++++--- Source/Engine/Level/Actors/EnvironmentProbe.h | 23 ++++-- Source/Engine/Renderer/Config.h | 5 +- Source/Engine/Renderer/ProbesRenderer.cpp | 9 ++- Source/Engine/Renderer/ReflectionsPass.cpp | 21 +++-- Source/Engine/Renderer/ReflectionsPass.h | 1 + Source/Engine/Renderer/RenderList.cpp | 16 +++- Source/Engine/Renderer/RenderList.h | 7 +- Source/Shaders/Common.hlsl | 12 --- Source/Shaders/Reflections.shader | 12 +-- Source/Shaders/ReflectionsCommon.hlsl | 81 ++++++++++++++----- 15 files changed, 176 insertions(+), 78 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl index 123201d1e..331981acb 100644 --- a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl +++ b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl @@ -4,8 +4,8 @@ #define MAX_LOCAL_LIGHTS 4 @1// Forward Shading: Includes #include "./Flax/LightingCommon.hlsl" -#if USE_REFLECTIONS #include "./Flax/ReflectionsCommon.hlsl" +#if USE_REFLECTIONS #define MATERIAL_REFLECTIONS_SSR 1 #if MATERIAL_REFLECTIONS == MATERIAL_REFLECTIONS_SSR #include "./Flax/SSR.hlsl" @@ -17,7 +17,7 @@ @2// Forward Shading: Constants LightData DirectionalLight; LightData SkyLight; -ProbeData EnvironmentProbe; +EnvProbeData EnvironmentProbe; ExponentialHeightFogData ExponentialHeightFog; float3 Dummy2; uint LocalLightsCount; @@ -32,7 +32,7 @@ Texture3D VolumetricFogTexture : register(t__SRV__); // Public accessors for lighting data, use them as data binding might change but those methods will remain. LightData GetDirectionalLight() { return DirectionalLight; } LightData GetSkyLight() { return SkyLight; } -ProbeData GetEnvironmentProbe() { return EnvironmentProbe; } +EnvProbeData GetEnvironmentProbe() { return EnvironmentProbe; } ExponentialHeightFogData GetExponentialHeightFog() { return ExponentialHeightFog; } uint GetLocalLightsCount() { return LocalLightsCount; } LightData GetLocalLight(uint i) { return LocalLights[i]; } diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index bb68520c0..9c01cf2d0 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -10,7 +10,7 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 179 +#define MATERIAL_GRAPH_VERSION 180 class Material; class GPUShader; diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp index effbe6e1b..ccaad5bb1 100644 --- a/Source/Engine/Graphics/RenderTools.cpp +++ b/Source/Engine/Graphics/RenderTools.cpp @@ -11,6 +11,7 @@ #include "Engine/Content/Assets/SkinnedModel.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Packed.h" +#include "Engine/Core/Math/OrientedBoundingBox.h" #include "Engine/Engine/Time.h" const Char* ToString(RendererType value) @@ -620,6 +621,15 @@ void RenderTools::ComputeSphereModelDrawMatrix(const RenderView& view, const Flo resultIsViewInside = Float3::DistanceSquared(view.Position, position) < Math::Square(radius * 1.1f); // Manually tweaked bias } +void RenderTools::ComputeBoxModelDrawMatrix(const RenderView& view, const OrientedBoundingBox& box, Matrix& resultWorld, bool& resultIsViewInside) +{ + // Construct world matrix + Matrix::Transformation(box.Transformation.Scale * box.Extents * 2.0f, box.Transformation.Orientation, box.Transformation.Translation, resultWorld); + + // Check if view is inside the sphere + resultIsViewInside = box.Contains(view.Position) == ContainmentType::Contains; +} + Float3 RenderTools::GetColorQuantizationError(PixelFormat format) { Float3 mantissaBits; diff --git a/Source/Engine/Graphics/RenderTools.h b/Source/Engine/Graphics/RenderTools.h index 5f0dc23dc..5d061c92c 100644 --- a/Source/Engine/Graphics/RenderTools.h +++ b/Source/Engine/Graphics/RenderTools.h @@ -140,6 +140,7 @@ public: static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal, const Float3& tangent); static void ComputeSphereModelDrawMatrix(const RenderView& view, const Float3& position, float radius, Matrix& resultWorld, bool& resultIsViewInside); + static void ComputeBoxModelDrawMatrix(const RenderView& view, const OrientedBoundingBox& box, Matrix& resultWorld, bool& resultIsViewInside); // Calculates error for a given render target format to reduce floating-point precision artifacts via QuantizeColor (from Noise.hlsl). static Float3 GetColorQuantizationError(PixelFormat format); diff --git a/Source/Engine/Level/Actors/EnvironmentProbe.cpp b/Source/Engine/Level/Actors/EnvironmentProbe.cpp index af22e7a4e..73f80bd9f 100644 --- a/Source/Engine/Level/Actors/EnvironmentProbe.cpp +++ b/Source/Engine/Level/Actors/EnvironmentProbe.cpp @@ -14,6 +14,7 @@ #include "Engine/Content/Deprecated.h" #include "Engine/ContentExporters/AssetExporters.h" #include "Engine/ContentImporters/AssetsImportingManager.h" +#include "Engine/Core/Math/OrientedBoundingBox.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Level/Scene/Scene.h" @@ -22,6 +23,7 @@ EnvironmentProbe::EnvironmentProbe(const SpawnParams& params) : Actor(params) , _radius(3000.0f) , _isUsingCustomProbe(false) + , BoxProjection(false) { _drawCategory = SceneRendering::PreRender; _sphere = BoundingSphere(Vector3::Zero, _radius); @@ -48,11 +50,6 @@ void EnvironmentProbe::SetRadius(float value) UpdateBounds(); } -float EnvironmentProbe::GetScaledRadius() const -{ - return _radius * _transform.Scale.MaxValue(); -} - GPUTexture* EnvironmentProbe::GetProbe() const { return _probe ? _probe->GetTexture() : _probeTexture; @@ -169,8 +166,16 @@ void EnvironmentProbe::SetProbeData(TextureData& data) void EnvironmentProbe::UpdateBounds() { - _sphere = BoundingSphere(GetPosition(), GetScaledRadius()); - BoundingBox::FromSphere(_sphere, _box); + if (BoxProjection) + { + OrientedBoundingBox(_radius, _transform).GetBoundingBox(_box); + BoundingSphere::FromBox(_box, _sphere); + } + else + { + _sphere = BoundingSphere(_transform.Translation, _radius * _transform.Scale.MaxValue()); + BoundingBox::FromSphere(_sphere, _box); + } if (_sceneRenderingKey != -1) GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, ISceneRenderingListener::Bounds); } @@ -183,7 +188,7 @@ void EnvironmentProbe::Draw(RenderContext& renderContext) { // Size culling const Float3 position = _sphere.Center - renderContext.View.Origin; - const float radius = GetScaledRadius(); + const float radius = _sphere.Radius; const float drawMinScreenSize = 0.02f; const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View); const float screenRadiusSquared = RenderTools::ComputeBoundsScreenRadiusSquared(position, radius, *lodView) * renderContext.View.ModelLODDistanceFactorSqrt; @@ -200,10 +205,17 @@ void EnvironmentProbe::Draw(RenderContext& renderContext) RenderEnvironmentProbeData data; data.Texture = texture; data.Position = position; - data.Radius = radius; + data.Radius = _radius; data.Brightness = Brightness; data.SortOrder = SortOrder; data.HashID = GetHash(_id); + data.BlendDistance = Math::Clamp(BlendDistance, 0.001f, _radius * _transform.Scale.MinValue()); + data.BoxProjection = BoxProjection; + if (data.BoxProjection) + { + data.Orientation = _transform.Orientation; + data.Scale = _transform.Scale; + } renderContext.List->EnvironmentProbes.Add(data); } } @@ -216,7 +228,19 @@ void EnvironmentProbe::Draw(RenderContext& renderContext) void EnvironmentProbe::OnDebugDrawSelected() { // Draw influence range - DEBUG_DRAW_WIRE_SPHERE(_sphere, Color::CornflowerBlue, 0, true); + auto rangeColor = Color::CornflowerBlue; + auto rangeColorAlpha = rangeColor.AlphaMultiplied(0.2f); + if (BoxProjection) + { + OrientedBoundingBox box(_radius, _transform); + DEBUG_DRAW_WIRE_BOX(box, rangeColorAlpha, 0, false); + DEBUG_DRAW_WIRE_BOX(box, rangeColor, 0, true); + } + else + { + DEBUG_DRAW_WIRE_SPHERE(_sphere, rangeColorAlpha, 0, false); + DEBUG_DRAW_WIRE_SPHERE(_sphere, rangeColor, 0, true); + } // Draw capture point (if offset) if (!CaptureOffset.IsZero()) @@ -245,7 +269,9 @@ void EnvironmentProbe::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(Radius, _radius); SERIALIZE(CubemapResolution); + SERIALIZE(BoxProjection); SERIALIZE(Brightness); + SERIALIZE(BlendDistance); SERIALIZE(SortOrder); SERIALIZE(UpdateMode); SERIALIZE(CaptureNearPlane); @@ -261,7 +287,9 @@ void EnvironmentProbe::Deserialize(DeserializeStream& stream, ISerializeModifier DESERIALIZE_MEMBER(Radius, _radius); DESERIALIZE(CubemapResolution); + DESERIALIZE(BoxProjection); DESERIALIZE(Brightness); + DESERIALIZE(BlendDistance); DESERIALIZE(SortOrder); DESERIALIZE(UpdateMode); DESERIALIZE(CaptureNearPlane); diff --git a/Source/Engine/Level/Actors/EnvironmentProbe.h b/Source/Engine/Level/Actors/EnvironmentProbe.h index 62bca7ed5..bf65222ee 100644 --- a/Source/Engine/Level/Actors/EnvironmentProbe.h +++ b/Source/Engine/Level/Actors/EnvironmentProbe.h @@ -42,21 +42,33 @@ public: /// /// The reflections texture resolution. /// - API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Quality\")") + API_FIELD(Attributes="EditorOrder(110), EditorDisplay(\"Quality\")") ProbeCubemapResolution CubemapResolution = ProbeCubemapResolution::UseGraphicsSettings; /// /// The probe update mode. /// - API_FIELD(Attributes = "EditorOrder(10), EditorDisplay(\"Quality\")") + API_FIELD(Attributes = "EditorOrder(100), EditorDisplay(\"Quality\")") ProbeUpdateMode UpdateMode = ProbeUpdateMode::Manual; + /// + /// If checked, probe will use box-projection for mapping reelections onto the object surfaces. This method suits better for indoor areas. + /// + API_FIELD(Attributes = "EditorOrder(10), EditorDisplay(\"Probe\")") + bool BoxProjection; + /// /// The reflections brightness. /// - API_FIELD(Attributes="EditorOrder(0), Limit(0, 1000, 0.01f), EditorDisplay(\"Probe\")") + API_FIELD(Attributes="EditorOrder(10), Limit(0, 1000, 0.01f), EditorDisplay(\"Probe\")") float Brightness = 1.0f; + /// + /// The probe shape inner margin over which blend happens to smooth borders. + /// + API_FIELD(Attributes="EditorOrder(15), Limit(0, 1000, 0.1f), EditorDisplay(\"Probe\"), VisibleIf(nameof(BoxProjection))") + float BlendDistance = 100.0f; + /// /// The probe rendering order. The higher values are render later (on top). /// @@ -87,11 +99,6 @@ public: /// API_PROPERTY() void SetRadius(float value); - /// - /// Gets probe scaled radius. - /// - API_PROPERTY() float GetScaledRadius() const; - /// /// Gets the probe texture used during rendering (baked or custom one). /// diff --git a/Source/Engine/Renderer/Config.h b/Source/Engine/Renderer/Config.h index b39d6ae99..302817948 100644 --- a/Source/Engine/Renderer/Config.h +++ b/Source/Engine/Renderer/Config.h @@ -88,8 +88,9 @@ GPU_CB_STRUCT(ShaderLightData { /// Packed env probe data /// GPU_CB_STRUCT(ShaderEnvProbeData { - Float4 Data0; // x - Position.x, y - Position.y, z - Position.z, w - unused - Float4 Data1; // x - Radius , y - 1 / Radius, z - Brightness, w - unused + Float4 Data0; + Float4 Data1; + Float4 Data2; }); // Minimum roughness value used for shading (prevent 0 roughness which causes NaNs in Vis_SmithJointApprox) diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp index 8c8e2bc14..772905e37 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -18,6 +18,7 @@ #include "Engine/Content/Content.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/AssetReference.h" +#include "Engine/Engine/Units.h" #include "Engine/Graphics/PixelFormat.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/Textures/GPUTexture.h" @@ -449,11 +450,11 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context) { auto envProbe = (EnvironmentProbe*)_current.Actor.Get(); Vector3 position = envProbe->GetTransform().LocalToWorld(envProbe->CaptureOffset); - float radius = envProbe->GetScaledRadius(); - float nearPlane = Math::Max(0.1f, envProbe->CaptureNearPlane); + float radius = envProbe->GetSphere().Radius; + float nearPlane = Math::Max(METERS_TO_UNITS(0.001f), envProbe->CaptureNearPlane); // Adjust far plane distance - float farPlane = Math::Max(radius, nearPlane + 100.0f); + float farPlane = Math::Max(radius, nearPlane + METERS_TO_UNITS(1.0f)); farPlane *= farPlane < 10000 ? 10 : 4; Function f(&FixFarPlane); SceneQuery::TreeExecute(f, position, farPlane); @@ -467,7 +468,7 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context) auto skyLight = (SkyLight*)_current.Actor.Get(); Vector3 position = skyLight->GetPosition(); float nearPlane = 10.0f; - float farPlane = Math::Max(nearPlane + 1000.0f, skyLight->SkyDistanceThreshold * 2.0f); + float farPlane = Math::Max(nearPlane + METERS_TO_UNITS(10.0f), skyLight->SkyDistanceThreshold * 2.0f); _customCullingNear = skyLight->SkyDistanceThreshold; // Setup view diff --git a/Source/Engine/Renderer/ReflectionsPass.cpp b/Source/Engine/Renderer/ReflectionsPass.cpp index 790867724..eef50652f 100644 --- a/Source/Engine/Renderer/ReflectionsPass.cpp +++ b/Source/Engine/Renderer/ReflectionsPass.cpp @@ -6,6 +6,7 @@ #include "ScreenSpaceReflectionsPass.h" #include "Engine/Core/Collections/Sorting.h" #include "Engine/Content/Content.h" +#include "Engine/Core/Math/OrientedBoundingBox.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderTools.h" @@ -169,8 +170,9 @@ bool ReflectionsPass::Init() // Load assets _shader = Content::LoadAsyncInternal(TEXT("Shaders/Reflections")); _sphereModel = Content::LoadAsyncInternal(TEXT("Engine/Models/Sphere")); + _boxModel = Content::LoadAsyncInternal(TEXT("Engine/Models/Box")); _preIntegratedGF = Content::LoadAsyncInternal(PRE_INTEGRATED_GF_ASSET_NAME); - if (_shader == nullptr || _sphereModel == nullptr || _preIntegratedGF == nullptr) + if (_shader == nullptr || _sphereModel == nullptr || _boxModel == nullptr || _preIntegratedGF == nullptr) return true; #if COMPILE_WITH_DEV_ENV _shader.Get()->OnReloading.Bind(this); @@ -182,7 +184,7 @@ bool ReflectionsPass::Init() bool ReflectionsPass::setupResources() { // Wait for the assets - if (!_sphereModel->CanBeRendered() || !_preIntegratedGF->IsLoaded() || !_shader->IsLoaded()) + if (!_sphereModel->CanBeRendered() || !_boxModel->CanBeRendered() || !_preIntegratedGF->IsLoaded() || !_shader->IsLoaded()) return true; const auto shader = _shader->GetShader(); CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); @@ -219,7 +221,6 @@ bool ReflectionsPass::setupResources() void ReflectionsPass::Dispose() { - // Base RendererPass::Dispose(); // Cleanup @@ -227,6 +228,7 @@ void ReflectionsPass::Dispose() SAFE_DELETE_GPU_RESOURCE(_psProbeInside); SAFE_DELETE_GPU_RESOURCE(_psCombinePass); _shader = nullptr; + _boxModel = nullptr; _sphereModel = nullptr; _preIntegratedGF = nullptr; } @@ -305,14 +307,18 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light // Render all env probes auto& sphereMesh = _sphereModel->LODs.Get()[0].Meshes.Get()[0]; + auto& boxMesh = _boxModel->LODs.Get()[0].Meshes.Get()[0]; for (int32 i = 0; i < probesCount; i++) { const RenderEnvironmentProbeData& probe = renderContext.List->EnvironmentProbes.Get()[i]; - // Calculate world view projection matrix for the light sphere + // Calculate world*view*projection matrix Matrix world, wvp; bool isViewInside; - RenderTools::ComputeSphereModelDrawMatrix(renderContext.View, probe.Position, probe.Radius, world, isViewInside); + if (probe.BoxProjection) + RenderTools::ComputeBoxModelDrawMatrix(renderContext.View, OrientedBoundingBox(probe.Radius, Transform(probe.Position, probe.Orientation, probe.Scale)), world, isViewInside); + else + RenderTools::ComputeSphereModelDrawMatrix(renderContext.View, probe.Position, probe.Radius, world, isViewInside); Matrix::Multiply(world, view.ViewProjection(), wvp); // Pack probe properties buffer @@ -324,7 +330,10 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light context->BindCB(0, cb); context->BindSR(4, probe.Texture); context->SetState(isViewInside ? _psProbeInside : _psProbe); - sphereMesh.Render(context); + if (probe.BoxProjection) + boxMesh.Render(context); + else + sphereMesh.Render(context); } context->UnBindSR(4); diff --git a/Source/Engine/Renderer/ReflectionsPass.h b/Source/Engine/Renderer/ReflectionsPass.h index 22a2eafdc..2bf2725b9 100644 --- a/Source/Engine/Renderer/ReflectionsPass.h +++ b/Source/Engine/Renderer/ReflectionsPass.h @@ -22,6 +22,7 @@ private: GPUPipelineState* _psCombinePass = nullptr; AssetReference _sphereModel; + AssetReference _boxModel; AssetReference _preIntegratedGF; public: diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index 544438bb5..c7800d4ee 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -163,8 +163,20 @@ void RenderSkyLightData::SetShaderData(ShaderLightData& data, bool useShadow) co void RenderEnvironmentProbeData::SetShaderData(ShaderEnvProbeData& data) const { - data.Data0 = Float4(Position, 0); - data.Data1 = Float4(Radius, 1.0f / Radius, Brightness, 0); + data.Data0 = Float4(Position, Brightness); + if (BoxProjection) + { + data.Data0.W *= -1; + data.Data1 = Float4(Scale * Radius, BlendDistance); + Quaternion invQuat; + Quaternion::Invert(Orientation, invQuat); + data.Data2 = *(Float4*)&invQuat; + } + else + { + data.Data1 = Float4(Radius, 0, 0, 0); + data.Data2 = Float4::Zero; + } } void* RendererAllocation::Allocate(uintptr size) diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index 8eb3540e0..b7d10dac2 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -5,6 +5,7 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Memory/ArenaAllocation.h" #include "Engine/Core/Math/Half.h" +#include "Engine/Core/Math/Quaternion.h" #include "Engine/Graphics/PostProcessSettings.h" #include "Engine/Graphics/DynamicBuffer.h" #include "Engine/Scripting/ScriptingObject.h" @@ -154,10 +155,14 @@ struct RenderEnvironmentProbeData { GPUTexture* Texture; Float3 Position; - float Radius; + float Radius; // unscaled for box float Brightness; int32 SortOrder; uint32 HashID; + bool BoxProjection; + Float3 Scale; // box-only + float BlendDistance; + Quaternion Orientation; // box-only void SetShaderData(ShaderEnvProbeData& data) const; }; diff --git a/Source/Shaders/Common.hlsl b/Source/Shaders/Common.hlsl index 66303546a..5249e38fb 100644 --- a/Source/Shaders/Common.hlsl +++ b/Source/Shaders/Common.hlsl @@ -170,18 +170,6 @@ struct AtmosphericFogData float AtmosphericFogDensityOffset; }; -// Packed env probe data -struct ProbeData -{ - float4 Data0; // x - Position.x, y - Position.y, z - Position.z, w - unused - float4 Data1; // x - Radius , y - 1 / Radius, z - Brightness, w - unused -}; - -#define ProbePos Data0.xyz -#define ProbeRadius Data1.x -#define ProbeInvRadius Data1.y -#define ProbeBrightness Data1.z - struct Quad_VS2PS { float4 Position : SV_Position; diff --git a/Source/Shaders/Reflections.shader b/Source/Shaders/Reflections.shader index 56a95146e..a6d0d7f54 100644 --- a/Source/Shaders/Reflections.shader +++ b/Source/Shaders/Reflections.shader @@ -1,22 +1,16 @@ // Copyright (c) Wojciech Figat. All rights reserved. #include "./Flax/Common.hlsl" -#include "./Flax/MaterialCommon.hlsl" #include "./Flax/BRDF.hlsl" -#include "./Flax/Random.hlsl" #include "./Flax/Noise.hlsl" -#include "./Flax/MonteCarlo.hlsl" -#include "./Flax/LightingCommon.hlsl" #include "./Flax/GBuffer.hlsl" +#include "./Flax/MaterialCommon.hlsl" #include "./Flax/ReflectionsCommon.hlsl" -#include "./Flax/BRDF.hlsl" META_CB_BEGIN(0, Data) - -ProbeData PData; +EnvProbeData PData; float4x4 WVP; GBufferData GBuffer; - META_CB_END DECLARE_GBUFFERDATA_ACCESS(GBuffer) @@ -25,7 +19,7 @@ TextureCube Probe : register(t4); Texture2D Reflections : register(t5); Texture2D PreIntegratedGF : register(t6); -// Vertex Shader for models rendering +// Vertex Shader for probe shape rendering META_VS(true, FEATURE_LEVEL_ES2) META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) Model_VS2PS VS_Model(ModelInput_PosOnly input) diff --git a/Source/Shaders/ReflectionsCommon.hlsl b/Source/Shaders/ReflectionsCommon.hlsl index 87e871edf..d17993fca 100644 --- a/Source/Shaders/ReflectionsCommon.hlsl +++ b/Source/Shaders/ReflectionsCommon.hlsl @@ -4,45 +4,86 @@ #define __REFLECTIONS_COMMON__ #include "./Flax/GBufferCommon.hlsl" +#include "./Flax/Quaternion.hlsl" // Hit depth (view space) threshold to detect if sky was hit (value above it where 1.0f is default) #define REFLECTIONS_HIT_THRESHOLD 0.9f +// Packed env probe data +struct EnvProbeData +{ + float4 Data0; // x - Position.x | y - Position.y | z - Position.z | w - Brightness (negative for BoxProjection) + float4 Data1; // x - Radius/BoxExtent.x | y - BoxExtent.y | z - BoxExtent.z | w - BlendDistance + float4 Data2; // x - BoxInvQuat.x | y - BoxInvQuat.y | z - BoxInvQuat.z | w - BoxInvQuat.w +}; + +#define EnvProbePosition(data) data.Data0.xyz +#define EnvProbeBrightness(data) abs(data.Data0.w) +#define EnvProbeBoxProjection(data) (data.Data0.w < 0.0f) +#define EnvProbeBoxExtent(data) data.Data1.xyz +#define EnvProbeBoxInvQuat(data) data.Data2 +#define EnvProbeSphereRadius(data) data.Data1.x +#define EnvProbeBlendDistance(data) data.Data1.w + float GetSpecularOcclusion(float NoV, float roughnessSq, float ao) { return saturate(pow(NoV + ao, roughnessSq) - 1 + ao); } -float4 SampleReflectionProbe(float3 viewPos, TextureCube probe, ProbeData data, float3 positionWS, float3 normal, float roughness) +float4 SampleReflectionProbe(float3 viewPos, TextureCube probe, EnvProbeData data, float3 positionWS, float3 normal, float roughness) { - // Calculate distance from probe to the pixel - float3 captureVector = positionWS - data.ProbePos; - float captureVectorLength = length(captureVector); - - // Check if cannot light pixel - // TODO: maybe remove this check?? - test it out with dozens of probes - BRANCH - if (captureVectorLength >= data.ProbeRadius) + // Calculate fade based on distance to the probe + float3 captureVector = positionWS - EnvProbePosition(data); + float distanceAlpha; + if (EnvProbeBoxProjection(data)) { - // End - return 0; + // Box shape + float3 boxExtent = EnvProbeBoxExtent(data); + float blendDistance = EnvProbeBlendDistance(data); + float3 pos = QuaternionRotate(EnvProbeBoxInvQuat(data), captureVector); + float3 clampedPos = clamp(pos, -boxExtent + blendDistance, boxExtent - blendDistance); + float distanceToBox = length(clampedPos - pos); + distanceAlpha = saturate(1 - distanceToBox / blendDistance); + } + else + { + // Sphere shape + float normalizedDistanceToCapture = saturate(length(captureVector) / EnvProbeSphereRadius(data)); + distanceAlpha = 1.0 - smoothstep(0.7f, 1, normalizedDistanceToCapture); } - // Fade out based on distance to capture - float normalizedDistanceToCapture = saturate(captureVectorLength * data.ProbeInvRadius); - float distanceAlpha = 1.0 - smoothstep(0.7, 1, normalizedDistanceToCapture); - float fade = distanceAlpha * data.ProbeBrightness; + // Early out without sampling texture if out of the bounds + BRANCH + if (distanceAlpha <= 0.0f) + return float4(0, 0, 0, 0); - // Calculate reflection vector + // Calculate probe sampling coordinates + float3 sampleVector; float3 V = normalize(positionWS - viewPos); float3 R = reflect(V, normal); - float3 D = data.ProbeInvRadius * captureVector + R; + if (EnvProbeBoxProjection(data)) + { + // Box projection + float3 rotatedReflection = QuaternionRotate(EnvProbeBoxInvQuat(data), R); + float3 boxExtent = EnvProbeBoxExtent(data); + float3 boxMinMax = select(rotatedReflection > 0.0f, boxExtent, -boxExtent); + float3 pos = QuaternionRotate(EnvProbeBoxInvQuat(data), captureVector); + float3 rotatedPos = float3(boxMinMax - pos) / rotatedReflection; + float minDir = min(min(rotatedPos.x, rotatedPos.y), rotatedPos.z); + float3 dir = pos + rotatedReflection * minDir; + sampleVector = QuaternionRotate(float4(-EnvProbeBoxInvQuat(data).xyz, EnvProbeBoxInvQuat(data).w), dir); + } + else + { + // Sphere projection + sampleVector = captureVector / EnvProbeSphereRadius(data) + R; + } // Sample probe at valid mip level based on surface roughness value - half mip = ProbeMipFromRoughness(roughness); - float4 probeSample = probe.SampleLevel(SamplerLinearClamp, D, mip); + float mip = ProbeMipFromRoughness(roughness); + float4 probeSample = probe.SampleLevel(SamplerLinearClamp, sampleVector, mip); - return probeSample * fade; + return probeSample * (distanceAlpha * EnvProbeBrightness(data)); } // Calculates the reflective environment lighting to multiply the raw reflection color for the specular light (eg. from Env Probe or SSR).