From 8312f16e062de35d3302597152463e24c91a59ec Mon Sep 17 00:00:00 2001 From: JAE JOON LEE Date: Wed, 29 Mar 2023 13:38:58 +0900 Subject: [PATCH] Improve depth(for picking) bits precision --- src/framebuffer.ts | 47 ++++++++++++++++++++++++------------- src/shaders/depth-reader.ts | 38 ++++++++++++++++++++++++++---- src/viewer.ts | 4 +++- 3 files changed, 68 insertions(+), 21 deletions(-) diff --git a/src/framebuffer.ts b/src/framebuffer.ts index d0d5318a1..039de8402 100644 --- a/src/framebuffer.ts +++ b/src/framebuffer.ts @@ -3,6 +3,7 @@ import { vec3 } from "gl-matrix"; export class Framebuffer { + public static glContextStencilBits = 0; //class static variable public framebuffer: WebGLFramebuffer; public renderbuffer: WebGLRenderbuffer; public texture: WebGLTexture; @@ -70,10 +71,17 @@ export class Framebuffer { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - if (this._glVersion === 1) { + + //If the stencil bit is 8 bits or more, set to record the depth texture with DEPTH24_STENCIL8 if available (24+4 expands to a total of 32 bits to record depth => needs confirmation) + if(Framebuffer.glContextStencilBits >= 8) { + //32bit(24+8) precision + gl.texImage2D(gl.TEXTURE_2D, 0, gl["DEPTH24_STENCIL8"], width, height, 0, gl.DEPTH_STENCIL, gl["UNSIGNED_INT_24_8"], null); + } else if (this._glVersion === 1) { + //8bit precision gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); } else { - gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT16, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + //16bit precision + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT16, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null); } } @@ -109,6 +117,23 @@ export class Framebuffer { return result; } + public getPixelAsFloat(x: number, y: number): number { + if (!this.isReady) + return null; + var result = new Uint8Array(4); + this.gl.readPixels(x, y, 1, 1, this.gl.RGBA, this.gl.UNSIGNED_BYTE, result); + return this.decodeFloatFromRGBA(result); + } + + public decodeFloatFromRGBA(rgba : Uint8Array):number { + const rightVec4 = [1.0, 1/255.0, 1/65025.0, 1/16581375.0]; + const dot = (rgba[0] / 255.0) * rightVec4[0] + + (rgba[1] / 255.0) * rightVec4[1] + + (rgba[2] / 255.0) * rightVec4[2] + + (rgba[3] / 255.0) * rightVec4[3]; + return dot; + } + public getDepth(x: number, y: number): number { if (!this.isReady) return null; @@ -135,15 +160,12 @@ export class Framebuffer { const depths = this.getDepths(points); return depths.map((depth, i) => { - if (depth === 255) { // infinity (= nothing, no value) - return null; - } // convert values to clip space where x and y are [-1, 1] and z is [0, 1] const point = points[i]; const xc = point.x / this.width * 2.0 - 1.0; const yc = point.y / this.height * 2.0 - 1.0; - const zc = (depth / 255.0 - 0.5) * 2.0; + const zc = (depth-0.5) * 2.0; return vec3.fromValues(xc, yc, zc); }); @@ -177,20 +199,13 @@ export class Framebuffer { } const depth = this.getDepth(x, y); - if (depth === 255) { // infinity - return null; - } // convert values to clip space where x and y are [-1, 1] and z is [0, 1] const xc = x / this.width * 2.0 - 1.0; const yc = y / this.height * 2.0 - 1.0; - const zc = (depth / 255.0 - 0.5) * 2.0; - - const depthNear = Math.max(depth - 1, 0); - const zcn = (depthNear / 255.0 - 0.5) * 2.0; - - const depthFar = Math.min(depth + 1, 255); - const zcf = (depthFar / 255.0 - 0.5) * 2.0; + const zc = 0.5; + const zcn = 0.0; + const zcf = 1.0; return { far: vec3.fromValues(xc, yc, zcf), diff --git a/src/shaders/depth-reader.ts b/src/shaders/depth-reader.ts index 386ed1bb5..4531f6394 100644 --- a/src/shaders/depth-reader.ts +++ b/src/shaders/depth-reader.ts @@ -41,7 +41,38 @@ export class DepthReader { //fragment shader this.fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); - let fsCompiled = compile(this.fragmentShader, depth_fragment_shader); + + const depth_fragment_shader_ws = ` +#ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; + precision highp int; +#else + precision mediump float; + precision mediump int; + #define highp mediump +#endif + +varying vec2 position; +uniform sampler2D texture; + +vec4 packDepth(float depth) +{ + // See Aras Pranckevičius' post Encoding Floats to RGBA + // http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/ + vec4 enc = vec4(1.0, 255.0, 65025.0, 16581375.0) * depth; + enc = fract(enc); + enc -= enc.yzww * vec4(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0, 0.0); + return enc; +} + +void main() { + float depth = texture2D(texture, position).x; + gl_FragColor = packDepth(texture2D(texture, position).r); + //[original code] gl_FragColor = vec4(depth, depth, depth, 1.0); +} +`; + + let fsCompiled = compile(this.fragmentShader, depth_fragment_shader_ws); //change for ws if (!fsCompiled) { throw new Error("Failed to compile depth reading fragment shader"); } @@ -92,9 +123,8 @@ export class DepthReader { gl.enableVertexAttribArray(this.vertAttrs); gl.vertexAttribPointer(this.vertAttrs, 2, gl.FLOAT, false, 0, 0); - gl.clearColor(0, 0, 0, 0); //zero colour for no-values - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); // draw the quad (2 triangles, 6 vertices) gl.drawArrays(gl.TRIANGLES, 0, 6); @@ -112,7 +142,7 @@ export class DepthReader { this.draw(tex); // all components should be the same (therefore just using [0]) - var depths = points.map(p => fb.getPixel(p.x, p.y)[0]) ; + var depths = points.map(p => fb.getPixelAsFloat(p.x, p.y)) ; // free resources fb.delete(); diff --git a/src/viewer.ts b/src/viewer.ts index f644893b0..b071cda41 100644 --- a/src/viewer.ts +++ b/src/viewer.ts @@ -376,7 +376,7 @@ export class Viewer { WebGLUtils.setupWebGL(this.canvas, (ctx, version) => { this.gl = ctx; this.glVersion = version; - }, { preserveDrawingBuffer: true }, (err) => { + }, { preserveDrawingBuffer: true, stencil: true}, (err) => { this.error(err); }); @@ -386,6 +386,8 @@ export class Viewer { return; } + let stencilBits = this.gl.getParameter(this.gl.STENCIL_BITS); + Framebuffer.glContextStencilBits = stencilBits; // keep reference to the function in case it gets into zone. For example Angular uses // NgZone forked from the root Zone to refresh data content. But we make heavy use of this