WebGL in TypeScript
Dieser Artikel erklärt WebGL in TypeScript.
Wir stellen das Konzept von WebGL, dessen minimale Konfiguration, Rendering, Erweiterungen und das Klassendesign Schritt für Schritt mit Beispielcode vor.
YouTube Video
WebGL in TypeScript
WebGL ist eine Low-Level-API, mit der Sie die GPU direkt im Browser steuern können.
Durch die Verwendung von TypeScript können Sie „Implementierungsfehler“ in WebGL durch Typsicherheit, Code-Vervollständigung und strukturiertes Programmieren erheblich reduzieren.
TypeScript ist insbesondere in folgenden Punkten effektiv:.
- Die Typen der WebGL-Objekte werden klar ersichtlich.
- Fehler beim Umgang mit Shader-Variablen können reduziert werden.
- Es wird einfacher, die Struktur nachzuvollziehen.
Minimale Konfiguration von WebGL
Folgende Mindestanforderungen sind für das Rendering mit WebGL erforderlich:.
WebGL-Kontext- Vertex-Shader
- Fragment-Shader
Lassen Sie uns zunächst einen Zustand schaffen, in dem der Bildschirm initialisiert werden kann.
Das Canvas-Element und den WebGL-Kontext erhalten
Zunächst holen Sie das canvas-Element und den WebGLRenderingContext.
Hier verwenden wir TypeScript, wobei der Fokus auf Null-Sicherheit liegt.
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}- Ab diesem Punkt ist
gldie Einstiegstelle für alle WebGL-Operationen.
Den Bildschirm löschen, um zu bestätigen, dass WebGL funktioniert.
Vor dem Rendering kontrollieren wir, ob die Hintergrundfarbe gesetzt werden kann.
Dies ist die erste Überprüfung, um sicherzustellen, dass WebGL korrekt funktioniert.
1gl.clearColor(0.1, 0.1, 0.1, 1.0);
2gl.clear(gl.COLOR_BUFFER_BIT);- Bis zu diesem Punkt wird das Canvas mit einer dunkelgrauen Farbe gefüllt.
Was ist ein Shader?
In WebGL werden Zeichenprozesse mit einer speziellen Sprache namens GLSL beschrieben. In GLSL bereiten Sie hauptsächlich zwei Arten von Shadern vor: Vertex-Shader und Fragment-Shader.
Zunächst erstellen wir einen 'minimal funktionsfähigen Shader', der beide Shader-Typen nutzt.
Den Vertex-Shader erstellen
Vertex-Shader bestimmen die Positionen der zu zeichnenden Punkte oder Formen.
Der folgende einfache Code platziert einen einzelnen Punkt in der Bildschirmmitte.
1const vertexShaderSource = `
2attribute vec2 a_position;
3
4void main() {
5 gl_Position = vec4(a_position, 0.0, 1.0);
6}
7`;a_positionist die vom JavaScript übergebene Koordinate.
Den Fragment-Shader erstellen
Fragment-Shader bestimmen die Farben, die auf dem Bildschirm erscheinen.
Dieses Mal gibt er immer Rot zurück.
1const fragmentShaderSource = `
2precision mediump float;
3
4void main() {
5 gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
6}
7`;precisionmuss in WebGL immer angegeben werden.
Eine Funktion erstellen, um Shader zu kompilieren
Da die Erstellung von Shadern immer gleich abläuft, kapseln wir sie in einer 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}- Diese Funktion erstellt einen Shader des angegebenen Typs, kompiliert den Quellcode, wirft bei Fehlschlag einen Fehler und gibt den kompilierten Shader bei Erfolg zurück.
Ein Programm erstellen (eine Sammlung von Shadern)
Den Vertex- und den Fragment-Shader kombinieren.
Dies wird als WebGLProgram bezeichnet.
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}- Dieser Code verbindet die Vertex- und Fragment-Shader zu einem Programm und prüft, ob sie zum Rendern verwendet werden können.
Vertex-Daten vorbereiten
Hier werden wir Vertex-Daten zum Zeichnen eines Dreiecks vorbereiten.
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]);- Das Koordinatensystem in WebGL reicht von
-1.0bis1.0.
Erstellen eines Puffers und Übertragung an die GPU
Als Nächstes übertragen wir das Array mit Vertex-Daten mithilfe eines Puffers, damit die GPU es verwenden kann.
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);- Dieser Code erstellt einen Puffer zum Speichern von Vertex-Daten und überträgt dessen Inhalt an die GPU.
Attribut mit dem Buffer verknüpfen
a_position im Shader mit dem zuvor erstellten Buffer verbinden.
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
Verwenden Sie schließlich das Programm, um den Rendering-Befehl auszuführen.
1gl.useProgram(program);
2gl.drawArrays(gl.TRIANGLES, 0, 3);- Wenn Sie dieses Programm ausführen, wird ein rotes Dreieck angezeigt.
Klassendesign mit WebGL
WebGL ist eine imperative API, daher kann der Code schnell unübersichtlich werden, wenn er so geschrieben wird, wie er ist. Durch ein passendes Klassendesign kann man Initialisierung, Rendering und Ressourcenmanagement klar trennen.
Hier nutzen wir die Vorteile von TypeScript und entwickeln das Design schrittweise weiter, um den Fokus auf Trennung von Verantwortlichkeiten, Wiederverwendbarkeit und Wartbarkeit zu legen.
Design-Richtlinie (Minimal, aber praktisch)
Die Klasse ist in die folgenden Rollen unterteilt.
GLAppsteuert die gesamte Initialisierung und das Rendering.ShaderProgramverwaltet die Shader.TriangleMeshverwaltet die Vertexdaten.
Zuerst erstellen wir die Klasse, die alles steuert.
Einstiegsklasse für eine WebGL-Anwendung
GLApp verwaltet das Canvas und den WebGL-Kontext und dient als Ausgangspunkt für das 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}- Diese Klasse verwaltet die Initialisierung und das Rendering von WebGL und ist dafür verantwortlich, mit Shadern und Meshes auf den Bildschirm zu zeichnen.
- Durch die Trennung von
initializeundrenderwird es einfacher, später eine erneute Initialisierung oder Animation zu unterstützen.
Klasse zur Verwaltung der Shader
Erstellen Sie als Nächstes eine Klasse zur Verwaltung von Shadern, die das Erstellen, Verknüpfen und Verwenden der Shader an einem Ort zusammenfasst. Dies ermöglicht es der Render-Seite, sich ausschließlich auf die Nutzung zu konzentrieren.
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}- Diese Klasse vereint die Erstellung, das Verknüpfen und die Verwendung von Vertex- und Fragment-Shadern, sodass die Render-Seite sie sicher verwenden kann.
- Durch das Bereitstellen von
getAttribLocationkann von der Mesh-Seite sicher darauf zugegriffen werden.
Klasse zur Verwaltung von Meshes (Vertexdaten)
Wir werden auch eine Klasse zur Verwaltung der Vertex-Daten erstellen. Durch die Aufteilung der Mesh-Klassen nach darzustellendem Objekt wird die Erweiterung vereinfacht.
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}- Diese Klasse verwaltet die Vertex-Daten des Dreiecks und übernimmt durch die Verbindung mit dem Shader das eigentliche Rendering.
- Alle für das Rendering benötigten Informationen sind in
TriangleMeshenthalten.
Einstiegspunkt (Anwendungsbeispiel)
Kombinieren Sie abschließend die Klassen, um die Anwendung zu starten.
1const canvas = document.getElementById('gl') as HTMLCanvasElement;
2
3const app = new GLApp(canvas);
4app.initialize();
5app.render();- Mit dieser Struktur wird das Hinzufügen von Animationen oder mehreren Meshes einfach.
Praktische Tipps zur Entwicklung von WebGL mit TypeScript
Die Verwendung von TypeScript mit WebGL bietet folgende Vorteile:.
- Durch die Umwandlung von WebGL-Prozessen in Klassen können diese nach Aufgabenbereich organisiert werden, was Wartung und Erweiterung erleichtert.
- Durch die Aufteilung von Verantwortlichkeiten wie Rendering und Shader-Verwaltung wird die Lesbarkeit des Codes verbessert.
- Durch die Nutzung der TypeScript-Typvervollständigung lassen sich Fehler beim Aufrufen von WebGL-APIs und bei der Angabe von Parametern reduzieren.
Zusammenfassung
Mit TypeScript können selbst Low-Level-WebGL-Prozesse stabil mit Typsicherheit und Struktur gehandhabt werden. Durch das Verständnis des Ablaufs von der minimalen Konfiguration bis zum Rendering sowie durch die Anwendung von Klassendesign zur Trennung von Aufgaben wie Initialisierung, Rendering und Ressourcenmanagement können Lesbarkeit und Wartbarkeit verbessert werden. Durch schrittweises Vorgehen lernen Sie WebGL als praktisches Wissen, das keine Blackbox ist und in realen Projekten angewandt werden kann.
Sie können den obigen Artikel mit Visual Studio Code auf unserem YouTube-Kanal verfolgen. Bitte schauen Sie sich auch den YouTube-Kanal an.