337 lines
8.7 KiB
HTML
337 lines
8.7 KiB
HTML
<!DOCTYPE html>
|
|
<title>noe pfp generator</title>
|
|
<link rel="preconnect" href="https://fonts.bunny.net" />
|
|
<link
|
|
href="https://fonts.bunny.net/css?family=atkinson-hyperlegible:400,400i,700,700i"
|
|
rel="stylesheet"
|
|
/>
|
|
<script
|
|
src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/3.4.2/gl-matrix-min.js"
|
|
referrerpolicy="no-referrer"
|
|
></script>
|
|
<style>
|
|
:root {
|
|
font-family: "Atkinson Hyperlegible", sans-serif;
|
|
background-color: black;
|
|
color: #efefef;
|
|
}
|
|
|
|
a {
|
|
color: gold;
|
|
}
|
|
|
|
section {
|
|
padding: 2em;
|
|
}
|
|
|
|
#error {
|
|
color: pink;
|
|
}
|
|
|
|
#draw {
|
|
border: 3px solid grey;
|
|
}
|
|
|
|
.num {
|
|
width: 4em;
|
|
}
|
|
</style>
|
|
|
|
<main>
|
|
<p id="error"></p>
|
|
<noscript>this tool uses webgl, so js must be enabled. sorry!</noscript>
|
|
<section>
|
|
albedo <input type="file" id="albedo" accept="image/*" /><br />
|
|
x power
|
|
<input type="number" class="num" id="x-pow" value="1" step="0.1" /> y power
|
|
<input type="number" class="num" id="y-pow" value="1" step="0.1" /><br />
|
|
<button id="run-frame">Run Frame</button>
|
|
</section>
|
|
<section>
|
|
<canvas width="256" height="256" id="draw" />
|
|
</section>
|
|
<section>
|
|
<p>This tool is what it uses to generate its profile pictures.</p>
|
|
<p>
|
|
In short, it takes the "albedo" texture that you upload, applies the "<a
|
|
href="/-~-/pfp/noegram.png"
|
|
>noegram </a
|
|
>" pattern over the top, where the overlay switches to a random UV offset
|
|
per line, causing a glitch-like effect.
|
|
</p>
|
|
<p>
|
|
<img
|
|
src="/-~-/pfp/noegram.png"
|
|
id="noegram"
|
|
width="256"
|
|
height="256"
|
|
alt="noegram"
|
|
/>
|
|
</p>
|
|
</section>
|
|
</main>
|
|
|
|
<script type="text/x-vertex-shader" id="vertexShader">
|
|
in vec4 a_vertex;
|
|
in vec2 a_uv0;
|
|
|
|
uniform mat4 u_model_view;
|
|
uniform mat4 u_projection;
|
|
|
|
out vec2 uv0;
|
|
|
|
void main() {
|
|
gl_Position = u_projection * u_model_view * a_vertex;
|
|
uv0 = a_uv0;
|
|
}
|
|
</script>
|
|
<script type="text/x-fragment-shader" id="fragmentShader">
|
|
uniform vec2 u_noe_power;
|
|
uniform vec2 u_noise_offset;
|
|
|
|
uniform sampler2D u_texture_0; // albedo
|
|
uniform sampler2D u_texture_1; // noegram
|
|
uniform sampler2D u_texture_2; // noise
|
|
|
|
in vec2 uv0;
|
|
|
|
out vec4 fragColor;
|
|
|
|
vec2 sawtooth(vec2 inp) {
|
|
return 2.0 * (inp - floor(0.5 + inp));
|
|
}
|
|
|
|
void main() {
|
|
vec4 noegram = texture(u_texture_1, uv0);
|
|
vec4 albedo = texture(u_texture_0, uv0 / abs(sin(sawtooth(uv0) * noegram.x)));
|
|
|
|
fragColor = vec4(albedo.rgb, 1.0);
|
|
}
|
|
</script>
|
|
|
|
<script defer async>
|
|
(() => {
|
|
const { mat4, vec2 } = glMatrix;
|
|
|
|
const xPowEl = document.querySelector("#x-pow");
|
|
const yPowEl = document.querySelector("#y-pow");
|
|
|
|
const getPower = () => {
|
|
return [xPowEl.value, yPowEl.value];
|
|
};
|
|
|
|
const canvasEl = document.querySelector("#draw");
|
|
const albedoEl = document.querySelector("#albedo");
|
|
const renderEl = document.querySelector("#run-frame");
|
|
|
|
const gl = canvasEl.getContext("webgl2");
|
|
if (gl === null) {
|
|
document.querySelector("#error").innerHTML = "WebGL2 context failed.";
|
|
}
|
|
|
|
const makeTexture = async (blob, texture) => {
|
|
const image = await createImageBitmap(blob, {
|
|
imageOrientation: "flipY",
|
|
});
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
0,
|
|
gl.RGBA,
|
|
image.width,
|
|
image.height,
|
|
0,
|
|
gl.RGBA,
|
|
gl.UNSIGNED_BYTE,
|
|
image
|
|
);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); // configurable?
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
};
|
|
|
|
const albedoTex = gl.createTexture();
|
|
|
|
let noegramOk = false;
|
|
const noegramEl = document.querySelector("#noegram");
|
|
const noegramTex = gl.createTexture();
|
|
noegramEl.onload = async () => {
|
|
await makeTexture(noegramEl, noegramTex);
|
|
noegramOk = true;
|
|
};
|
|
|
|
gl.getExtension("EXT_texture_filter_anisotropic");
|
|
|
|
const vao = gl.createVertexArray();
|
|
gl.bindVertexArray(vao);
|
|
|
|
const projectionMatrix = mat4.create();
|
|
mat4.perspective(projectionMatrix, 45, 1, 0.1, 100);
|
|
|
|
const modelViewMatrix = mat4.create();
|
|
mat4.translate(modelViewMatrix, modelViewMatrix, [0, 0, -1.79]);
|
|
|
|
const clear = () => {
|
|
gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque
|
|
gl.clearDepth(1.0); // Clear everything
|
|
gl.enable(gl.DEPTH_TEST); // Enable depth testing
|
|
gl.depthFunc(gl.LEQUAL); // Near things obscure far things
|
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
};
|
|
|
|
const initBuffer = (array) => {
|
|
const buffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);
|
|
|
|
return buffer;
|
|
};
|
|
|
|
// Plane
|
|
const vertexPositions = initBuffer(
|
|
new Float32Array([-1.0, -1.0, +1.0, -1.0, -1.0, +1.0, +1.0, +1.0])
|
|
);
|
|
const textureCoords = initBuffer(
|
|
new Float32Array([0, 0, 1, 0, 0, 1, 1, 1])
|
|
);
|
|
|
|
// Shaders
|
|
const program = gl.createProgram();
|
|
|
|
const addShader = (type, source) => {
|
|
const shader = gl.createShader(type);
|
|
gl.shaderSource(shader, source);
|
|
gl.compileShader(shader);
|
|
|
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
throw new Error(
|
|
"An error occurred compiling the shaders: " +
|
|
gl.getShaderInfoLog(shader)
|
|
);
|
|
}
|
|
|
|
gl.attachShader(program, shader);
|
|
};
|
|
|
|
addShader(
|
|
gl.VERTEX_SHADER,
|
|
`#version 300 es\nprecision highp float;\n${
|
|
document.querySelector("#vertexShader").innerHTML
|
|
}`
|
|
);
|
|
addShader(
|
|
gl.FRAGMENT_SHADER,
|
|
`#version 300 es\nprecision highp float;\n${
|
|
document.querySelector("#fragmentShader").innerHTML
|
|
}`
|
|
);
|
|
|
|
gl.linkProgram(program);
|
|
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
throw new Error(
|
|
"Unable to initialize the shader program: " +
|
|
gl.getProgramInfoLog(program)
|
|
);
|
|
}
|
|
|
|
const bindAttrib = (buffer, attribute) => {
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
gl.vertexAttribPointer(
|
|
gl.getAttribLocation(program, attribute),
|
|
2,
|
|
gl.FLOAT,
|
|
false,
|
|
0,
|
|
0
|
|
);
|
|
gl.enableVertexAttribArray(gl.getAttribLocation(program, attribute));
|
|
};
|
|
|
|
bindAttrib(vertexPositions, "a_vertex");
|
|
bindAttrib(textureCoords, "a_uv0");
|
|
|
|
const renderFrame = () => {
|
|
clear();
|
|
|
|
gl.bindVertexArray(vao);
|
|
|
|
// Shader activation
|
|
gl.useProgram(program);
|
|
gl.uniformMatrix4fv(
|
|
gl.getUniformLocation(program, "u_model_view"),
|
|
false,
|
|
modelViewMatrix
|
|
);
|
|
gl.uniformMatrix4fv(
|
|
gl.getUniformLocation(program, "u_projection"),
|
|
false,
|
|
projectionMatrix
|
|
);
|
|
|
|
// Verts and UVs
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositions);
|
|
gl.vertexAttribPointer(
|
|
gl.getAttribLocation(program, "a_vertex"),
|
|
2,
|
|
gl.FLOAT,
|
|
false,
|
|
0,
|
|
0
|
|
);
|
|
gl.enableVertexAttribArray(gl.getAttribLocation(program, "a_vertex"));
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, textureCoords);
|
|
gl.vertexAttribPointer(
|
|
gl.getAttribLocation(program, "a_uv0"),
|
|
2,
|
|
gl.FLOAT,
|
|
false,
|
|
0,
|
|
0
|
|
);
|
|
gl.enableVertexAttribArray(gl.getAttribLocation(program, "a_uv0"));
|
|
|
|
// Textures
|
|
gl.activeTexture(gl.TEXTURE0);
|
|
gl.bindTexture(gl.TEXTURE_2D, albedoTex);
|
|
gl.uniform1i(gl.getUniformLocation(program, "u_texture_0"), 0);
|
|
|
|
gl.activeTexture(gl.TEXTURE1);
|
|
gl.bindTexture(gl.TEXTURE_2D, noegramTex);
|
|
gl.uniform1i(gl.getUniformLocation(program, "u_texture_1"), 1);
|
|
|
|
gl.activeTexture(gl.TEXTURE2);
|
|
gl.bindTexture(gl.TEXTURE_2D, noegramTex); // TODO: replace with noise
|
|
gl.uniform1i(gl.getUniformLocation(program, "u_texture_2"), 2);
|
|
|
|
// draw!
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositions);
|
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
|
|
const err = gl.getError();
|
|
if (err !== 0) {
|
|
console.log({ err });
|
|
throw new Error(`webgl failure: ${err}`);
|
|
}
|
|
};
|
|
|
|
renderEl.addEventListener("click", async () => {
|
|
if (albedoEl.files.length === 0) {
|
|
console.warn("cannot render, albedo not loaded yet");
|
|
return;
|
|
}
|
|
|
|
await makeTexture(albedoEl.files[0], albedoTex);
|
|
|
|
if (!noegramOk) {
|
|
console.warn("cannot render, noegram.png not loaded yet");
|
|
return;
|
|
}
|
|
|
|
console.log("running frame");
|
|
renderFrame();
|
|
});
|
|
})();
|
|
</script>
|