WebGL i TypeScript
Denne artikkelen forklarer WebGL i TypeScript.
Vi vil introdusere konseptet WebGL, dets minimale konfigurasjon, rendering, utvidelser og klasse-design steg for steg med eksempelkode.
YouTube Video
WebGL i TypeScript
WebGL er et lavnivå-API som lar deg kontrollere GPU-en direkte i nettleseren.
Ved å bruke TypeScript kan du i stor grad redusere 'implementeringsfeil' i WebGL gjennom typesikkerhet, kodefullføring og strukturert koding.
TypeScript er spesielt effektiv på følgende områder:.
- Typene til WebGL-objektene blir tydelige.
- Feil ved håndtering av shader-variabler kan reduseres.
- Det blir enklere å følge strukturen.
Minimal konfigurasjon av WebGL
Følgende er de minimale kravene for å gjengi med WebGL:.
<canvas>-elementWebGL-kontekst- Vertex-shader
- Fragment-shader
Først skal vi etablere en tilstand hvor skjermen kan initialiseres.
Hente Canvas og WebGL-kontekst
Først, hent canvas og WebGLRenderingContext.
Her bruker vi TypeScript-koding med vekt på null-sikkerhet.
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 tidspunktet blir
glinngangspunktet for alle WebGL-operasjoner.
Tømme skjermen for å bekrefte at WebGL fungerer
Før rendering, sjekk om bakgrunnsfargen kan fylles.
Dette er den første sjekken for å verifisere at WebGL fungerer korrekt.
1gl.clearColor(0.1, 0.1, 0.1, 1.0);
2gl.clear(gl.COLOR_BUFFER_BIT);- Fram til dette punktet vil lerretet fylles med en mørk grå farge.
Hva er en shader?
I WebGL beskrives tegneprosesser ved hjelp av et spesielt språk kalt GLSL. I GLSL forbereder du hovedsakelig to typer shader: vertex-shadere og fragment-shadere.
Først lager vi en ‘minimal fungerende shader’ ved å bruke begge disse shaderne.
Skrive vertex-shaderen
Vertex-shadere bestemmer posisjonene til punktene eller figurene som skal tegnes.
Følgende er en enkel kode som plasserer et enkelt punkt midt på skjermen.
1const vertexShaderSource = `
2attribute vec2 a_position;
3
4void main() {
5 gl_Position = vec4(a_position, 0.0, 1.0);
6}
7`;a_positioner koordinatet som sendes inn fra JavaScript-siden.
Skrive fragment-shaderen
Fragment-shadere bestemmer hvilke farger som skal vises på skjermen.
Denne gangen gir den alltid ut rødt.
1const fragmentShaderSource = `
2precision mediump float;
3
4void main() {
5 gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
6}
7`;precisionmå alltid spesifiseres i WebGL.
Lage en funksjon for å kompilere shadere
Siden shader-oppretting alltid er lik, gjør vi det om til en funksjon.
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 funksjonen lager en shader av angitt type, kompilerer kildekoden, kaster en feil hvis det mislykkes, og returnerer den kompilerte shaderen hvis det lykkes.
Lage et program (et sett med shadere)
Kombiner vertex- og fragment-shadere til ett program.
Dette kalles 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 koden lenker vertex- og fragment-shaderen til ett program og sjekker om de kan brukes til rendering.
Forberede vertex-data
Her forbereder vi vertexdata for å 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 går fra
-1.0til1.0.
Lage en buffer og sende den til GPU-en
Deretter, for å gjøre arrayet med vertexdata brukbart for GPU-en, overfører vi dataene med 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 koden lager en buffer for å lagre vertex-data og sender innholdet til GPU-en.
Knytte attributtet til bufferen
Koble a_position i shaderen med den tidligere opprettede bufferen.
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
Til slutt bruker du programmet for å sende rendering-kommandoen.
1gl.useProgram(program);
2gl.drawArrays(gl.TRIANGLES, 0, 3);- Når du kjører dette programmet, vises en rød trekant.
Klasse-design med WebGL
WebGL er et imperativt API, så koden kan raskt bli uoversiktlig hvis den skrives som den er. Gjennom klasse-design kan du tydelig skille mellom initialisering, rendering og ressursstyring.
Her benytter vi TypeScripts fordeler og utvikler gradvis designet for å fokusere på ansvarsdeling, gjenbruk og vedlikeholdbarhet.
Designpolicy (minimalt, men praktisk)
Klassen er delt inn i følgende roller.
GLAppstyrer all initialisering og rendering.ShaderProgramhåndterer shaderne.TriangleMeshadministrerer vertex-dataene.
Først lager vi klassen som styrer alt.
Hovedklasse for en WebGL-applikasjon
GLApp håndterer canvas og WebGL-kontekst, og er 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 klassen styrer initialiserings- og renderingprosessene for WebGL og har ansvaret for å tegne til skjermen via shaders og mesh-objekter.
- Ved å skille
initializeogrender, er det enklere å støtte re-initialisering og animasjon i fremtiden.
Klasse for å håndtere shadere
Deretter oppretter vi en klasse for å håndtere shadere, og samler shaderopprettelse, lenking og bruk på ett sted. Dette gjør at renderingsdelen bare trenger å fokusere på å ‘bruke’ 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 klassen samler oppretting, lenking og bruk av vertex- og fragment-shadere, slik at renderingssiden kan bruke shaderne trygt.
- Ved å eksponere
getAttribLocationkan den trygt refereres fra mesh-siden.
Klasse for å håndtere mesh (vertex-data)
Vi oppretter også en klasse for å håndtere vertexdataene. Ved å dele opp mesh-klasser etter objekt å tegne, blir det lett å utvide.
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 klassen håndterer vertex-dataene til trekanten, og ved å koble det til shaderen, tar den seg av selve rendering.
- All informasjon som trengs for rendering er samlet i
TriangleMesh.
Startpunkt (brukseksempel)
Til slutt setter vi sammen klassene for å starte appen.
1const canvas = document.getElementById('gl') as HTMLCanvasElement;
2
3const app = new GLApp(canvas);
4app.initialize();
5app.render();- Med denne strukturen er det lett å legge til animasjoner eller flere mesh-objekter.
Praktiske tips for å skrive WebGL i TypeScript
Ved å bruke TypeScript med WebGL får du følgende fordeler:.
- Ved å gjøre WebGL-prosedyrer om til klasser, kan de organiseres etter rolle, noe som gjør det enklere å vedlikeholde og utvide.
- Ved å separere ansvarsområder som rendering og shader-håndtering, forbedres lesbarheten i koden.
- Ved å bruke TypeScripts typeutfylling kan du redusere feil ved bruk av WebGL-APIer eller når du angir parametre.
Sammendrag
Ved å bruke TypeScript kan selv lavnivå WebGL-prosesser håndteres stabilt med typesikkerhet og struktur. Ved å forstå flyten fra minimal konfigurasjon til rendering, og ved å bruke klasser for å separere roller som initialisering, rendering og ressursstyring, kan du forbedre både lesbarhet og vedlikehold. Ved å implementere steg for steg kan du lære WebGL som praktisk kunnskap, slik at det ikke blir en svart boks, og du kan bruke det i virkelig arbeid.
Du kan følge med på artikkelen ovenfor ved å bruke Visual Studio Code på vår YouTube-kanal. Vennligst sjekk ut YouTube-kanalen.