threejs 绘制地球、飞机、轨迹

13

threejs官网:https://threejs.org/

首先我们来看下要实现的效果

图片描述

这个缩小后的图片,下面我们来看下近距离的动态效果。。

图片描述

效果比较简陋,需要后期再处理。。。

下面进入主题,代码篇。。

HTML部分:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>全球航班</title>
    
    <style>
        html{overflow: hidden;}
        body { margin: 0;}
    </style>

    <script src="js/jquery.min.js"></script>
</head>
<body>
    <!-- 地国 -->
    <div id="zh_globe_container"></div> <!-- 容器 -->
    
    <script src="js/threejs/Detector.js"></script> <!-- webGL浏览器支持检测 -->
    <script src="js/threejs/three.min.js"></script> <!-- 核心js -->
    <script src="js/threejs/stats.min.js"></script> <!-- 性能测试 -->
    <script src="js/threejs/OrbitControls.js"></script> <!-- 地球控制 -->
    <script src="js/socketio-1.4.5.js"></script> <!-- socket -->

    <script src="js/globe.js"></script> <!--  -->
</body>
</html>

JS部分(globe.js)

1、实现地球
地球贴图(可以在网上下载)
图片描述

// 地球
function globe() {
    var globeTextureLoader = new THREE.TextureLoader();
    globeTextureLoader.load('images/textures/earth.jpg', function (texture) {
        var globeGgeometry = new THREE.SphereGeometry(200, 100, 100);
        var globeMaterial = new THREE.MeshStandardMaterial({map: texture});
        var globeMesh = new THREE.Mesh(globeGgeometry, globeMaterial);
        group.add(globeMesh);
        group.rotation.x = THREE.Math.degToRad(35);
        group.rotation.y = THREE.Math.degToRad(170);
    });
}

2、添加球面光源(这里使用的是半球光)

// 光
function lights() {
    var hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x333333, 2);
    hemisphereLight.position.x = 0;
    hemisphereLight.position.y = 0;
    hemisphereLight.position.z = -200;
    group.add(hemisphereLight);
}

3、添加星点

// 星点
function stars() {
    var starsGeometry = new THREE.Geometry();
    for (var i = 0; i < 2000; i ++) {
        var starVector = new THREE.Vector3(
            THREE.Math.randFloatSpread(2000),
            THREE.Math.randFloatSpread(2000),
            THREE.Math.randFloatSpread(2000)
        );
        starsGeometry.vertices.push(starVector);
    }
    var starsMaterial = new THREE.PointsMaterial({color: 0x888888})
    var starsPoint = new THREE.Points(starsGeometry, starsMaterial);
    group.add(starsPoint);
}

4、添加飞机

这里需要我们把 经纬度坐标 转成 xyz 坐标

// 获取position
function getPosition(lng, lat, alt) {
    var phi = (90-lat)*(Math.PI/180),
        theta = (lng+180)*(Math.PI/180),
        radius = alt+200,
        x = -(radius * Math.sin(phi) * Math.cos(theta)),
        z = (radius * Math.sin(phi) * Math.sin(theta)),
        y = (radius * Math.cos(phi));
    return {x: x, y: y, z: z};
}

画飞机

// 飞机形状(不想画的,可以下载个 飞机模型 使用加载器加载进来)
var planeShape = new THREE.Shape();
planeShape.moveTo( 0, 0);
planeShape.lineTo(0.2, -0.2);
planeShape.lineTo(0.2, -1.3);
planeShape.lineTo(1.6,-2.7);
planeShape.lineTo(1.6,-3);
planeShape.lineTo(0.2, -2.1);
planeShape.lineTo(0.2, -3);
planeShape.lineTo(0.5, -3.4);
planeShape.lineTo(0.5, -3.7);
planeShape.lineTo(0, -3.3);
planeShape.lineTo(-0.5, -3.7);
planeShape.lineTo(-0.5, -3.4);
planeShape.lineTo(-0.2, -3);
planeShape.lineTo(-0.2, -2.1);
planeShape.lineTo(-1.6,-3);
planeShape.lineTo(-1.6,-2.7);
planeShape.lineTo(-0.2, -1.3);
planeShape.lineTo(-0.2, -0.2);
var planeGeometry = new THREE.ShapeGeometry(planeShape);
// 飞机材质
var planeMaterial = new THREE.MeshPhongMaterial({color: 0x0FB4DD, side: THREE.DoubleSide, depthTest: true});

depthTest作用是能否透过球体看到飞机,如果是false则旋转到球体另一面也能看到飞机

添加飞机

// 添加飞机
function addPlane(item) {
    if(item.anum && item.lng && item.lat) {
        var plane = new THREE.Mesh(planeGeometry, planeMaterial);
        // 旋转
        plane.rotation.z = THREE.Math.degToRad(item.ang);
        // 定位
        var position = getPosition(item.lng, item.lat, 5);
        plane.position.set(position.x, position.y, position.z);
        // 显示/隐藏
        // plane.visible = false;
        // 保存
        planeMarkers[item.anum] = plane;
        // 添加到场景
        group.add(plane);
        // 绘制历史轨迹
        drawHistoryTrack(item.anum);
    }
}

绘制轨迹(使用socket来获取的飞行轨迹经纬度坐标点)

// 时间段
var curTime = Date.parse(new Date())/1000;
var depTime = curTime - 30*60;
// 轨迹线质
var trackMaterial = new THREE.LineBasicMaterial({color : 0x1B94B1});
// 绘制历史轨迹
function drawHistoryTrack(anum) {
    socket.emit("fullPath", anum, depTime, curTime, function(status, data){
        if(status) {
            var dLength = data.length;
            if(dLength>=2) {
                var trackCoordArr = [];
                for(var i=0; i<dLength; i++) {
                    if(data[i].lng && data[i].alt) {
                        trackCoordArr.push({lng: data[i].lng, lat: data[i].lat});
                    }
                }

                var tcaLength = trackCoordArr.length;
                if(tcaLength>=2) {
                    var tcaHalfLength = Math.ceil(tcaLength/2),
                        vertexArr = [];

                    // 这里只取了三个点(起点、中点、终点)
                    var p1 = getPosition(trackCoordArr[0].lng, trackCoordArr[0].lat, 0),
                        p2 = getPosition(trackCoordArr[tcaHalfLength].lng, trackCoordArr[tcaHalfLength].lat, tcaLength*0.01),
                        p3 = getPosition(trackCoordArr[tcaLength-1].lng, trackCoordArr[tcaLength-1].lat, 0);

                    var trackCurve = new THREE.CatmullRomCurve3([
                        new THREE.Vector3(p1.x, p1.y, p1.z),
                        new THREE.Vector3(p2.x, p2.y, p2.z),
                        new THREE.Vector3(p3.x, p3.y, p3.z)
                    ]);

                    var trackGeometry = new THREE.Geometry(),
                        verticesArr = trackCurve.getPoints(tcaLength);

                    trackGeometry.vertices = verticesArr;
                    
                    var trackLine = new THREE.Line(trackGeometry, trackMaterial);
                    group.add(trackLine);

                    // 动画点
                    addLightPoint(p1, tcaLength, verticesArr);
                }
            }
        }
    });
}

如果要绘制所有点,且头尾是在球面上的曲线,则需要两次循环

var tcaRemainLength = tcaLength-tcaHalfLength
for(var j=0; j<tcaHalfLength; j++) { // 前一半
    var p1 = getPosition(trackCoordArr[j].lng, trackCoordArr[j].lat, j*0.05);
    vertexArr.push(new THREE.Vector3(p1.x, p1.y, p1.z));    
}
for(var k=tcaRemainLength; k>0; k--) { // 后一半
    var p2 = getPosition(trackCoordArr[tcaLength-k].lng, trackCoordArr[tcaLength-k].lat, k*0.05);
    vertexArr.push(new THREE.Vector3(p2.x, p2.y, p2.z));    
}

var trackCurve = new THREE.CatmullRomCurve3(vertexArr);

这个部分看看就行了。。

光点动画

// 点动画
var pointGeometry = new THREE.SphereGeometry(0.2, 20, 20);
var pointMaterial = new THREE.MeshBasicMaterial({color: 0x40E0D0});
function addLightPoint(pos, coordsNum ,verArr) {    
    var pointMesh = new THREE.Mesh(pointGeometry, pointMaterial);
    pointMesh.position.set(pos.x, pos.y, pos.z);
    group.add(pointMesh);

    var index = 0;
    function pointAnimate() {
        index++;
        if(index>coordsNum) {
            index = 0;
        }
        pointMesh.position.set(verArr[index].x, verArr[index].y, verArr[index].z);
        requestAnimationFrame(pointAnimate);
    }
    pointAnimate();
}

这个点使用的是sphere,,当然也可以用顶点来实现,如下

var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(0, 0, 0))
geometry.colors.push(new THREE.Color(0xffffff));

var material = new THREE.PointsMaterial({size: 1, vertexColors: THREE.VertexColors, opacity: 0.75, sizeAttenuation: true, transparent: true});
var point = new THREE.Points(geometry, material);
point.position.set(x, y, z);
group.add(point);

另外不想用光点动画的话,也可以用线动画,实现原理是不断更新顶点坐标,如下

var curveGeometry = new THREE.Geometry(); 
var curveData = new THREE.CatmullRomCurve3(verArr.slice(0, 10));  
curveGeometry.vertices = curveData.getPoints(10);

var curveMaterial = new THREE.LineBasicMaterial({color: 0x40E0D0});
var curveLine = new THREE.Line(curveGeometry, curveMaterial);
group.add(curveLine);

var index = 0;
function lineAnimate() {
    index++;
    if(index>coordsNum-10) {
        index = 0;
    }
    var offsetData = verArr.slice(index, 10+index);
    if(offsetData.length > 0) {
        curveData = new THREE.CatmullRomCurve3(offsetData);  
           curveLine.geometry.vertices = curveData.getPoints(10);
        curveLine.geometry.verticesNeedUpdate = true;
    }
    requestAnimationFrame(lineAnimate);
}
lineAnimate();

最后就是布置场景和事件了

// 初始化
function init() {
    container = document.getElementById('zh_globe_container');

    scene = new THREE.Scene();
    var bgTexture = new THREE.TextureLoader().load("images/textures/starfield.jpg");
    scene.background = bgTexture;

    camera = new THREE.PerspectiveCamera(50, winWth/winHgt, 1, 2000);
    camera.up.x = 0;
    camera.up.y = 1;
    camera.up.z = 0;
    camera.position.x = 0;
    camera.position.y = 0;
    camera.position.z = 400;
    camera.lookAt(0,0,0);

    group = new THREE.Group();
    scene.add(group);

    // 地球    
    globe();

    // 飞机
    plane();

    // 星点
    stars();

    // 半球光
    lights();

    // 渲染器
    renderer = new THREE.WebGLRenderer({antialias: true, preserveDrawingBuffer: true});
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(winWth, winHgt);
    container.appendChild(renderer.domElement);

    // 盘旋控制
    var orbitControl = new THREE.OrbitControls(camera, renderer.domElement);
    orbitControl.minDistrance = 20;
    orbitControl.maxDistrance = 50;
    orbitControl.maxPolarAngle = Math.PI/2;

    // 性能测试
    stats = new Stats();
    container.appendChild(stats.dom);

    // resize事件
    window.addEventListener('resize', onWindowResize, false);
}

// 窗口大小改变
function onWindowResize() {
    camera.aspect = window.innerWidth/window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}

// 渲染
function render() {
    group.rotation.y -= 0.0005;
    renderer.render(scene, camera);
}

// 动画
function animate() {
    requestAnimationFrame(animate);
    render();
    stats.update();
}

init();
animate();    

完整代码:

var log = console.log.bind(console);

var globeObj = (function() {
    'use strict';

    // 判断浏览器是否支持webgl
    if(!Detector.webgl) Detector.addGetWebGLMessage();

    var container, stats;
    var camera, scene, renderer;
    var group;
    var mouseX = 0, mouseY = 0;
    var winWth = window.innerWidth, winHgt = window.innerHeight;

    // 获取position
    function getPosition(lng, lat, alt) {
        var phi = (90-lat)*(Math.PI/180),
            theta = (lng+180)*(Math.PI/180),
            radius = alt+200,
            x = -(radius * Math.sin(phi) * Math.cos(theta)),
            z = (radius * Math.sin(phi) * Math.sin(theta)),
            y = (radius * Math.cos(phi));
        return {x: x, y: y, z: z};
    }

    // 飞机
    function plane() {
        var socket = io('https://loc.variflight.com/*****此处接口地址不能给了', {transports: ['websocket']});

        var clientBounds = [52.793056,72.427908,2.970897,135.181814];

        // 连接
        socket.on('connect', function() {
            socket.emit("sub", clientBounds, -1, '', function(){});
        });

        // 飞机标记
        var planeMarkers = {};

        // 飞机形状
        var planeShape = new THREE.Shape();
        planeShape.moveTo( 0, 0);
        planeShape.lineTo(0.2, -0.2);
        planeShape.lineTo(0.2, -1.3);
        planeShape.lineTo(1.6,-2.7);
        planeShape.lineTo(1.6,-3);
        planeShape.lineTo(0.2, -2.1);
        planeShape.lineTo(0.2, -3);
        planeShape.lineTo(0.5, -3.4);
        planeShape.lineTo(0.5, -3.7);
        planeShape.lineTo(0, -3.3);
        planeShape.lineTo(-0.5, -3.7);
        planeShape.lineTo(-0.5, -3.4);
        planeShape.lineTo(-0.2, -3);
        planeShape.lineTo(-0.2, -2.1);
        planeShape.lineTo(-1.6,-3);
        planeShape.lineTo(-1.6,-2.7);
        planeShape.lineTo(-0.2, -1.3);
        planeShape.lineTo(-0.2, -0.2);
        var planeGeometry = new THREE.ShapeGeometry(planeShape);
        // 飞机材质
        var planeMaterial = new THREE.MeshPhongMaterial({color: 0x0FB4DD, side: THREE.DoubleSide, depthTest: true});
        // 添加飞机
        function addPlane(item) {
            if(item.anum && item.lng && item.lat) {
                var plane = new THREE.Mesh(planeGeometry, planeMaterial);
                // 旋转
                plane.rotation.z = THREE.Math.degToRad(item.ang);
                // 定位
                var position = getPosition(item.lng, item.lat, 5);
                plane.position.set(position.x, position.y, position.z);
                // 显示/隐藏
                // plane.visible = false;
                // 保存
                planeMarkers[item.anum] = plane;
                // 添加到场景
                group.add(plane);
                // 绘制历史轨迹
                drawHistoryTrack(item.anum);
            }
        }

        // 时间段
        var curTime = Date.parse(new Date())/1000;
        var depTime = curTime - 30*60;
        // 轨迹线质
        var trackMaterial = new THREE.LineBasicMaterial({color : 0x1B94B1});
        // 绘制历史轨迹
        function drawHistoryTrack(anum) {
            socket.emit("fullPath", anum, depTime, curTime, function(status, data){
                if(status) {
                    var dLength = data.length;
                    if(dLength>=2) {
                        var trackCoordArr = [];
                        for(var i=0; i<dLength; i++) {
                            if(data[i].lng && data[i].alt) {
                                trackCoordArr.push({lng: data[i].lng, lat: data[i].lat});
                            }
                        }

                        var tcaLength = trackCoordArr.length;
                        if(tcaLength>=2) {
                            var tcaHalfLength = Math.ceil(tcaLength/2),
                                tcaRemainLength = tcaLength-tcaHalfLength,
                                vertexArr = [];

                            /* 所有点
                            for(var j=0; j<tcaHalfLength; j++) {
                                var p1 = getPosition(trackCoordArr[j].lng, trackCoordArr[j].lat, j*0.05);
                                vertexArr.push(new THREE.Vector3(p1.x, p1.y, p1.z));    
                            }
                            for(var k=tcaRemainLength; k>0; k--) {
                                var p2 = getPosition(trackCoordArr[tcaLength-k].lng, trackCoordArr[tcaLength-k].lat, k*0.05);
                                vertexArr.push(new THREE.Vector3(p2.x, p2.y, p2.z));    
                            }
                            
                            var trackCurve = new THREE.CatmullRomCurve3(vertexArr);
                            */

                            // 三个点
                            var p1 = getPosition(trackCoordArr[0].lng, trackCoordArr[0].lat, 0),
                                p2 = getPosition(trackCoordArr[tcaHalfLength].lng, trackCoordArr[tcaHalfLength].lat, tcaLength*0.01),
                                p3 = getPosition(trackCoordArr[tcaLength-1].lng, trackCoordArr[tcaLength-1].lat, 0);

                            var trackCurve = new THREE.CatmullRomCurve3([
                                new THREE.Vector3(p1.x, p1.y, p1.z),
                                new THREE.Vector3(p2.x, p2.y, p2.z),
                                new THREE.Vector3(p3.x, p3.y, p3.z)
                            ]);

                            var trackGeometry = new THREE.Geometry(),
                                verticesArr = trackCurve.getPoints(tcaLength);

                            trackGeometry.vertices = verticesArr;
                            
                            var trackLine = new THREE.Line(trackGeometry, trackMaterial);
                            group.add(trackLine);

                            // 动画点
                            addLightPoint(p1, tcaLength, verticesArr);
                        }
                    }
                }
            });
        }

        // 点动画
        var pointGeometry = new THREE.SphereGeometry(0.2, 20, 20);
        var pointMaterial = new THREE.MeshBasicMaterial({color: 0x40E0D0});
        function addLightPoint(pos, coordsNum ,verArr) {
            var pointMesh = new THREE.Mesh(pointGeometry, pointMaterial);
            pointMesh.position.set(pos.x, pos.y, pos.z);
            group.add(pointMesh);

            var index = 0;
            function pointAnimate() {
                index++;
                if(index>coordsNum) {
                    index = 0;
                }
                pointMesh.position.set(verArr[index].x, verArr[index].y, verArr[index].z);
                requestAnimationFrame(pointAnimate);
            }
            pointAnimate();

            /*var curveGeometry = new THREE.Geometry(); 
            var curveData = new THREE.CatmullRomCurve3(verArr.slice(0, 10));  
            curveGeometry.vertices = curveData.getPoints(10);

            var curveMaterial = new THREE.LineBasicMaterial({color: 0x40E0D0});
            var curveLine = new THREE.Line(curveGeometry, curveMaterial);
            group.add(curveLine);

            var index = 0;
            function lineAnimate() {
                index++;
                if(index>coordsNum-10) {
                    index = 0;
                }
                var offsetData = verArr.slice(index, 10+index);
                if(offsetData.length > 0) {
                    curveData = new THREE.CatmullRomCurve3(offsetData);  
                       curveLine.geometry.vertices = curveData.getPoints(10);
                    curveLine.geometry.verticesNeedUpdate = true;
                }
                requestAnimationFrame(lineAnimate);
            }
            lineAnimate();*/
        }

        // 监听数据(添加并更新)
        socket.on('~', function(res) {
            if($.isEmptyObject(planeMarkers)) {
                $.each(res, function(i, item) {
                    addPlane(item);
                });
            } else {
                $.each(res, function(i, item) {
                    if(planeMarkers[item.anum]) {
                        if(item.lng && item.lat) {
                            var pos = getPosition(item.lng, item.lat, 5);
                            planeMarkers[item.anum].position.set(pos.x, pos.y, pos.z);
                        }
                    } else {
                        addPlane(item);
                    }
                });
            }
        });
    }

    // 地球
    function globe() {
        var globeTextureLoader = new THREE.TextureLoader();
        globeTextureLoader.load('images/textures/earth.jpg', function (texture) {
            var globeGgeometry = new THREE.SphereGeometry(200, 100, 100);
            var globeMaterial = new THREE.MeshStandardMaterial({map: texture});
            var globeMesh = new THREE.Mesh(globeGgeometry, globeMaterial);
            group.add(globeMesh);
            group.rotation.x = THREE.Math.degToRad(35);
            group.rotation.y = THREE.Math.degToRad(170);
        });
    }

    // 星点
    function stars() {
        var starsGeometry = new THREE.Geometry();
        for (var i = 0; i < 2000; i ++) {
            var starVector = new THREE.Vector3(
                THREE.Math.randFloatSpread(2000),
                THREE.Math.randFloatSpread(2000),
                THREE.Math.randFloatSpread(2000)
            );
            starsGeometry.vertices.push(starVector);
        }
        var starsMaterial = new THREE.PointsMaterial({color: 0x888888})
        var starsPoint = new THREE.Points(starsGeometry, starsMaterial);
        group.add(starsPoint);
    }

    // 光
    function lights() {
        var hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x333333, 2);
        hemisphereLight.position.x = 0;
        hemisphereLight.position.y = 0;
        hemisphereLight.position.z = -200;
        group.add(hemisphereLight);
    }

    // 初始化
    function init() {
        container = document.getElementById('zh_globe_container');

        scene = new THREE.Scene();
        var bgTexture = new THREE.TextureLoader().load("images/textures/starfield.jpg");
        scene.background = bgTexture;

        camera = new THREE.PerspectiveCamera(50, winWth/winHgt, 1, 2000);
        camera.up.x = 0;
        camera.up.y = 1;
        camera.up.z = 0;
        camera.position.x = 0;
        camera.position.y = 0;
        camera.position.z = 400;
        camera.lookAt(0,0,0);

        group = new THREE.Group();
        scene.add(group);

        // 地球    
        globe();

        // 飞机
        plane();

        // 星点
        stars();
    
        // 半球光
        lights();

        // 渲染器
        renderer = new THREE.WebGLRenderer({antialias: true, preserveDrawingBuffer: true});
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(winWth, winHgt);
        container.appendChild(renderer.domElement);

        // 盘旋控制
        var orbitControl = new THREE.OrbitControls(camera, renderer.domElement);
        orbitControl.minDistrance = 20;
        orbitControl.maxDistrance = 50;
        orbitControl.maxPolarAngle = Math.PI/2;

        // 性能测试
        stats = new Stats();
        container.appendChild(stats.dom);

        // resize事件
        window.addEventListener('resize', onWindowResize, false);
    }

    // 窗口大小改变
    function onWindowResize() {
        camera.aspect = window.innerWidth/window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    }
    
    // 渲染
    function render() {
        group.rotation.y -= 0.0005;
        renderer.render(scene, camera);
    }

    // 动画
    function animate() {
        requestAnimationFrame(animate);
        render();
        stats.update();
    }

    init();
    animate();    
})();

场景背景图
图片描述

谢谢关注,如有帮助请推荐一下!

你可能感兴趣的

13 条评论
小栗子 · 2017年03月28日

请教大神,是不是我用的three的版本问题,报错THREE.Group is not a constructor.

回复

0

可能是吧

zhangqh 作者 · 2017年03月28日
0

@zhangqh 我能加下您的qq吗?我的是344219323,有些问题请教您

小栗子 · 2017年03月28日
0

@zhangqh 谢谢

金甲 · 2017年10月20日
Story · 2017年05月17日

请问,var socket = io('https://loc.variflight.com/

回复

0

这是使用socket 拿到接口数据

zhangqh 作者 · 2017年05月17日
0

还有其他办法使飞机有轨迹的运动吗?

Story · 2017年06月01日
0

@Story 有,这个要自己去尝试,这个东西我没有搞完就挂起了,,有时间你可以看下unity3D做的效果很酷炫

zhangqh 作者 · 2017年06月01日
爱因思念 · 2018年04月08日

哥哥 demo运行起来了 非常好,非常感谢,想问个问题,关于那个地理坐标的,我把一个百度地图上获取的经纬度坐标 放进去 调用// 获取position function getPosition(lng, lat, alt)方法,在地球上展现的位置不正确呢,一个上海的经纬度,标到印度洋去了,经纬度参考一般是本初子午线对吗,你这的代码中是绘制地球的时候旋转角度那里调整的吗? 能加个联系方式不,经纬度问题,我搞不对呢。求帮忙,qq:41194132微信:weixin41194132

回复

0

可以参考:https://github.com/wandergis/...。我邮箱:1551827898@qq.com

linghuam · 2018年12月24日
JGLeseul · 2018年09月27日

有源码吗

回复

JGLeseul · 2018年09月27日

求帮忙 qq:2640497232

回复

0

这个项目我没有搞完就挂起了,这里做个笔记分享,其实本人对threejs也不是很熟练,所以需要你自己去找方案了

zhangqh 作者 · 2018年09月30日
载入中...