Skip to content

Commit 3bc135f

Browse files
authored
Added support for texture cubemaps and IBL using reflection probes (#561)
* Added texture cube support * Added reflection render pass (generates a cubemap for each reflection probe) * Added reflection render feature (sends the cubemap to an entity about to be drawn. * Reworked scene parsing: * Side effect: cannot cull per-model anymore, can only cull per-mesh * Added Mat3 uniform support * Added visibility flags to the material renderer, to exlude an actor from certain passes * Deprecated cull model, cull mesh is preferred for now (will be added back when tackling #564) * Added "depth test" option to debug shape drawer, so that bounds/volumes can be shown through objects * Reflection probe data is passed to the shader as a UBO. In the future, we should implement a proper solution to handle multiple UBOs, and track their binding index * Added material settings to enable/disable reflection capture & usage on a particular material. * Separated material editor settings into pipeline (state mask) & general settings. * Vastly improved PBR shader (re-organized)
1 parent 215338d commit 3bc135f

File tree

106 files changed

+3430
-1028
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+3430
-1028
lines changed

Resources/Engine/Shaders/Common/Buffers/EngineUBO.ovfxh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
layout (std140) uniform EngineUBO
1+
layout (std140, binding = 0) uniform EngineUBO
22
{
33
mat4 ubo_Model;
44
mat4 ubo_View;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
layout (std140, binding = 1) uniform ReflectionUBO
2+
{
3+
vec4 position;
4+
mat4 rotation;
5+
vec4 boxCenter;
6+
vec4 boxHalfExtents;
7+
float brightness;
8+
bool boxProjection;
9+
} ubo_ReflectionProbeData;
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
bool IsPointInAABB(vec3 point, vec3 aabbCenter, vec3 aabbHalfSize)
1+
float IsPointInAABB(vec3 p, vec3 center, vec3 halfSize)
22
{
3-
return
4-
point.x > aabbCenter.x - aabbHalfSize.x && point.x < aabbCenter.x + aabbHalfSize.x &&
5-
point.y > aabbCenter.y - aabbHalfSize.y && point.y < aabbCenter.y + aabbHalfSize.y &&
6-
point.z > aabbCenter.z - aabbHalfSize.z && point.z < aabbCenter.z + aabbHalfSize.z;
3+
vec3 d = abs(p - center) - halfSize;
4+
return step(0.0, -max(d.x, max(d.y, d.z))); // returns 1.0 if inside, 0.0 if outside
75
}
86

9-
bool IsPointInSphere(vec3 point, vec3 sphereCenter, float sphereRadius)
7+
float IsPointInSphere(vec3 p, vec3 center, float radius)
108
{
11-
return distance(point, sphereCenter) <= sphereRadius;
12-
}
9+
float distSq = dot(p - center, p - center);
10+
return step(distSq, radius * radius); // 1.0 if inside, 0.0 if outside
11+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#include ":Shaders/Common/Buffers/ReflectionUBO.ovfxh"
2+
3+
// https://seblagarde.wordpress.com/wp-content/uploads/2012/08/parallax_corrected_cubemap-siggraph2012.pdf
4+
vec3 BoxProjectionOBB(vec3 reflectionVector, vec3 worldPos, vec3 probePos, vec3 obbCenter, vec3 obbHalfExtents, mat3 obbOrientation)
5+
{
6+
// Transform world position and reflection vector to OBB local space
7+
vec3 localPos = obbOrientation * (worldPos - obbCenter);
8+
vec3 localReflection = obbOrientation * reflectionVector;
9+
vec3 localProbePos = obbOrientation * (probePos - obbCenter);
10+
11+
// Perform AABB intersection in local space
12+
vec3 ndr = normalize(localReflection);
13+
vec3 rbmax = (obbHalfExtents - localPos) / ndr;
14+
vec3 rbmin = (-obbHalfExtents - localPos) / ndr;
15+
16+
vec3 rbminmax;
17+
rbminmax.x = (ndr.x > 0.0) ? rbmax.x : rbmin.x;
18+
rbminmax.y = (ndr.y > 0.0) ? rbmax.y : rbmin.y;
19+
rbminmax.z = (ndr.z > 0.0) ? rbmax.z : rbmin.z;
20+
float fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z);
21+
22+
// Find intersection point in local space
23+
vec3 localIntersection = localPos + ndr * fa;
24+
25+
// Transform intersection back to world space
26+
vec3 worldIntersection = transpose(obbOrientation) * localIntersection + obbCenter;
27+
28+
return normalize(worldIntersection - probePos);
29+
}
30+
31+
vec3 ParallaxCorrect(vec3 reflectionDir, vec3 worldPos)
32+
{
33+
if (ubo_ReflectionProbeData.boxProjection)
34+
{
35+
return BoxProjectionOBB(
36+
reflectionDir,
37+
worldPos,
38+
ubo_ReflectionProbeData.position.xyz,
39+
ubo_ReflectionProbeData.boxCenter.xyz,
40+
ubo_ReflectionProbeData.boxHalfExtents.xyz,
41+
mat3(ubo_ReflectionProbeData.rotation)
42+
);
43+
}
44+
45+
return reflectionDir; // No box projection, return original reflection direction
46+
}
47+
48+
// BRDF integration approximation for IBL specular
49+
vec2 EnvBRDFApprox(float NdotV, float roughness)
50+
{
51+
// Approximation from "Moving Frostbite to PBR" by Lagarde & de Rousiers
52+
// This is great because it mitigates the need to sample a precomputed BRDF LUT!
53+
const vec4 c0 = vec4(-1, -0.0275, -0.572, 0.022);
54+
const vec4 c1 = vec4(1, 0.0425, 1.04, -0.04);
55+
vec4 r = roughness * c0 + c1;
56+
float a004 = min(r.x * r.x, exp2(-9.28 * NdotV)) * r.x + r.y;
57+
vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;
58+
return AB;
59+
}
60+
61+
// Modified Fresnel-Schlick approximation that includes roughness term for IBL
62+
vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
63+
{
64+
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
65+
}
66+
67+
vec3 CalculateImageBasedLighting(
68+
samplerCube environmentMap,
69+
vec3 fragPos,
70+
vec3 V,
71+
vec3 N,
72+
float roughness,
73+
float metallic,
74+
vec3 F0,
75+
float NdotV,
76+
vec3 albedo
77+
) {
78+
// 0. Constants
79+
const int environmentMaxLOD = textureQueryLevels(environmentMap) - 1;
80+
81+
// 1. Calculate reflection vector
82+
const vec3 R = ParallaxCorrect(reflect(-V, N), fragPos);
83+
84+
// 2. Diffuse irradiance (environment lighting)
85+
const vec3 irradiance = textureLod(environmentMap, N, environmentMaxLOD).rgb; // High mip level for diffuse
86+
87+
// 3. Specular reflection
88+
const vec3 prefilteredColor = textureLod(environmentMap, R, roughness * environmentMaxLOD).rgb;
89+
90+
// 4. Apply BRDF
91+
const vec2 brdf = EnvBRDFApprox(NdotV, roughness);
92+
93+
// 5. Calculate Fresnel for IBL
94+
const vec3 F_IBL = FresnelSchlickRoughness(NdotV, F0, roughness);
95+
96+
// 6. Calculate specular and diffuse IBL components
97+
const vec3 kS_IBL = F_IBL;
98+
const vec3 kD_IBL = (1.0 - kS_IBL) * (1.0 - metallic);
99+
100+
const vec3 diffuseIBL = kD_IBL * irradiance * albedo;
101+
const vec3 specularIBL = prefilteredColor * (F_IBL * brdf.x + brdf.y);
102+
103+
// 7. Return IBL contribution
104+
return (diffuseIBL + specularIBL) * ubo_ReflectionProbeData.brightness;
105+
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
#include ":Shaders/Common/Physics.ovfxh"
2+
#include ":Shaders/Lighting/Shadow.ovfxh"
3+
4+
struct Light
5+
{
6+
int type;
7+
float intensity;
8+
vec3 color;
9+
mat4 data;
10+
};
11+
12+
struct PointLight
13+
{
14+
float intensity;
15+
vec3 color;
16+
vec3 position;
17+
float constant;
18+
float linear;
19+
float quadratic;
20+
};
21+
22+
struct DirectionalLight
23+
{
24+
float intensity;
25+
vec3 color;
26+
vec3 direction;
27+
bool hasShadow;
28+
};
29+
30+
struct SpotLight
31+
{
32+
float intensity;
33+
vec3 color;
34+
vec3 position;
35+
vec3 direction;
36+
float innerCutoff;
37+
float outerCutoff;
38+
float constant;
39+
float linear;
40+
float quadratic;
41+
};
42+
43+
struct AmbientBoxLight
44+
{
45+
float intensity;
46+
vec3 color;
47+
vec3 position;
48+
vec3 size;
49+
};
50+
51+
struct AmbientSphereLight
52+
{
53+
float intensity;
54+
vec3 color;
55+
vec3 position;
56+
float radius;
57+
};
58+
59+
Light ExtractLight(mat4 light)
60+
{
61+
Light lightInfo;
62+
lightInfo.type = int(light[3][0]);
63+
lightInfo.intensity = light[3][3];
64+
lightInfo.color = UnPack(light[2][0]);
65+
lightInfo.data = light;
66+
return lightInfo;
67+
}
68+
69+
PointLight ExtractPointLight(Light light)
70+
{
71+
PointLight pointLight;
72+
pointLight.intensity = light.intensity;
73+
pointLight.color = light.color;
74+
pointLight.position = light.data[0].rgb;
75+
pointLight.constant = light.data[0][3];
76+
pointLight.linear = light.data[1][3];
77+
pointLight.quadratic = light.data[2][3];
78+
return pointLight;
79+
}
80+
81+
DirectionalLight ExtractDirectionalLight(Light light)
82+
{
83+
DirectionalLight dirLight;
84+
dirLight.intensity = light.intensity;
85+
dirLight.color = light.color;
86+
dirLight.direction = light.data[1].rgb;
87+
dirLight.hasShadow = light.data[2][1] > 0.0;
88+
return dirLight;
89+
}
90+
91+
SpotLight ExtractSpotLight(Light light)
92+
{
93+
SpotLight spotLight;
94+
spotLight.intensity = light.intensity;
95+
spotLight.color = light.color;
96+
spotLight.position = light.data[0].rgb;
97+
spotLight.direction = light.data[1].rgb;
98+
spotLight.innerCutoff = light.data[3][1];
99+
spotLight.outerCutoff = light.data[3][1] + light.data[3][2];
100+
spotLight.constant = light.data[0][3];
101+
spotLight.linear = light.data[1][3];
102+
spotLight.quadratic = light.data[2][3];
103+
return spotLight;
104+
}
105+
106+
AmbientBoxLight ExtractAmbientBoxLight(Light light)
107+
{
108+
AmbientBoxLight boxLight;
109+
boxLight.intensity = light.intensity;
110+
boxLight.color = light.color;
111+
boxLight.position = light.data[0].rgb;
112+
boxLight.size = vec3(light.data[0][3], light.data[1][3], light.data[2][3]);
113+
return boxLight;
114+
}
115+
116+
AmbientSphereLight ExtractAmbientSphereLight(Light light)
117+
{
118+
AmbientSphereLight sphereLight;
119+
sphereLight.position = light.data[0].rgb;
120+
sphereLight.color = light.color;
121+
sphereLight.intensity = light.intensity;
122+
sphereLight.radius = light.data[0][3];
123+
return sphereLight;
124+
}
125+
126+
struct LightContribution
127+
{
128+
vec3 radiance;
129+
vec3 L;
130+
};
131+
132+
float CalculateAttenuation(vec3 lightPos, vec3 fragPos, float constant, float linear, float quadratic)
133+
{
134+
const float distance = length(lightPos - fragPos);
135+
const float attenuation = constant + linear * distance + quadratic * (distance * distance);
136+
return 1.0 / attenuation;
137+
}
138+
139+
LightContribution CalculatePointLightContribution(PointLight light, vec3 fragPos)
140+
{
141+
const vec3 L = normalize(light.position - fragPos);
142+
const float attenuation = CalculateAttenuation(light.position, fragPos, light.constant, light.linear, light.quadratic);
143+
const vec3 radiance = light.color * light.intensity * attenuation;
144+
145+
return LightContribution(radiance, L);
146+
}
147+
148+
LightContribution CalculateDirectionalLightContribution(DirectionalLight light, vec3 fragPos, vec3 N, sampler2D shadowMap, mat4 lightSpaceMatrix)
149+
{
150+
const vec3 L = -light.direction;
151+
152+
float lightCoeff = light.intensity;
153+
154+
// Apply shadow if enabled
155+
if (light.hasShadow)
156+
{
157+
vec4 fragPosLightSpace = lightSpaceMatrix * vec4(fragPos, 1.0);
158+
float shadow = CalculateShadow(fragPosLightSpace, shadowMap, N, light.direction);
159+
lightCoeff *= (1.0 - shadow);
160+
}
161+
162+
const vec3 radiance = light.color * lightCoeff;
163+
164+
return LightContribution(radiance, L);
165+
}
166+
167+
LightContribution CalculateSpotLightContribution(SpotLight light, vec3 fragPos)
168+
{
169+
const vec3 L = normalize(light.position - fragPos);
170+
const float attenuation = CalculateAttenuation(light.position, fragPos, light.constant, light.linear, light.quadratic);
171+
172+
// Calculate spot intensity
173+
const float cutOff = cos(radians(light.innerCutoff));
174+
const float outerCutOff = cos(radians(light.outerCutoff));
175+
const vec3 lightDirection = normalize(light.position - fragPos);
176+
const float theta = dot(lightDirection, normalize(-light.direction));
177+
const float epsilon = cutOff - outerCutOff;
178+
const float spotIntensity = clamp((theta - outerCutOff) / epsilon, 0.0, 1.0);
179+
180+
const vec3 radiance = light.color * light.intensity * attenuation * spotIntensity;
181+
182+
return LightContribution(radiance, L);
183+
}
184+
185+
vec3 CalculateAmbientBoxLightContribution(AmbientBoxLight light, vec3 fragPos)
186+
{
187+
float inside = IsPointInAABB(fragPos, light.position, light.size);
188+
return inside * light.color * light.intensity;
189+
}
190+
191+
vec3 CalculateAmbientSphereLightContribution(AmbientSphereLight light, vec3 fragPos)
192+
{
193+
float inside = IsPointInSphere(fragPos, light.position, light.radius);
194+
return inside * light.color * light.intensity;
195+
}

0 commit comments

Comments
 (0)