Skip to content

Instantly share code, notes, and snippets.

@kcinnu
Created December 5, 2023 21:55
Show Gist options
  • Save kcinnu/47e7d380dc483194a326d9ac8b44ea24 to your computer and use it in GitHub Desktop.
Save kcinnu/47e7d380dc483194a326d9ac8b44ea24 to your computer and use it in GitHub Desktop.
procedural binary tree pixelart
<!DOCTYPE html>
<html>
<head>
<style>
html {
background-color: black;
}
canvas {
image-rendering: pixelated;
}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script src="main.js"></script>
</body>
</html>
const c = document.getElementById("c");
const gl = c.getContext("webgl2", { antialias: false });
function createShader(type) {
return src => {
const shd = gl.createShader(type);
gl.shaderSource(shd, src);
gl.compileShader(shd);
if (gl.getShaderParameter(shd, gl.COMPILE_STATUS)) {
return shd;
}
throw new Error("couldnt compile shader:\n" + gl.getShaderInfoLog(shd));
};
}
const fragShd = createShader(gl.FRAGMENT_SHADER);
const vertShd = createShader(gl.VERTEX_SHADER);
function createProgram(unifs, attrs, vert, frag) {
const prog = gl.createProgram();
gl.attachShader(prog, vert);
gl.attachShader(prog, frag);
gl.linkProgram(prog);
if (gl.getProgramParameter(prog, gl.LINK_STATUS)) {
let obj = { prog, u: {}, a: {} };
for (const u of unifs.split(' ')) {
obj.u[u] = gl.getUniformLocation(prog, u);
}
for (const a of attrs.split(' ')) {
obj.a[a] = gl.getAttribLocation(prog, a);
}
return obj;
}
throw new Error("couldnt link shaders:\n" + gl.getProgramInfoLog(prog));
}
function add([ax, ay], [bx, by]) {
return [ax + bx, ay + by];
}
function sub([ax, ay], [bx, by]) {
return [ax - bx, ay - by];
}
function mul([ax, ay], [bx, by]) {
return [ax * bx, ay * by];
}
function div([ax, ay], [bx, by]) {
return [ax / bx, ay / by];
}
function scale([x, y], s) {
return [x * s, y * s];
}
function rot90([x, y]) {
return [-y, x];
}
function rot([x, y], a) {
const c = Math.cos(a);
const s = Math.sin(a);
return [x * c - y * s, y * c + x * s];
}
function round([x, y]) {
return [Math.round(x), Math.round(y)];
}
const d_tree = createProgram("adj", "pos col", vertShd`#version 300 es
precision highp float;
in vec2 pos;
in vec3 col;
out vec3 col_;
uniform vec4 adj;
void main() {
gl_Position = vec4(pos * adj.xy + adj.zw, 0, 1);
col_ = col;
}
`, fragShd`#version 300 es
precision highp float;
in vec3 col_;
out vec4 col;
void main() {
//col = vec4(.5,.3,0,1);
col = vec4(mix(col_, vec3(.5,.3,0), .7), 1);
}
`);
const d_stars = createProgram("", "pos uv bright", vertShd`#version 300 es
precision highp float;
in vec4 pos;
in vec2 uv;
out vec2 uv_;
in float bright;
out float bright_;
void main() {
gl_Position = pos;
uv_ = uv;
bright_ = bright;
}
`, fragShd`#version 300 es
precision highp float;
in vec2 uv_;
out vec4 col;
in float bright_;
void main() {
vec2 uv = abs(uv_);
uv = vec2(min(uv.x, uv.y), max(uv.x, uv.y));
if (uv.x > (1.-uv.y) * .5) discard;
col = vec2(bright_ * exp2(-uv.x*2.),0).xxxy;
}
`);
const d_sky = createProgram("", "pos", vertShd`#version 300 es
precision highp float;
in vec4 pos;
out vec2 uv;
void main() {
gl_Position = pos;
uv = pos.xy;
}
`, fragShd`#version 300 es
precision highp float;
in vec2 uv;
out vec4 col;
void main() {
float x = .5-uv.y*.5;
col = vec4(vec3(x*x*x*x*x*x*.9,x*x*.4,.3*x+.1)*.5, 1);
}
`);
c.width = 300;
c.height = 128 * 1.5;
c.style.width = c.width / devicePixelRatio * 3 + 'px';
c.style.height = c.height / devicePixelRatio * 3 + 'px';
let lines = [];
function cr_tree(lvl, pos, dir) {
const npos = add(pos, dir);
lines.push({
a: pos,
b: npos,
});
if (lvl === 10) return;
if (lvl <= 4 || Math.random() < .8) cr_tree(lvl + 1, npos, scale(rot(dir, 0.4 + (Math.random() - .5) * .7), .8));
if (lvl <= 4 || Math.random() < .8) cr_tree(lvl + 1, npos, scale(rot(dir, -0.4 + (Math.random() - .5) * .7), .8));
}
cr_tree(0, [0, 0], [0, 1]);
let stars = [];
for (let i = 0; i < 100; i++) {
stars.push({
pos: [Math.random() * 2 - 1, Math.random() * 2 - 1],
size: Math.random() * 3 + 1,
});
}
const star_pos = gl.createBuffer();
const star_uv = gl.createBuffer();
const star_br = gl.createBuffer();
{
const buf = new Float32Array(stars.length * 6 * 2);
for (let i = 0; i < stars.length; i++) {
const { pos, size } = stars[i];
const rsc = scale([2 / c.width, 2 / c.height], 1);
const pos2 = mul(add(round(div(pos, rsc)), [.5, .5]), rsc);
const offs = scale([2 / c.width, 2 / c.height], size);
const x = add(pos2, mul(offs, [-1, -1]));
const y = add(pos2, mul(offs, [1, -1]));
const z = add(pos2, mul(offs, [-1, 1]));
const w = add(pos2, mul(offs, [1, 1]));
buf.set([].concat(x, z, w, x, w, y), i * 12);
}
gl.bindBuffer(gl.ARRAY_BUFFER, star_pos);
gl.bufferData(gl.ARRAY_BUFFER, buf, gl.STATIC_DRAW);
for (let i = 0; i < stars.length; i++) {
const x = [-1, -1];
const y = [1, -1];
const z = [-1, 1];
const w = [1, 1];
buf.set([].concat(x, z, w, x, w, y), i * 12);
}
gl.bindBuffer(gl.ARRAY_BUFFER, star_uv);
gl.bufferData(gl.ARRAY_BUFFER, buf, gl.STATIC_DRAW);
for (let i = 0; i < stars.length; i++) {
const r = Math.random() * .5;
buf.set([r, r, r, r, r, r], i * 6);
}
gl.bindBuffer(gl.ARRAY_BUFFER, star_br);
gl.bufferData(gl.ARRAY_BUFFER, buf.slice(0, stars.length * 6), gl.STATIC_DRAW);
}
const tree_pos = gl.createBuffer();
const tree_col = gl.createBuffer();
{
const buf = new Float32Array(lines.length * 6 * 2);
for (let i = 0; i < lines.length; i++) {
const { a, b } = lines[i];
const x = sub(a, scale(rot90(sub(b, a)), .15));
const y = add(a, scale(rot90(sub(b, a)), .15));
const z = sub(b, scale(rot90(sub(b, a)), .15));
const w = add(b, scale(rot90(sub(b, a)), .15));
buf.set([].concat(x, z, w, x, w, y), i * 6 * 2);
}
gl.bindBuffer(gl.ARRAY_BUFFER, tree_pos);
gl.bufferData(gl.ARRAY_BUFFER, buf, gl.DYNAMIC_DRAW);
const cbuf = new Float32Array(lines.length * 6 * 3);
for (let i = 0; i < lines.length; i++) {
// const col = [Math.random(), Math.random(), Math.random()];
const tmp = Math.random();
const col = [tmp, tmp, tmp];
cbuf.set([].concat(col, col, col, col, col, col), i * 6 * 3);
}
gl.bindBuffer(gl.ARRAY_BUFFER, tree_col);
gl.bufferData(gl.ARRAY_BUFFER, cbuf, gl.DYNAMIC_DRAW);
}
const sky_pos = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, sky_pos);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 3, 3, -1]), gl.STATIC_DRAW);
gl.viewport(0, 0, c.width, c.height);
// gl.clearColor(0, 0, 0.2, 1);
// gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(d_sky.prog)
gl.enableVertexAttribArray(d_stars.a.pos);
gl.bindBuffer(gl.ARRAY_BUFFER, sky_pos);
gl.vertexAttribPointer(d_sky.a.pos, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.disableVertexAttribArray(d_sky.a.pos);
gl.useProgram(d_stars.prog);
gl.enableVertexAttribArray(d_stars.a.pos);
gl.bindBuffer(gl.ARRAY_BUFFER, star_pos);
gl.vertexAttribPointer(d_tree.a.pos, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(d_stars.a.uv);
gl.bindBuffer(gl.ARRAY_BUFFER, star_uv);
gl.vertexAttribPointer(d_stars.a.uv, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(d_stars.a.bright);
gl.bindBuffer(gl.ARRAY_BUFFER, star_br);
gl.vertexAttribPointer(d_stars.a.bright, 1, gl.FLOAT, false, 0, 0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.drawArrays(gl.TRIANGLES, 0, stars.length * 6);
gl.disable(gl.BLEND);
gl.disableVertexAttribArray(d_stars.a.pos);
gl.disableVertexAttribArray(d_stars.a.uv_);
gl.disableVertexAttribArray(d_stars.a.bright);
gl.useProgram(d_tree.prog);
gl.enableVertexAttribArray(d_tree.a.pos);
gl.bindBuffer(gl.ARRAY_BUFFER, tree_pos);
gl.vertexAttribPointer(d_tree.a.pos, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(d_tree.a.col);
gl.bindBuffer(gl.ARRAY_BUFFER, tree_col);
gl.vertexAttribPointer(d_tree.a.col, 3, gl.FLOAT, false, 0, 0);
gl.uniform4f(d_tree.u.adj, c.height / c.width * 2 / 5, 2 / 5, 0, -1);
gl.drawArrays(gl.TRIANGLES, 0, lines.length * 6);
gl.disableVertexAttribArray(d_tree.a.pos);
gl.disableVertexAttribArray(d_tree.a.col);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment