mirror of
https://github.com/muerwre/muerwre.github.io.git
synced 2025-04-25 02:46:39 +07:00
added webgl basics
This commit is contained in:
parent
509e2b9406
commit
189028c533
4 changed files with 381 additions and 0 deletions
243
content/Frontend/WebGL/Basics of WebGL (Drawing a Cube).md
Normal file
243
content/Frontend/WebGL/Basics of WebGL (Drawing a Cube).md
Normal file
|
@ -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`).
|
||||||
|
|
||||||
|

|
||||||
|
## 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);
|
||||||
|
}
|
||||||
|
```
|
25
content/Frontend/WebGL/Fragment Shaders.md
Normal file
25
content/Frontend/WebGL/Fragment Shaders.md
Normal file
|
@ -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);
|
||||||
|
}```
|
|
@ -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] });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
45
content/Frontend/WebGL/Vertex Shaders.md
Normal file
45
content/Frontend/WebGL/Vertex Shaders.md
Normal file
|
@ -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).
|
Loading…
Add table
Add a link
Reference in a new issue