diff --git a/Tests.cmake b/Tests.cmake index 0a3c2826..5ee22d21 100644 --- a/Tests.cmake +++ b/Tests.cmake @@ -31,12 +31,12 @@ function(add_dz_test TEST_NAME TEST_SRC) ) add_test(NAME ${TEST_NAME} COMMAND $/${TEST_NAME}${TEST_EXT}) endfunction() -add_dz_test(DZ_ShaderReflect tests/ShaderReflect.cpp) -add_dz_test(DZ_Particle2D tests/Particle2D.cpp) -add_dz_test(DZ_AssetStream tests/AssetStream.cpp) -add_dz_test(DZ_LineGrid tests/LineGrid.cpp) -add_dz_test(DZ_D7Stream tests/D7Stream.cpp) -add_dz_test(DZ_ImGuiTest tests/ImGui.cpp) +# add_dz_test(DZ_ShaderReflect tests/ShaderReflect.cpp) +# add_dz_test(DZ_Particle2D tests/Particle2D.cpp) +# add_dz_test(DZ_AssetStream tests/AssetStream.cpp) +# add_dz_test(DZ_LineGrid tests/LineGrid.cpp) +# add_dz_test(DZ_D7Stream tests/D7Stream.cpp) +# add_dz_test(DZ_ImGuiTest tests/ImGui.cpp) add_dz_test(DZ_ECSTest tests/ECS.cpp) file(COPY images/Suzuho-Ueda.bmp DESTINATION ${CMAKE_BINARY_DIR}/images) file(COPY images/hi.bmp DESTINATION ${CMAKE_BINARY_DIR}/images) diff --git a/include/dz/ECS/HDRI.hpp b/include/dz/ECS/HDRI.hpp index 4d7b2539..cc14b402 100644 --- a/include/dz/ECS/HDRI.hpp +++ b/include/dz/ECS/HDRI.hpp @@ -60,12 +60,12 @@ vec4 SampleIrradiance(in int hdri_index, in vec3 v) { vec2 packed_rect = HDRIs.data[hdri_index].irradiance_atlas_pack.zw; return SampleAtlas(SampleSphericalMap(v), image_size, packed_rect, IrradianceAtlas); } -vec4 SampleRadiance(in int hdri_index, in vec3 v) { +vec4 SampleRadiance(in int hdri_index, in vec3 v, float lod) { vec2 image_size = HDRIs.data[hdri_index].radiance_atlas_pack.xy; if (image_size.x == -1.0) return vec4(0.0); vec2 packed_rect = HDRIs.data[hdri_index].radiance_atlas_pack.zw; - return SampleAtlas(SampleSphericalMap(v), image_size, packed_rect, RadianceAtlas); + return SampleAtlasLOD(SampleSphericalMap(v), image_size, packed_rect, RadianceAtlas, lod); } )" } }; @@ -82,9 +82,6 @@ layout(binding = @BINDING@) uniform sampler2D RadianceAtlas; {0.5f, R"( )", ShaderModuleType::Vertex}, {0.5f, R"( - vec4 hdri_sample = SampleHDRI(0, inLocalPosition); - vec4 irradiance_sample = SampleIrradiance(0, inLocalPosition); - vec4 radiance_sample = SampleRadiance(0, inLocalPosition); )", ShaderModuleType::Fragment} }; diff --git a/include/dz/ECS/Material.hpp b/include/dz/ECS/Material.hpp index d0b38df0..4b06c5cd 100644 --- a/include/dz/ECS/Material.hpp +++ b/include/dz/ECS/Material.hpp @@ -69,6 +69,13 @@ vec4 SampleAtlas(in vec2 uv, in vec2 image_size, in vec2 packed_rect, in sampler vec2 packed_uv = offset_uv + uv * scale_uv; return texture(atlas, packed_uv); } +vec4 SampleAtlasLOD(in vec2 uv, in vec2 image_size, in vec2 packed_rect, in sampler2D atlas, in float lod) { + vec2 resolution = vec2(textureSize(atlas, 0)); + vec2 offset_uv = packed_rect / resolution; + vec2 scale_uv = image_size / resolution; + vec2 packed_uv = offset_uv + uv * scale_uv; + return textureLod(atlas, packed_uv, lod); +} void EnsureMaterialFragColor(in vec2 uv, in SubMesh submesh, inout vec4 current_color) { vec2 image_size = Materials.data[submesh.material_index].albedo_atlas_pack.xy; if (image_size.x == -1.0) diff --git a/include/dz/ECS/PhysicallyBasedLighting.hpp b/include/dz/ECS/PhysicallyBasedLighting.hpp index 625926e4..c7105449 100644 --- a/include/dz/ECS/PhysicallyBasedLighting.hpp +++ b/include/dz/ECS/PhysicallyBasedLighting.hpp @@ -52,20 +52,44 @@ float gaSchlickGGX(float cosLi, float NdotV, float roughness) } vec3 IBL(vec3 F0, vec3 N, vec3 V) { - // diffuse IBL (view-independent) // - vec3 irradiance = SampleIrradiance( 0, N ).rgb; - vec3 diffuseBRDF = mParams.albedo / PI; - vec3 diffuse = diffuseBRDF * irradiance; - - // specular IBL (view-dependent) // - float NdotV = max( dot( N, V ), 0.0 ); - vec3 R = reflect( -V, N ); // reflection vector - int maxMips = textureQueryLevels( RadianceAtlas ); - float mip = mParams.roughness * mParams.roughness * float( maxMips ); - vec3 radiance = SampleRadiance( 0, R ).rgb; - vec2 brdfLUT = texture( brdfLUT, vec2( NdotV, 1.0 - mParams.roughness ) ).rg; - vec3 F = fresnelSchlickRoughness( F0, NdotV, mParams.roughness ); - vec3 specular = radiance * ( F * brdfLUT.x + brdfLUT.y ); + // irradiance / diffuse + vec3 irradiance = SampleIrradiance(0, N).rgb; + vec3 kd = (1.0 - F0) * (1.0 - mParams.metalness); + vec3 diffuseBRDF = kd * mParams.albedo; + vec3 diffuse = diffuseBRDF * irradiance; + + // reflection vector and radiance LOD + vec3 R = reflect(-V, N); + R.y = -R.y; + float maxMip = float(textureQueryLevels(RadianceAtlas)) - 1.0; + float lod = clamp(mParams.roughness * maxMip, 0.0, maxMip); + vec3 radiance = SampleRadiance(0, R, lod).rgb; + + // safe-guard radiance (avoid NaN / negative) + radiance = max(radiance, vec3(0.0)); + + // --- BRDF LUT and Fresnel --- + // clamp / bias coordinates to safe range + float safeNdotV = clamp(lParams.NdotV, 0.0001, 1.0); // avoid exact zero + float safeRoughness = clamp(mParams.roughness, 0.0, 1.0); + vec2 brdfUV = vec2(safeNdotV, safeRoughness); + + // Use explicit LOD 0 for BRDF LUT (typical LUT has no mips) + vec2 brdf = textureLod(brdfLUT, brdfUV, 0.0).rg; + + // ensure brdf components are sane (clamp to avoid negative / huge values) + brdf = clamp(brdf, vec2(0.0), vec2(16.0)); + + vec3 F = fresnelSchlickRoughness(F0, safeNdotV, safeRoughness); + + // combine + vec3 specular = radiance * (F * brdf.x + brdf.y); + + // debug toggles — uncomment to diagnose: + // return radiance; // shows raw radiance + // return vec3(brdf.x); // visualise BRDF.x + // return vec3(brdf.y); // visualise BRDF.y + // return vec3(F.x); // visualise Fresnel (F) return diffuse + specular; } @@ -76,18 +100,16 @@ vec3 PBDL(vec3 F0, in Light light) { vec3 Lradiance = vec3(light.intensity); vec3 Lh = normalize(Li + lParams.viewDirection); - // Calculate angles between surface normal and various light vectors. float cosLi = max(0.0, dot(lParams.normal, Li)); float cosLh = max(0.0, dot(lParams.normal, Lh)); vec3 F = fresnelSchlick(F0, max(0.0, dot(Lh, lParams.viewDirection))); float D = ndfGGX(cosLh, mParams.roughness); float G = gaSchlickGGX(cosLi, lParams.NdotV, mParams.roughness); - + vec3 kd = (1.0 - F) * (1.0 - mParams.metalness); vec3 diffuseBRDF = kd * mParams.albedo; - - // Cook-Torrance + vec3 specularBRDF = (F * D * G) / max(Epsilon, 4.0 * cosLi * lParams.NdotV); result += (diffuseBRDF + specularBRDF) * Lradiance * cosLi; diff --git a/src/ECS/Material.cpp b/src/ECS/Material.cpp index fe86ebc4..92b5cf00 100644 --- a/src/ECS/Material.cpp +++ b/src/ECS/Material.cpp @@ -147,6 +147,9 @@ vec2 Hammersley(uint i, uint N) vec2 IntegrateBRDF(float NdotV, float Roughness) { + // Clamp NdotV to avoid division by zero in geometry visibility term + NdotV = max(NdotV, 0.0001); + vec3 V; V.x = sqrt(1.0 - NdotV * NdotV); V.y = 0.0; @@ -171,7 +174,10 @@ vec2 IntegrateBRDF(float NdotV, float Roughness) if (NdotL > 0.0) { float G = GeometrySmith(NdotV, NdotL, Roughness); - float G_Vis = (G * VdotH) / (NdotH * NdotV); + + // Guard against divide-by-zero in NdotH * NdotV + float G_Vis = (G * VdotH) / max(NdotH * NdotV, 0.0001); + float Fc = pow(1.0 - VdotH, 5.0); A += (1.0 - Fc) * G_Vis; @@ -190,11 +196,12 @@ void main() if (pixelCoord.x >= 512 || pixelCoord.y >= 512) return; - vec2 uv = vec2(pixelCoord) / vec2(511.0, 511.0); + vec2 uv = vec2(pixelCoord) / vec2(512.0, 512.0); float NdotV = uv.x; float Roughness = uv.y; vec2 integratedBRDF = IntegrateBRDF(NdotV, Roughness); + integratedBRDF = clamp(integratedBRDF, 0.0, 1.0); imageStore(brdfLUT, pixelCoord, vec4(integratedBRDF, 0.0, 1.0)); }