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

This commit is contained in:
2026-01-14 09:49:01 +01:00
parent b7e32e13ab
commit 18778aa511
15 changed files with 176 additions and 78 deletions
-12
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;
+3 -9
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)
+61 -20
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).