在Android平台上使用Three.js优雅的加载3D模型

一、Three.js是什么?

Three.js是一个基于WebGL(Web Graphics Library)的3D图形渲染库,因为浏览器里最底层的3D API是WebGL。直接写会像写 OpenGL一样复杂,繁琐。Three.js 就是 WebGL 的封装层,提供了更高层次的接口,让你用几行代码就能创建出复杂的 3D 场景。

二、Three.js的核心概念

1.Scene(场景)

这是一个容器,一个无限大的虚拟空间。你之后创建的所有物体、光源、相机都必须被添加到这个场景中。

js 复制代码
const scene = new THREE.Scene();

2.Camera(相机)

它决定了我们从哪个角度、以何种视野去观察这个场景。最常用的是透视相机 PerspectiveCamera,它模拟了人眼的视觉效果(近大远小)。

js 复制代码
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

3.WebGLRenderer(渲染器)

渲染器会根据相机的位置和视野,将场景中的内容计算并绘制到一个HTML的元素上。

js 复制代码
const renderer = new THREE.WebGLRenderer({ antialias: true }); // antialias开启抗锯齿
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement); // 将canvas添加到页面

4.OrbitControls(控制器)

控制器可以让用户用鼠标或触摸来旋转、缩放、平移相机。

js 复制代码
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用阻尼效果,使旋转更平滑
controls.update();

5.Light光源

模拟自然光照、阴影、反光等效果

  • 环境光 AmbientLight: 提供一种无方向的、均匀的基础光照,确保场景中最暗的地方也不是纯黑。
  • 平行光 DirectionalLight: 模拟像太阳光一样的效果,从一个方向平行照射过来,可以产生阴影。
js 复制代码
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // 颜色, 强度
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7); // 设置光源位置
scene.add(directionalLight);

三、实战案例

核心分4大块:

1.Android原生:作为宿主,负责创建和管理WebView,处理原生UI交互,并准备3D模型文件(可以是本地,也可以是网络上获取的)。

2.WebView:负责加载本地的HTML和JavaScript文件。

3.JavaScript Bridge (@JavascriptInterface):这是原生代码与WebView中JavaScript代码双向通信的通道。

4.Assets:存放我们的index.html、Three.js库文件、模型文件以及核心的渲染逻辑main.js。

完整代码如下:

index.html主要是搭建一个网页环境,用于加载和执行main.js。

js 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>Three.js on Android</title>
    <style>
        body { margin: 0; overflow: hidden; }
        canvas { display: block; }
    </style>
</head>
<body>
<script type="importmap">
    {
        "imports": {
            "three": "https://unpkg.com/three@0.158.0/build/three.module.js",
            "three/addons/": "https://unpkg.com/three@0.158.0/examples/jsm/"
        }
    }
</script>

<script type="module" src="./main.js"></script>
</body>
</html>

main.js主要处理3D场景的创建、物体添加、动画循环等核心逻辑。

js 复制代码
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

//创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xeeeeee); // 设置背景色

//创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); // 75:场景范围
camera.position.set(0, 1, 5);

//创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement); // renderer.domElement 本质是canvas

//添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // 环境光
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1); // 平行光
directionalLight.position.set(5, 10, 7.5);
scene.add(directionalLight);

//添加控制器,允许用户用手势旋转、缩放模型
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用阻尼效果,使旋转更平滑
controls.update();


function getUrl() {
   const urlString = androidBridge.getUrl()
   const info = JSON.parse(androidBridge.getUrl())
   return info.url
}

//加载 3D 模型 (GLTF/GLB)
const loader = new GLTFLoader();
loader.load(getUrl(),
    function (gltf) {
        const model = gltf.scene;
        // 调整模型大小和位置
        model.scale.set(5, 5, 5); // 调整模型初始的大小
        model.position.set(0, 0, 0);
        model.rotation.y += Math.PI; // 修改模型的 rotation属性 ,GLB 模型 180 度旋转
        scene.add(model);
    },

    function (xhr) {
        console.log((xhr.loaded / xhr.total * 100) + '% loaded');
        androidBridge.sendDataToAndroid(xhr.loaded / xhr.total * 100);
    },
    
    function (error) {
        console.error('An error happened', error);
    }
);

// 动画循环 
function animate() {
    requestAnimationFrame(animate);

    controls.update(); // 更新控制器

    renderer.render(scene, camera);
}

animate();

//处理窗口大小变化
window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
});

AndroidBridge.kt主要是把从服务器获取的3d模型url传递到main.js中,然后再从main.js中获取加载进度回调到Android原生代码中。

js 复制代码
class AndroidBridge {
    private var info: String? = null


    private var progressCallback: ProgressCallback? = null

    // 定义回调接口
    interface ProgressCallback {
        fun onProgressUpdate(progress: Int)
    }

    // 设置回调
    fun setProgressCallback(callback: ProgressCallback) {
        this.progressCallback = callback
    }

    @JavascriptInterface
    fun getUrl(): String {
        return info.orEmpty()
    }

    fun setUrl(info: String?) {
        this.info = info
    }

    @JavascriptInterface
    fun sendDataToAndroid(progress: Int) {
        progressCallback?.onProgressUpdate(progress)
    }
}

MainActivity.kt主要是提供webview,以及从服务器获取3D模型url,完成和js的交互。

js 复制代码
1.初始化WebView
val webSettings = settings
webSettings.javaScriptEnabled = true
webSettings.allowFileAccess = true
webSettings.allowFileAccessFromFileURLs = true
webSettings.allowContentAccess = true
webSettings.domStorageEnabled = true
webSettings.builtInZoomControls = false
webSettings.displayZoomControls = false
webSettings.allowUniversalAccessFromFileURLs = true
setWebChromeClient(object : WebChromeClient() {
    override fun onConsoleMessage(cm: ConsoleMessage): Boolean {
        Log.d("three", cm.message() + " at " + cm.sourceId() + ":" + cm.lineNumber())
        return true
    }
})
    
2.获取3D模型url
bindViewModel {
    ai3Data.observe(this@WebViewActivity) {
        if (!TextUtils.isEmpty(it.data?.glb_url)) {
            bindView {
                val glbUrl = it.data?.glb_url
                val bridge = AndroidBridge()
                webView.addJavascriptInterface(bridge,"androidBridge")
                bridge.setUrl("{"url":"$glbUrl"}")
                webView.loadUrl("file:///android_asset/threejs/index.html")
                bridge.setProgressCallback(object :AndroidBridge.ProgressCallback {
                    override fun onProgressUpdate(progress: Int) {
                        runOnUiThread {
                            if (progress < 100) {
                                tvStateQuest.text = "glb文件加载进度$progress%"
                            } else {
                                tvStateQuest.text = "glb文件加载进度$progress%"
                                tvStateQuest.text = "glb文件加载结束"
                            }
                        }
                    }
                })
            }
        }
    }
}

以上代码就是利用WebView作为桥梁,驱动Three.js,在Android原生应用中渲染3D模型的例子。

四、Three.js还能做什么?

1.网页3D游戏

2.炫酷的动画特效

3.3D图表的数据可视化

4.电商网站的3D产品展示

5.房产APP中房屋的3D展示

相关推荐
小雨青年9 分钟前
MateChat 进阶实战:打造零后端、隐私安全的“端侧记忆”智能体
前端·华为·ai·华为云·状态模式
勇气要爆发22 分钟前
问:ES5和ES6的区别
前端·ecmascript·es6
秃了也弱了。29 分钟前
MySQL空间函数详解,MySQL记录经纬度并进行计算
android·数据库·mysql
永不停歇的蜗牛1 小时前
Maven的POM文件相关标签作用
服务器·前端·maven
.豆鲨包1 小时前
【Android】Binder机制浅析
android·binder
芳草萋萋鹦鹉洲哦1 小时前
【vue/js】文字超长悬停显示的几种方式
前端·javascript·vue.js
HIT_Weston1 小时前
47、【Ubuntu】【Gitlab】拉出内网 Web 服务:Nginx 事件驱动分析(一)
前端·ubuntu·gitlab
开发者小天2 小时前
React中的 闭包陷阱
前端·javascript·react.js
翔云 OCR API2 小时前
承兑汇票识别接口技术解析-开发者接口
开发语言·前端·数据库·人工智能·ocr
涔溪2 小时前
Vue3 的核心语法
前端·vue.js·typescript