Android开发利用Three.js和WebView实现3D模型渲染

该文章已生成可运行项目,

        项目中需要渲染3D模型,最初采用的是google的filament,好用是好用,但是有3个痛点:一是会出现kotlin版本不兼容问题,二是apk大小陡增了17.5M,所以考虑换一种实现方式。

        而Three.js+原生WebView就完美解决了上述痛点,一是不需要依赖第三方库没有版本兼容问题,二是只需要引入少量js文件、一个html文件、极少量java代码,apk大小只增加了750k。

       文件总览:

        

        以下就是具体实现方式:

在assets中放置4个文件,three.min.js(核心库)、GLTFLoader.js(根据自己的模型文件类型选择)、OrbitControls.js(手势控制,放缩旋转之类)、index.html。

js库也可以通过cdn引入,那样会更加精简,但是这里必须使用133版本,否则有些代码不兼容。

js下载地址(防止cdn失效):【免费】v133:three.min.js+GLTFLoader.js+OrbitControls.js资源-优快云文库

index.html内容为:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Three.js 3D Model</title>
    <style>
        body { margin: 0; }
        canvas { display: block; }
    </style>
</head>
<body>
<!-- 引入 Three.js -->
<!--<script src="https://cdn.jsdelivr.net/npm/three@0.133.0/build/three.min.js"></script>-->
<!--<script src="https://cdn.jsdelivr.net/npm/three@0.133.0/examples/js/loaders/GLTFLoader.js"></script>-->
<!--<script src="https://cdn.jsdelivr.net/npm/three@0.133.0/examples/js/controls/OrbitControls.js"></script>-->
<script src="three.min.js"></script>
<script src="GLTFLoader.js"></script>
<script src="OrbitControls.js"></script>
<script>
       // 从 URL 参数中获取模型名称
        const urlParams = new URLSearchParams(window.location.search);
        const modelName = urlParams.get('model');
        // 场景、相机、渲染器
        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, // 启用抗锯齿
            powerPreference: 'high-performance' // 启用高性能模式
        });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(window.devicePixelRatio); // 使用设备的像素比例提高清晰度
        document.body.appendChild(renderer.domElement);

        // 添加环境光源,参数2为亮度值
        const ambientLight = new THREE.AmbientLight(0xffffff, 8);
        scene.add(ambientLight);

        // 加载 3D 模型
        const loader = new THREE.GLTFLoader();
        let model;
        loader.load(modelName, function (gltf) {
            model = gltf.scene;
            scene.add(model);
            // 计算模型的包围盒
            const box = new THREE.Box3().setFromObject(model);
            const size = new THREE.Vector3();
            box.getSize(size);

            // 计算模型的中心点
            const center = new THREE.Vector3();
            box.getCenter(center);
            model.position.sub(center);

            // 计算相机的位置
            const maxSize = Math.max(size.x, size.y, size.z);
            const fov = camera.fov * (Math.PI / 180);
            const distance = maxSize / (2 * Math.tan(fov / 2))*2;// 最后*2是为了缩小模型
            camera.position.set(0, 0, distance);
        }, undefined, function (error) {
            console.error('Error loading model:', error);
        });
        // 初始化 OrbitControls
        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        // 渲染循环
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            renderer.render(scene, camera);
        }
        animate();
</script>
</body>
</html>

自定义一个WebView:

package com.hivision.base.android.module.render.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.webkit.WebSettings;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hivision.base.android.module.render.R;
/**
 * @Author :        ZSC
 * @DATE :        2024/12/17 10:59
 * @Des :        3D渲染View
 */
public class Render3dView extends WebView {
    private Context context;
    private String model;

    public Render3dView(@NonNull Context context) {
        super(context);
        this.context = context;
        init();
    }

    public Render3dView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init(attrs);
        init();
    }

    private void init() {
        setBackgroundColor(Color.BLACK);
        WebSettings webSettings = getSettings();
        // 启用基本设置
        webSettings.setJavaScriptEnabled(true);
        webSettings.setAllowFileAccess(true); // 允许访问文件
        // 解决CORS问题的关键设置
        webSettings.setAllowFileAccessFromFileURLs(true); // 允许file协议跨域
        webSettings.setAllowUniversalAccessFromFileURLs(true); // 允许任意跨域访问
        webSettings.setAllowContentAccess(true); // 允许内容访问
        if (!TextUtils.isEmpty(model)){
            loadUrl("file:///android_asset/index.html?model=" + model); // 加载本地 HTML 文件
        }
    }

    private void init(AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Render3dView);
        model = typedArray.getString(R.styleable.Render3dView_model);
        typedArray.recycle();
    }

    public void loadModel(String name) {
        loadUrl("file:///android_asset/index.html?model=" + name);
    }

}

针对自定义WebView的xml自定义静态属性配置文件attrs.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="Render3dView">
        <attr name="model" format="string"></attr>
    </declare-styleable>
</resources>

也可以在Activity中对3D模型文件动态配置:

   private WebView myWebView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_render);
        render3dView = findViewById(R.id.sv_controller_device);
        render3dView.loadModel("cabinet.glb");
    }

记得在destroy中销毁WebView,否则重新加载url时会概率性白屏:

  @Override
    protected void onDestroy() {
        render3dView.destroy();
        super.onDestroy();
    }
本文章已经生成可运行项目
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值