前端效果积累 | 酷炫、实用3D地球路径飞行效果实现

📌个人主页个人主页
​🧀 推荐专栏前端开发成神之路 --【这是一个为想要入门和进阶前端开发专门开启的精品专栏!从个人到商业的全套开发教程,实打实的干货分享,确定不来看看? 😻😻】
📝作者简介从web开发,再到大数据算法,踩过了无数的坑,用心总结经验教训,助你在技术生涯一臂之力!若想获取更多精彩内容,敬请订阅专栏或者关注😁😂🤣😃😆😉😊😋😍😘🥰
⭐️您的小小关注是我持续输出的动力!⭐️


干货内容推荐

🥇入门和进阶小程序开发,不可错误的精彩内容🥇 :



一、效果预览

请添加图片描述

二、技术要点及用途

这样的3D地球飞行线效果,其主要的应用场景在于酷炫的数据报表大屏!

在日常的开发工作中,搭建数据大屏其实是很多前端工程师都必须经历的一个基础需求!可能你觉得这不就是拿百度的Eachart组件搭建一下不就可以了!这放在过去几年,这个需求说不定还存在!

大哥,现在都是2023年了,数字孪生这个热词被炒的沸沸扬扬。可以见的现在的数据大屏报表需求逐步往3D化、场景可视化、从操控一体化的方向发展了!所以说,现在要搞定酷炫的数据可视化仅仅是一个前端工程师加一个UI设计可能已经是跟不上配置了!一个数字孪生效果的建立,可能需要用到建模师、工程测量、电子工程师等依托于场景的各类专业技术人才!详细的数据可视化技术积累可以看我另一篇文章:超干货!数据可视化最全解决方案!酷炫效果分分钟拿捏!【附全网高质量学习资料】

本文中的技术要点无非是:Three.js。通过three.js构建3D空间效果,同时设置不同的点,再将不同的点用线进行连接!

三、完整代码

<div id="container"></div>
<script src="https://wow.techbrood.com/uploads/user_upload/56822/three.r111.js"></script>
<script src="https://wow.techbrood.com/uploads/user_upload/56822/OrbitControls.js"></script>

<script id="vertex-shader" type="x-shader/x-vertex">
    attribute float percent;
    uniform float time;
    uniform float number;
    uniform float speed;
    uniform float length;
    varying float opacity;
    uniform float size;

    void main()
    {
        float l = clamp(1.0-length, 0.0, 1.0);

        gl_PointSize = clamp(fract(percent*number + l - time*number*speed)-l, 0.0, 1.) * size * (1./length);

        opacity = gl_PointSize/size;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
</script>

<script id="fragment-shader" type="x-shader/x-fragment">
    varying float opacity;
    uniform vec3 color;

    void main(){
        if (opacity <=0.2){
            discard;
        }
        gl_FragColor = vec4(color, 1.0);
    }
</script>


<script type="text/javascript">
    var container = document.getElementById('container');
var scene, camera, renderer;
var globeMesh = undefined;
var groupDots, groupLines, groupAnimDots;
var controls;

var _PI2 = Math.PI * 2; // 弧度的取值为0-2π

// 配置参数
var params = {
    globeRadius: 100, // 地球半径
    pointsLength: 20, // 点数
}
var commonUniforms = {
    time: {
        value: 0
    },
    number: {
        value: 1
    },
    speed: {
        value: 0.5
    },
    length: {
        value: 0.4
    },
    size: {
        value: 4
    }
};

var vertexShader = document.getElementById('vertex-shader').innerHTML;
var fragmentShader = document.getElementById('fragment-shader').innerHTML;

// 预制件
var Prefab = {
    Sphere: (function() {
        var instance;
        return function(clone = true) {
            if (!instance) {
                instance = new createSphere();
            }
            if (clone) return instance.clone();
            else return instance;
        }
    })()
}

init();
update();

function init() {
    // 场景
    scene = new THREE.Scene();
    groupDots = new THREE.Group();
    groupLines = new THREE.Group();
    groupAnimDots = new THREE.Group();
    scene.add(groupDots, groupLines, groupAnimDots);

    // 相机
    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
    camera.position.x = -200;
    camera.position.y = 200;
    camera.position.z = -200;
    camera.lookAt(scene.position);

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

    // 光
    var light = new THREE.HemisphereLight(0xffffff, 0xffffff, 1);
    scene.add(light);

    // 控制器
    controls = new THREE.OrbitControls(camera, renderer.domElement);
    controls.minDistance = 200;
    controls.maxDistance = 400;
    controls.rotateSpeed = 0.5;
    controls.enableDamping = true;
    controls.enablePan = false;

    initGlobe();
    initFlyLines();

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

function update() {
    requestAnimationFrame(update);
    renderer.render(scene, camera);

    commonUniforms.time.value += 0.01;

    controls.update();
}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

function initGlobe() {
    // 地球
    var geo = new THREE.SphereGeometry(params.globeRadius, 32, 32);
    var texture = new THREE.TextureLoader().load('https://wow.techbrood.com/uploads/user_upload/56822/earth.jpg');
    texture.minFilter = THREE.LinearFilter;
    var material = new THREE.MeshPhongMaterial({
        map: texture,
        // wireframe: true
    });

    globeMesh = new THREE.Mesh(geo, material);
    scene.add(globeMesh);
}

// 地球飞线和点
function initFlyLines() {
    // 球面随机点
    for (let i = 0; i < params.pointsLength; i++) {
        addPoints(groupDots, params.globeRadius);
    }

    // 点到点生成曲线
    groupDots.children.forEach(function(elem, index) {
        if (elem != groupDots.children[0]) {
            addLines(groupDots.children[index - 1].position, elem.position);
        }
    });
}

// 3d球面取点
function getEarthPos(radius, a, b) {
    var x = radius * Math.sin(a) * Math.cos(b);
    var y = radius * Math.sin(a) * Math.sin(b);
    var z = radius * Math.cos(a);
    return {
        x, y, z
    };
}

// 添加随机点
function addPoints(group, radius) {
    var mesh = new Prefab.Sphere();
    var pos = getEarthPos(radius, _PI2 * Math.random(), _PI2 * Math.random());
    mesh.position.set(pos.x, pos.y, pos.z);
    group.add(mesh);
}

function addLines(v0, v3) {
    var angle = v0.angleTo(v3);

    var vtop = v0.clone().add(v3);
    vtop = vtop.normalize().multiplyScalar(params.globeRadius);

    var n;

    if (angle <= 1) {
        n = params.globeRadius / 5 * angle;
    } else if (angle > 1 && angle < 2) {
        n = params.globeRadius / 5 * Math.pow(angle, 2);
    } else {
        n = params.globeRadius / 5 * Math.pow(angle, 1.5);
    }

    var v1 = v0.clone().add(vtop).normalize().multiplyScalar(params.globeRadius + n);
    var v2 = v3.clone().add(vtop).normalize().multiplyScalar(params.globeRadius + n);

    // addLineHelper(globeMesh.position, v1);
    // addLineHelper(globeMesh.position, v2);
    // addLineHelper(globeMesh.position, vtop)

    // 三维三次贝塞尔曲线(v0起点,v1第一个控制点,v2第二个控制点,v3终点)
    var curve = new THREE.CubicBezierCurve3(v0, v1, v2, v3);
    var points = curve.getPoints(500);
    var geometry = new THREE.BufferGeometry().setFromPoints(points);

    let length = points.length;
    var percents = new Float32Array(length);
    for (let i = 0; i < length; i += 1) {
        percents[i] = (i / length);
    }
    geometry.setAttribute('percent', new THREE.BufferAttribute(percents, 1));

    var material = createLineMaterial();
    var flyLine = new THREE.Points(geometry, material);
    groupLines.add(flyLine);
}

function createLineMaterial() {
    let uniforms = {
        time: commonUniforms.time,
        number: commonUniforms.number,
        speed: commonUniforms.speed,
        length: commonUniforms.length,
        size: commonUniforms.size,
        color: {
            value: new THREE.Color(Math.random(), Math.random(), Math.random())
        }
    };

    var material = new THREE.ShaderMaterial({
        uniforms: uniforms,
        vertexShader: vertexShader,
        fragmentShader: fragmentShader,
        transparent: true,
        blending: THREE.AdditiveBlending,
    });

    return material;
}

// 小球
function createSphere() {
    var geometry = new THREE.SphereBufferGeometry(1);
    var material = new THREE.MeshBasicMaterial({
        color: 0x00ffff
    });
    var mesh = new THREE.Mesh(geometry, material);
    return mesh;
}

// 贝塞尔曲线辅助点
function addPointHelper(pos) {
    var mesh = new Prefab.Sphere();
    mesh.material = new THREE.MeshBasicMaterial({
        color: 0xff0000
    });
    mesh.position.copy(pos);
    scene.add(mesh);
}

// 贝塞尔曲线辅助线
function addLineHelper(pos1, pos2) {
    var material = new THREE.LineBasicMaterial({
        color: 0x0000ff
    });

    var geometry = new THREE.Geometry();
    geometry.vertices.push(pos1, pos2);
    var line = new THREE.Line(geometry, material);
    scene.add(line);
}
</script>



<style type="text/css">
    
        html,
        body {
            width: 100%;
            height: 100%;
            margin: 0;
            overflow: hidden;
        }

</style>

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陶人超有料

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值