diff --git a/generators/work.html.template b/generators/work.html.template index 915b545..eae123a 100644 --- a/generators/work.html.template +++ b/generators/work.html.template @@ -2,20 +2,21 @@ com.mekanoe.art // ##name##
- +
- - \ No newline at end of file + + diff --git a/html/001-platform-provenance.html b/html/001-platform-provenance.html index a465086..d5949c6 100644 --- a/html/001-platform-provenance.html +++ b/html/001-platform-provenance.html @@ -2,20 +2,21 @@ com.mekanoe.art // 001-platform-provenance
- +
- - \ No newline at end of file + + diff --git a/html/001-platform-provenance.js b/html/001-platform-provenance.js index 1790fe9..0ec9e62 100644 --- a/html/001-platform-provenance.js +++ b/html/001-platform-provenance.js @@ -1,3 +1,57 @@ -main(() => { - console.log("Hello, world!"); +import { main } from "./platform.js"; +import { Shader } from "./shader.js"; +import { BasicPlane } from "./basic-plane.js"; + +main((gl, core) => { + const shader = new Shader(gl) + .attach( + gl.VERTEX_SHADER, + ` + attribute vec4 aVertexPosition; + attribute vec2 aTextureCoord; + + uniform mat4 uModelViewMatrix; + uniform mat4 uProjectionMatrix; + + varying highp vec2 vTextureCoord; + + void main() { + gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; + vTextureCoord = aTextureCoord; + } + ` + ) + .attach( + gl.FRAGMENT_SHADER, + ` + uniform lowp float uTime; + uniform lowp float uSinTime; + uniform lowp float uCosTime; + + varying highp vec2 vTextureCoord; + + void main() { + lowp float slowedSinTime = sin(uTime*0.001)*0.5+0.5; + + gl_FragColor = vec4(vTextureCoord, slowedSinTime, 1.0); + } + ` + ) + .link(); + + const plane = new BasicPlane(gl, core); + plane.attachShader(shader); + + const render = () => { + core.clear(); + plane.draw2D(); + + if (gl.getError() !== gl.NO_ERROR) { + throw new Error("WebGL error"); + } + + requestAnimationFrame(render); + }; + + requestAnimationFrame(render); }); diff --git a/html/basic-plane.html b/html/basic-plane.html new file mode 100644 index 0000000..560e4e9 --- /dev/null +++ b/html/basic-plane.html @@ -0,0 +1,22 @@ + + +com.mekanoe.art // basic-plane + + +
+ +
+ + diff --git a/html/basic-plane.js b/html/basic-plane.js new file mode 100644 index 0000000..1555156 --- /dev/null +++ b/html/basic-plane.js @@ -0,0 +1,62 @@ +export class BasicPlane { + constructor(gl, core) { + this.gl = gl; + this.core = core; + + this.vertexPositions = new Float32Array([ + -1.0, -1.0, +1.0, -1.0, -1.0, +1.0, +1.0, +1.0, + ]); + this.positionBuffer = this.initBuffer(this.vertexPositions); + this.textureBuffer = this.initBuffer( + new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]) + ); + } + + initBuffer(data, draw = this.gl.STATIC_DRAW) { + const buffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(data), draw); + + return buffer; + } + + attachShader(shader) { + this.shader = shader; + this.vertexPosition = shader.location("aVertexPosition"); + this.textureCoord = shader.location("aTextureCoord"); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); + this.gl.vertexAttribPointer( + this.vertexPosition, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + this.gl.enableVertexAttribArray(this.vertexPosition); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.textureBuffer); + this.gl.vertexAttribPointer( + this.textureCoord, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + this.gl.enableVertexAttribArray(this.textureCoord); + + return this; + } + + draw2D() { + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); + this.shader.activate(this.core.projectionMatrix, this.core.modelViewMatrix); + this.gl.drawArrays( + this.gl.TRIANGLE_STRIP, + 0, + this.vertexPositions.length / 2 + ); + } +} diff --git a/html/index.html b/html/index.html index de18cba..3c75588 100644 --- a/html/index.html +++ b/html/index.html @@ -51,7 +51,9 @@
diff --git a/html/platform.js b/html/platform.js index cf00263..aaf0d48 100644 --- a/html/platform.js +++ b/html/platform.js @@ -1,5 +1,11 @@ -function main(next) { +export const clear = (gl) => {}; + +export const main = (next) => { const canvas = document.querySelector("canvas"); + + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + // Initialize the GL context const gl = canvas.getContext("webgl"); @@ -11,10 +17,34 @@ function main(next) { return; } - // Set clear color to black, fully opaque - gl.clearColor(0.0, 0.0, 0.0, 1.0); - // Clear the color buffer with specified clear color - gl.clear(gl.COLOR_BUFFER_BIT); + const core = renderingCore(gl); + core.clear(); - next(); -} + next(gl, core); +}; + +const renderingCore = (gl) => { + const fieldOfView = (10 * Math.PI) / 180; // in radians + const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; + const zNear = 0.1; + const zFar = 100.0; + const projectionMatrix = glMatrix.mat4.create(); + glMatrix.mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar); + + const modelViewMatrix = glMatrix.mat4.create(); + glMatrix.mat4.translate(modelViewMatrix, modelViewMatrix, [-0.0, 0.0, -6.0]); + + return { + projectionMatrix, + modelViewMatrix, + 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); + }, + }; +}; + +export const glMatrix = window.glMatrix; diff --git a/html/shader.html b/html/shader.html new file mode 100644 index 0000000..026da81 --- /dev/null +++ b/html/shader.html @@ -0,0 +1,22 @@ + + +com.mekanoe.art // shader + + +
+ +
+ + diff --git a/html/shader.js b/html/shader.js new file mode 100644 index 0000000..0b945f5 --- /dev/null +++ b/html/shader.js @@ -0,0 +1,74 @@ +export class Shader { + constructor(gl) { + this.gl = gl; + this.program = gl.createProgram(); + this.startTime = Date.now(); + } + + attach(type, source) { + console.log("attaching shader", { type, source }); + const shader = this.gl.createShader(type); + + this.gl.shaderSource(shader, source); + this.gl.compileShader(shader); + + if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { + throw new Error( + "An error occurred compiling the shaders: " + + this.gl.getShaderInfoLog(shader) + ); + } + + this.gl.attachShader(this.program, shader); + + return this; + } + + link() { + this.gl.linkProgram(this.program); + + if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) { + throw new Error( + "Unable to initialize the shader program: " + + this.gl.getProgramInfoLog(this.program) + ); + } + + console.log("shader linked"); + + return this; + } + + location(name) { + if (name[0] === "a") { + return this.gl.getAttribLocation(this.program, name); + } else if (name[0] === "u") { + return this.gl.getUniformLocation(this.program, name); + } + } + + updateTime() { + const now = Date.now(); + const time = now - this.startTime; + const sinTime = Math.sin(time); + const cosTime = Math.cos(time); + this.gl.uniform1f(this.location("uTime"), time); + this.gl.uniform1f(this.location("uSinTime"), sinTime); + this.gl.uniform1f(this.location("uCosTime"), cosTime); + } + + activate(projectionMatrix, modelViewMatrix) { + this.gl.useProgram(this.program); + this.gl.uniformMatrix4fv( + this.location("uProjectionMatrix"), + false, + projectionMatrix + ); + this.gl.uniformMatrix4fv( + this.location("uModelViewMatrix"), + false, + modelViewMatrix + ); + this.updateTime(); + } +}