diff --git a/content/Frontend/WebGL/Basics of WebGL (Drawing a Cube).md b/content/Frontend/WebGL/Basics of WebGL (Drawing a Cube).md new file mode 100644 index 0000000..23cb2af --- /dev/null +++ b/content/Frontend/WebGL/Basics of WebGL (Drawing a Cube).md @@ -0,0 +1,243 @@ +## Helpful documentation +- https://open.gl/ +- This youtube series: [Basics](https://www.youtube.com/watch?v=kB0ZVUrI4Aw), [Making a cube, applying transformations](https://www.youtube.com/watch?v=3yLL9ADo-ko), [Texturing](https://www.youtube.com/watch?v=hpnd11doMgc&t=52s) + +## 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](Vertex%20Shaders.md) describe **vertice** positions, so Graphic Card can position them by connecting with **indices** and project to 2D canvas. + +[Fragment Shaders](Fragment%20Shaders.md) 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](Fragment%20Shaders.md) can also access data from [Vertex Shaders](Vertex%20Shaders.md) (that ones called `varyings`). + +![](https://open.gl/media/img/c2_pipeline.png) +## Program + +Program, as far as I understand, is a scene, that's described with **Vertices**, **Indices**, specific [Vertex Shaders](Vertex%20Shaders.md) and [Fragment Shaders](Fragment%20Shaders.md). + +## Applying transformations + +The best way to change positions inside [Vertex Shaders](Vertex%20Shaders.md) or color in [Fragment Shaders](Fragment%20Shaders.md) is to pass parameters (also called `uniforms` and `varyings`). + +Read about that at [open.gl](https://open.gl/transformations) and at [Vertex Shaders](Vertex%20Shaders.md). + +## Source code with explanations + +```typescript +// render-a-cube.ts +import { 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 requestAnimationFrame +drawCube(ctx)(); + +function drawCube ( + gl: WebGL2RenderingContext, + width: number, + height: number +) { + // Initializing viewport + gl.viewport(0, 0, width, height); + gl.clearColor(0, 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, 0.0); + gl.enable(gl.DEPTH_TEST); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.viewport(0, 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 + 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.length, + gl.UNSIGNED_SHORT, + 0, + ); + + if (i > 1 || i < 0) { + speed = -speed; + } + + i += speed; + }; +}; +``` + +## Shader compiler + +```typescript +// create-shader.ts +export 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](Vertex%20Shaders.md) + +```c +// current vertice position {x,y,z,w} +attribute vec4 aVertexPosition; +// final vertice position with all transformations applied, +// that will be passed to Fragment Shader +varying vec4 v_positionWithOffset; +// Parameters passed from Javascript loop +uniform 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](Fragment%20Shaders.md). + +```c +precision highp float; + +// parameter from Vertex Shader +varying 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); +} +``` \ No newline at end of file diff --git a/content/Frontend/WebGL/Fragment Shaders.md b/content/Frontend/WebGL/Fragment Shaders.md new file mode 100644 index 0000000..6d59e9f --- /dev/null +++ b/content/Frontend/WebGL/Fragment Shaders.md @@ -0,0 +1,25 @@ +Fragment shaders describe ==how polygons are painted== (or textured). Read [Basics of WebGL (Drawing a Cube)](Basics%20of%20WebGL%20(Drawing%20a%20Cube).md) first. + +## Sample fragment vertex + +Parameters could be passed here as written at [Vertex Shaders](Vertex%20Shaders.md). + +```c +precision highp float; + +// parameter from Vertex Shader +varying vec4 v_positionWithOffset; +// parameters passed from Javascript loop +uniform float slide; +uniform float aspect; + +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); +}``` \ No newline at end of file diff --git a/content/Frontend/WebGL/Rendering without blocking in a Worker.md b/content/Frontend/WebGL/Rendering without blocking in a Worker.md new file mode 100644 index 0000000..e77920d --- /dev/null +++ b/content/Frontend/WebGL/Rendering without blocking in a Worker.md @@ -0,0 +1,68 @@ +Rendering items on #canvas in main loop ==might cause interface freezes==, preventing render process from executing properly by flooding execution stack with operations. + +To handle it properly, we can start separate #worker thread, that will handle rendering in its own event loop, so that won't affect the source tab's event loop. + +Workers can have access to [Transferrable Objects](https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects) from main thread by receiving memory address. ==That's a lot faster than cloning==. In this case [ImageBitmap](https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap) is transferrable. + +Code for the main thread component: + +```typescript +// main.ts +const instance = new Worker('./render-worker.ts'); +const canvas = document.getElementById('view'); + +// attaching event listener to worker +instance.onmessage = (event: MessageEvent) => { + const ctx = canvas?.getContext("2d"); + if (!ctx) { + throw new Error(`Can't get 2D context`); + } + + ctx.drawImage(event.data as ImageBitmap, 0, 0); +} + +// sending canvas contents to worker +const renderInCanvas = () => { + if (!canvas.current) { + return; + } + + createImageBitmap(canvas.current).then(image => { + instance.postMessage(image, { transfer: [image] }) + }); +}; +``` + +Worker code: + +```typescript +// render-worker.ts +export default () => { + self.onmessage = (message: MessageEvent) => { + const data = message.data; + + if (!(data instanceof ImageBitmap)) { + throw new Error('Received unknown data') + } + + // OffscreenCanvas can be set up inside workers + const canvas = new OffscreenCanvas(data.width, data.height); + const ctx = canvas.getContext("2d"); + + if (!ctx) { + throw new Error(`Can't get 2D context`); + } + + ctx.drawImage(data, 0, 0); + + // That will block execution inside worker for + // a couple of seconds + doHardRenderingStuffHere(ctx, data.width, data.height); + + // Sending resulting image back to main thread + createImageBitmap(canvas).then((image) => { + self.postMessage(image, { transfer: [image] }); + }); + }; +}; +``` \ No newline at end of file diff --git a/content/Frontend/WebGL/Vertex Shaders.md b/content/Frontend/WebGL/Vertex Shaders.md new file mode 100644 index 0000000..7202b3e --- /dev/null +++ b/content/Frontend/WebGL/Vertex Shaders.md @@ -0,0 +1,45 @@ +Read [Basics of WebGL (Drawing a Cube)](Basics%20of%20WebGL%20(Drawing%20a%20Cube).md) first. + +**Vertex Shaders** define vertice positions in 3D-space. That's just a function, that defines `gl_Position` value by applying different transformations to it. + +## Sample code + +```c +// current vertice position {x,y,z,w} +attribute vec4 aVertexPosition; +// final vertice position with all transformations applied, +// that will be passed to Fragment Shader +varying vec4 v_positionWithOffset; +// Parameters passed from Javascript loop +uniform 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); +} +``` + +## Passing parameters to VertexShader + +Search for `Uniforms` [at open.gl](https://open.gl/drawing) for further reading. + +There're 3 ways to pass parameters. + +- `attribute` are parameters, that won't change. Good for vertex buffers. +- `uniform` are meant to change over the time. Good for passing transformations. +- `varying` are parameters, that's shared between Vertex and [Fragment Shaders](Fragment%20Shaders.md). + +## Applying transformations + +Every vertice position is defined as `{x,y,z,w}`, where `w` is a common denominator, that's used to achieve fast coord transformations by multiplying number of square matrices with original vertice coordinates. + +==We don't change vertice position buffer==, because it's slow when being run inside Javascript loop, we ==pass transformation matrices== instead and ==multiply vertice positions with transformation matrices== inside a Graphic Card's GPU, because that's what GPU made for. + +Good explanation can be found here: [open.gl](https://open.gl/transformations). \ No newline at end of file