diff --git a/filament/src/materials/ssao/sao.mat b/filament/src/materials/ssao/sao.mat index e9e7204915..02dacb63a1 100644 --- a/filament/src/materials/ssao/sao.mat +++ b/filament/src/materials/ssao/sao.mat @@ -189,14 +189,47 @@ fragment { // are essentially equivalent to textureGather (which we don't have on ES3.0), // and this is executed just once. highp vec3 computeViewSpaceNormal(const highp vec3 position, const highp vec2 uv) { + precision highp float; highp vec2 uvdx = uv + vec2(materialParams.resolution.z, 0.0); highp vec2 uvdy = uv + vec2(0.0, materialParams.resolution.w); - highp vec3 px = computeViewSpacePositionFromDepth(uvdx, + vec3 px = computeViewSpacePositionFromDepth(uvdx, sampleDepthLinear(materialParams_depth, uvdx, 0.0, materialParams.depthParams)); - highp vec3 py = computeViewSpacePositionFromDepth(uvdy, + vec3 py = computeViewSpacePositionFromDepth(uvdy, sampleDepthLinear(materialParams_depth, uvdy, 0.0, materialParams.depthParams)); - highp vec3 dpdx = px - position; - highp vec3 dpdy = py - position; + vec3 dpdx = px - position; + vec3 dpdy = py - position; + return faceNormal(dpdx, dpdy); + } + + // Accurate view-space normal reconstruction + // Based on Yuwen Wu "Accurate Normal Reconstruction" + // (https://atyuwen.github.io/posts/normal-reconstruction) + highp vec3 computeViewSpaceNormalAccurate(const highp vec3 position, const highp vec2 uv) { + precision highp float; + + vec3 pos_c = position; + highp vec2 dx = vec2(materialParams.resolution.z, 0.0); + highp vec2 dy = vec2(0.0, materialParams.resolution.w); + + vec4 H; + H.x = sampleDepth(materialParams_depth, uv - dx, 0.0); + H.y = sampleDepth(materialParams_depth, uv + dx, 0.0); + H.z = sampleDepth(materialParams_depth, uv - dx * 2.0, 0.0); + H.w = sampleDepth(materialParams_depth, uv + dx * 2.0, 0.0); + vec2 he = abs((2.0 * H.xy - H.zw) - pos_c.z); + vec3 pos_l = computeViewSpacePositionFromDepth(uv - dx, linearizeDepth(H.x, materialParams.depthParams)); + vec3 pos_r = computeViewSpacePositionFromDepth(uv + dx, linearizeDepth(H.y, materialParams.depthParams)); + vec3 dpdx = (he.x < he.y) ? (pos_c - pos_l) : (pos_r - pos_c); + + vec4 V; + V.x = sampleDepth(materialParams_depth, uv - dy, 0.0); + V.y = sampleDepth(materialParams_depth, uv + dy, 0.0); + V.z = sampleDepth(materialParams_depth, uv - dy * 2.0, 0.0); + V.w = sampleDepth(materialParams_depth, uv + dy * 2.0, 0.0); + vec2 ve = abs((2.0 * V.xy - V.zw) - pos_c.z); + vec3 pos_d = computeViewSpacePositionFromDepth(uv - dy, linearizeDepth(V.x, materialParams.depthParams)); + vec3 pos_u = computeViewSpacePositionFromDepth(uv + dy, linearizeDepth(V.y, materialParams.depthParams)); + vec3 dpdy = (ve.x < ve.y) ? (pos_c - pos_d) : (pos_u - pos_c); return faceNormal(dpdx, dpdy); } @@ -314,7 +347,13 @@ fragment { highp float depth = sampleDepthLinear(materialParams_depth, uv, 0.0, materialParams.depthParams); highp vec3 origin = computeViewSpacePositionFromDepth(uv, depth); - vec3 normal = computeViewSpaceNormal(origin, uv); + + // todo: maybe make this a quality parameter of SSAO + #if FILAMENT_QUALITY == FILAMENT_QUALITY_HIGH + vec3 normal = computeViewSpaceNormalAccurate(origin, uv); + #else + vec3 normal = computeViewSpaceNormal(origin, uv); + #endif float occlusion = 0.0; diff --git a/filament/src/materials/ssao/ssaoUtils.fs b/filament/src/materials/ssao/ssaoUtils.fs index 8adda0d8a9..bb52cbbffc 100644 --- a/filament/src/materials/ssao/ssaoUtils.fs +++ b/filament/src/materials/ssao/ssaoUtils.fs @@ -9,15 +9,19 @@ highp float linearizeDepth(highp float depth, highp float depthParams) { return depthParams / max(depth, preventDiv0); } -highp float sampleDepthLinear(const highp sampler2D depthTexture, const highp vec2 uv, float lod, highp float depthParams) { +highp float sampleDepth(const highp sampler2D depthTexture, const highp vec2 uv, float lod) { #if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT) // On metal/vulkan, texture space is flipped vertically and we need to adjust the uv // coordinates. - return linearizeDepth(textureLod(depthTexture, vec2(uv.x, 1.0 - uv.y), lod).r, depthParams); + return textureLod(depthTexture, vec2(uv.x, 1.0 - uv.y), lod).r; #else - return linearizeDepth(textureLod(depthTexture, uv, lod).r, depthParams); + return textureLod(depthTexture, uv, lod).r; #endif } +highp float sampleDepthLinear(const highp sampler2D depthTexture, const highp vec2 uv, float lod, highp float depthParams) { + return linearizeDepth(sampleDepth(depthTexture, uv, lod), depthParams); +} + #endif // MATERIALS_SSAO_UTILS diff --git a/shaders/src/ambient_occlusion.fs b/shaders/src/ambient_occlusion.fs index 8cfe4264b0..67431fd225 100644 --- a/shaders/src/ambient_occlusion.fs +++ b/shaders/src/ambient_occlusion.fs @@ -28,13 +28,10 @@ float evaluateSSAO() { // Read four AO samples and their depths values #if defined(FILAMENT_HAS_FEATURE_TEXTURE_GATHER) - vec4 ao = textureGather(light_ssao, uv, 0); // 01, 11, 10, 00 - vec4 dg = textureGather(light_ssao, uv, 1); // 01, 11, 10, 00 - vec4 db = textureGather(light_ssao, uv, 2); // 01, 11, 10, 00 + vec4 ao = textureGather(light_ssao, uv, 0); + vec4 dg = textureGather(light_ssao, uv, 1); + vec4 db = textureGather(light_ssao, uv, 2); #else - // on mobile we can't use textureGather() because we're limited to ES3.0, - // so we emulate it with texelFetch(), on Pixel 4 this doesn't seem to have any - // significant impact on performance. vec3 s01 = textureLodOffset(light_ssao, uv, 0.0, ivec2(0, 1)).rgb; vec3 s11 = textureLodOffset(light_ssao, uv, 0.0, ivec2(1, 1)).rgb; vec3 s10 = textureLodOffset(light_ssao, uv, 0.0, ivec2(1, 0)).rgb;