**Add Box Projection to Environment Probe** for better indoor areas

This commit is contained in:
Wojtek Figat
2026-01-14 09:49:01 +01:00
parent b7e32e13ab
commit 18778aa511
15 changed files with 176 additions and 78 deletions

View File

@@ -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]; }

View File

@@ -10,7 +10,7 @@
/// <summary>
/// Current materials shader version.
/// </summary>
#define MATERIAL_GRAPH_VERSION 179
#define MATERIAL_GRAPH_VERSION 180
class Material;
class GPUShader;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -42,21 +42,33 @@ public:
/// <summary>
/// The reflections texture resolution.
/// </summary>
API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Quality\")")
API_FIELD(Attributes="EditorOrder(110), EditorDisplay(\"Quality\")")
ProbeCubemapResolution CubemapResolution = ProbeCubemapResolution::UseGraphicsSettings;
/// <summary>
/// The probe update mode.
/// </summary>
API_FIELD(Attributes = "EditorOrder(10), EditorDisplay(\"Quality\")")
API_FIELD(Attributes = "EditorOrder(100), EditorDisplay(\"Quality\")")
ProbeUpdateMode UpdateMode = ProbeUpdateMode::Manual;
/// <summary>
/// If checked, probe will use box-projection for mapping reelections onto the object surfaces. This method suits better for indoor areas.
/// </summary>
API_FIELD(Attributes = "EditorOrder(10), EditorDisplay(\"Probe\")")
bool BoxProjection;
/// <summary>
/// The reflections brightness.
/// </summary>
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;
/// <summary>
/// The probe shape inner margin over which blend happens to smooth borders.
/// </summary>
API_FIELD(Attributes="EditorOrder(15), Limit(0, 1000, 0.1f), EditorDisplay(\"Probe\"), VisibleIf(nameof(BoxProjection))")
float BlendDistance = 100.0f;
/// <summary>
/// The probe rendering order. The higher values are render later (on top).
/// </summary>
@@ -87,11 +99,6 @@ public:
/// </summary>
API_PROPERTY() void SetRadius(float value);
/// <summary>
/// Gets probe scaled radius.
/// </summary>
API_PROPERTY() float GetScaledRadius() const;
/// <summary>
/// Gets the probe texture used during rendering (baked or custom one).
/// </summary>

View File

@@ -88,8 +88,9 @@ GPU_CB_STRUCT(ShaderLightData {
/// Packed env probe data
/// </summary>
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)

View File

@@ -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<bool(Actor*, const Vector3&, float&)> f(&FixFarPlane);
SceneQuery::TreeExecute<const Vector3&, float&>(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

View File

@@ -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<Shader>(TEXT("Shaders/Reflections"));
_sphereModel = Content::LoadAsyncInternal<Model>(TEXT("Engine/Models/Sphere"));
_boxModel = Content::LoadAsyncInternal<Model>(TEXT("Engine/Models/Box"));
_preIntegratedGF = Content::LoadAsyncInternal<Texture>(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<ReflectionsPass, &ReflectionsPass::OnShaderReloading>(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);

View File

@@ -22,6 +22,7 @@ private:
GPUPipelineState* _psCombinePass = nullptr;
AssetReference<Model> _sphereModel;
AssetReference<Model> _boxModel;
AssetReference<Texture> _preIntegratedGF;
public:

View File

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

View File

@@ -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;
};

View File

@@ -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;

View File

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

View File

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