Để tạo ra các mô hình 3D phức tạp, sẽ rất khó nếu chúng ta làm việc đó thủ công bằng cách kết hợp các Geometry cơ bản mà Three.js cung cấp. Thay vào đó, chúng ta nên tạo các mô hình này bằng các phần mềm chuyên dụng như Blender, Maya,… Sau đó, chúng ta sẽ load mô hình (model) vào trong cảnh của chúng ta rồi hiển thị.
Các định dạng file và các Loader
Có rất nhiều các định dạng file để lưu thông tin 3D model. Tuy nhiên, trong những năm gần đây, một định dạng file nổi lên như là chuẩn đó là glTF (GL Transmission Format). Định dạng này có ưu điểm là mã nguồn mở, được thiết kế tối ưu để hiển thị chứ không phải để chỉnh sửa, dung lượng nhẹ, tải nhanh, có đầy đủ các tính năng như Material, Animation. Ngoài ra, một số các định dạng khác cũng phổ biến như OBJ, FBX,…
Định dạng glTF lại có thể ở hai dạng:
- Dạng file JSON chuẩn
.gltf
không nén và có thể đi kèm thêm với các file.bin
- Dạng file binary
.glb
chứa tất cả dữ liệu trong chỉ một file
Tùy các định dạng file model, chúng ta sẽ có các Loader tương ứng. Ví dụ với định dạng glTF, OBJ sẽ có GLTFLoader, OBJLoader. Chúng ta sử dụng các Loader này tương tự cách chúng ta sử dụng các Loader khác đã tìm hiểu ở các bài trước như FontLoader, TTFLoader, SVGLoader, TextureLoader,…
Load model tĩnh
Đầu tiên, chúng ta cần một model mẫu. Có rất nhiều các model miễn phí mà bạn có thể sử dụng trên Internet như:
glTF-Sample-Models/2.0 at master · KhronosGroup/glTF-Sample-Models
3D Models for Free – Free3D.com
Poly Pizza: Free 3D models for everyone
Chúng ta nên sử dụng các model dạng low poly để có thể hiển thị nhẹ nhàng trên cả các thiết bị yếu.
Chúng ta sẽ sử dụng model flamingo (chim hồng hạc) từ trang chủ của Three.js. Model này cũng có Animation để có thể sử dụng ở phần tiếp theo.
https://threejs.org/examples/models/gltf/Flamingo.glb
Đầu tiên, chúng ta cũng tạo một cảnh 3D đơn giản với Scene, Camera, Renderer, AmbientLight, PointLight, OrbitControls. Chúng ta cũng tạo một vòng lặp render đơn giản.
Tiếp theo, chúng ta import class GLTFLoader từ file trong thư mục examples
, khởi tạo một đối tượng Loader, sau đó gọi phương thức loadAsync()
. Kết quả trả về sẽ là một đối tượng gltf
. Bây giờ chúng ta hãy thử in đối tượng này ra thôi xem nó bao gồm những gì. Sau đó, chúng ta bắt đầu vòng lặp render.
import{ GLTFLoader }from'three/examples/jsm/loaders/GLTFLoader.js';classThreejsExample{asyncloadModel(){const url ='https://threejs.org/examples/models/gltf/Flamingo.glb';const gltfLoader =newGLTFLoader();const gltf =await gltfLoader.loadAsync(url);
console.log(gltf);requestAnimationFrame(this.render.bind(this));}}
Kết quả in ra của đối tượng gltf
ở Console có dạng như sau:
{
animations: [AnimationClip],
asset: {version: '2.0', generator: 'THREE.GLTFExporter'},
cameras: [],
parser: GLTFParser {json: {…}, extensions: {…}, plugins: {…}, options: {…}, cache: {…}, …},
scene: Group {uuid: '7ED632DD-C02F-429A-84D7-DDA5A7D396EB', name: 'AuxScene', type: 'Group', parent: null, children: Array(0), …},
scenes: [Group],
userData: {}
}
Trong đó:
gltf.animations
là một mảng các AnimationClip, chứa các hoạt họa của modelgltf.asset
chứa các metadatagltf.cameras
là một mảng các Cameragltf.parser
chứa các thông tin kỹ thuật về GLTFParsergltf.scene
là một đối tượng Group chứa các Meshgltf.scenes
là một mảng các Group (định dạng glTF hỗ trợ nhiều cảnh trong một file)gltf.userData
có thể chứa các thông tin thêm
Hai thuộc tính mà chúng ta cần lưu ý, hay sử dụng là scene
và animations
.
Thuộc tính scene
là một đối tượng Group. Để lấy ra chỉ model chim hồng hạc thôi, chúng ta có thể làm như sau:
const mesh = gltf.scene;// const mesh = gltf.scene.children[0];
Tùy model cụ thể, chúng ta có thể lấy bằng gltf.scene
hoặc gltf.scene.children[0]
hoặc một node khác trên scenegraph. Hãy tìm hiểu nội dung log ở Console để lấy ra chính xác.
Tiếp theo, chúng ta có thể phải chỉnh lại scale
(nếu model trông quá lớn hoặc quá bé), position
, rotation
cho phù hợp với cảnh của chúng ta. Cuối cùng, thêm Mesh vào trong Scene.
const scale =0.005;
mesh.scale.multiplyScalar(scale);this.scene.add(mesh);
Chú ý: Khi Mesh được thêm vào Scene của chúng ta thì đồng thời nó sẽ bị loại bỏ khỏi gltf.scene
. Do đó nó có thể không hiển thị ở gltf.scene
nữa khi bạn sử dụng console.log()
.
Lỗi hiển thị model trên mobile
Lần đầu tiên khi tôi chạy ví dụ trên máy tính thì kết quả hiển thị bình thường, tuy nhiên trên mobile (điện thoại Android Oppo của tôi) thì không hiển thị ra gì. Log lỗi trên mobile như sau:
THREE.WebGLProgram: Shader Error 0 - VALIDATE_STATUS false
Program Info Log:
VERTEX
WebGL: INVALID_OPERATION: useProgram: program not valid
WebGL: INVALID_OPERATION: drawElements: no valid shader program in use
Lỗi này chỉ trên một số model (ví dụ model Flamingo.glb), không phải tất cả. Một số model khác vẫn chạy được. Lỗi này bị cả ở ví dụ trên trang chủ của Three.js.
three.js webgl – lights – hemisphere light
Để khắc phục lỗi trên mobile này, tạm thời chúng ta sẽ không sử dụng version 0.137.5 nữa mà quay lại dùng version cũ hơn là 0.132.2. Version mới nhất ở thời điểm viết bài này là 0.138.3 cũng không chạy được.
Chỉnh kích thước và căn giữa model
Trong nhiều trường hợp, khi download một model ở trên mạng và hiển thị ở cảnh của chúng ta, model sẽ bị quá lớn hoặc quá bé, và không hiển thị ở giữa màn hình. Chúng ta có thể chỉnh kích thước cho phù hợp và căn giữa một cách tự động bằng đoạn code sau:
resizeAndCenter(mesh){const box =newBox3().setFromObject(mesh);const size = box.getSize(newVector3());const maxSize = Math.max(size.x, size.y, size.z);const desizedSize =1.5;
mesh.scale.multiplyScalar(desizedSize / maxSize);
box.setFromObject(mesh);const center = box.getCenter(newVector3());
mesh.position.sub(center);}
Load model có animation
Một số model thường có thể đi kèm với Animation. Để có thể làm cho model chuyển động với Animation, chúng ta cần:
- Tạo một AnimationMixer với Mesh
- Lấy ra một đối tượng AnimationClip từ mảng
gltf.animations
- Tạo một AnimationAction từ AnimationMixer và AnimationClip
- Bắt đầu cho chuyển động với phương thức
play()
của AnimationAction - Trong vòng lặp render, cần gọi phương thức
update()
của AnimationMixer. Chúng ta cần truyền tham số là một khoảng thời gian delta. Chúng ta có thể lấy khoảng thời gian này từ một Clock.
setupAnimation(mesh, gltf){this.clock =newClock();this.mixer =newAnimationMixer(mesh);const clip = gltf.animations[0];const action =this.mixer.clipAction(clip);
action.play();}render(){// ...if(this.mixer){const delta =this.clock.getDelta();this.mixer.update(delta);}// ...}
Chúng ta đã đi đến cuối hành trình tìm hiểu cơ bản về Three.js. Hy vọng kiến thức trong các bài viết giúp bạn có cái nhìn tổng quan, sơ lược về Three.js, là nền tảng để bạn có thể tiếp tục tìm hiểu sâu hơn nữa về Three.js, khám phá các kỹ thuật nâng cao mới, tạo ra các cảnh 3D chân thực, ấn tượng.
Nội dung của tất cả các chương được tôi tổng hợp lại dưới một quyển ebook nhỏ định dạng EPUB, bạn có thể download ở địa chỉ sau:
Nguồn: viblo.asia