Skip to content

Instantly share code, notes, and snippets.

@shengsword
Forked from transitive-bullshit/billboard_sao.frag
Created August 28, 2019 04:23
Show Gist options
  • Save shengsword/5873fa6b6aff8691a2fbda334aef6305 to your computer and use it in GitHub Desktop.
Save shengsword/5873fa6b6aff8691a2fbda334aef6305 to your computer and use it in GitHub Desktop.

Revisions

  1. Travis Fischer created this gist Sep 30, 2013.
    132 changes: 132 additions & 0 deletions billboard_sao.frag
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,132 @@
    // total number of samples at each fragment
    #define NUM_SAMPLES {{ numSamples }}

    #define NUM_SPIRAL_TURNS {{ numSpiralTurns }}

    #define USE_ACTUAL_NORMALS {{ useActualNormals }}

    #define VARIATION {{ variation }}

    uniform sampler2D sGBuffer;
    uniform sampler2D sNoise;

    uniform float uFOV;
    uniform float uIntensity;
    uniform vec2 uNoiseScale;
    uniform float uSampleRadiusWS;
    uniform float uBias;

    // reconstructs view-space unit normal from view-space position
    vec3 reconstructNormalVS(vec3 positionVS) {
    return normalize(cross(dFdx(positionVS), dFdy(positionVS)));
    }

    vec3 getPositionVS(vec2 uv) {
    float depth = decodeGBufferDepth(sGBuffer, uv, clipFar);

    vec2 uv2 = uv * 2.0 - vec2(1.0);
    vec4 temp = viewProjectionInverseMatrix * vec4(uv2, -1.0, 1.0);
    vec3 cameraFarPlaneWS = (temp / temp.w).xyz;

    vec3 cameraToPositionRay = normalize(cameraFarPlaneWS - cameraPositionWorldSpace);
    vec3 originWS = cameraToPositionRay * depth + cameraPositionWorldSpace;
    vec3 originVS = (viewMatrix * vec4(originWS, 1.0)).xyz;

    return originVS;
    }

    // returns a unit vector and a screen-space radius for the tap on a unit disk
    // (the caller should scale by the actual disk radius)
    vec2 tapLocation(int sampleNumber, float spinAngle, out float radiusSS) {
    // radius relative to radiusSS
    float alpha = (float(sampleNumber) + 0.5) * (1.0 / float(NUM_SAMPLES));
    float angle = alpha * (float(NUM_SPIRAL_TURNS) * 6.28) + spinAngle;

    radiusSS = alpha;
    return vec2(cos(angle), sin(angle));
    }

    vec3 getOffsetPositionVS(vec2 uv, vec2 unitOffset, float radiusSS) {
    uv = uv + radiusSS * unitOffset * (1.0 / viewportResolution);

    return getPositionVS(uv);
    }

    float sampleAO(vec2 uv, vec3 positionVS, vec3 normalVS, float sampleRadiusSS,
    int tapIndex, float rotationAngle)
    {
    const float epsilon = 0.01;
    float radius2 = uSampleRadiusWS * uSampleRadiusWS;

    // offset on the unit disk, spun for this pixel
    float radiusSS;
    vec2 unitOffset = tapLocation(tapIndex, rotationAngle, radiusSS);
    radiusSS *= sampleRadiusSS;

    vec3 Q = getOffsetPositionVS(uv, unitOffset, radiusSS);
    vec3 v = Q - positionVS;

    float vv = dot(v, v);
    float vn = dot(v, normalVS) - uBias;

    #if VARIATION == 0

    // (from the HPG12 paper)
    // Note large epsilon to avoid overdarkening within cracks
    return float(vv < radius2) * max(vn / (epsilon + vv), 0.0);

    #elif VARIATION == 1 // default / recommended

    // Smoother transition to zero (lowers contrast, smoothing out corners). [Recommended]
    float f = max(radius2 - vv, 0.0) / radius2;
    return f * f * f * max(vn / (epsilon + vv), 0.0);

    #elif VARIATION == 2

    // Medium contrast (which looks better at high radii), no division. Note that the
    // contribution still falls off with radius^2, but we've adjusted the rate in a way that is
    // more computationally efficient and happens to be aesthetically pleasing.
    float invRadius2 = 1.0 / radius2;
    return 4.0 * max(1.0 - vv * invRadius2, 0.0) * max(vn, 0.0);

    #else

    // Low contrast, no division operation
    return 2.0 * float(vv < radius2) * max(vn, 0.0);

    #endif
    }

    void main() {
    vec3 originVS = getPositionVS(vUV);

    #if USE_ACTUAL_NORMALS
    gBufferGeomComponents gBufferValue = decodeGBufferGeom(sGBuffer, vUV, clipFar);

    vec3 normalVS = gBufferValue.normal;
    #else
    vec3 normalVS = reconstructNormalVS(originVS);
    #endif

    vec3 sampleNoise = texture2D(sNoise, vUV * uNoiseScale).xyz;

    float randomPatternRotationAngle = 2.0 * PI * sampleNoise.x;

    float radiusSS = 0.0; // radius of influence in screen space
    float radiusWS = 0.0; // radius of influence in world space
    float occlusion = 0.0;

    // TODO (travis): don't hardcode projScale
    float projScale = 40.0;//1.0 / (2.0 * tan(uFOV * 0.5));
    radiusWS = uSampleRadiusWS;
    radiusSS = projScale * radiusWS / originVS.y;

    for (int i = 0; i < NUM_SAMPLES; ++i) {
    occlusion += sampleAO(vUV, originVS, normalVS, radiusSS, i,
    randomPatternRotationAngle);
    }

    occlusion = 1.0 - occlusion / (4.0 * float(NUM_SAMPLES));
    occlusion = clamp(pow(occlusion, 1.0 + uIntensity), 0.0, 1.0);
    gl_FragColor = vec4(occlusion, occlusion, occlusion, 1.0);
    }