Last active
January 5, 2024 20:35
-
-
Save PhoenixIllusion/3c488c4e626d98af75f306edbcc07ec4 to your computer and use it in GitHub Desktop.
Stereogram Shader with configurable Pattern, Depth, and values
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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