215 lines
4.9 KiB
JavaScript
215 lines
4.9 KiB
JavaScript
|
(function () {
|
||
|
const canvas = document.querySelector(".myCanvas");
|
||
|
const image = document.querySelector(".myPreview__image");
|
||
|
const gl = canvas.getContext("webgl", {
|
||
|
preserveDrawingBuffer: true
|
||
|
});
|
||
|
|
||
|
const plane = createPlane(gl);
|
||
|
const texture = createTexture(gl);
|
||
|
const shader = createImageShader(gl);
|
||
|
let matrix = [];
|
||
|
|
||
|
function update() {
|
||
|
gl.clearColor(255.0, 255.0, 255.0, 1.0);
|
||
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||
|
|
||
|
shader.use().setProjection(matrix).setTexture(texture);
|
||
|
plane.draw(shader);
|
||
|
}
|
||
|
|
||
|
function loadTexture() {
|
||
|
drawToTexure(gl, texture, image);
|
||
|
update();
|
||
|
}
|
||
|
|
||
|
function toMatrix(t) {
|
||
|
// prettier-ignore
|
||
|
return [
|
||
|
t[0], t[1], 0, t[2],
|
||
|
t[3], t[4], 0, t[5],
|
||
|
0, 0, 1, 0,
|
||
|
t[6], t[7], 0, t[8]
|
||
|
];
|
||
|
}
|
||
|
|
||
|
if (image.naturalWidth && image.naturalHeight) {
|
||
|
loadTexture();
|
||
|
} else {
|
||
|
image.addEventListener("load", () => {
|
||
|
loadTexture();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Event handlers
|
||
|
*/
|
||
|
|
||
|
document.addEventListener("myPointsChanged", (event) => {
|
||
|
const points = event.detail.reduce(
|
||
|
(result, { x, y }) => [...result, x / 150 - 1, 1 - y / 150],
|
||
|
[]
|
||
|
);
|
||
|
|
||
|
const transform = new PerspT([1, -1, -1, -1, -1, 1, 1, 1], points);
|
||
|
matrix = toMatrix(transform.coeffs);
|
||
|
|
||
|
update();
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Primitives
|
||
|
*/
|
||
|
|
||
|
function createPlane(gl) {
|
||
|
const positionBuffer = createBuffer(
|
||
|
gl,
|
||
|
[1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0]
|
||
|
);
|
||
|
|
||
|
return {
|
||
|
draw(shader) {
|
||
|
shader.bindPositionBuffer(positionBuffer);
|
||
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||
|
},
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function createImageShader(gl) {
|
||
|
const vertexSource = `
|
||
|
attribute vec4 aPosition;
|
||
|
|
||
|
uniform mat4 uProjection;
|
||
|
|
||
|
varying highp vec2 vTextureCoord;
|
||
|
|
||
|
void main() {
|
||
|
vTextureCoord = vec2((aPosition.x - 1.0) * 0.5, (aPosition.y - 1.0) * 0.5);
|
||
|
|
||
|
gl_Position = vec4(aPosition.x, aPosition.y, 0.0, 1.0) * uProjection;
|
||
|
}
|
||
|
`;
|
||
|
|
||
|
const fragmentSource = `
|
||
|
varying highp vec2 vTextureCoord;
|
||
|
|
||
|
uniform sampler2D uSampler;
|
||
|
|
||
|
void main() {
|
||
|
gl_FragColor = texture2D(uSampler, vTextureCoord);
|
||
|
}
|
||
|
`;
|
||
|
|
||
|
const program = createProgram(
|
||
|
gl,
|
||
|
createShader(gl, vertexSource, gl.VERTEX_SHADER),
|
||
|
createShader(gl, fragmentSource, gl.FRAGMENT_SHADER)
|
||
|
);
|
||
|
|
||
|
const positionAttribute = gl.getAttribLocation(program, "aPosition");
|
||
|
const projectionLocation = gl.getUniformLocation(program, "uProjection");
|
||
|
const samplerLocation = gl.getUniformLocation(program, "uSampler");
|
||
|
|
||
|
const instance = {
|
||
|
use() {
|
||
|
gl.useProgram(program);
|
||
|
gl.enableVertexAttribArray(positionAttribute);
|
||
|
return instance;
|
||
|
},
|
||
|
bindPositionBuffer(buffer) {
|
||
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||
|
gl.vertexAttribPointer(positionAttribute, 2, gl.FLOAT, false, 0, 0);
|
||
|
},
|
||
|
setProjection(value) {
|
||
|
gl.uniformMatrix4fv(projectionLocation, false, value);
|
||
|
return instance;
|
||
|
},
|
||
|
setTexture(value) {
|
||
|
gl.activeTexture(gl.TEXTURE0);
|
||
|
gl.bindTexture(gl.TEXTURE_2D, value);
|
||
|
gl.uniform1i(samplerLocation, 0);
|
||
|
return instance;
|
||
|
},
|
||
|
};
|
||
|
|
||
|
return instance;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* WebGL helpers
|
||
|
* https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial
|
||
|
*/
|
||
|
|
||
|
function createBuffer(gl, positions) {
|
||
|
const positionBuffer = gl.createBuffer();
|
||
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
|
||
|
|
||
|
return positionBuffer;
|
||
|
}
|
||
|
|
||
|
function createProgram(gl, vertexShader, fragmentShader) {
|
||
|
const program = gl.createProgram();
|
||
|
gl.attachShader(program, vertexShader);
|
||
|
gl.attachShader(program, fragmentShader);
|
||
|
gl.linkProgram(program);
|
||
|
|
||
|
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||
|
const info = gl.getProgramInfoLog(program);
|
||
|
throw `Could not compile WebGL program. \n\n${info}`;
|
||
|
}
|
||
|
|
||
|
return program;
|
||
|
}
|
||
|
|
||
|
function createShader(gl, source, type) {
|
||
|
const shader = gl.createShader(type);
|
||
|
gl.shaderSource(shader, source);
|
||
|
gl.compileShader(shader);
|
||
|
|
||
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||
|
const info = gl.getShaderInfoLog(shader);
|
||
|
throw `Could not compile WebGL program. \n\n${info}`;
|
||
|
}
|
||
|
|
||
|
return shader;
|
||
|
}
|
||
|
|
||
|
function createTexture(gl) {
|
||
|
const texture = gl.createTexture();
|
||
|
const pixel = new Uint8Array([0, 0, 255, 255]);
|
||
|
|
||
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||
|
gl.texImage2D(
|
||
|
gl.TEXTURE_2D,
|
||
|
0,
|
||
|
gl.RGBA,
|
||
|
1,
|
||
|
1,
|
||
|
0,
|
||
|
gl.RGBA,
|
||
|
gl.UNSIGNED_BYTE,
|
||
|
pixel
|
||
|
);
|
||
|
|
||
|
return texture;
|
||
|
}
|
||
|
|
||
|
function drawToTexure(gl, texture, source) {
|
||
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
|
||
|
|
||
|
if (isPowerOf2(source.naturalWidth) && isPowerOf2(source.naturalHeight)) {
|
||
|
gl.generateMipmap(gl.TEXTURE_2D);
|
||
|
} else {
|
||
|
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);
|
||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function isPowerOf2(value) {
|
||
|
return (value & (value - 1)) === 0;
|
||
|
}
|
||
|
})();
|