WebGL i TypeScript
Denne artikel forklarer WebGL i TypeScript.
Vi vil introducere konceptet med WebGL, dets minimale opsætning, rendering, udvidelser og klasse-design trin for trin med eksempelkode.
YouTube Video
WebGL i TypeScript
WebGL er et low-level API, der giver dig mulighed for at manipulere GPU'en direkte i browseren.
Ved at bruge TypeScript kan du kraftigt reducere 'implementeringsfejl' i WebGL gennem typesikkerhed, kodefuldførelse og struktureret kodning.
TypeScript er især effektiv på følgende punkter:.
- Typerne af WebGL-objekter bliver tydelige.
- Fejl i håndteringen af shader-variabler kan reduceres.
- Det bliver lettere at følge strukturen.
Minimal opsætning af WebGL
Følgende er de minimale krav til rendering med WebGL:.
<canvas>elementWebGL-kontekst- Vertex-shader
- Fragment-shader
Lad os først skabe en tilstand, hvor skærmen kan initialiseres.
Erhvervelse af Canvas og WebGL-kontekst
Først skal du hente canvas og WebGLRenderingContext.
Her bruger vi TypeScript-kodning med vægt på null-sikkerhed.
1const canvas = document.getElementById('gl') as HTMLCanvasElement | null;
2
3if (!canvas) {
4 throw new Error('Canvas not found');
5}
6
7const gl = canvas.getContext('webgl');
8
9if (!gl) {
10 throw new Error('WebGL not supported');
11}- På dette tidspunkt bliver
glindgangspunktet for alle WebGL-operationer.
Rydning af skærmen for at bekræfte, at WebGL fungerer
Før rendering, kontroller om baggrundsfarven kan udfyldes.
Dette er den første kontrol for at verificere, at WebGL fungerer korrekt.
1gl.clearColor(0.1, 0.1, 0.1, 1.0);
2gl.clear(gl.COLOR_BUFFER_BIT);- Indtil dette punkt vil lærredet blive udfyldt med en mørkegrå farve.
Hvad er en shader?
I WebGL beskrives tegneprocesser ved hjælp af et specielt sprog kaldet GLSL. I GLSL forbereder du hovedsageligt to typer shaders: vertex shaders og fragment shaders.
Først vil vi lave en 'minimalt fungerende shader' ved hjælp af disse to shaders.
Skrivning af vertex-shader
Vertex-shaders bestemmer positionerne af punkter eller figurer, der skal tegnes.
Følgende er enkel kode, der placerer et enkelt punkt i midten af skærmen.
1const vertexShaderSource = `
2attribute vec2 a_position;
3
4void main() {
5 gl_Position = vec4(a_position, 0.0, 1.0);
6}
7`;a_positioner koordinatet, der overføres fra JavaScript-siden.
Skrivning af fragment-shader
Fragment-shaders bestemmer farverne, der vises på skærmen.
Denne gang vises der altid rød.
1const fragmentShaderSource = `
2precision mediump float;
3
4void main() {
5 gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
6}
7`;precisionskal altid angives i WebGL.
Oprettelse af en funktion til at kompilere shaders
Da processen for oprettelse af shaders altid er ens, laver vi det til en funktion.
1function createShader(
2 gl: WebGLRenderingContext,
3 type: number,
4 source: string
5): WebGLShader {
6 const shader = gl.createShader(type);
7 if (!shader) {
8 throw new Error('Failed to create shader');
9 }
10
11 gl.shaderSource(shader, source);
12 gl.compileShader(shader);
13
14 if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
15 throw new Error(gl.getShaderInfoLog(shader) ?? 'Shader compile error');
16 }
17
18 return shader;
19}- Denne funktion opretter en shader af den angivne type, kompilerer kildekoden, kaster en fejl, hvis det mislykkes, og returnerer den kompilerede shader, hvis det lykkes.
Oprettelse af et program (et sæt shaders)
Kombiner vertex- og fragment-shaders til én.
Dette kaldes et WebGLProgram.
1const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
2const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
3
4const program = gl.createProgram();
5if (!program) {
6 throw new Error('Failed to create program');
7}
8
9gl.attachShader(program, vertexShader);
10gl.attachShader(program, fragmentShader);
11gl.linkProgram(program);
12
13if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
14 throw new Error(gl.getProgramInfoLog(program) ?? 'Program link error');
15}- Denne kode forbinder vertex- og fragment-shaderen i ét program og kontrollerer, om de kan bruges til rendering.
Forberedelse af vertex-data
Her forbereder vi vertex-data til at tegne en trekant.
1// Vertex positions for a triangle in clip space (x, y)
2// Coordinates range from -1.0 to 1.0
3const vertices = new Float32Array([
4 0.0, 0.5, // Top vertex
5 -0.5, -0.5, // Bottom-left vertex
6 0.5, -0.5, // Bottom-right vertex
7]);- Koordinatsystemet i WebGL spænder fra
-1.0til1.0.
Oprettelse af en buffer og overførsel til GPU'en
Dernæst, for at gøre arrayet af vertex-data brugbart for GPU'en, overfører vi dataene ved hjælp af en buffer.
1const buffer = gl.createBuffer();
2if (!buffer) {
3 throw new Error('Failed to create buffer');
4}
5
6gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
7gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);- Denne kode opretter en buffer til lagring af vertex-data og overfører dens indhold til GPU'en.
Knytte attributten til bufferen
Forbind a_position i shaderen med den tidligere oprettede buffer.
1const positionLocation = gl.getAttribLocation(program, 'a_position');
2
3gl.enableVertexAttribArray(positionLocation);
4gl.vertexAttribPointer(
5 positionLocation,
6 2,
7 gl.FLOAT,
8 false,
9 0,
10 0
11);Rendering
Brug til sidst programmet til at udføre render-kommandoen.
1gl.useProgram(program);
2gl.drawArrays(gl.TRIANGLES, 0, 3);- Når du kører dette program, bliver en rød trekant vist.
Klassedesign med WebGL
WebGL er et imperativt API, så koden kan hurtigt blive oppustet, hvis den skrives som den er. Ved hjælp af klassedesign kan du tydeligt adskille initialisering, rendering og ressourcestyring.
Her vil vi udnytte fordelene ved TypeScript og gradvist udvikle designet med fokus på ansvarsadskillelse, genanvendelighed og vedligeholdbarhed.
Designpolitik (Minimal men Praktisk)
Klassen er delt op i følgende roller.
GLAppstyrer den overordnede initialisering og rendering.ShaderProgramstyrer shaders.TriangleMeshstyrer vertex-data.
Lad os først oprette klassen, der styrer det hele.
Indgangsklasse for en WebGL-applikation
GLApp styrer lærredet og WebGL-konteksten og fungerer som startpunktet for rendering.
1export class GLApp {
2 private gl: WebGLRenderingContext;
3 private shader!: ShaderProgram;
4 private mesh!: TriangleMesh;
5
6 constructor(private canvas: HTMLCanvasElement) {
7 const gl = canvas.getContext('webgl');
8 if (!gl) {
9 throw new Error('WebGL not supported');
10 }
11 this.gl = gl;
12 }
13
14 initialize() {
15 this.gl.clearColor(0.1, 0.1, 0.1, 1.0);
16
17 this.shader = new ShaderProgram(this.gl);
18 this.mesh = new TriangleMesh(this.gl);
19 }
20
21 render() {
22 this.gl.clear(this.gl.COLOR_BUFFER_BIT);
23
24 this.shader.use();
25 this.mesh.draw(this.shader);
26 }
27}- Denne klasse styrer initialiseringen og renderingen i WebGL og står for at tegne på skærmen ved hjælp af shaders og meshes.
- Ved at adskille
initializeogrenderer det lettere at understøtte reinitialisering og animation i fremtiden.
Klasse til håndtering af shaders
Dernæst opretter vi en klasse til at håndtere shaders, så oprettelse, sammenkædning og brug af shaders samles ét sted. Dette gør det muligt for rendering-delen kun at fokusere på at 'bruge' dem.
1export class ShaderProgram {
2 private program: WebGLProgram;
3
4 constructor(private gl: WebGLRenderingContext) {
5 const vertexSource = `
6 attribute vec2 a_position;
7 void main() {
8 gl_Position = vec4(a_position, 0.0, 1.0);
9 }
10 `;
11
12 const fragmentSource = `
13 precision mediump float;
14 void main() {
15 gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
16 }
17 `;
18
19 const vs = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
20 const fs = this.createShader(this.gl.FRAGMENT_SHADER, fragmentSource);
21
22 const program = this.gl.createProgram();
23 if (!program) throw new Error('Program create failed');
24
25 this.gl.attachShader(program, vs);
26 this.gl.attachShader(program, fs);
27 this.gl.linkProgram(program);
28
29 if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {
30 throw new Error(this.gl.getProgramInfoLog(program) ?? 'Link error');
31 }
32
33 this.program = program;
34 }
35
36 use() {
37 this.gl.useProgram(this.program);
38 }
39
40 getAttribLocation(name: string): number {
41 return this.gl.getAttribLocation(this.program, name);
42 }
43
44 private createShader(type: number, source: string): WebGLShader {
45 const shader = this.gl.createShader(type);
46 if (!shader) throw new Error('Shader create failed');
47
48 this.gl.shaderSource(shader, source);
49 this.gl.compileShader(shader);
50
51 if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
52 throw new Error(this.gl.getShaderInfoLog(shader) ?? 'Compile error');
53 }
54
55 return shader;
56 }
57}- Denne klasse samler oprettelsen, sammenkædningen og brugen af vertex- og fragment-shaders, så rendering-delen kan bruge shaders sikkert.
- Ved at eksponere
getAttribLocationkan det sikkert refereres fra mesh-delen.
Klasse til håndtering af meshes (vertex-data)
Vi opretter også en klasse til at håndtere vertex-data. Ved at opdele mesh-klasserne efter objekt til tegning bliver det nemt at udvide.
1export class TriangleMesh {
2 private buffer: WebGLBuffer;
3 private vertexCount = 3;
4
5 constructor(private gl: WebGLRenderingContext) {
6 const vertices = new Float32Array([
7 0.0, 0.5,
8 -0.5, -0.5,
9 0.5, -0.5,
10 ]);
11
12 const buffer = gl.createBuffer();
13 if (!buffer) throw new Error('Buffer create failed');
14
15 this.buffer = buffer;
16
17 gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
18 gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
19 }
20
21 draw(shader: ShaderProgram) {
22 const gl = this.gl;
23 const positionLoc = shader.getAttribLocation('a_position');
24
25 gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
26 gl.enableVertexAttribArray(positionLoc);
27 gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
28
29 gl.drawArrays(gl.TRIANGLES, 0, this.vertexCount);
30 }
31}- Denne klasse styrer trekantens vertex-data og håndterer selve rendering ved at forbinde den med shaderen.
- Alle nødvendige oplysninger til rendering er indeholdt i
TriangleMesh.
Indgangspunkt (eksempel på anvendelse)
Endelig kombineres klasserne for at starte appen.
1const canvas = document.getElementById('gl') as HTMLCanvasElement;
2
3const app = new GLApp(canvas);
4app.initialize();
5app.render();- Med denne struktur bliver det nemt at tilføje animationer eller flere meshes.
Praktiske tips til at skrive WebGL i TypeScript
Brug af TypeScript med WebGL giver følgende fordele:.
- Ved at omdanne WebGL-procedurer til klasser kan de organiseres efter rolle, hvilket gør vedligeholdelse og udvidelse lettere.
- Ved at adskille ansvar som rendering og shader-styring forbedres kodens læsbarhed.
- Ved at udnytte TypeScripts typefuldførelse kan du reducere fejl ved kald af WebGL API'er eller angivelse af parametre.
Sammendrag
Ved at bruge TypeScript kan selv low-level WebGL-processer håndteres stabilt med type-sikkerhed og struktur. Ved at forstå flowet fra minimal opsætning til rendering og anvende klassedesign til at opdele roller som initialisering, rendering og ressourcestyring, kan du forbedre læsbarhed og vedligeholdelse. Ved gradvist at implementere trin for trin kan du lære WebGL som praktisk viden, der ikke er en sort boks og kan bruges i det virkelige arbejde.
Du kan følge med i ovenstående artikel ved hjælp af Visual Studio Code på vores YouTube-kanal. Husk også at tjekke YouTube-kanalen.