WebGL - Màu sắc: Hướng dẫn cơ bản về cách thêm sự sống cho hình ảnh 3D của bạn

Xin chào các bạn đam mê WebGL! Tôi rất vui mừng được làm hướng dẫn viên cho bạn trong hành trình đầy màu sắc này qua thế giới của WebGL. Là một ai đó đã dạy đồ họa máy tính hơn một thập kỷ, tôi có thể告诉 bạn rằng việc thêm màu sắc vào các cảnh 3D của bạn giống như mang lại sự sống cho một bức ảnh đen trắng. Điều này rất kỳ diệu, và hôm nay, chúng ta sẽ cùng nhau解锁 điều kỳ diệu đó!

WebGL - Colors

Hiểu về Màu sắc trong WebGL

Trước khi chúng ta nhảy vào phần chi tiết của việc áp dụng màu sắc, hãy dành một chút thời gian để hiểu màu sắc có nghĩa là gì trong ngữ cảnh của WebGL. Trong thế giới kỹ thuật số này, màu sắc được đại diện bằng mô hình màu RGB (Đỏ, Xanh lá, Xanh dương). Mỗi màu là sự kết hợp của ba màu cơ bản này, với giá trị trong khoảng từ 0.0 đến 1.0.

Ví dụ:

  • Đỏ: (1.0, 0.0, 0.0)
  • Xanh lá: (0.0, 1.0, 0.0)
  • Xanh dương: (0.0, 0.0, 1.0)
  • Trắng: (1.0, 1.0, 1.0)
  • Đen: (0.0, 0.0, 0.0)

Hãy tưởng tượng như việc trộn màu sơn, nhưng với ánh sáng thay vì chất màu. Điều này giống như bạn là một nghệ sĩ kỹ thuật số với một bảng màu vô hạn ở ngay fingertips của bạn!

Áp dụng Màu sắc trong WebGL

Bây giờ chúng ta đã hiểu được alap, hãy cùng nhau làm việc với việc áp dụng màu sắc trong WebGL.

Bước để Áp dụng Màu sắc

  1. Định nghĩa các thuộc tính màu trong vertex shader
  2. Chuyển dữ liệu màu từ mã JavaScript
  3. Sử dụng màu trong fragment shader

Hãy phá vỡ các bước này với một số ví dụ mã.

Bước 1: Định nghĩa Các thuộc tính Màu trong Vertex Shader

Đầu tiên, chúng ta cần告诉 vertex shader rằng chúng ta sẽ làm việc với màu sắc. Dưới đây là cách chúng ta làm điều đó:

attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
v_Color = a_Color;
}

Trong đoạn mã này, chúng ta đang định nghĩa một thuộc tính a_Color để nhận dữ liệu màu và một biến v_Color để chuyển màu sang fragment shader. Điều này giống như thiết lập một đường ống màu từ mã JavaScript của chúng ta đến các pixel!

Bước 2: Chuyển Dữ liệu Màu từ JavaScript

Bây giờ, chúng ta cần gửi dữ liệu màu từ mã JavaScript của chúng ta đến shader. Dưới đây là một ví dụ về cách chúng ta có thể làm điều đó:

// Định nghĩa đỉnh và màu sắc
var vertices = new Float32Array([
0.0, 0.5, 1.0, 0.0, 0.0,  // Đỉnh 1: x, y, r, g, b
-0.5,-0.5, 0.0, 1.0, 0.0,  // Đỉnh 2: x, y, r, g, b
0.5,-0.5, 0.0, 0.0, 1.0   // Đỉnh 3: x, y, r, g, b
]);

// Tạo một bộ nhớ đệm và gửi dữ liệu đến nó
var vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

// Báo cho WebGL cách đọc bộ nhớ đệm
var FSIZE = vertices.BYTES_PER_ELEMENT;
var a_Position = gl.getAttribLocation(program, 'a_Position');
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 5, 0);
gl.enableVertexAttribArray(a_Position);

var a_Color = gl.getAttribLocation(program, 'a_Color');
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
gl.enableVertexAttribArray(a_Color);

Đoạn mã này có thể trông đáng sợ ban đầu, nhưng hãy phá vỡ nó:

  1. Chúng ta định nghĩa các đỉnh và màu sắc trong một mảng duy nhất. Mỗi đỉnh có 5 giá trị: x, y, r, g, b.
  2. Chúng ta tạo một bộ nhớ đệm và gửi dữ liệu của chúng ta đến nó.
  3. Chúng ta báo cho WebGL cách đọc bộ nhớ đệm này, cả cho vị trí (hai giá trị đầu tiên) và màu sắc (ba giá trị cuối cùng).

Điều này giống như đóng vali với quần áo và đồ dùng cá nhân, và sau đó告诉 bạn朋友 làm thế nào để unpack nó!

Bước 3: Sử dụng Màu trong Fragment Shader

Cuối cùng, chúng ta sử dụng màu trong fragment shader:

precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}

Fragment shader đơn giản này lấy màu chúng ta đã chuyển từ vertex shader và áp dụng nó cho fragment (pixel). Đây là bước cuối cùng trong hành trình màu sắc của chúng ta, nơi màu sắc cuối cùng được chiếu sáng trên màn hình!

Ví dụ - Áp dụng Màu sắc

Hãy kết hợp tất cả lại với một ví dụ hoàn chỉnh. Chúng ta sẽ tạo một tam giác màu sắc sử dụng mã chúng ta đã thảo luận.

<!DOCTYPE html>
<html>
<head>
<title>Tam giác WebGL Màu sắc</title>
</head>
<body>
<canvas id="glCanvas" width="640" height="480"></canvas>
<script>
// Vertex shader program
const vsSource = `
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
v_Color = a_Color;
}
`;

// Fragment shader program
const fsSource = `
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`;

function main() {
const canvas = document.querySelector("#glCanvas");
const gl = canvas.getContext("webgl");

if (!gl) {
alert("Unable to initialize WebGL. Your browser or machine may not support it.");
return;
}

// Initialize a shader program
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);

// Get the attribute locations
const programInfo = {
program: shaderProgram,
attribLocations: {
vertexPosition: gl.getAttribLocation(shaderProgram, 'a_Position'),
vertexColor: gl.getAttribLocation(shaderProgram, 'a_Color'),
},
};

// Create the buffer
const buffers = initBuffers(gl);

// Draw the scene
drawScene(gl, programInfo, buffers);
}

function initBuffers(gl) {
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

const positions = [
0.0,  0.5,  1.0, 0.0, 0.0,
-0.5, -0.5,  0.0, 1.0, 0.0,
0.5, -0.5,  0.0, 0.0, 1.0,
];

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

return {
position: positionBuffer,
};
}

function drawScene(gl, programInfo, buffers) {
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);

gl.useProgram(programInfo.program);

{
const numComponents = 2;  // pull out 2 values per iteration
const type = gl.FLOAT;    // the data in the buffer is 32bit floats
const normalize = false;  // don't normalize
const stride = 20;        // how many bytes to get from one set of values to the next
const offset = 0;         // how many bytes inside the buffer to start from
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
gl.vertexAttribPointer(
programInfo.attribLocations.vertexPosition,
numComponents,
type,
normalize,
stride,
offset);
gl.enableVertexAttribArray(
programInfo.attribLocations.vertexPosition);
}

{
const numComponents = 3;
const type = gl.FLOAT;
const normalize = false;
const stride = 20;
const offset = 8;
gl.vertexAttribPointer(
programInfo.attribLocations.vertexColor,
numComponents,
type,
normalize,
stride,
offset);
gl.enableVertexAttribArray(
programInfo.attribLocations.vertexColor);
}

gl.drawArrays(gl.TRIANGLES, 0, 3);
}

function initShaderProgram(gl, vsSource, fsSource) {
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);

const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);

if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
return null;
}

return shaderProgram;
}

function loadShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);

if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}

return shader;
}

window.onload = main;
</script>
</body>
</html>

Khi bạn chạy đoạn mã này, bạn sẽ thấy một tam giác đẹp mắt với các đỉnh đỏ, xanh lá và xanh dương. Điều này giống như nhìn thấy một cầu vồng sống động trên màn hình!

Kết luận

Và thế là bạn đã có nó, các bạn! Chúng ta đã cùng nhau hành trình qua thế giới đầy màu sắc của WebGL, từ việc hiểu cách màu sắc được đại diện đến việc áp dụng chúng vào hình ảnh 3D của chúng ta. Nhớ rằng, đây chỉ là bắt đầu. Với những alap này, bạn đã sẵn sàng để tạo ra những hình ảnh 3D rực rỡ, sống động.

Khi chúng ta kết thúc, tôi nhớ lại một học sinh曾经说过 rằng việc học màu sắc trong WebGL giống như học vẽ với ánh sáng. Và bạn biết đấy, cô ấy hoàn toàn đúng. Vậy hãy tiến lên, các học sinh yêu quý của tôi, và vẽ những canvas kỹ thuật số của bạn với tất cả các màu sắc của gió (vâng, đó là một引用 từ Disney, và không, tôi không hối hận về điều đó)!

Chúc các bạn viết mã vui vẻ, và mong rằng các cuộc phiêu lưu WebGL của bạn sẽ luôn đầy màu sắc!

Credits: Image by storyset