UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

3 lines (2 loc) 7.95 kB
declare const _default: "\n #include \"screenDepthPS\"\n\n varying uv0: vec2f;\n\n uniform uInvResolution: vec2f;\n uniform uAspect: f32;\n\n // Largely based on 'Dominant Light Shadowing'\n // 'Lighting Technology of The Last of Us Part II' by Hawar Doghramachi, Naughty Dog, LLC\n\n fn getWFromProjectionMatrix(p: mat4x4f, v: vec3f) -> f32 {\n // this essentially returns (p * vec4(v, 1.0)).w, but we make some assumptions\n // this assumes a perspective projection\n return -v.z;\n // this assumes a perspective or ortho projection\n // return p[2][3] * v.z + p[3][3];\n }\n\n fn getViewSpaceZFromW(p: mat4x4f, w: f32) -> f32 {\n // this assumes a perspective projection\n return -w;\n // this assumes a perspective or ortho projection\n // return (w - p[3][3]) / p[2][3];\n }\n\n const kLog2LodRate: f32 = 3.0;\n\n // random number between 0 and 1, using interleaved gradient noise\n fn random(w: vec2f) -> f32 {\n const m: vec3f = vec3f(0.06711056, 0.00583715, 52.9829189);\n return fract(m.z * fract(dot(w, m.xy)));\n }\n\n // returns the frag coord in the GL convention with (0, 0) at the bottom-left\n fn getFragCoord() -> vec2f {\n return pcPosition.xy;\n }\n\n fn computeViewSpacePositionFromDepth(uv: vec2f, linearDepth: f32) -> vec3f {\n return vec3f((0.5 - uv) * vec2f(uniform.uAspect, 1.0) * linearDepth, linearDepth);\n }\n\n fn faceNormal(dpdx: vec3f, dpdy: vec3f) -> vec3f {\n return normalize(cross(dpdx, dpdy));\n }\n\n // Compute normals using derivatives, which essentially results in half-resolution normals\n // this creates artifacts around geometry edges.\n // Note: when using the spirv optimizer, this results in much slower execution time because\n // this whole expression is inlined in the AO loop below.\n fn computeViewSpaceNormalDeriv(position: vec3f) -> vec3f {\n return faceNormal(dpdx(position), dpdy(position));\n }\n\n // Compute normals directly from the depth texture, resulting in full resolution normals\n // Note: This is actually as cheap as using derivatives because the texture fetches\n // are essentially equivalent to textureGather (which we don't have on ES3.0),\n // and this is executed just once.\n fn computeViewSpaceNormalDepth(position: vec3f, uv: vec2f) -> vec3f {\n let uvdx: vec2f = uv + vec2f(uniform.uInvResolution.x, 0.0);\n let uvdy: vec2f = uv + vec2f(0.0, uniform.uInvResolution.y);\n let px: vec3f = computeViewSpacePositionFromDepth(uvdx, -getLinearScreenDepth(uvdx));\n let py: vec3f = computeViewSpacePositionFromDepth(uvdy, -getLinearScreenDepth(uvdy));\n let dpdx: vec3f = px - position;\n let dpdy: vec3f = py - position;\n return faceNormal(dpdx, dpdy);\n }\n\n // Ambient Occlusion, largely inspired from:\n // 'The Alchemy Screen-Space Ambient Obscurance Algorithm' by Morgan McGuire\n // 'Scalable Ambient Obscurance' by Morgan McGuire, Michael Mara and David Luebke\n\n uniform uSampleCount: vec2f;\n uniform uSpiralTurns: f32;\n\n const PI: f32 = 3.14159;\n\n fn tapLocation(i: f32, noise: f32) -> vec3f {\n let offset: f32 = ((2.0 * PI) * 2.4) * noise;\n let angle: f32 = ((i * uniform.uSampleCount.y) * uniform.uSpiralTurns) * (2.0 * PI) + offset;\n let radius: f32 = (i + noise + 0.5) * uniform.uSampleCount.y;\n return vec3f(cos(angle), sin(angle), radius * radius);\n }\n\n fn startPosition(noise: f32) -> vec2f {\n let angle: f32 = ((2.0 * PI) * 2.4) * noise;\n return vec2f(cos(angle), sin(angle));\n }\n\n uniform uAngleIncCosSin: vec2f;\n\n fn tapAngleStep() -> mat2x2f {\n let t: vec2f = uniform.uAngleIncCosSin;\n return mat2x2f(vec2f(t.x, t.y), vec2f(-t.y, t.x));\n }\n\n fn tapLocationFast(i: f32, p: vec2f, noise_in: f32) -> vec3f {\n let radius: f32 = (i + noise_in + 0.5) * uniform.uSampleCount.y;\n return vec3f(p.x, p.y, radius * radius);\n }\n\n uniform uMaxLevel: f32;\n uniform uInvRadiusSquared: f32;\n uniform uMinHorizonAngleSineSquared: f32;\n uniform uBias: f32;\n uniform uPeak2: f32;\n\n fn computeAmbientOcclusionSAO(occlusion_ptr: ptr<function, f32>, i: f32, ssDiskRadius: f32,\n uv: vec2f, origin: vec3f, normal: vec3f,\n tapPosition: vec2f, noise: f32) {\n\n let tap: vec3f = tapLocationFast(i, tapPosition, noise);\n\n let ssRadius: f32 = max(1.0, tap.z * ssDiskRadius); // at least 1 pixel screen-space radius\n\n let uvSamplePos: vec2f = uv + (ssRadius * tap.xy) * uniform.uInvResolution;\n\n // TODO: level is not used, but could be used with mip-mapped depth texture\n let level: f32 = clamp(floor(log2(ssRadius)) - kLog2LodRate, 0.0, uniform.uMaxLevel);\n let occlusionDepth: f32 = -getLinearScreenDepth(uvSamplePos);\n let p: vec3f = computeViewSpacePositionFromDepth(uvSamplePos, occlusionDepth);\n\n // now we have the sample, compute AO\n let v: vec3f = p - origin; // sample vector\n let vv: f32 = dot(v, v); // squared distance\n let vn: f32 = dot(v, normal); // distance * cos(v, normal)\n\n // discard samples that are outside of the radius, preventing distant geometry to cast\n // shadows -- there are many functions that work and choosing one is an artistic decision.\n var w_val: f32 = max(0.0, 1.0 - vv * uniform.uInvRadiusSquared);\n w_val = w_val * w_val;\n\n // discard samples that are too close to the horizon to reduce shadows cast by geometry\n // not sufficiently tessellated. The goal is to discard samples that form an angle 'beta'\n // smaller than 'epsilon' with the horizon. We already have dot(v,n) which is equal to the\n // sin(beta) * |v|. So the test simplifies to vn^2 < vv * sin(epsilon)^2.\n w_val = w_val * step(vv * uniform.uMinHorizonAngleSineSquared, vn * vn);\n\n *occlusion_ptr = *occlusion_ptr + w_val * max(0.0, vn + origin.z * uniform.uBias) / (vv + uniform.uPeak2);\n }\n\n uniform uProjectionScaleRadius: f32;\n uniform uIntensity: f32;\n uniform uRandomize: f32;\n\n fn scalableAmbientObscurance(uv: vec2f, origin: vec3f, normal: vec3f) -> f32 {\n let noise: f32 = random(getFragCoord()) + uniform.uRandomize;\n var tapPosition: vec2f = startPosition(noise);\n let angleStep: mat2x2f = tapAngleStep();\n\n // Choose the screen-space sample radius\n // proportional to the projected area of the sphere\n let ssDiskRadius: f32 = -(uniform.uProjectionScaleRadius / origin.z);\n\n var occlusion: f32 = 0.0;\n for (var i: i32 = 0; i < i32(uniform.uSampleCount.x); i = i + 1) {\n computeAmbientOcclusionSAO(&occlusion, f32(i), ssDiskRadius, uv, origin, normal, tapPosition, noise);\n tapPosition = angleStep * tapPosition;\n }\n return occlusion;\n }\n\n uniform uPower: f32;\n\n @fragment\n fn fragmentMain(input: FragmentInput) -> FragmentOutput {\n var output: FragmentOutput;\n\n let uv: vec2f = input.uv0; // interpolated to pixel center\n\n let depth: f32 = -getLinearScreenDepth(input.uv0);\n let origin: vec3f = computeViewSpacePositionFromDepth(uv, depth);\n let normal: vec3f = computeViewSpaceNormalDepth(origin, uv);\n\n var occlusion: f32 = 0.0;\n if (uniform.uIntensity > 0.0) {\n occlusion = scalableAmbientObscurance(uv, origin, normal);\n }\n\n // occlusion to visibility\n var ao: f32 = max(0.0, 1.0 - occlusion * uniform.uIntensity);\n ao = pow(ao, uniform.uPower);\n\n output.color = vec4f(ao, ao, ao, 1.0);\n return output;\n }\n"; export default _default;