import { Matrix4, Vector2 } from 'three'; /** * TODO */ const SAOShader = { name: 'SAOShader', defines: { 'NUM_SAMPLES': 7, 'NUM_RINGS': 4, 'DIFFUSE_TEXTURE': 0, 'PERSPECTIVE_CAMERA': 1 }, uniforms: { 'tDepth': { value: null }, 'tDiffuse': { value: null }, 'tNormal': { value: null }, 'size': { value: new Vector2( 512, 512 ) }, 'cameraNear': { value: 1 }, 'cameraFar': { value: 100 }, 'cameraProjectionMatrix': { value: new Matrix4() }, 'cameraInverseProjectionMatrix': { value: new Matrix4() }, 'scale': { value: 1.0 }, 'intensity': { value: 0.1 }, 'bias': { value: 0.5 }, 'minResolution': { value: 0.0 }, 'kernelRadius': { value: 100.0 }, 'randomSeed': { value: 0.0 } }, vertexShader: /* glsl */` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }`, fragmentShader: /* glsl */` #include varying vec2 vUv; #if DIFFUSE_TEXTURE == 1 uniform sampler2D tDiffuse; #endif uniform highp sampler2D tDepth; uniform highp sampler2D tNormal; uniform float cameraNear; uniform float cameraFar; uniform mat4 cameraProjectionMatrix; uniform mat4 cameraInverseProjectionMatrix; uniform float scale; uniform float intensity; uniform float bias; uniform float kernelRadius; uniform float minResolution; uniform vec2 size; uniform float randomSeed; // RGBA depth #include vec4 getDefaultColor( const in vec2 screenPosition ) { #if DIFFUSE_TEXTURE == 1 return texture2D( tDiffuse, vUv ); #else return vec4( 1.0 ); #endif } float getDepth( const in vec2 screenPosition ) { return texture2D( tDepth, screenPosition ).x; } float getViewZ( const in float depth ) { #if PERSPECTIVE_CAMERA == 1 return perspectiveDepthToViewZ( depth, cameraNear, cameraFar ); #else return orthographicDepthToViewZ( depth, cameraNear, cameraFar ); #endif } vec3 getViewPosition( const in vec2 screenPosition, const in float depth, const in float viewZ ) { float clipW = cameraProjectionMatrix[2][3] * viewZ + cameraProjectionMatrix[3][3]; vec4 clipPosition = vec4( ( vec3( screenPosition, depth ) - 0.5 ) * 2.0, 1.0 ); clipPosition *= clipW; // unprojection. return ( cameraInverseProjectionMatrix * clipPosition ).xyz; } vec3 getViewNormal( const in vec3 viewPosition, const in vec2 screenPosition ) { return unpackRGBToNormal( texture2D( tNormal, screenPosition ).xyz ); } float scaleDividedByCameraFar; float minResolutionMultipliedByCameraFar; float getOcclusion( const in vec3 centerViewPosition, const in vec3 centerViewNormal, const in vec3 sampleViewPosition ) { vec3 viewDelta = sampleViewPosition - centerViewPosition; float viewDistance = length( viewDelta ); float scaledScreenDistance = scaleDividedByCameraFar * viewDistance; return max(0.0, (dot(centerViewNormal, viewDelta) - minResolutionMultipliedByCameraFar) / scaledScreenDistance - bias) / (1.0 + pow2( scaledScreenDistance ) ); } // moving costly divides into consts const float ANGLE_STEP = PI2 * float( NUM_RINGS ) / float( NUM_SAMPLES ); const float INV_NUM_SAMPLES = 1.0 / float( NUM_SAMPLES ); float getAmbientOcclusion( const in vec3 centerViewPosition ) { // precompute some variables require in getOcclusion. scaleDividedByCameraFar = scale / cameraFar; minResolutionMultipliedByCameraFar = minResolution * cameraFar; vec3 centerViewNormal = getViewNormal( centerViewPosition, vUv ); // jsfiddle that shows sample pattern: https://jsfiddle.net/a16ff1p7/ float angle = rand( vUv + randomSeed ) * PI2; vec2 radius = vec2( kernelRadius * INV_NUM_SAMPLES ) / size; vec2 radiusStep = radius; float occlusionSum = 0.0; float weightSum = 0.0; for( int i = 0; i < NUM_SAMPLES; i ++ ) { vec2 sampleUv = vUv + vec2( cos( angle ), sin( angle ) ) * radius; radius += radiusStep; angle += ANGLE_STEP; float sampleDepth = getDepth( sampleUv ); if( sampleDepth >= ( 1.0 - EPSILON ) ) { continue; } float sampleViewZ = getViewZ( sampleDepth ); vec3 sampleViewPosition = getViewPosition( sampleUv, sampleDepth, sampleViewZ ); occlusionSum += getOcclusion( centerViewPosition, centerViewNormal, sampleViewPosition ); weightSum += 1.0; } if( weightSum == 0.0 ) discard; return occlusionSum * ( intensity / weightSum ); } void main() { float centerDepth = getDepth( vUv ); if( centerDepth >= ( 1.0 - EPSILON ) ) { discard; } float centerViewZ = getViewZ( centerDepth ); vec3 viewPosition = getViewPosition( vUv, centerDepth, centerViewZ ); float ambientOcclusion = getAmbientOcclusion( viewPosition ); gl_FragColor = getDefaultColor( vUv ); gl_FragColor.xyz *= 1.0 - ambientOcclusion; }` }; export { SAOShader };