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).