diff --git a/android/filament-android/src/main/cpp/View.cpp b/android/filament-android/src/main/cpp/View.cpp index a2ed9d2f6d..a68ccd61f9 100644 --- a/android/filament-android/src/main/cpp/View.cpp +++ b/android/filament-android/src/main/cpp/View.cpp @@ -228,7 +228,7 @@ Java_com_google_android_filament_View_nSetSSCTOptions(JNIEnv *, jclass, jlong na View* view = (View*) nativeView; View::AmbientOcclusionOptions options = view->getAmbientOcclusionOptions(); options.ssct.lightConeRad = ssctLightConeRad; - options.ssct.startTraceDistance = ssctStartTraceDistance; + options.ssct.shadowDistance = ssctStartTraceDistance; options.ssct.contactDistanceMax = ssctContactDistanceMax; options.ssct.intensity = ssctIntensity; options.ssct.lightDirection = math::float3{ ssctLightDirX, ssctLightDirY, ssctLightDirZ }; diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h index 6bbf1be3ea..2331766dcc 100644 --- a/filament/include/filament/View.h +++ b/filament/include/filament/View.h @@ -231,8 +231,8 @@ public: */ struct Ssct { float lightConeRad = 1.0f; //!< full cone angle in radian, between 0 and pi/2 - float startTraceDistance = 0.01f; //!< distance where tracing starts - float contactDistanceMax = 1.0f; //!< max distance shadows are cast + float shadowDistance = 0.3f; //!< how far shadows can be cast + float contactDistanceMax = 1.0f; //!< max distance for contact float intensity = 0.8f; //!< intensity math::float3 lightDirection{ 0, -1, 0 }; //!< light direction float depthBias = 0.01f; //!< depth bias in world units (mitigate self shadowing) diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index ddd0d51211..c6ed757684 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -519,13 +519,9 @@ FrameGraphId PostProcessManager::screenSpaceAmbientOcclusion( mi->setParameter("angleIncCosSin", float2{ std::cos(inc), std::sin(inc) }); mi->setParameter("invFarPlane", 1.0f / -cameraInfo.zf); - mi->setParameter("ssctConeTraceParams", float4{ - options.ssct.enabled ? std::tan(options.ssct.lightConeRad * 0.5f) : 0.0f, - std::sin(options.ssct.lightConeRad * 0.5f), - options.ssct.startTraceDistance * projectionScale, - 1.0f / options.ssct.contactDistanceMax - }); - + mi->setParameter("ssctShadowDistance", options.ssct.shadowDistance); + mi->setParameter("ssctConeAngleTangeant", std::tan(options.ssct.lightConeRad * 0.5f)); + mi->setParameter("ssctContactDistanceMaxInv", 1.0f / options.ssct.contactDistanceMax); // light direction in view space // (note: this is actually equivalent to using the camera view matrix -- before the // world matrix is accounted for) @@ -533,9 +529,8 @@ FrameGraphId PostProcessManager::screenSpaceAmbientOcclusion( const float3 l = normalize( mat3f::getTransformForNormals(m.upperLeft()) * options.ssct.lightDirection); - mi->setParameter("ssctIntensity", - options.ssct.intensity); + options.ssct.enabled ? options.ssct.intensity : 0.0f); mi->setParameter("ssctVsLightDirection", -l); mi->setParameter("ssctDepthBias", float2{ options.ssct.depthBias, options.ssct.depthSlopeBias }); diff --git a/filament/src/details/View.h b/filament/src/details/View.h index 8bde00d7aa..c9b3b98cc0 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -296,7 +296,7 @@ public: options.intensity = std::max(0.0f, options.intensity); options.minHorizonAngleRad = math::clamp(options.minHorizonAngleRad, 0.0f, math::f::PI_2); options.ssct.lightConeRad = math::clamp(options.ssct.lightConeRad, 0.0f, math::f::PI_2); - options.ssct.startTraceDistance = std::max(0.0f, options.ssct.startTraceDistance); + options.ssct.shadowDistance = std::max(0.0f, options.ssct.shadowDistance); options.ssct.contactDistanceMax = std::max(0.0f, options.ssct.contactDistanceMax); options.ssct.intensity = std::max(0.0f, options.ssct.intensity); options.ssct.lightDirection = normalize(options.ssct.lightDirection); diff --git a/filament/src/materials/ssao/sao.mat b/filament/src/materials/ssao/sao.mat index 5a0a83145b..9fedcb8f79 100644 --- a/filament/src/materials/ssao/sao.mat +++ b/filament/src/materials/ssao/sao.mat @@ -82,8 +82,16 @@ material { name : reserved }, { - type : float4, - name : ssctConeTraceParams + type : float, + name : ssctShadowDistance + }, + { + type : float, + name : ssctConeAngleTangeant + }, + { + type : float, + name : ssctContactDistanceMaxInv }, { type : float3, @@ -256,24 +264,31 @@ fragment { float dominantLightShadowing(highp vec2 uv, highp vec3 origin, vec3 normal) { ConeTraceSetup cone; + cone.ssStartPos = uv * materialParams.resolution.xy; cone.vsStartPos = origin; cone.vsNormal = normal; + + cone.vsConeDirection = materialParams.ssctVsLightDirection; + cone.shadowDistance = materialParams.ssctShadowDistance; + cone.coneAngleTangeant = materialParams.ssctConeAngleTangeant; + cone.contactDistanceMaxInv = materialParams.ssctContactDistanceMaxInv; + cone.screenFromViewMatrix = materialParams.screenFromViewMatrix; cone.depthParams = materialParams.depthParams; - cone.vsConeDirection = materialParams.ssctVsLightDirection; cone.projectionScale = materialParams.projectionScale; cone.resolution = materialParams.resolution; - cone.coneTraceParams = materialParams.ssctConeTraceParams; + cone.maxLevel = float(materialParams.maxLevel); + cone.intensity = materialParams.ssctIntensity; cone.depthBias = materialParams.ssctDepthBias.x; cone.slopeScaledDepthBias = materialParams.ssctDepthBias.y; cone.sampleCount = materialParams.ssctSampleCount; - cone.maxLevel = float(materialParams.maxLevel); + float occlusion = 0.0; for (float i = 1.0; i <= materialParams.ssctRayCount.x; i += 1.0) { cone.jitterOffset.x = random(gl_FragCoord.xy * i) * 2.0 - 1.0; // direction - cone.jitterOffset.y = random(gl_FragCoord.xy * i * vec2(11, 3)); // step + cone.jitterOffset.y = random(gl_FragCoord.xy * i * vec2(3, 11)); // step occlusion += coneTraceOcclusion(cone, materialParams_depth); } return occlusion * materialParams.ssctRayCount.y; @@ -292,7 +307,7 @@ fragment { occlusion = scalableAmbientObscurance(uv, origin, normal); } - if (materialParams.ssctConeTraceParams.x > 0.0) { + if (materialParams.ssctIntensity > 0.0) { occlusion = max(occlusion, dominantLightShadowing(uv, origin, normal)); } diff --git a/filament/src/materials/ssao/ssct.fs b/filament/src/materials/ssao/ssct.fs index 944af1ec90..7756fb61be 100644 --- a/filament/src/materials/ssao/ssct.fs +++ b/filament/src/materials/ssao/ssct.fs @@ -5,7 +5,7 @@ #include "ssaoUtils.fs" -const float kSSCTLog2LodRate = 4.0; +const float kSSCTLog2LodRate = 3.0; struct ConeTraceSetup { // fragment info @@ -15,7 +15,9 @@ struct ConeTraceSetup { // light (cone) info vec3 vsConeDirection; - vec4 coneTraceParams; // { tan(angle), sin(angle), start trace distance, inverse max contact distance } + float shadowDistance; + float coneAngleTangeant; + float contactDistanceMaxInv; vec2 jitterOffset; // (x = direction offset, y = step offset) // scene infos @@ -33,9 +35,18 @@ struct ConeTraceSetup { }; highp float getWFromProjectionMatrix(const mat4 p, const vec3 v) { - // this assumes a projection matrix perspective or ortho - // returns (p * v).w - return p[2][3] * v.z + p[3][3]; + // this essentially returns (p * vec4(v, 1.0)).w, but we make some assumptions + // this assumes a perspective projection + return -v.z; + // this assumes a perspective or ortho projection + //return p[2][3] * v.z + p[3][3]; +} + +highp float getViewSpaceZFromW(const mat4 p, const float w) { + // this assumes a perspective projection + return -w; + // this assumes a perspective or ortho projection + return (w - p[3][3]) / p[2][3]; } float coneTraceOcclusion(in ConeTraceSetup setup, const sampler2D depthTexture) { @@ -49,63 +60,48 @@ float coneTraceOcclusion(in ConeTraceSetup setup, const sampler2D depthTexture) // start position of cone trace highp vec2 ssStartPos = setup.ssStartPos; highp vec3 vsStartPos = setup.vsStartPos; - highp float ssStartPosInvW = 1.0 / getWFromProjectionMatrix(setup.screenFromViewMatrix, vsStartPos); + highp float ssStartPosW = getWFromProjectionMatrix(setup.screenFromViewMatrix, vsStartPos); + highp float ssStartPosWInv = 1.0 / ssStartPosW; // end position of cone trace - highp vec3 vsEndPos = setup.vsConeDirection + vsStartPos; + highp vec3 vsEndPos = setup.vsConeDirection * setup.shadowDistance + vsStartPos; highp float ssEndPosW = getWFromProjectionMatrix(setup.screenFromViewMatrix, vsEndPos); - highp float ssEndPosInvW = 1.0 / ssEndPosW; - highp vec2 ssEndPos = (setup.screenFromViewMatrix * vec4(vsEndPos, 1.0)).xy * ssEndPosInvW; + highp float ssEndPosWInv = 1.0 / ssEndPosW; + highp vec2 ssEndPos = (setup.screenFromViewMatrix * vec4(vsEndPos, 1.0)).xy * ssEndPosWInv; // cone trace direction in screen-space - float ssConeLength = length(ssEndPos - ssStartPos); - float ssInvConeLength = 1.0 / ssConeLength; - vec2 ssConeDirection = (ssEndPos - ssStartPos) * ssInvConeLength; + float ssConeLength = length(ssEndPos - ssStartPos); // do the math in highp + vec2 ssConeVector = ssEndPos - ssStartPos; // direction perpendicular to cone trace direction - vec2 perpConeDir = vec2(ssConeDirection.y, -ssConeDirection.x); + vec2 perpConeDir = normalize(vec2(ssConeVector.y, -ssConeVector.x)); + float vsEndRadius = setup.coneAngleTangeant * setup.shadowDistance; - // avoid self-occlusion and reduce banding artifacts by normal variation - vec3 vsViewVector = normalize(vsStartPos); - float minTraceDistance = (1.0 - abs(dot(setup.vsNormal, vsViewVector))) * setup.projectionScale * 0.005; + // normalized step + highp float dt = 1.0 / float(setup.sampleCount); - // init trace distance and sample radius - highp float invLinearDepth = 1.0 / -setup.vsStartPos.z; - highp float ssTracedDistance = max(setup.coneTraceParams.z, minTraceDistance) * invLinearDepth; - - float ssSampleRadius = setup.coneTraceParams.y * ssTracedDistance; - float ssEndRadius = setup.coneTraceParams.y * ssConeLength; - float vsEndRadius = ssEndRadius * (1.0 / setup.projectionScale) * invLinearDepth * ssEndPosW; + // normalized (0 to 1) screen-space postion on the ray + highp float t = dt * setup.jitterOffset.y; // calculate depth bias float vsDepthBias = saturate(1.0 - NoL) * setup.slopeScaledDepthBias + setup.depthBias; float occlusion = 0.0; - for (uint i = 0u; i < setup.sampleCount; i++) { - // step along cone in screen space - float ssNextSampleRadius = ssSampleRadius * (ssSampleRadius + ssTracedDistance) / (ssTracedDistance - ssSampleRadius); - float ssStepDistance = ssSampleRadius + ssNextSampleRadius; - ssSampleRadius = ssNextSampleRadius; + for (uint i = 0u; i < setup.sampleCount; i++, t += dt) { + float ssTracedDistance = ssConeLength * t; + float ssSliceRadius = setup.jitterOffset.x * (setup.coneAngleTangeant * ssTracedDistance); + highp vec2 ssSamplePos = perpConeDir * ssSliceRadius + ssConeVector * t + ssStartPos; - // apply jitter offset - float ssJitterStepDistance = ssStepDistance * setup.jitterOffset.y; - float ssJitteredTracedDistance = ssTracedDistance + ssJitterStepDistance; - float ssJitteredSampleRadius = setup.jitterOffset.x * setup.coneTraceParams.x * ssJitteredTracedDistance; - ssTracedDistance += ssStepDistance; - - // sample depth buffer, using lower LOD as the radius (i.e. distance from origin) grows - highp vec2 ssSamplePos = perpConeDir * ssJitteredSampleRadius + ssConeDirection * ssJitteredTracedDistance + ssStartPos; - float level = clamp(floor(log2(ssJitteredTracedDistance)) - kSSCTLog2LodRate, 0.0, setup.maxLevel); - float vsSampleDepthLinear = -sampleDepthLinear(depthTexture, ssSamplePos * setup.resolution.zw, level, setup.depthParams); - - // calculate depth of cone center - float ratio = ssJitteredTracedDistance * ssInvConeLength; - float vsConeAxisDepth = 1.0 / mix(ssStartPosInvW, ssEndPosInvW, ratio); + float level = clamp(floor(log2(ssSliceRadius)) - kSSCTLog2LodRate, 0.0, float(setup.maxLevel)); + float vsSampleDepthLinear = -sampleDepthLinear(depthTexture, ssSamplePos * setup.resolution.zw, 0.0, setup.depthParams); // calculate depth range of cone slice - float vsConeRadius = (ratio * vsEndRadius) * vsConeAxisDepth; - float vsJitteredSampleRadius = vsConeRadius * setup.jitterOffset.x; - float vsSliceHalfRange = sqrt(vsConeRadius * vsConeRadius - vsJitteredSampleRadius * vsJitteredSampleRadius); + float vsSliceRadius = vsEndRadius * t; + + // calculate depth of cone center + float vsConeAxisDepth = -getViewSpaceZFromW(setup.screenFromViewMatrix, 1.0 / mix(ssStartPosWInv, ssEndPosWInv, t)); + float vsJitteredSampleRadius = vsSliceRadius * setup.jitterOffset.x; + float vsSliceHalfRange = sqrt(vsSliceRadius * vsSliceRadius - vsJitteredSampleRadius * vsJitteredSampleRadius); float vsSampleDepthMax = vsConeAxisDepth + vsSliceHalfRange; // calculate overlap of depth buffer height-field with trace cone @@ -113,13 +109,12 @@ float coneTraceOcclusion(in ConeTraceSetup setup, const sampler2D depthTexture) float overlap = saturate((vsDepthDifference - vsDepthBias) / (vsSliceHalfRange * 2.0)); // attenuate by distance to avoid false occlusion - float attenuation = saturate(1.0 - (vsDepthDifference * setup.coneTraceParams.w)); + float attenuation = saturate(1.0 - (vsDepthDifference * setup.contactDistanceMaxInv)); occlusion = max(occlusion, overlap * attenuation); - if (occlusion >= 1.0) { // note: this can't get > 1.0 by construction // fully occluded, early exit break; } } return occlusion * setup.intensity; -} \ No newline at end of file +} diff --git a/libs/viewer/src/Settings.cpp b/libs/viewer/src/Settings.cpp index 015498e8d4..ed5065671e 100644 --- a/libs/viewer/src/Settings.cpp +++ b/libs/viewer/src/Settings.cpp @@ -289,8 +289,8 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, i = parse(tokens, i + 1, jsonChunk, &out->enabled); } else if (compare(tok, jsonChunk, "lightConeRad") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->lightConeRad); - } else if (compare(tok, jsonChunk, "startTraceDistance") == 0) { - i = parse(tokens, i + 1, jsonChunk, &out->startTraceDistance); + } else if (compare(tok, jsonChunk, "shadowDistance") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->shadowDistance); } else if (compare(tok, jsonChunk, "contactDistanceMax") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->contactDistanceMax); } else if (compare(tok, jsonChunk, "intensity") == 0) { @@ -758,7 +758,7 @@ std::string writeJson(const AmbientOcclusionOptions::Ssct& in) { oss << "{\n" << "\"enabled\": " << writeJson(in.enabled) << ",\n" << "\"lightConeRad\": " << writeJson(in.lightConeRad) << ",\n" - << "\"startTraceDistance\": " << writeJson(in.startTraceDistance) << ",\n" + << "\"shadowDistance\": " << writeJson(in.shadowDistance) << ",\n" << "\"contactDistanceMax\": " << writeJson(in.contactDistanceMax) << ",\n" << "\"intensity\": " << writeJson(in.intensity) << ",\n" << "\"lightDirection\": " << writeJson(in.lightDirection) << ",\n" diff --git a/libs/viewer/src/SimpleViewer.cpp b/libs/viewer/src/SimpleViewer.cpp index 26ed8a1168..2cfb7fd117 100644 --- a/libs/viewer/src/SimpleViewer.cpp +++ b/libs/viewer/src/SimpleViewer.cpp @@ -314,7 +314,7 @@ void SimpleViewer::updateUserInterface() { int sampleCount = ssao.ssct.sampleCount; ImGui::Checkbox("Enabled##dls", &ssao.ssct.enabled); ImGui::SliderFloat("Cone angle", &ssao.ssct.lightConeRad, 0.0f, (float)M_PI_2); - ImGui::SliderFloat("Start dist", &ssao.ssct.startTraceDistance, 0.0f, 1.0f); + ImGui::SliderFloat("Shadow Distance", &ssao.ssct.shadowDistance, 0.0f, 10.0f); ImGui::SliderFloat("Contact dist max", &ssao.ssct.contactDistanceMax, 0.0f, 100.0f); ImGui::SliderFloat("Intensity##dls", &ssao.ssct.intensity, 0.0f, 10.0f); ImGui::SliderFloat("Depth bias", &ssao.ssct.depthBias, 0.0f, 1.0f); diff --git a/libs/viewer/tests/test_settings.cpp b/libs/viewer/tests/test_settings.cpp index 70d3babde0..a91a502ca5 100644 --- a/libs/viewer/tests/test_settings.cpp +++ b/libs/viewer/tests/test_settings.cpp @@ -70,7 +70,7 @@ static const char* JSON_TEST_DEFAULTS = R"TXT( "ssct": { "enabled": false, "lightConeRad": 1.0, - "startTraceDistance": 0.01, + "shadowDistance": 0.3, "contactDistanceMax": 1.0, "intensity": 0.8, "lightDirection": [0, -1, 0], diff --git a/samples/material_sandbox.cpp b/samples/material_sandbox.cpp index e0138c08be..b92ff40540 100644 --- a/samples/material_sandbox.cpp +++ b/samples/material_sandbox.cpp @@ -623,7 +623,7 @@ static void gui(filament::Engine* engine, filament::View*) { int rayCount = params.ssaoOptions.ssct.rayCount; ImGui::Checkbox("Enabled##dls", ¶ms.ssaoOptions.ssct.enabled); ImGui::SliderFloat("Cone angle", ¶ms.ssaoOptions.ssct.lightConeRad, 0.0f, (float)M_PI_2); - ImGui::SliderFloat("Start dist", ¶ms.ssaoOptions.ssct.startTraceDistance, 0.0f, 1.0f); + ImGui::SliderFloat("Shadow Distance", ¶ms.ssaoOptions.ssct.shadowDistance, 0.0f, 10.0f); ImGui::SliderFloat("Contact dist max", ¶ms.ssaoOptions.ssct.contactDistanceMax, 0.0f, 100.0f); ImGui::SliderFloat("Intensity##dls", ¶ms.ssaoOptions.ssct.intensity, 0.0f, 10.0f); ImGui::SliderFloat("Depth bias", ¶ms.ssaoOptions.ssct.depthBias, 0.0f, 1.0f); diff --git a/shaders/src/shadowing.fs b/shaders/src/shadowing.fs index 9915fccf9d..093bf4e204 100644 --- a/shaders/src/shadowing.fs +++ b/shaders/src/shadowing.fs @@ -256,9 +256,9 @@ void initScreenSpaceRay(out ScreenSpaceRay ray, highp vec3 wsRayStart, vec3 wsRa highp vec4 csViewRayEnd = csRayStart + viewToClip * vec4(0.0, 0.0, wsRayLength, 0.0); // ray start/end in screen space - ray.ssRayStart = csRayStart.xyz * 1.0 / csRayStart.w; - ray.ssRayEnd = csRayEnd.xyz * 1.0 / csRayEnd.w; - ray.ssViewRayEnd = csViewRayEnd.xyz * 1.0 / csViewRayEnd.w; + ray.ssRayStart = csRayStart.xyz * (1.0 / csRayStart.w); + ray.ssRayEnd = csRayEnd.xyz * (1.0 / csRayEnd.w); + ray.ssViewRayEnd = csViewRayEnd.xyz * (1.0 / csViewRayEnd.w); // convert all to uv (texture) space highp vec3 uvRayEnd = ray.ssRayEnd.xyz * 0.5 + 0.5;