[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
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}