[READ-ONLY] Mirror of https://github.com/flo-bit/autostereogram-renderer. real time rendering of an autostereogram using the depth texture of a three.js scene flo-bit.github.io/autostereogram-renderer/
3d-rendering autostereogram javascript threejs
0

Configure Feed

Select the types of activity you want to include in your feed.

autostereogram-renderer / autostereogram.js
7.2 kB 255 lines
1import * as THREE from "https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js"; 2 3const postVertShader = ` 4varying vec2 vUv; 5void main() { 6 vUv = uv; 7 gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 8} 9`; 10 11const postFragShader = ` 12#include <packing> 13 14varying vec2 vUv; 15 16uniform sampler2D tOriginal; 17uniform sampler2D tDepth; 18uniform sampler2D tTile; 19 20uniform float u_camnear; 21uniform float u_camfar; 22 23uniform float u_showorig; 24uniform float u_showdepth; 25 26uniform vec2 u_res; 27 28uniform float tileSize; 29uniform float u_maxStep; 30 31float readDepth(sampler2D depthSampler, vec2 coord) { 32 float fragCoordZ = texture2D(depthSampler, coord).x; 33 float viewZ = perspectiveDepthToViewZ(fragCoordZ, u_camnear, u_camfar); 34 return 1.0 - viewZToOrthographicDepth(viewZ, u_camnear, u_camfar); 35} 36void main() { 37 float depth = readDepth(tDepth, vUv); 38 39 float maxStep = tileSize * u_maxStep; 40 float d = 0.; 41 42 vec2 uv = vUv * u_res; 43 for(int count = 0; count < 100; count++) { 44 if(uv.x < tileSize) break; 45 46 float d = readDepth(tDepth, uv / u_res); 47 uv.x -= tileSize - (d * maxStep); 48 } 49 float x = mod(uv.x, tileSize) / tileSize; 50 float y = mod(uv.y, tileSize) / tileSize; 51 52 vec3 stereogram_color = texture2D(tTile, vec2(x,y)).rgb; 53 vec3 depth_color = vec3(readDepth(tDepth, vUv)); 54 vec3 orig_color = texture2D(tOriginal, vUv).rgb; 55 56 gl_FragColor = vec4(mix(stereogram_color, mix(depth_color, orig_color, u_showorig), u_showdepth), 1.0); 57} 58`; 59 60export class AutostereogramRenderer { 61 constructor(renderer, camera, scene, opts) { 62 opts = opts || {}; 63 if (!renderer.extensions.get("WEBGL_depth_texture")) { 64 console.error("FATAL ERROR: depth_texture extension not available!"); 65 return undefined; 66 } 67 this.renderer = renderer; 68 this.camera = camera; 69 this.scene = scene; 70 71 this.tileSize = opts.tileSize || 32; 72 this.tileCount = opts.tileCount || 20; 73 this.tiles = opts.tiles; 74 this.palette = opts.palette; 75 76 this.createTiles(); 77 this.setup(); 78 } 79 80 setup() { 81 // create a render target for the main scene 82 if (this.target) this.target.dispose(); 83 84 this.target = new THREE.WebGLRenderTarget( 85 window.innerWidth, 86 window.innerHeight 87 ); 88 this.target.texture.format = THREE.RGBFormat; 89 this.target.texture.minFilter = THREE.NearestFilter; 90 this.target.texture.magFilter = THREE.NearestFilter; 91 this.target.texture.generateMipmaps = false; 92 this.target.stencilBuffer = false; 93 this.target.depthBuffer = true; 94 this.target.depthTexture = new THREE.DepthTexture(); 95 this.target.depthTexture.format = THREE.DepthFormat; 96 this.target.depthTexture.type = THREE.UnsignedShortType; 97 98 // create an extra scene for postprocessing with a 99 // quad showing depth texture of the main scene 100 this.postCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); 101 this.postMaterial = new THREE.ShaderMaterial({ 102 vertexShader: postVertShader.trim(), 103 fragmentShader: postFragShader.trim(), 104 uniforms: { 105 u_camnear: { value: this.camera.near }, 106 u_camfar: { value: this.camera.far }, 107 u_res: { value: [window.innerWidth, window.innerHeight] }, 108 u_showorig: { value: 0 }, 109 u_showdepth: { value: 0 }, 110 u_maxStep: { value: 0.3 }, 111 tileSize: { value: 100 }, 112 tTile: { value: null }, 113 tDepth: { value: null }, 114 tOriginal: { value: null }, 115 }, 116 }); 117 this.postScene = new THREE.Scene(); 118 var postQuad = new THREE.Mesh( 119 new THREE.PlaneBufferGeometry(2, 2), 120 this.postMaterial 121 ); 122 this.postScene.add(postQuad); 123 124 if (this.shown) this.show(this.shown); 125 } 126 127 render() { 128 // render depth into depthTexture 129 this.renderer.setRenderTarget(this.target); 130 this.renderer.render(this.scene, this.camera); 131 132 this.postMaterial.uniforms.tDepth.value = this.target.depthTexture; 133 this.postMaterial.uniforms.tOriginal.value = this.target.texture; 134 this.postMaterial.uniforms.tTile.value = this.randomTile(); 135 136 // render result into screen 137 this.renderer.setRenderTarget(null); 138 this.renderer.render(this.postScene, this.postCamera); 139 } 140 141 createTiles() { 142 if (this.tiles) return; 143 144 if (!this.palette) { 145 this.palette = AutostereogramRenderer.createRandomPalette(3); 146 this.palette.push([0, 0, 0], [255, 255, 255]); 147 } 148 149 this.tiles = AutostereogramRenderer.createTexturesFromPalette( 150 this.tileCount, 151 this.tileSize, 152 this.palette 153 ); 154 } 155 randomTile() { 156 return this.tiles[Math.floor(Math.random() * this.tiles.length)]; 157 } 158 159 get tileScale() { 160 return this.postMaterial.uniforms.tileSize.value; 161 } 162 set tileScale(value) { 163 this.postMaterial.uniforms.tileSize.value = value; 164 } 165 166 get maxStep() { 167 return this.postMaterial.uniforms.u_maxStep.value; 168 } 169 set maxStep(value) { 170 this.postMaterial.uniforms.u_maxStep.value = value; 171 } 172 173 /** 174 * sets what to show in the postprocessing quad 175 * 176 * @param {string} option - valid values: "depth", "stereo", "original" 177 */ 178 show(option) { 179 this.shown = option; 180 181 if (option == "depth") { 182 this.postMaterial.uniforms.u_showdepth.value = 1.0; 183 this.postMaterial.uniforms.u_showorig.value = 0.0; 184 } else if (option == "stereo") { 185 this.postMaterial.uniforms.u_showdepth.value = 0.0; 186 this.postMaterial.uniforms.u_showorig.value = 0.0; 187 } else if (option == "original") { 188 this.postMaterial.uniforms.u_showdepth.value = 1.0; 189 this.postMaterial.uniforms.u_showorig.value = 1.0; 190 } 191 } 192 showDepth() { 193 this.show("depth"); 194 } 195 showStereo() { 196 this.show("stereo"); 197 } 198 showOriginal() { 199 this.show("original"); 200 } 201 202 static createTextureFromPalette(texSize, palette) { 203 var data = new Uint8Array(3 * texSize * texSize); 204 205 for (var i = 0; i < 3 * texSize * texSize; i += 3) { 206 var ci = Math.floor(Math.random() * palette.length); 207 let c = palette[ci]; 208 data[i] = c[0]; 209 data[i + 1] = c[1]; 210 data[i + 2] = c[2]; 211 } 212 var tileTexture = new THREE.DataTexture( 213 data, 214 texSize, 215 texSize, 216 THREE.RGBFormat 217 ); 218 tileTexture.wrapS = THREE.RepeatWrapping; 219 tileTexture.wrapT = THREE.RepeatWrapping; 220 return tileTexture; 221 } 222 static createTexturesFromPalette(num, texSize, palette) { 223 let textures = []; 224 for (let j = 0; j < num; j++) { 225 textures.push( 226 AutostereogramRenderer.createTextureFromPalette(texSize, palette) 227 ); 228 } 229 return textures; 230 } 231 static createRandomPalette(num) { 232 let palette = []; 233 for (let i = 0; i < num; i++) { 234 var data = new Uint8Array(3); 235 data[0] = Math.random() * 256; 236 data[1] = Math.random() * 256; 237 data[2] = Math.random() * 256; 238 palette.push(data); 239 } 240 return palette; 241 } 242 243 /* 244 245 // this is not working TODO fix it 246 // current workaround is to call setup() after resize 247 248 changeSize(w, h) { 249 this.postMaterial.uniforms.u_res.values = [w, h]; 250 this.target.setSize(w, h); 251 this.postCamera.aspect = w / h; 252 this.postCamera.updateProjectionMatrix(); 253 } 254 */ 255}