Published
- 2 min read
WebGL
Since WebGL is a Web API. A self contained HTML file is suffice to demo it.
Here’s the HTML file
The content is like this:
<html lang="id">
<head>
<style>
#canvas {
width: 100%;
height: 100vw;
}
</style>
<title>Triangle Demo</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="text/javascript">
const gl = canvas.getContext('webgl');
function shaderLoader(gl, type, sourceCode) {
const shader = gl.createShader(type);
gl.shaderSource(shader, sourceCode);
gl.compileShader(shader);
// check nasty compilation warning
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(`An error occurred compiling the shaders ${type}: ${gl.getShaderInfoLog(shader)}`);
gl.deleteShader(shader);
return null;
}
return shader;
}
// origin 0,0
const triangleVertices = {
RED: {
x: 0.75/Math.sqrt(3), y: -0.25
},
GREEN: {
x: 0, y: 0.5
},
BLUE: {
x: -0.75/Math.sqrt(3), y: -0.25
}
}
const glProgram = ((gl) => {
const program = gl.createProgram();
const vertexShader = shaderLoader(gl, gl.VERTEX_SHADER, `
precision mediump float;
attribute vec2 a_position;
uniform float u_time;
varying vec2 v_texture;
void main() {
// texture based on original position
v_texture = a_position;
// rotate vertex around origin based on time
gl_Position.xy = a_position;
gl_Position.z = 0.0;
gl_Position.w = 1.0;
mat2 rotation = mat2(cos(u_time), sin(u_time), -sin(u_time), cos(u_time));
gl_Position.xy *= rotation;
}
`);
const fragmentShader = shaderLoader(gl, gl.FRAGMENT_SHADER, `
precision mediump float;
varying vec2 v_texture;
void main() {
// using dot product to interpolate radial colors
float red = dot(v_texture, vec2(${triangleVertices.RED.x}, ${triangleVertices.RED.y}));
float green = dot(v_texture, vec2(${triangleVertices.GREEN.x}, ${triangleVertices.GREEN.y}));
float blue = dot(v_texture, vec2(${triangleVertices.BLUE.x}, ${triangleVertices.BLUE.y}));
gl_FragColor = vec4(red, green, blue, 0.9);
}
`);
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
console.log(gl.getProgramInfoLog(program));
console.log('Program Linked')
return program;
})(gl)
const pipelineStructure = {
// pointer to program
program: glProgram,
// pointer to the buffers/vertex stream
vertexBuffer: ((gl) => {
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
triangleVertices.RED.x, triangleVertices.RED.y,
triangleVertices.GREEN.x, triangleVertices.GREEN.y,
triangleVertices.BLUE.x, triangleVertices.BLUE.y,
]), gl.STATIC_DRAW);
return vertexBuffer;
})(gl),
// pointer to a_position (cursor)
aPosition: ((gl) => {
const aPosition = gl.getAttribLocation(glProgram, 'a_position');
gl.enableVertexAttribArray(aPosition);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
return aPosition;
})(gl),
// pointer to time
uTime: ((gl) => {
const uTime = gl.getUniformLocation(glProgram, 'u_time');
return uTime;
})(gl)
}
function render(gl, pipeline, time) {
// scale the fps
tick = time * 0.001;
// reset background
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
{
// send position data
gl.bindBuffer(gl.ARRAY_BUFFER, pipeline.vertexBuffer);
gl.vertexAttribPointer(pipeline.aPosition, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(pipeline.aPosition);
}
gl.useProgram(pipeline.program);
{
// variate tick/time
gl.uniform1f(pipeline.uTime, tick);
}
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3);
}
function main() {
const renderLoop = (time) => {
render(gl, pipelineStructure, time);
requestAnimationFrame(renderLoop);
}
requestAnimationFrame(renderLoop);
}
main()
</script>
</body>
</html>