muerwre.github.io/content/Frontend/WebGL/Basics of WebGL (Drawing a Cube).md
2022-11-27 19:44:07 +06:00

243 lines
No EOL
6.9 KiB
Markdown

## 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);
}
```