前言
随着HarmonyOS的发展,3D渲染需求在鸿蒙应用中越来越常见。本文将详细介绍如何在ArkTS项目中集成Three.js,实现3D模型的渲染。我们将从环境搭建开始,逐步深入到具体实现和优化技巧。
环境准备
1. 项目初始化
首先,确保你已经安装了最新版本的DevEco Studio,然后创建一个新的ArkTS项目:
# 确保已经安装了最新版本的ohpm
npm install -g @ohos/ohpm
# 创建新项目
ohpm init
2. 安装依赖
在项目根目录下的oh-package.json5
中添加Three.js依赖:
{
"license": "ISC",
"devDependencies": {},
"dependencies": {
"three": "^0.159.0",
"@types/three": "^0.159.0"
}
}
然后安装依赖:
ohpm install
基础实现
1. 创建WebGL组件
在pages
目录下创建ThreeDemo.ets
文件:
@Entry
@Component
struct ThreeDemo {
private webController: WebController = new WebController()
private threeJsCode: string = ''
aboutToAppear() {
// 初始化Three.js代码
this.initThreeJsCode()
}
build() {
Column() {
Web({
src: 'data:text/html;charset=utf-8,' + encodeURIComponent(this.threeJsCode),
controller: this.webController
})
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
}
private initThreeJsCode() {
this.threeJsCode = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Three.js in HarmonyOS</title>
<style>
body { margin: 0; }
canvas { width: 100%; height: 100% }
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r159/three.min.js"></script>
</head>
<body>
<script>
// Three.js初始化代码
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 创建一个立方体
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 添加光源
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1);
scene.add(light);
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
camera.position.z = 5;
// 动画循环
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
// 响应窗口大小变化
window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
</script>
</body>
</html>
`
}
}
2. 加载3D模型
创建一个新的方法来加载GLTF模型:
private initThreeJsCode() {
this.threeJsCode = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Three.js in HarmonyOS</title>
<style>
body { margin: 0; }
canvas { width: 100%; height: 100% }
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r159/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.159.0/examples/jsm/loaders/GLTFLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.159.0/examples/jsm/controls/OrbitControls.js"></script>
</head>
<body>
<script>
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
// 添加轨道控制器
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// 添加光源
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1);
scene.add(light);
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
camera.position.z = 5;
// 加载GLTF模型
const loader = new THREE.GLTFLoader();
loader.load(
'model.gltf', // 模型URL
function (gltf) {
scene.add(gltf.scene);
// 自动调整相机位置以适应模型
const box = new THREE.Box3().setFromObject(gltf.scene);
const center = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());
const maxDim = Math.max(size.x, size.y, size.z);
const fov = camera.fov * (Math.PI / 180);
let cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2));
camera.position.z = cameraZ * 1.5;
camera.lookAt(center);
controls.target.copy(center);
controls.update();
},
function (xhr) {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
},
function (error) {
console.error('An error occurred loading the model:', error);
}
);
// 动画循环
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
// 响应窗口大小变化
window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
</script>
</body>
</html>
`
}
高级功能实现
1. 与ArkTS交互
创建一个桥接接口来实现ArkTS和Three.js的通信:
@Entry
@Component
struct ThreeDemo {
private webController: WebController = new WebController()
@State private modelLoaded: boolean = false
build() {
Column() {
Web({
src: 'data:text/html;charset=utf-8,' + encodeURIComponent(this.threeJsCode),
controller: this.webController
})
.width('100%')
.height('80%')
.onMessageEvent((event: string) => {
const message = JSON.parse(event);
if (message.type === 'modelLoaded') {
this.modelLoaded = true;
}
})
if (this.modelLoaded) {
Row() {
Button('旋转模型')
.onClick(() => {
this.webController.runJavaScript('rotateModel()');
})
Button('重置视角')
.onClick(() => {
this.webController.runJavaScript('resetCamera()');
})
}
.width('100%')
.height('20%')
.justifyContent(FlexAlign.SpaceAround)
}
}
}
private initThreeJsCode() {
// 添加与ArkTS通信的方法
const bridgeCode = `
function rotateModel() {
scene.traverse((object) => {
if (object.isMesh) {
object.rotation.y += Math.PI / 2;
}
});
}
function resetCamera() {
camera.position.set(0, 0, 5);
camera.lookAt(0, 0, 0);
controls.target.set(0, 0, 0);
controls.update();
}
// 模型加载完成后通知ArkTS
loader.load(
'model.gltf',
function (gltf) {
scene.add(gltf.scene);
window.postMessage(JSON.stringify({ type: 'modelLoaded' }));
}
);
`;
// 将桥接代码添加到原有代码中
this.threeJsCode = // ... 原有代码 ... + bridgeCode;
}
}
2. 性能优化
添加性能优化相关代码:
private initThreeJsCode() {
const optimizationCode = `
// 使用实例化渲染优化大量相似对象
const instancedMesh = new THREE.InstancedMesh(
geometry,
material,
1000 // 实例数量
);
// 设置每个实例的矩阵
const matrix = new THREE.Matrix4();
for (let i = 0; i < 1000; i++) {
matrix.setPosition(
Math.random() * 100 - 50,
Math.random() * 100 - 50,
Math.random() * 100 - 50
);
instancedMesh.setMatrixAt(i, matrix);
}
// 使用对象池优化频繁创建的对象
const vectorPool = [];
for (let i = 0; i < 1000; i++) {
vectorPool.push(new THREE.Vector3());
}
let vectorIndex = 0;
function getVector() {
const vector = vectorPool[vectorIndex];
vectorIndex = (vectorIndex + 1) % vectorPool.length;
return vector;
}
// 使用分层细节(LOD)优化远近物体
const lod = new THREE.LOD();
const highDetailGeometry = new THREE.BoxGeometry(1, 1, 1, 16, 16, 16);
const mediumDetailGeometry = new THREE.BoxGeometry(1, 1, 1, 8, 8, 8);
const lowDetailGeometry = new THREE.BoxGeometry(1, 1, 1, 4, 4, 4);
lod.addLevel(new THREE.Mesh(highDetailGeometry, material), 10);
lod.addLevel(new THREE.Mesh(mediumDetailGeometry, material), 50);
lod.addLevel(new THREE.Mesh(lowDetailGeometry, material), 100);
scene.add(lod);
// 使用WebGL压缩纹理
renderer.capabilities.isWebGL2 && renderer.getContext().getExtension('WEBGL_compressed_texture_astc');
// 使用裁剪平面优化渲染
const clippingPlanes = [
new THREE.Plane(new THREE.Vector3(1, 0, 0), 0),
new THREE.Plane(new THREE.Vector3(-1, 0, 0), 0)
];
renderer.clippingPlanes = clippingPlanes;
`;
this.threeJsCode = // ... 原有代码 ... + optimizationCode;
}
3. 后处理效果
添加后处理效果相关代码:
private initThreeJsCode() {
const postProcessingCode = `
// 引入后处理模块
const composer = new THREE.EffectComposer(renderer);
// 添加渲染通道
const renderPass = new THREE.RenderPass(scene, camera);
composer.addPass(renderPass);
// 添加抗锯齿通道
const fxaaPass = new THREE.ShaderPass(THREE.FXAAShader);
composer.addPass(fxaaPass);
// 添加泛光效果
const bloomPass = new THREE.UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.5, // 强度
0.4, // 半径
0.85 // 阈值
);
composer.addPass(bloomPass);
// 更新动画循环
function animate() {
requestAnimationFrame(animate);
controls.update();
composer.render();
}
`;
this.threeJsCode = // ... 原有代码 ... + postProcessingCode;
}