WebGL dalam TypeScript

WebGL dalam TypeScript

Artikel ini menjelaskan tentang WebGL dalam TypeScript.

Kami akan memperkenalkan konsep WebGL, konfigurasi minimal, proses rendering, ekstensi, dan desain kelas secara bertahap dengan kode contoh.

YouTube Video

WebGL dalam TypeScript

WebGL adalah API tingkat rendah yang memungkinkan Anda memanipulasi GPU secara langsung di browser.

Dengan menggunakan TypeScript, Anda dapat mengurangi 'kesalahan implementasi' dalam WebGL secara signifikan melalui keamanan tipe, penyelesaian kode, dan pengodean yang terstruktur.

TypeScript sangat efektif terutama dalam hal-hal berikut:.

  • Tipe-tipe objek WebGL menjadi lebih jelas.
  • Kesalahan dalam menangani variabel shader dapat dikurangi.
  • Menjadi lebih mudah untuk melacak struktur.

Konfigurasi minimal WebGL

Berikut adalah persyaratan minimum untuk merender dengan WebGL:.

  • Elemen <canvas>
  • Konteks WebGL
  • Vertex shader
  • Fragment shader

Pertama, mari kita buat keadaan di mana layar dapat diinisialisasi.

Mendapatkan Canvas dan Konteks WebGL

Pertama, dapatkan canvas dan WebGLRenderingContext.

Di sini, kami menggunakan penulisan kode TypeScript dengan penekanan pada keamanan null.

 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}
  • Pada titik ini, gl menjadi titik masuk untuk semua operasi WebGL.

Membersihkan layar untuk memastikan bahwa WebGL sudah berjalan

Sebelum merender, periksa apakah warna background dapat diisi.

Ini adalah pengecekan awal untuk memastikan bahwa WebGL berfungsi dengan baik.

1gl.clearColor(0.1, 0.1, 0.1, 1.0);
2gl.clear(gl.COLOR_BUFFER_BIT);
  • Sampai titik ini, canvas akan terisi dengan warna abu-abu gelap.

Apa itu shader?

Di WebGL, proses menggambar dijelaskan menggunakan bahasa khusus yang disebut GLSL. Dalam GLSL, Anda terutama menyiapkan dua jenis shader: vertex shader dan fragment shader.

Pertama, kita akan membuat ‘minimal working shader’ menggunakan kedua shader ini.

Menulis vertex shader

Vertex shader menentukan posisi titik atau gambar yang akan digambar.

Berikut adalah kode sederhana yang menempatkan satu titik di tengah layar.

1const vertexShaderSource = `
2attribute vec2 a_position;
3
4void main() {
5	gl_Position = vec4(a_position, 0.0, 1.0);
6}
7`;
  • a_position adalah koordinat yang dikirimkan dari sisi JavaScript.

Menulis fragment shader

Fragment shader menentukan warna yang muncul di layar.

Kali ini, shader selalu menghasilkan warna merah.

1const fragmentShaderSource = `
2precision mediump float;
3
4void main() {
5	gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
6}
7`;
  • precision harus selalu ditentukan di WebGL.

Membuat fungsi untuk mengompilasi shader

Karena proses pembuatan shader selalu sama, kita ubah menjadi sebuah fungsi.

 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}
  • Fungsi ini membuat shader dengan tipe tertentu, mengompilasi kode sumbernya, menampilkan error jika gagal, dan mengembalikan shader yang sudah dikompilasi jika berhasil.

Membuat program (sekumpulan shader)

Gabungkan vertex shader dan fragment shader menjadi satu.

Ini disebut 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}
  • Kode ini menghubungkan vertex shader dan fragment shader menjadi satu program serta memeriksa apakah programnya dapat digunakan untuk rendering.

Menyiapkan data vertex

Di sini, kita akan menyiapkan data vertex untuk menggambar sebuah segitiga.

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]);
  • Sistem koordinat di WebGL berkisar dari -1.0 hingga 1.0.

Membuat buffer dan mentransfernya ke GPU

Selanjutnya, agar array data vertex dapat digunakan oleh GPU, kita mentransfer data tersebut menggunakan sebuah 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);
  • Kode ini membuat buffer untuk menyimpan data vertex dan mentransfer isinya ke GPU.

Mengaitkan atribut dengan buffer

Hubungkan a_position di shader dengan buffer yang sebelumnya dibuat.

 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

Akhirnya, gunakan program untuk menjalankan perintah rendering.

1gl.useProgram(program);
2gl.drawArrays(gl.TRIANGLES, 0, 3);
  • Saat Anda menjalankan program ini, segitiga merah akan ditampilkan.

Desain kelas dengan WebGL

WebGL adalah API imperatif, sehingga kodenya dapat dengan cepat menjadi rumit jika ditulis langsung. Melalui desain kelas, Anda dapat dengan jelas memisahkan inisialisasi, rendering, dan manajemen sumber daya.

Di sini, kita akan memanfaatkan kelebihan TypeScript dan secara bertahap mengembangkan desain agar fokus pada separation of concerns, reusability, dan maintainability.

Kebijakan Desain (Minimalis namun Praktis)

Kelas dibagi menjadi peran-peran berikut.

  • GLApp mengontrol inisialisasi dan rendering secara keseluruhan.
  • ShaderProgram mengelola shader.
  • TriangleMesh mengelola data vertex.

Pertama, mari buat kelas yang mengontrol semuanya.

Kelas utama untuk aplikasi WebGL

GLApp mengelola canvas dan konteks WebGL, serta menjadi titik awal untuk 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}
  • Kelas ini mengelola proses inisialisasi dan rendering WebGL serta bertanggung jawab menggambar ke layar menggunakan shader dan mesh.
  • Dengan memisahkan initialize dan render, akan lebih mudah untuk mendukung inisialisasi ulang dan animasi di masa mendatang.

Kelas untuk mengelola shader

Selanjutnya, buatlah sebuah kelas untuk mengelola shader, yang mengkonsolidasikan pembuatan, penghubungan, dan penggunaan shader di satu tempat. Hal ini membuat bagian rendering hanya perlu fokus pada 'menggunakan' shader tersebut.

 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}
  • Kelas ini menyatukan proses pembuatan, penggabungan, dan penggunaan vertex serta fragment shader, sehingga sisi rendering dapat menggunakan shader dengan aman.
  • Dengan menyediakan getAttribLocation, atribut dapat direferensikan dengan aman dari sisi mesh.

Kelas untuk mengelola mesh (data vertex)

Kita juga akan membuat sebuah kelas untuk mengelola data vertex. Dengan membagi kelas mesh berdasarkan objek yang akan digambar, menjadi mudah untuk melakukan ekstensi.

 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}
  • Kelas ini mengelola data vertex dari segitiga, dan dengan menghubungkannya ke shader, menangani proses rendering secara langsung.
  • Semua informasi yang dibutuhkan untuk rendering terdapat di dalam TriangleMesh.

Entry point (Contoh penggunaan)

Akhirnya, gabungkan kelas-kelas tersebut untuk menjalankan aplikasi.

1const canvas = document.getElementById('gl') as HTMLCanvasElement;
2
3const app = new GLApp(canvas);
4app.initialize();
5app.render();
  • Dengan struktur ini, menambahkan animasi atau beberapa mesh menjadi mudah.

Tips praktis menulis WebGL dalam TypeScript

Menggunakan TypeScript dengan WebGL menawarkan keuntungan berikut:.

  • Dengan mengubah prosedur WebGL menjadi kelas, semuanya dapat diatur sesuai peran, sehingga lebih mudah dalam perawatan dan pengembangan.
  • Dengan memisahkan tanggung jawab seperti rendering dan pengelolaan shader, keterbacaan kode menjadi lebih baik.
  • Dengan memanfaatkan auto-completion tipe di TypeScript, Anda dapat mengurangi kesalahan dalam pemanggilan API WebGL atau penentuan parameter.

Ringkasan

Dengan menggunakan TypeScript, bahkan proses WebGL tingkat rendah dapat ditangani dengan aman melalui keamanan tipe dan strukturisasi. Dengan memahami alur dari konfigurasi minimal hingga rendering, serta menerapkan desain kelas untuk memisahkan inisialisasi, rendering, dan manajemen sumber daya, Anda dapat meningkatkan keterbacaan dan kemudahan perawatan kode. Dengan menerapkannya secara bertahap, Anda dapat mempelajari WebGL sebagai pengetahuan praktis yang tidak menjadi kotak hitam dan dapat diterapkan dalam pekerjaan nyata.

Anda dapat mengikuti artikel di atas menggunakan Visual Studio Code di saluran YouTube kami. Silakan periksa juga saluran YouTube kami.

YouTube Video