在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展示

相关推荐
冴羽2 小时前
能让 GitHub 删除泄露的苹果源码还有 8000 多个相关仓库的 DMCA 是什么?
前端·javascript·react.js
悟能不能悟2 小时前
jsp怎么拿到url参数
java·前端·javascript
带电的小王2 小时前
Android设备:无busybox工具解决
android·busybox
程序猿小蒜2 小时前
基于SpringBoot的企业资产管理系统开发与设计
java·前端·spring boot·后端·spring
Mapmost2 小时前
零代码+三维仿真!实现自然灾害的可视化模拟与精准预警
前端
程序猿_极客2 小时前
JavaScript 的 Web APIs 入门到实战全总结(day7):从数据处理到交互落地的全链路实战(附实战案例代码)
开发语言·前端·javascript·交互·web apis 入门到实战
suzumiyahr2 小时前
用awesome-digital-human-live2d创建属于自己的数字人
前端·人工智能·后端
萧曵 丶2 小时前
Python 字符串、列表、元组、字典、集合常用函数
开发语言·前端·python
申阳2 小时前
Day 10:08. 基于Nuxt开发博客项目-关于我页面开发
前端·后端·程序员