美文网首页
前端项目练习之太阳系

前端项目练习之太阳系

作者: 简单一点点 | 来源:发表于2020-07-12 20:14 被阅读0次

使用three.js实现太阳系,参考文章 ThreeJS 轻松实现主视觉太阳系漫游 。 效果如下:

solarSystem.gif

html部分

html部分主要定义了canvas。引入了一些必须的js文件:

  • three.js: 一个浏览器中的3D引擎,我们主要使用的js库。
  • OrbitControls.js: three.js实现的轨道控制器,用来控制场景变换。
  • stats.min.js: three.js中用来统计FPS的小插件
  • main.js: 我们要实现的部分。
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chorme=1">
        <title>Solar System</title>
        <style>
            body {
                margin: 0;
                padding: 0;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <canvas id="main"></canvas>

        <script src="js/three.js"></script>
        <script src="js/OrbitControls.js"></script>
        <script src='js/stats.min.js'></script>
        <script src="js/main.js"></script>
    </body>
</html>

JS部分

准备变量

首先准备本部分要使用到的变量。

// 场景,渲染器,镜头,背景星星,帧率器,第一人称控制
let scene, renderer, camera, particleSystem, stat, control;

// 初始化星体
let sun, // 太阳
    mercury, // 水星
    venus, //金星
    earth, //地球
    mars, //火星
    jupiter, // 木星
    saturn, // 土星
    uranus, //天王星
    neptune, // 海王星
    stars = [];

const cameraFar = 3000; // 镜头视距

let starNames = {}; // 指向显示的星星名字对象
let dispplayName; // 当前显示名字
let clock = new THREE.Clock(); // 第一人称控制需要,用于计算时间间隔

let raycaster = new THREE.Raycaster(); // 指向镭射
let mouse = new THREE.Vector2(); // 鼠标屏幕向量

let displayName; // 当前显示名字

初始化

初始化three.js中的必备要素: render、scene和camera。

// 画布
const canvas = document.getElementById("main");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

// 渲染器
renderer = new THREE.WebGLRenderer({canvas});
renderer.shadowMap.enabled = true;
renderer.shadowMapSoft = true;
renderer.setClearColor(0xffffff, 0);

// 场景
scene = new THREE.Scene();

// 镜头
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, cameraFar);
camera.position.set(-200, 50, 0)
camera.lookAt(new THREE.Vector3(0, 0, 0));
scene.add(camera);

添加行星

首先添加最重要的太阳。

// 太阳
let sunSkinPic = new THREE.TextureLoader().load('img/sun_bg.jpg', {}, function() {
    renderer.render(scene, camera);
});
sun = new THREE.Mesh( new THREE.SphereGeometry( 12 ,16 ,16 ),
  new THREE.MeshLambertMaterial({
    color: 0xffff00,
    emissive: 0xdd4422,
    map: sunSkinPic
  })
);
sun.name='Sun';
sun.castShadow = true;
sun.receiveShadow = true;
scene.add(sun);

然后批量添加其它行星。

// 星球
mercury = this.initPlanet('Mercury', 0.02, 0, 'rgb(124, 131, 203)', 20, 2);
stars.push(mercury);

venus = this.initPlanet("venus", 0.012, 0,'rgb(190, 130, 44)', 30, 4);
stars.push(venus);

earth = this.initPlanet('Earth', 0.010, 0,'rgb(46,69,119)',40,5);
stars.push(earth);

mars = this.initPlanet('Mars', 0.008, 0,'rgb(210,81,16)',50,4);
stars.push(mars);

jupiter = this.initPlanet('Jupiter', 0.006, 0,'rgb(254,208,101)',70,9);
stars.push(jupiter);

saturn = this.initPlanet('Saturn', 0.005, 0,'rgb(210,140,39)',100,7, {
    color: 'rgb(136, 75, 30)',
    innerRadius: 9,
    outerRadius:11
});
stars.push(saturn);

uranus = this.initPlanet('Uranus', 0.003, 0,'rgb(49,168,218)',120,4);
stars.push(uranus);

neptune = this.initPlanet('Neptune', 0.002, 0,'rgb(84,125,204)',150,3);
stars.push(neptune);   

// 环境光
let ambient = new THREE.AmbientLight(0x999999);
scene.add(ambient);

// 太阳光
let sunLight = new THREE.PointLight(0xddddaa, 1.5, 500);
scene.add(sunLight);

// 初始化行星
function initPlanet(name, speed, angle, color, distance, volume, ringMsg) {
    let mesh = new THREE.Mesh(new THREE.SphereGeometry( volume, 16, 16),
        new THREE.MeshLambertMaterial( { color } )
    );
    mesh.position.x = -distance
    mesh.receiveShadow = true
    mesh.castShadow = true;
    mesh.name = name;

    let star = {
        name,
        speed,
        angle,
        distance,
        volume,
        Mesh: mesh
    }

    // 如果有碎星带
    if(ringMsg) {
        let ring = new THREE.Mesh( new THREE.RingGeometry(ringMsg.innerRadius, ringMsg.outerRadius, 32, 6),
            new THREE.MeshBasicMaterial( { color: ringMsg.color, side: THREE.DoubleSide, 
                opacity: 0.7, transparent:true }));
        ring.name = 'Ring of ' + name;
        ring.rotation.x = -Math.PI / 3;
        ring.rotation.y = -Math.PI / 4;
        scene.add(ring);

        star.ring = ring;
    }

    // 轨道
    let track = new THREE.Mesh( new THREE.RingGeometry(distance - 0.2, distance + 0.2, 64, 1),
        new THREE.MeshBasicMaterial( {color: 0x888888, side: THREE.DoubleSide }));
    track.rotation.x = -Math.PI / 2;
    scene.add(track); 
    
    scene.add(mesh);
    return star
}

还要添加一个让星星动起来的方法。

// 运动
function moveEachStar(star) {
    star.angle += star.speed;
    if(star.angle > Math.PI * 2) {
        star.angle -= Math.PI * 2;
    }
    star.Mesh.position.set(star.distance * Math.sin(star.angle), 0, star.distance * Math.cos(star.angle));

    // 碎星带
    if(star.ring) {
        star.ring.position.set(star.distance * Math.sin(star.angle), 0, 
            star.distance * Math.cos(star.angle));
    }
}

镜头控制

借助OrbitControls.js实现镜头控制

control = new THREE.OrbitControls(camera, canvas);
control.target = new THREE.Vector3(0, 0, 0);
control.autoRatate = false; // 关闭自动旋转

帧率监测

引入一个帧率监视的工具,以便我们对整个动画的效率的掌握。

// 帧率监视
stat = new Stats();
stat.domElement.style.position = 'absolute';
stat.domElement.style.right = '0px';
stat.domElement.style.top = '0px';
document.body.appendChild(stat.domElement);

添加鼠标移动事件

camera.lookAt(new THREE.Vector3(0, 0, 0));
window.addEventListener('mousemove', onMouseMove, false);

 /*鼠标指针指向响应*/
function onMouseMove(event) {
    // calculate mouse position in normalized device coordinates
    // (-1 to +1) for both components
    mouse.x = event.clientX / window.innerWidth * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}

添加背景星星

为了让夜空更好看,添加一些星星。

// 背景星星
const particles = 20000; // 数量
let bufferGeometry = new THREE.BufferGeometry();
let positions = new Float32Array(particles * 3);
let colors = new Float32Array(particles * 3);
let color = new THREE.Color();
const gap = 1000; // 星星最近出现位置
for(let i = 0; i < positions.length; i +=3) {
    let x = (Math.random() * gap * 2) * (Math.random() < 0.5 ? -1 : 1);
    let y = (Math.random() * gap * 2) * (Math.random() < 0.5 ? -1 : 1);
    let z = (Math.random() * gap * 2) * (Math.random() < 0.5 ? -1 : 1);

    let biggest = Math.abs(x) > Math.abs(y) ? Math.abs(x) > Math.abs(z) ? 'x' : 'z' :
        Math.abs(y) > Math.abs(z) ? 'y' : 'z';

    let pos = {x, y, z}
    if(Math.abs(pos[biggest]) < gap) pos[biggest] = pos[biggest] < 0 ? -gap : gap;

    x = pos['x']
    y = pos['y']
    z = pos['z']

    positions[i] = x;
    positions[i + 1] = y;
    positions[i + 2] = z;

    let hasColor = Math.random() > 0.3;
    let vs, vy, vz;

    if(hasColor) {
        vx = (Math.random() + 1) / 2;
        vy = (Math.random() + 1) / 2;
        vz = (Math.random() + 1) / 2;
    } else {
        vx = 1;
        vy = 1;
        vz = 1;
    }

    color.setRGB(vx, vy, vz);
    colors[i] = color.r;
    colors[i + 1] = color.g;
    colors[i + 2] = color.b;
}

bufferGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
bufferGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
bufferGeometry.computeBoundingSphere();

let material = new THREE.PointsMaterial( {size:6, vertexColors: THREE.vertexColors});
particleSystem = new THREE.Points( bufferGeometry, material);
scene.add(particleSystem);

显示行星名称

添加鼠标移动到行星显示名称的功能。

// 显示行星名称
function displayPlanetName() {
    var loader = new THREE.FontLoader();
    for(var i = 0; i < stars.length; i++) {
        nameConstructor(stars[i].name, stars[i].volume);
    }
    nameConstructor('Sun', 12);

    function nameConstructor(name, volume) {
       
        loader.load('fonts/helvetiker_regular.typeface.json', function (font) {
            let planetName = new THREE.Mesh(
                new THREE.TextGeometry(
                    name, {
                        font: font,
                        size: 4,
                        height: 4
                    }
                ),
                new THREE.MeshBasicMaterial({ color: 0xffffff, side: THREE.DoubleSide})
            );
            planetName.volume = volume;
            planetName.visible = false;
            starNames[name] = planetName;
            scene.add(planetName);
        });
        
    }

}
displayPlanetName();

动画

最后,使用requestAnimationFrame()实现动画。

// 更新物品需要重新渲染
renderer.render(scene, camera);
requestAnimationFrame( ()=>move());

// 运动
function move() {
    for(var i = 0; i < stars.length; i++) {
        moveEachStar(stars[i]);
    }

    // 太阳自转
    sun.rotation.y = (sun.rotation.y == 2 * Math.PI ? 0.0008 * Math.PI : sun.rotation.y + 0.0008 * Math.PI);

    control.update( clock.getDelta() ); // 鼠标控制视角
    // 限制镜头
    camera.position.x = THREE.Math.clamp(camera.position.x, -400, 400);
    camera.position.y = THREE.Math.clamp(camera.position.y, -400, 400);
    camera.position.z = THREE.Math.clamp(camera.position.z, -400, 400);

    stat.update();

    /*鼠标指向行星显示名字*/
    raycaster.setFromCamera( mouse, camera ); 
    // 交汇点对象
    let intersects = raycaster.intersectObjects( scene.children );
    if(intersects.length > 0) {
        let obj = intersects[0].object;

        let name = obj.name;
        // 把上一个显示隐藏
        displayName && (displayName.visible = false)
        if(starNames[name]) {
            starNames[name].visible = true;
            displayName = starNames[name];
            displayName.position.copy(obj.position);
            displayName.geometry.center();
            displayName.position.y = starNames[name].volume + 4;
            displayName.lookAt(camera.position);
        }
    } else {
        displayName && displayName.visible && (displayName.visible = false);
    }

    // 更新物品需要重新渲染
    renderer.render(scene, camera);
    requestAnimationFrame(()=> move());
}

相关文章

  • 前端项目练习之太阳系

    使用three.js实现太阳系,参考文章 ThreeJS 轻松实现主视觉太阳系漫游 。 效果如下: html部分 ...

  • 前端新手项目练习之可拖动弹窗

    前端新手项目练习之可拖动弹窗 前端新手记录自己在网络上找到的前端练习项目。最近工作忙得要死,感觉自己有些扛不住,这...

  • 前端新手项目练习之钟表

    前端新手项目练习值钟表 前端新手记录自己在网络上找到的前端练习项目。 项目简介 一个简单的钟表,有刻度和数字,利用...

  • 前端新手项目练习之星级评分

    前端新手项目练习之星级评分 前端新手记录自己在网络上找到的前端练习项目。 项目简介 一个简单的星评分系统,鼠标移上...

  • 自学前端:用豆瓣API来写电影搜索页面

    这是楼主自学前端练习的一个小项目,欢迎大家来指正错误。欢迎加入前端自学群68701672。本群以项目为核心进行前端...

  • 前端新手项目练习之网页换肤

    前端新手记录自己在网络上找到的前端练习项目。 项目简介 项目包含红、绿、黑3个简单的按钮和一个导航栏,通过点击按钮...

  • 前端新手项目练习之广告轮播

    前端新手记录自己在网络上找到的前端练习项目。 项目简介 5个广告轮流播放,鼠标放上去会停止切换,鼠标移开继续轮播,...

  • 前端新手项目练习之select控件

    前端新手记录自己在网络上找到的前端练习项目。昨天去通州考试,早上五点出发赶第一班地铁,真是累的要死。 项目简介 一...

  • 前端新手项目练习之拼图游戏

    前端新手记录自己在网络上找到的前端练习项目。趁热打铁,再练习一下鼠标拖动相关的事件,这个小游戏是一个不错的选择,玩...

  • 前端项目练习之3D相册

    用CSS实现一个抖音上很火的3D相册,首先看下效果图。 首先创建index.html文件,其中主要包含2个列表,用...

网友评论

      本文标题:前端项目练习之太阳系

      本文链接:https://www.haomeiwen.com/subject/rayncktx.html