WebGL i TypeScript
Den här artikeln förklarar WebGL i TypeScript.
Vi introducerar konceptet WebGL, dess minimala konfiguration, rendering, tillägg och klassdesign steg för steg med exempelkod.
YouTube Video
WebGL i TypeScript
WebGL är ett lågnivå-API som låter dig styra GPU:n direkt i webbläsaren.
Genom att använda TypeScript kan du kraftigt minska 'implementeringsmisstag' i WebGL genom typsäkerhet, kodkomplettering och strukturerad kodning.
TypeScript är särskilt effektivt på följande punkter:.
- Typen på WebGL-objekt blir tydlig.
- Misstag vid hantering av shader-variabler kan minskas.
- Det blir enklare att följa strukturen.
Minimal konfiguration av WebGL
Följande är minimikraven för rendering med WebGL:.
<canvas>-elementWebGL-kontext- Vertexshader
- Fragmentshader
Låt oss först skapa ett tillstånd där skärmen kan initieras.
Hämta Canvas och WebGL-kontext
Först, hämta canvas och WebGLRenderingContext.
Här använder vi TypeScript-kodning med fokus på nullsäkerhet.
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}- Vid denna punkt blir
glingångspunkten för alla WebGL-operationer.
Rensa skärmen för att bekräfta att WebGL fungerar
Innan rendering, kontrollera om bakgrundsfärgen kan fyllas.
Detta är den ursprungliga kontrollen för att verifiera att WebGL fungerar korrekt.
1gl.clearColor(0.1, 0.1, 0.1, 1.0);
2gl.clear(gl.COLOR_BUFFER_BIT);- Fram till denna punkt kommer canvasen att fyllas med en mörkgrå färg.
Vad är en shader?
I WebGL beskrivs ritningsprocesser med ett speciellt språk som kallas GLSL. I GLSL förbereder du huvudsakligen två typer av shaders: vertex shaders och fragment shaders.
Först skapar vi en 'minimalt fungerande shader' med dessa två shaders.
Skriva vertexshadern
Vertexshaders bestämmer positioner för punkter eller figurer som ska ritas.
Följande är enkel kod som placerar en enda punkt i mitten av skärmen.
1const vertexShaderSource = `
2attribute vec2 a_position;
3
4void main() {
5 gl_Position = vec4(a_position, 0.0, 1.0);
6}
7`;a_positionär koordinaten som skickas från JavaScript-sidan.
Skriva fragmentshadern
Fragmentshaders bestämmer färgerna som visas på skärmen.
Den här gången matas rött alltid ut.
1const fragmentShaderSource = `
2precision mediump float;
3
4void main() {
5 gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
6}
7`;precisionmåste alltid anges i WebGL.
Skapa en funktion för att kompilera shaders
Eftersom skapandet av shaders alltid sker på samma sätt, gör vi det till 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}- Denna funktion skapar en shader av angiven typ, kompilerar källkoden, kastar ett fel om det misslyckas och returnerar den kompilerade shadern om det lyckas.
Skapa ett program (en uppsättning shaders)
Kombinera vertex- och fragmentshadern till en.
Detta kallas för ett 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}- Den här koden länkar vertex- och fragmentshadern till ett enda program och kontrollerar om de kan användas för rendering.
Förbereda vertexdata
Här kommer vi att förbereda vertexdata för att rita en triangel.
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 sträcker sig från
-1.0till1.0.
Skapa en buffert och överföra den till GPU:n
Därefter, för att göra arrayen av vertexdata användbar för GPU:n, för vi över datan med hjälp av 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);- Den här koden skapar en buffert för att lagra vertexdata och överför dess innehåll till GPU:n.
Koppla attributet till bufferten
Koppla a_position i shadern till bufferten som skapats tidigare.
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
Slutligen, använd programmet för att utföra rendering-kommandot.
1gl.useProgram(program);
2gl.drawArrays(gl.TRIANGLES, 0, 3);- När du kör detta program kommer en röd triangel att visas.
Klassdesign med WebGL
WebGL är ett imperativt API, så koden kan snabbt bli svåröverskådlig om den skrivs som den är. Genom klassdesign kan du tydligt separera initiering, rendering och resurshantering.
Här utnyttjar vi fördelarna med TypeScript och utvecklar successivt designen med fokus på ansvarsseparering, återanvändbarhet och underhållbarhet.
Designpolicy (Minimal men praktisk)
Klassen är uppdelad i följande roller.
GLAppstyr övergripande initiering och rendering.ShaderProgramhanterar shaders.TriangleMeshhanterar vertexdata.
Låt oss först skapa klassen som styr allt.
Startklass för en WebGL-applikation
GLApp hanterar canvas och WebGL-kontext och fungerar som startpunkt för 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}- Den här klassen hanterar WebGL:s initierings- och renderingsprocesser och ansvarar för att rita på skärmen med shaders och mesh.
- Genom att separera
initializeochrenderblir det enklare att stödja ominitiering och animation i framtiden.
Klass för hantering av shaders
Skapa sedan en klass för att hantera shaders och samla skapande, länkning och användning av shaders på ett ställe. Detta gör att rendringssidan enbart kan fokusera på att 'använda' 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}- Den här klassen samlar skapande, länkning och användning av vertex- och fragmentshaders, så att renderingssidan kan använda shaders på ett säkert sätt.
- Genom att exponera
getAttribLocationkan det refereras säkert från mesh-sidan.
Klass för hantering av mesh (vertexdata)
Vi kommer också att skapa en klass för att hantera vertexdatan. Genom att dela upp mesh-klasser efter objekt att rita, blir det enkelt att bygga ut.
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}- Den här klassen hanterar vertexdata för triangeln och genom att koppla det till shadern, hanterar den själva rendering.
- All information som behövs för rendering finns i
TriangleMesh.
Startpunkt (Användningsexempel)
Slutligen, kombinera klasserna för att starta appen.
1const canvas = document.getElementById('gl') as HTMLCanvasElement;
2
3const app = new GLApp(canvas);
4app.initialize();
5app.render();- Med denna struktur blir det lätt att lägga till animationer eller flera mesh.
Praktiska tips för att skriva WebGL i TypeScript
Att använda TypeScript med WebGL ger följande fördelar:.
- Genom att omvandla WebGL-processer till klasser kan de organiseras efter roll, vilket förenklar underhåll och vidareutveckling.
- Genom att separera ansvar som rendering och shader-hantering förbättras kodens läsbarhet.
- Genom att använda TypeScripts typkomplettering kan du minska misstag vid anrop av WebGL-API:er eller vid parameterangivelser.
Sammanfattning
Med TypeScript kan till och med lågnivå-WebGL-processer hanteras stabilt med typsäkerhet och struktur. Genom att förstå flödet från minimal konfiguration till rendering, och genom att tillämpa klassdesign för att separera roller såsom initiering, rendering och resurshantering, kan du förbättra läsbarhet och underhållbarhet. Genom att implementera steg för steg kan du lära dig WebGL som praktisk kunskap, som inte är en svart låda och som kan användas i verkliga projekt.
Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.