Skip to content

Instantly share code, notes, and snippets.

@PhoenixIllusion
Last active January 5, 2024 20:35
Show Gist options
  • Save PhoenixIllusion/3c488c4e626d98af75f306edbcc07ec4 to your computer and use it in GitHub Desktop.
Save PhoenixIllusion/3c488c4e626d98af75f306edbcc07ec4 to your computer and use it in GitHub Desktop.
Stereogram Shader with configurable Pattern, Depth, and values
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GLSL Stereogram</title>
<style>
canvas {
image-rendering: crisp-edges;
image-rendering: pixelated;
}
html,
body {
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
max-height: 100vh;
}
#source {
display: flex;
flex-direction: row;
}
#source>div {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
#source canvas {
border: 1px solid black;
height: 140px;
width: auto;
}
#canvas {
height: 0;
flex: 1;
width: auto;
border: 1px solid black;
}
</style>
</head>
<body>
<canvas id="canvas"="800" height="800"></canvas>
<div id="source">
<div class="col-source pattern" contenteditable>
<label>Pattern</label>
<canvas id="pattern" width="80" height="80"></canvas>
<div class="buttons">
<button class="open pattern">Open</button>
<button class="paste pattern">Paste</button>
</div>
</div>
<div class="col-source depth" contenteditable>
<label>Depth</label>
<canvas id="depth" width="80" height="80"></canvas>
<div class="buttons">
<button class="open depth">Open</button>
<button class="paste depth">Paste</button>
</div>
</div>
</div>
<input type="range" min="1" max="40" value="10" id="u_pixel_repeat" />
<input type="range" min="1" max="20" value="3" id="u_depth_scale" />
<input type="file" style="display: none" id="file-select-pattern"
accept="image/png, image/jpg, image/gif, image/webp, image/jpeg" />
<input type="file" style="display: none" id="file-select-depth"
accept="image/png, image/jpg, image/gif, image/webp, image/jpeg" />
<script type="module">
import * as TWGL from 'https://cdnjs.cloudflare.com/ajax/libs/twgl.js/5.5.3/twgl.js';
const filePattern = document.querySelector("#file-select-pattern")
const fileDepth = document.querySelector("#file-select-depth")
const canvasPattern = document.getElementById('pattern');
const canvasDepth = document.getElementById('depth');
const pixelRepeat = document.getElementById('u_pixel_repeat');
const depth = document.getElementById('u_depth_scale');
async function loadCanvas(file, type) {
if (!file) return;
const imageData = await createImageBitmap(file);
const canvas = type == 'pattern' ? canvasPattern : canvasDepth;
canvas.width = imageData.width; canvas.height = imageData.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(imageData, 0, 0);
twgl.setTextureFromElement(gl, textures[type], canvas)
}
async function pasteToCanvas(dest) {
try {
const clipboardItems = await navigator.clipboard.read();
for (const clipboardItem of clipboardItems) {
for (const type of clipboardItem.types) {
if (type.startsWith('image')) {
const blob = await clipboardItem.getType(type);
return loadCanvas(blob, dest);
}
}
}
} catch (err) {
console.error(err.name, err.message);
}
}
async function onPaste(event, dest) {
event.preventDefault();
if (event.clipboardData && event.clipboardData.items) {
for (const item of [...event.clipboardData.items]) {
if (item.type.startsWith('image')) {
return loadCanvas(item.getAsFile(), dest);
}
}
}
}
document.querySelector('div.col-source.pattern').addEventListener('paste', (e) => onPaste(e, 'pattern'));
document.querySelector('div.col-source.depth').addEventListener('paste', (e) => onPaste(e, 'depth'));
filePattern.addEventListener('change', () => { loadCanvas(filePattern.files[0], 'pattern') })
fileDepth.addEventListener('change', () => { loadCanvas(fileDepth.files[0], 'depth') })
document.querySelector('button.open.depth').addEventListener('click', () => fileDepth.click())
document.querySelector('button.open.pattern').addEventListener('click', () => filePattern.click())
document.querySelector('button.paste.depth').addEventListener('click', () => pasteToCanvas('depth'))
document.querySelector('button.paste.pattern').addEventListener('click', () => pasteToCanvas('pattern'))
const gl = document.querySelector("#canvas").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
const arrays = {
position: [-1, -1, 0, 1, -1, 0, -1, 1, 0, -1, 1, 0, 1, -1, 0, 1, 1, 0],
};
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
const textures = twgl.createTextures(gl, {
pattern: { src: canvasPattern },
depth: { src: canvasDepth }
});
function render(time) {
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const uniforms = {
u_depth: textures.depth,
u_pattern: textures.pattern,
u_pixel_repeat: parseFloat(pixelRepeat.value)/100,
u_depth_scale: parseFloat(depth.value)/100
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
</script>
<script id="vs" type="notjs">
attribute vec4 position;
varying vec4 v_pos;
void main() {
gl_Position = position;
v_pos = position * vec4(0.5,-0.5,0,0) + vec4(0.5,0.5,0,0);
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec4 v_pos;
uniform sampler2D u_depth;
uniform sampler2D u_pattern;
uniform float u_pixel_repeat;
uniform float u_depth_scale;
void main() {
float y = v_pos.y;
float x = v_pos.x;
// Loop logic from https://www.shadertoy.com/view/ldf3Dr
for(int i=0; i<64; i++)
{
// Step left u_pixel_repeat minus depth...
vec4 vDepth = texture2D(u_depth, vec2(x, y));
float fOffset = -u_pixel_repeat;
fOffset += vDepth.x * u_depth_scale;
x = x + fOffset;
// ...until we fall of the screen
if(x < 0.0)
{
break;
}
}
x = mod(x + u_pixel_repeat, u_pixel_repeat);
vec2 offset = (vec2(x,y) + 0.5) / u_pixel_repeat;
vec3 tex = texture2D(u_pattern, fract(offset)).xyz;
gl_FragColor = vec4(tex, 1.0);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment