Helpful documentation
- https://open.gl/
- This youtube series: Basics, Making a cube, applying transformations, Texturing
Vertices and Indices
Vertices are points with specific coordinates {x,y,z}
in a 3D-space. We can build any figure by connecting vertices in triangles with indices.
Shaders
Shaders are functions, written in C, which describe how to draw and color polygons to Graphic Card.
Vertex Shaders describe vertice positions, so Graphic Card can position them by connecting with indices and project to 2D canvas.
Fragment Shaders describe the way polygons should be colored by assigning colors to Vertices or by applying textures to polygons.
Shaders can have parameters passed from Javascript code (uniforms
, varyings
and attributes
). Fragment Shaders can also access data from Vertex Shaders (that ones called varyings
).
Program
Program, as far as I understand, is a scene, that's described with Vertices, Indices, specific Vertex Shaders and Fragment Shaders.
Applying transformations
The best way to change positions inside Vertex Shaders or color in Fragment Shaders is to pass parameters (also called uniforms
and varyings
).
Read about that at open.gl and at Vertex Shaders.
Source code with explanations
// render-a-cube.tsimport { createShader } from "./create-shader";import vxShader from "./vertex.glsl?raw";import fgShader from "./fragment.glsl?raw";const canvas = document.getElementyId('view');const ctx = canvas.getRenderingContext('webgl');// should be put inside requestAnimationFramedrawCube(ctx)(); function drawCube ( gl: WebGL2RenderingContext, width: number, height: number) { // Initializing viewport gl.viewport(0, width, height); gl.clearColor(0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); const prg = gl.createProgram(); if (!prg) { throw new Error("Can't init programm"); } // Setting up VERTEX and FRAGMENT shaders const vx = createShader(gl, vxShader, gl.VERTEX_SHADER); gl.attachShader(prg, vx); const fx = createShader(gl, fgShader, gl.FRAGMENT_SHADER); gl.attachShader(prg, fx); gl.linkProgram(prg); if (!gl.getProgramParameter(prg, gl.LINK_STATUS)) { throw new Error("Could not initialise shaders"); } // Cube's vertices Array<[x,y,z]>, 8 items const vertices = [ -1, -1, -1, // 0 1, -1, -1, // 1 1, 1, -1, // 2 -1, 1, -1, // 3 -1, -1, 1, // 4 1, -1, 1, // 5 1, 1, 1, // 6 -1, 1, 1, // 7 ]; // indices, that form triangles, that form cube sides const indices = [ 2, 1, 0, // side 0 (first triangle) 0, 3, 2, // side 0 (second triangle) 0, 4, 7, // side 1 (first triangle) 7, 3, 0, // side 1 (second triangle) 0, 1, 5, // ... 5, 4, 0, 1, 2, 6, 6, 5, 1, 2, 3, 7, 7, 6, 2, 4, 5, 6, 6, 7, 4, ]; // createe a vertex buffer and bind vertices to it const squareVertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, null); // create a vertex buffer and bind indices to it const squareIndexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareIndexBuffer); gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW ); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); // initial drawing gl.clearColor(0.0, 0.0, 0.0); gl.enable(gl.DEPTH_TEST); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.viewport(0, width, height); // bind squareVertexBuffer as vertex positions buffer gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexBuffer); // send every 3 bytes from squareVertexBuffer as {x,y,z} for each verticle gl.vertexAttribPointer( gl.getAttribLocation(prg, "aVertexPosition"), 3, // 3 bytes-long gl.FLOAT, false, // don't normalize (int to float) 0, 0 ); // send vertice buffer as `aVertexPosition` attribute inside vertex shader gl.enableVertexAttribArray( gl.getAttribLocation(prg, "aVertexPosition") ); let i = 0; let speed = 0.01; // that's the main rendering callback return () => { gl.useProgram(prg); const scale = i * 0.25; // used for scaling inside Vertex Shader gl.uniform1f(gl.getUniformLocation(prg, "slide"), scale); // GL Screen is square, so we need to fix it's aspect ration gl.uniform1f(gl.getUniformLocation(prg, "aspect"), height / width); gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, squareIndexBuffer ); gl.drawElements( gl.TRIANGLES, indices gl.UNSIGNED_SHORT, 0, ); if (i > || 0) { speed = -speed; } i += speed; };};
Shader compiler
// create-shader.tsexport const createShader = ( gl: WebGL2RenderingContext, sourceCode: string, type: number, // gl.VERTEX_SHADER or gl.FRAGMENT_SHADER) => { const shader = gl.createShader(type); if (!shader) { throw new Error(`Can't init shader`); } gl.shaderSource(shader, sourceCode); 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;};
Vertex Shader Example
Read more at Vertex Shaders
// current vertice position {x,y,z,w}attribute vec4 aVertexPosition;// final vertice position with all transformations applied,// that will be passed to Fragment Shadervarying vec4 v_positionWithOffset;// Parameters passed from Javascript loopuniform float slide;uniform float aspect;void main(){ // float array of 4 elements, same as [slide,slide,slide,1] vec4 scale=vec4(vec3(slide),1); // float array of 4 elements, same as [aspect,1,1,1] vec4 aspectRatioFix=vec4(aspect,vec3(1)); // vertice position, multiplied with matrices of scale and aspect ratio gl_Position=aVertexPosition*scale*aspectRatioFix, // vertice offset, that will be passed to fragment shader v_positionWithOffset=gl_Position+vec4(1,1,1,1);}
Fragment Shader Example
Read more at Fragment Shaders.
precision highp float;// parameter from Vertex Shadervarying vec4 v_positionWithOffset;void main(void){ // color, attached to current verticle {r,g,b,alpha} // same a[ // v_positionWithOffset.x, // v_positionWithOffset.y, // v_positionWithOffset.z, // 1 // ] gl_FragColor=vec4(v_positionWithOffset.xyz,1);}