UNPKG

@vrame/xeokit-sdk

Version:

3D BIM IFC Viewer SDK for AEC engineering applications. Open Source JavaScript Toolkit based on pure WebGL for top performance, real-world coordinates and full double precision

384 lines (285 loc) 14.5 kB
import {Program} from "./../Program.js"; import {ArrayBuf} from "./../ArrayBuf.js"; import {math} from "../../math/math.js"; import {WEBGL_INFO} from "../../webglInfo.js"; const tempVec2 = math.vec2(); /** * SAO implementation inspired from previous SAO work in THREE.js by ludobaka / ludobaka.github.io and bhouston * @private */ class SAOOcclusionRenderer { constructor(scene) { this._scene = scene; this._numSamples = null; // The program this._program = null; this._programError = false; // Variable locations this._aPosition = null; this._aUV = null; this._uDepthTexture = "uDepthTexture"; this._uCameraNear = null; this._uCameraFar = null; this._uCameraProjectionMatrix = null; this._uCameraInverseProjectionMatrix = null; this._uScale = null; this._uIntensity = null; this._uBias = null; this._uKernelRadius = null; this._uMinResolution = null; this._uRandomSeed = null; // VBOs this._uvBuf = null; this._positionsBuf = null; this._indicesBuf = null; this.init(); } init() { this._rebuild = true; } render(depthRenderBuffer) { this._build(); if (this._programError) { return; } if (!this._getInverseProjectMat) { // HACK: scene.camera not defined until render time this._getInverseProjectMat = (() => { let projMatDirty = true; this._scene.camera.on("projMatrix", function () { projMatDirty = true; }); const inverseProjectMat = math.mat4(); return () => { if (projMatDirty) { math.inverseMat4(scene.camera.projMatrix, inverseProjectMat); } return inverseProjectMat; } })(); } const gl = this._scene.canvas.gl; const program = this._program; const scene = this._scene; const sao = scene.sao; const viewportWidth = gl.drawingBufferWidth; const viewportHeight = gl.drawingBufferHeight; const projectState = scene.camera.project._state; const near = projectState.near; const far = projectState.far; const projectionMatrix = projectState.matrix; const inverseProjectionMatrix = this._getInverseProjectMat(); const randomSeed = Math.random(); const perspective = (scene.camera.projection === "perspective"); tempVec2[0] = viewportWidth; tempVec2[1] = viewportHeight; gl.viewport(0, 0, viewportWidth, viewportHeight); gl.clearColor(0, 0, 0, 1); gl.disable(gl.DEPTH_TEST); gl.disable(gl.BLEND); gl.frontFace(gl.CCW); gl.clear(gl.COLOR_BUFFER_BIT); program.bind(); gl.uniform1f(this._uCameraNear, near); gl.uniform1f(this._uCameraFar, far); gl.uniformMatrix4fv(this._uCameraProjectionMatrix, false, projectionMatrix); gl.uniformMatrix4fv(this._uCameraInverseProjectionMatrix, false, inverseProjectionMatrix); gl.uniform1i(this._uPerspective, perspective); gl.uniform1f(this._uScale, sao.scale * (far / 5)); gl.uniform1f(this._uIntensity, sao.intensity); gl.uniform1f(this._uBias, sao.bias); gl.uniform1f(this._uKernelRadius, sao.kernelRadius); gl.uniform1f(this._uMinResolution, sao.minResolution); gl.uniform2fv(this._uViewport, tempVec2); gl.uniform1f(this._uRandomSeed, randomSeed); const depthTexture = depthRenderBuffer.getDepthTexture(); program.bindTexture(this._uDepthTexture, depthTexture, 0); this._aUV.bindArrayBuffer(this._uvBuf); this._aPosition.bindArrayBuffer(this._positionsBuf); this._indicesBuf.bind(); gl.drawElements(gl.TRIANGLES, this._indicesBuf.numItems, this._indicesBuf.itemType, 0); } _build() { let dirty = false; const sao = this._scene.sao; if (sao.numSamples !== this._numSamples) { this._numSamples = Math.floor(sao.numSamples); dirty = true; } if (! (dirty || this._rebuild)) { return; } this._rebuild = false; const gl = this._scene.canvas.gl; if (this._program) { this._program.destroy(); this._program = null; } this._program = new Program(gl, { vertex: [`#version 300 es precision highp float; precision highp int; in vec3 aPosition; in vec2 aUV; out vec2 vUV; void main () { gl_Position = vec4(aPosition, 1.0); vUV = aUV; }`], fragment: [ `#version 300 es precision highp float; precision highp int; #define NORMAL_TEXTURE 0 #define PI 3.14159265359 #define PI2 6.28318530718 #define EPSILON 1e-6 #define NUM_SAMPLES ${this._numSamples} #define NUM_RINGS 4 in vec2 vUV; uniform sampler2D uDepthTexture; uniform float uCameraNear; uniform float uCameraFar; uniform mat4 uProjectMatrix; uniform mat4 uInverseProjectMatrix; uniform bool uPerspective; uniform float uScale; uniform float uIntensity; uniform float uBias; uniform float uKernelRadius; uniform float uMinResolution; uniform vec2 uViewport; uniform float uRandomSeed; float pow2( const in float x ) { return x*x; } highp float rand( const in vec2 uv ) { const highp float a = 12.9898, b = 78.233, c = 43758.5453; highp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI ); return fract(sin(sn) * c); } vec3 packNormalToRGB( const in vec3 normal ) { return normalize( normal ) * 0.5 + 0.5; } vec3 unpackRGBToNormal( const in vec3 rgb ) { return 2.0 * rgb.xyz - 1.0; } const float packUpscale = 256. / 255.; const float unpackDownScale = 255. / 256.; const vec3 packFactors = vec3( 256. * 256. * 256., 256. * 256., 256. ); const vec4 unPackFactors = unpackDownScale / vec4( packFactors, 1. ); const float shiftRights = 1. / 256.; vec4 packFloatToRGBA( const in float v ) { vec4 r = vec4( fract( v * packFactors ), v ); r.yzw -= r.xyz * shiftRights; return r * packUpscale; } float unpackRGBAToFloat( const in vec4 v ) { return dot( floor( v * 255.0 + 0.5 ) / 255.0, unPackFactors ); } float perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) { return ( near * far ) / ( ( far - near ) * invClipZ - far ); } float orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) { return linearClipZ * ( near - far ) - near; } float getDepth( const in vec2 screenPosition ) { return vec4(texture(uDepthTexture, screenPosition)).r; } float getViewZ( const in float depth ) { if (uPerspective) { return perspectiveDepthToViewZ( depth, uCameraNear, uCameraFar ); } else { return orthographicDepthToViewZ( depth, uCameraNear, uCameraFar ); } } vec3 getViewPos( const in vec2 screenPos, const in float depth, const in float viewZ ) { float clipW = uProjectMatrix[2][3] * viewZ + uProjectMatrix[3][3]; vec4 clipPosition = vec4( ( vec3( screenPos, depth ) - 0.5 ) * 2.0, 1.0 ); clipPosition *= clipW; return ( uInverseProjectMatrix * clipPosition ).xyz; } vec3 getViewNormal( const in vec3 viewPosition, const in vec2 screenPos ) { return normalize( cross( dFdx( viewPosition ), dFdy( viewPosition ) ) ); } 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 - uBias) / (1.0 + pow2( scaledScreenDistance ) ); } 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 ) { scaleDividedByCameraFar = uScale / uCameraFar; minResolutionMultipliedByCameraFar = uMinResolution * uCameraFar; vec3 centerViewNormal = getViewNormal( centerViewPosition, vUV ); float angle = rand( vUV + uRandomSeed ) * PI2; vec2 radius = vec2( uKernelRadius * INV_NUM_SAMPLES ) / uViewport; 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 = getViewPos( sampleUv, sampleDepth, sampleViewZ ); occlusionSum += getOcclusion( centerViewPosition, centerViewNormal, sampleViewPosition ); weightSum += 1.0; } if( weightSum == 0.0 ) discard; return occlusionSum * ( uIntensity / weightSum ); } out vec4 outColor; void main() { float centerDepth = getDepth( vUV ); if( centerDepth >= ( 1.0 - EPSILON ) ) { discard; } float centerViewZ = getViewZ( centerDepth ); vec3 viewPosition = getViewPos( vUV, centerDepth, centerViewZ ); float ambientOcclusion = getAmbientOcclusion( viewPosition ); outColor = packFloatToRGBA( 1.0- ambientOcclusion ); }`] }); if (this._program.errors) { console.error(this._program.errors.join("\n")); this._programError = true; return; } const uv = new Float32Array([1, 1, 0, 1, 0, 0, 1, 0]); const positions = new Float32Array([1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0]); // Mitigation: if Uint8Array is used, the geometry is corrupted on OSX when using Chrome with data-textures const indices = new Uint32Array([0, 1, 2, 0, 2, 3]); this._positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, positions, positions.length, 3, gl.STATIC_DRAW); this._uvBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, uv, uv.length, 2, gl.STATIC_DRAW); this._indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, indices, indices.length, 1, gl.STATIC_DRAW); this._program.bind(); this._uCameraNear = this._program.getLocation("uCameraNear"); this._uCameraFar = this._program.getLocation("uCameraFar"); this._uCameraProjectionMatrix = this._program.getLocation("uProjectMatrix"); this._uCameraInverseProjectionMatrix = this._program.getLocation("uInverseProjectMatrix"); this._uPerspective = this._program.getLocation("uPerspective"); this._uScale = this._program.getLocation("uScale"); this._uIntensity = this._program.getLocation("uIntensity"); this._uBias = this._program.getLocation("uBias"); this._uKernelRadius = this._program.getLocation("uKernelRadius"); this._uMinResolution = this._program.getLocation("uMinResolution"); this._uViewport = this._program.getLocation("uViewport"); this._uRandomSeed = this._program.getLocation("uRandomSeed"); this._aPosition = this._program.getAttribute("aPosition"); this._aUV = this._program.getAttribute("aUV"); this._dirty = false; } destroy() { if (this._program) { this._program.destroy(); this._program = null; } } } export {SAOOcclusionRenderer};