Skip to content

Instantly share code, notes, and snippets.

@bhouston
Created April 24, 2015 19:30
Show Gist options
  • Save bhouston/1dc2a760783314b95bd9 to your computer and use it in GitHub Desktop.
Save bhouston/1dc2a760783314b95bd9 to your computer and use it in GitHub Desktop.
Scalable Ambient Obsurance for ThreeJS.
/**
* @author bhouston / http://clara.io/
*
* Scaleable Ambient Obscurance
*
* based on:
* - https://gist.github.com/fisch0920/6770311
* - http://graphics.cs.williams.edu/papers/SAOHPG12/McGuire12SAO-talk.pdf
*/
THREE.SAOShader = {
uniforms: {
"tDepth": { type: "t", value: null },
//"tDiffuse": { type: "t", value: null },
"intensity": { type: "f", value: 100.0 },
"sampleRadiusWS": { type: "f", value: 5.0 },
"bias": { type: "f", value: 0.0 },
"zNear": { type: "f", value: 1.0 },
"zFar": { type: "f", value: 1000.0 },
"viewportResolution": { type: "v2", value: new THREE.Vector2( 256, 256 ) },
"projInfo": { type: "v4", value: new THREE.Vector4( 1.0, 1.0, 1.0, 0 ) },
"projScale": { type: "f", value: 100.0 }
},
vertexShader: [
"varying vec2 vUv;",
"void main() {",
"vUv = uv;",
"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
"}"
].join("\n"),
fragmentShader: [
// total number of samples at each fragment",
"#extension GL_OES_standard_derivatives : enable",
"#define NUM_SAMPLES 256",
"#define NUM_SPIRAL_TURNS 10",
"#define VARIATION 0",
"varying vec2 vUv;",
"uniform sampler2D tDepth;",
// "uniform sampler2D tDiffuse;",
"uniform float intensity;",
"uniform float sampleRadiusWS;",
"uniform float bias;",
"uniform float zNear;",
"uniform float zFar;",
"uniform vec4 projInfo;",
"uniform vec2 viewportResolution;",
"uniform float projScale;", // how many pixels a 1m size 1m away from the camera is. Or 500 is a good value.
// RGBA depth
"float unpackDepth( const in vec4 rgba_depth ) {",
"const vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );",
"float depth = dot( rgba_depth, bit_shift );",
"return depth;",
"}",
/*
Clipping plane constants for use by reconstructZ
clipInfo = (z_f == -inf()) ? Vector3(z_n, -1.0f, 1.0f) : Vector3(z_n * z_f, z_n - z_f, z_f);
*/
"float reconstructCSZ(float depth) {",
// " return clipInfo[0] / (clipInfo[1] * d + zFar[2]);",
" return ( zNear * zFar ) / ( (zNear - zFar) * depth + zFar );",
"}",
/** vec4(-2.0f / (width*P[0][0]),
-2.0f / (height*P[1][1]),
( 1.0f - P[0][2]) / P[0][0],
( 1.0f + P[1][2]) / P[1][1])
where P is the projection matrix that maps camera space points
to [-1, 1] x [-1, 1]. That is, GCamera::getProjectUnit(). */
/** Reconstruct camera-space P.xyz from screen-space S = (x, y) in
pixels and camera-space z < 0. Assumes that the upper-left pixel center
is at (0.5, 0.5) [but that need not be the location at which the sample tap
was placed!]
Costs 3 MADD. Error is on the order of 10^3 at the far plane, partly due to z precision.
*/
"vec3 reconstructCSPosition(vec2 S, float z) {",
" return vec3((S.xy * projInfo.xy + projInfo.zw) * z, z);",
"}",
/** Reconstructs screen-space unit normal from screen-space position */
"vec3 reconstructCSFaceNormal(vec3 C) {",
" return -normalize(cross(dFdy(C), dFdx(C)));",
"}",
"vec3 reconstructNonUnitCSFaceNormal(vec3 C) {",
" return -cross(dFdy(C), dFdx(C));",
"}",
//"// reconstructs view-space unit normal from view-space position",
// "vec3 reconstructNormalVS(vec3 positionVS) {",
// " return normalize(cross(dFdx(positionVS), dFdy(positionVS)));",
// "}",
// 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.x);",
" float zDepth = unpackDepth( texture2D( tDepth, uv ) );",
" float depthCS = reconstructCSZ( zDepth );",
" return reconstructCSPosition( uv, depthCS );",
"}",
"float sampleAO(vec2 uv, vec3 positionVS, vec3 normalVS, float sampleRadiusSS,",
" int tapIndex, float rotationAngle)",
"{",
" const float epsilon = 0.01;",
" float radius2 = sampleRadiusWS * sampleRadiusWS;",
" // 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) - bias;",
"#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() {",
" float zDepth = unpackDepth( texture2D( tDepth, vUv ) );",
" float depthCS = reconstructCSZ( zDepth );",
" vec3 originVS = reconstructCSPosition( vUv, depthCS );",
" vec3 normalVS = normalize( reconstructNonUnitCSFaceNormal(originVS) );",
" float randomPatternRotationAngle = fract( fract( vUv.x * viewportResolution.x * 894.0 ) * 3.0 + fract( vUv.y * viewportResolution.y * 999.0 ) * 5.0 + fract( zDepth * 131.0 ) + fract( originVS.x * 3234.0 ) + fract( originVS.y * 99.0 ) );",
// " float randomPatternRotationAngle = 2.0 * PI * sampleNoise.x;",
// radius of influence in screen space
" float radiusWS = sampleRadiusWS;",
// radius of influence in world space
" float radiusSS = projScale * radiusWS / originVS.y;",
" float occlusion = 0.0;",
" for (int i = 0; i < NUM_SAMPLES; ++i) {",
" occlusion += sampleAO(vUv, originVS, normalVS, radiusSS, i,",
" randomPatternRotationAngle);",
" }",
" occlusion = 1.0 - occlusion / float( NUM_SAMPLES );",
" occlusion = clamp(pow(occlusion, 1.0 + intensity), 0.0, 1.0);",
// " vec4 color = texture2D( tDiffuse, vUv );",
" gl_FragColor = vec4( occlusion, occlusion, occlusion, 1.0 );",
// " gl_FragColor = vec4(radiusSS, radiusSS, radiusSS, 1.0);",
"}"
].join('\n')
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment