Learn how to read and render standard 3D meshes using the glTF 2.0 format specification. Understand how binary buffers, bufferviews, and accessors translate into GPU vertex buffers in WebGPU.
This lesson is currently only available in Vietnamese. Please switch the language toggle in the menu to Vietnamese to read the full guide and take the interactive quiz.
Trong tất cả các bài học trước, chúng ta đều tự xây dựng thủ công các tọa độ đỉnh của hình tam giác, hình lập phương hoặc sinh vị trí hạt ngẫu nhiên trong bộ nhớ. Tuy nhiên, trong các dự án thực tế, các lập trình viên 3D không viết tọa độ bằng code; họ sử dụng các mô hình được thiết kế tinh xảo từ Blender hoặc Maya và xuất ra các tệp tin lưu trữ chuẩn đồ họa như glTF (GL Transmission Format).
Bài học này sẽ bóc tách cấu trúc lưu trữ dữ liệu nhị phân của glTF 2.0 – định dạng 3D chuẩn mở của Khronos Group, cách thức phân tích dữ liệu (parsing) và nạp trực tiếp các đệm nhị phân này vào GPU để dựng hình.
1. Cấu trúc đặc tả tệp glTF 2.0
glTF 2.0 được thiết kế để nạp tải mô hình 3D cực nhanh lên card đồ họa mà không cần xử lý phân tích chuỗi văn bản phức tạp ở runtime. Một tệp glTF thường bao gồm hai phần chính: một cấu trúc điều hướng dạng JSON và các tệp đệm nhị phân thô chứa thông tin toạ độ đỉnh có phần mở rộng `.bin`.
1.1 Accessors (Bộ truy cập định kiểu)
Accessor liên kết một `bufferView` với kiểu dữ liệu lập trình cụ thể để GPU hiểu được cách đọc đỉnh.
"accessors": [
{
"bufferView": 0,
"componentType": 5126, // 5126 tương đương với Float32
"count": 3612, // Số lượng phần tử (đỉnh)
"type": "VEC3" // Kiểu dữ liệu vec3 (x, y, z)
}
]
1.2 BufferViews (Phân đoạn đệm nhị phân)
BufferView chỉ ra vị trí và độ dài của một thuộc tính (như vị trí đỉnh, pháp tuyến) nằm ở đâu trong mảng nhị phân thô.
"bufferViews": [
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 43344
}
]
2. Nạp dữ liệu nhị phân thô trực tiếp vào GPU Buffer
Để hiển thị mô hình 3D, chúng ta trích xuất dữ liệu ArrayBuffer từ tệp nhị phân dựa trên các tham số `byteOffset` và `byteLength` đã khai báo trong `bufferView`, sau đó tạo `GPUBuffer` tương ứng và nạp thẳng dữ liệu đó bằng hàng đợi ghi:
// Trích xuất phân khúc nhị phân vị trí đỉnh (POSITION)
const posAccessor = gltf.accessors[0];
const posView = gltf.bufferViews[posAccessor.bufferView];
const posBuffer = device.createBuffer({
size: posView.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
// Nạp thẳng dữ liệu nhị phân thô vào GPU Buffer (Zero Parsing)
device.queue.writeBuffer(
posBuffer,
0,
gltfBinaryBuffer, // Buffer nhị phân thô của file .bin
posView.byteOffset,
posView.byteLength
);
3. Đổ bóng chất liệu phong cách Phong & Phủ vân Texture
Để mô hình 3D chân thực, ta kết hợp ánh xạ toạ độ UV (`TEXCOORD_0`) từ accessor glTF để mẫu vân bề mặt (texture) quấn quanh lưới đa giác. Trong WebGPU, ta dùng `textureSample(texture, sampler, uv)` để đọc màu vân ảnh tại tọa độ UV tương ứng và áp dụng mô hình chiếu sáng Phong để đổ bóng khối 3D.
Sân chơi tương tác: glTF 3D Parser & Renderer
Mô phỏng dưới đây mô tả chính xác cơ chế của một bộ phân tích glTF hoạt động. Một mô hình 3D Torus Knot phức tạp được đóng gói nhị phân thô vào một đệm ArrayBuffer duy nhất. WebGPU Parser đọc JSON metadata để phân khúc dữ liệu nhị phân này, nạp vào GPU đệm đỉnh và hiển thị mô hình kèm ánh sáng Phong.
*Hãy nhấn chuột và kéo rê trên Canvas để xoay mô hình 3D, hoặc lăn chuột để Zoom xa/gần.*
Cấu hình Hiển thị 3D
struct Uniforms {
modelMatrix : mat4x4<f32>,
viewMatrix : mat4x4<f32>,
projectionMatrix : mat4x4<f32>,
normalMatrix : mat4x4<f32>,
specularPower : f32,
};
@group(0) @binding(0) var<uniform> u : Uniforms;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var t : texture_2d<f32>;
struct VertexInput {
@location(0) position : vec3<f32>,
@location(1) normal : vec3<f32>,
@location(2) uv : vec2<f32>,
};
struct VertexOutput {
@builtin(position) pos : vec4<f32>,
@location(0) fragNormal : vec3<f32>,
@location(1) fragPosition : vec3<f32>,
@location(2) fragUv : vec2<f32>,
};
@vertex
fn vsMain(in : VertexInput) -> VertexOutput {
var out : VertexOutput;
let worldPos = u.modelMatrix * vec4<f32>(in.position, 1.0);
out.pos = u.projectionMatrix * u.viewMatrix * worldPos;
out.fragPosition = worldPos.xyz;
out.fragNormal = (u.normalMatrix * vec4<f32>(in.normal, 0.0)).xyz;
out.fragUv = in.uv;
return out;
}
@fragment
fn fsMain(in : VertexOutput) -> @location(0) vec4<f32> {
let N = normalize(in.fragNormal);
let L = normalize(vec3<f32>(2.0, 5.0, 3.0) - in.fragPosition);
let V = normalize(vec3<f32>(0.0, 0.0, 4.0) - in.fragPosition);
let H = normalize(L + V);
// Mẫu vân bề mặt Texture ánh xạ qua UV
let texColor = textureSample(t, s, in.fragUv);
let ambient = 0.22 * texColor.rgb;
let diffuse = max(dot(N, L), 0.0) * texColor.rgb;
let specular = pow(max(dot(N, H), 0.0), u.specularPower) * vec3<f32>(0.4);
return vec4<f32>(ambient + diffuse + specular, 1.0);
}
// Cú pháp đọc dữ liệu từ cấu trúc bufferViews và nạp trực tiếp vào GPU Buffer
const posAccessor = gltfJson.accessors[0];
const posView = gltfJson.bufferViews[posAccessor.bufferView];
const posBuffer = device.createBuffer({
size: posView.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
// Chép nhị phân thô không qua bước parsing chậm chạp
device.queue.writeBuffer(
posBuffer,
0,
gltfBinaryBuffer,
posView.byteOffset,
posView.byteLength
);
// Tương tự cho NORMAL, TEXCOORD_0 (UV) và INDEX buffers
const indexAccessor = gltfJson.accessors[3];
const indexView = gltfJson.bufferViews[indexAccessor.bufferView];
const indexBuffer = device.createBuffer({
size: indexView.byteLength,
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(
indexBuffer,
0,
gltfBinaryBuffer,
indexView.byteOffset,
indexView.byteLength
);
Tài liệu tham khảo chuyên sâu & Trích dẫn Học thuật:
- Đặc tả kỹ thuật chính thức định dạng glTF 2.0 của tập đoàn Khronos Group: Khronos Group - glTF 2.0 Specification.
- Hướng dẫn thiết kế kết cấu bề mặt đồ họa và nạp ảnh Texture trong WebGPU: W3C WebGPU Specification - Textures and Samplers.
Trắc nghiệm ôn tập
Câu 1: Tại sao định dạng glTF lại lưu dữ liệu hình học (đỉnh, pháp tuyến) dưới dạng các mảng nhị phân thô thay vì định dạng JSON text như OBJ?
Trắc nghiệm ôn tập
Câu 2: Trong đặc tả JSON của glTF, mục accessor có vai trò gì?