Skip to content

Commit 90945b5

Browse files
committed
feat: add webcam triangle grid demo
1 parent bebbb78 commit 90945b5

File tree

7 files changed

+221
-2
lines changed

7 files changed

+221
-2
lines changed

Diff for: README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ Most demos are using [GLea](lib/glea/), a minimalistic WebGL library written by
1313
* [Fractal](fractal/), using [Phenomenon](https://github.com/vaneenige/phenomenon/)
1414
* [Cube](cube/)
1515
* [Texture mapping](texture-mapping/)
16-
* [Access the webcam via webgl](webcam/)
16+
* [Access the webcam via webgl and apply distortions](webcam/)
17+
* [Webcam Triangle Grid](webcam-triangle-grid/)
1718
* [Raymarching](raymarching/)
1819
* [Infinite rocks (raymarching with shadows)](raymarching-shadows/)
1920

Diff for: webcam-triangle-grid/grid-geometry.mjs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/* More modelling in ASCII art :)
2+
3+
x--x--x
4+
| /| /|
5+
|/ |/ |
6+
x--x--x
7+
| /| /|
8+
|/ |/ |
9+
x--x--x
10+
*/
11+
12+
/**
13+
* Create 2D grid geometry
14+
*
15+
* @param delta {number} point distance, default 0.1
16+
* @returns {Array} flattened array of 2D coordinates
17+
*/
18+
export function grid(deltaX = 0.1, deltaY = 0.1, xMin = -1, yMin = -1, xMax = 1, yMax = 1) {
19+
const dimX = Math.round((xMax - xMin) / deltaX);
20+
const dimY = Math.round((yMax - yMin) / deltaY);
21+
const squares = Array(dimX * dimY).fill(0).map((_, idx) => {
22+
const col = idx % dimX;
23+
const row = (idx / dimX)|0;
24+
const x0 = xMin + deltaX * col;
25+
const y0 = yMin + deltaY * row;
26+
const x1 = x0 + deltaX;
27+
const y1 = y0 + deltaY;
28+
// return two triangles per square
29+
return [
30+
x0, y0, x1, y0, x0, y1,
31+
x0, y1, x1, y0, x1, y1
32+
];
33+
});
34+
return squares.flat();
35+
}

Diff for: webcam-triangle-grid/index.css

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
body {
2+
margin: 0;
3+
}
4+
5+
canvas {
6+
display: block;
7+
width: 100vw;
8+
height: 100vh;
9+
}
10+
11+
video {
12+
display: none;
13+
}

Diff for: webcam-triangle-grid/index.html

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
7+
<link rel="stylesheet" href="index.css">
8+
<title>Webcam controlled distance field</title>
9+
</head>
10+
<body>
11+
<video></video>
12+
<canvas></canvas>
13+
<script type="module" src="index.mjs"></script>
14+
</body>
15+
</html>

Diff for: webcam-triangle-grid/index.mjs

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { frag, vert } from './shaders.mjs';
2+
import { grid } from './grid-geometry.mjs';
3+
import GLea from '../lib/glea/glea.mjs';
4+
5+
let video = document.querySelector('video');
6+
let fallbackImage = null;
7+
let texture = null;
8+
9+
const mesh = grid(.05, .05);
10+
const numVertices = mesh.length / 2;
11+
12+
const glea = new GLea({
13+
glOptions: {
14+
preserveDrawingBuffer: true
15+
},
16+
shaders: [
17+
GLea.fragmentShader(frag),
18+
GLea.vertexShader(vert)
19+
],
20+
buffers: {
21+
'position': GLea.buffer(2, mesh),
22+
'direction': GLea.buffer(1, Array(numVertices).fill(0).map(_ => Math.random()))
23+
}
24+
}).create();
25+
26+
function loop(time) {
27+
const { gl } = glea;
28+
// Upload the image into the texture.
29+
// void gl.texImage2D(target, level, internalformat, format, type, HTMLVideoElement? pixels);
30+
// void gl.texSubImage2D(target, level, xoffset, yoffset, format, type, HTMLVideoElement? pixels);
31+
// gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage);
32+
33+
// the use of texSubImage2D vs texImage2D is faster
34+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video);
35+
glea.clear();
36+
glea.uni('width', glea.width);
37+
glea.uni('height', glea.height);
38+
glea.uni('time', time * .005);
39+
gl.drawArrays(gl.TRIANGLES, 0, numVertices);
40+
requestAnimationFrame(loop);
41+
}
42+
43+
function accessWebcam(video) {
44+
return new Promise((resolve, reject) => {
45+
const mediaConstraints = { audio: false, video: { width: 1280, height: 720 } };
46+
navigator.mediaDevices.getUserMedia(mediaConstraints).then(mediaStream => {
47+
video.srcObject = mediaStream;
48+
video.onloadedmetadata = (e) => {
49+
video.play();
50+
resolve(video);
51+
}
52+
}).catch(err => {
53+
reject(err);
54+
});
55+
});
56+
}
57+
58+
function loadImage(url) {
59+
return new Promise((resolve, reject) => {
60+
const img = new Image();
61+
img.crossOrigin = 'Anonymous';
62+
img.src = url;
63+
img.onload = () => {
64+
resolve(img);
65+
};
66+
img.onerror = () => {
67+
reject(img);
68+
};
69+
});
70+
}
71+
72+
async function setup() {
73+
const { gl } = glea;
74+
try {
75+
await accessWebcam(video);
76+
} catch (ex) {
77+
video = null;
78+
console.error(ex.message);
79+
}
80+
if (! video) {
81+
try {
82+
fallbackImage = await loadImage('https://placekitten.com/1280/720')
83+
} catch (ex) {
84+
console.error(ex.message);
85+
return false;
86+
}
87+
}
88+
texture = gl.createTexture();
89+
gl.bindTexture(gl.TEXTURE_2D, texture);
90+
91+
// Set the parameters so we can render any size image.
92+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
93+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
94+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
95+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
96+
97+
// Upload the image into the texture.
98+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage);
99+
100+
window.addEventListener('resize', () => {
101+
glea.resize();
102+
});
103+
loop(0);
104+
}
105+
106+
setup();
107+

Diff for: webcam-triangle-grid/shaders.mjs

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// for syntax highlighting (glsl-literal extension)
2+
const glsl = x => x;
3+
4+
export const vert = glsl`
5+
precision highp float;
6+
attribute vec2 position;
7+
attribute float direction;
8+
9+
uniform float time;
10+
uniform float width;
11+
uniform float height;
12+
uniform sampler2D image;
13+
14+
varying vec4 vTexColor;
15+
16+
const float PI = 3.1415926535;
17+
18+
vec4 invert(vec4 color) {
19+
return vec4(1.0 - color.x, 1.0 - color.y, 1.0 - color.z, 1.0);
20+
}
21+
22+
vec4 shuffleRB(vec4 color) {
23+
return vec4(color.z, color.y, color.x, 1.0);
24+
}
25+
26+
27+
void main() {
28+
vec2 randomVector = vec2(cos(direction * 2.0 * PI), sin(direction * 2.0 * PI)) * sin(time * .1) * .05;
29+
vec2 texCoords = (1.0 - position) * .5 + randomVector;
30+
vTexColor = shuffleRB(texture2D(image, texCoords));
31+
gl_Position = vec4(position, 0.0, 1.0);
32+
}
33+
`;
34+
35+
export const frag = glsl`
36+
precision highp float;
37+
38+
uniform float width;
39+
uniform float height;
40+
uniform float time;
41+
42+
varying vec4 vTexColor;
43+
44+
void main() {
45+
gl_FragColor = vTexColor;
46+
}
47+
`;

Diff for: webcam/index.mjs

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ window.addEventListener('resize', () => {
2727
function loop(time) {
2828
const { gl } = glea;
2929
// Upload the image into the texture.
30-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage);
30+
// gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage);
31+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video);
3132
glea.clear();
3233
glea.uni('width', glea.width);
3334
glea.uni('height', glea.height);

0 commit comments

Comments
 (0)