zoukankan      html  css  js  c++  java
  • three.js一步一步来--如何画出一个完整的太阳系、转动、附带奔跑的div标签、手动挨行详细注释

    完整的太阳系、一行一行仔细查资料注释,费了好大劲呢~~~

    <template>
      <div style="100%; height:800px" class="my_div">
        <p>3D太陽系</p>
        <canvas ref="main" />
        <div id="canvas-frame" ref="myBody" style="1000px; height:800px" />
      </div>
    </template>
    
    <script>
    import * as THREE from 'three'
    import { OBJLoader, MTLLoader } from 'three-obj-mtl-loader'
    // import MTLLoader from  'three-mtl-loader';
    // import OBJLoader from  'three-obj-loader';
    import { CSS2DRenderer, CSS2DObject } from 'three-css2drender'
    // import { Geometry, Line } from 'three'
    // import { Geometry, Material, Scene, WebGLBufferRenderer } from 'three';
    const OrbitControls = require('three-orbit-controls')(THREE)
    
    export default {
      data() {
        return {
          canvas: Element,
          loader: THREE.TextureLoader,
          sunSystem: THREE.Object3D,
          sun: THREE.Mesh,
          orbitcontrols: OrbitControls,
          planets: [],
          labelRenderer: new CSS2DRenderer(),
          scene: new THREE.Scene(), // 場景
          camera: new THREE.PerspectiveCamera(
            45,
            window.innerWidth / window.innerHeight,
            1,
            10000
          ), // 透視相機
          renderer: new THREE.WebGLRenderer(), // 渲染器
          geometry: new THREE.Geometry(), // 設置物體
          material: new THREE.LineBasicMaterial({ vertexColors: true }), // 設置材料
          cube: {}, // 合起來
          // 開始設置線條
          light: new THREE.DirectionalLight(0xff0000, 1.0, 0)
        }
      },
      mounted() {
        this.threeStart()
        console.log()
      },
      methods: {
        initThree() {
          this.canvas = this.$refs.main
          this.canvas.width = window.innerWidth // 只读的Window 属性 innerWidth 返回以像素为单位的窗口的内部宽度。如果垂直滚动条存在,则这个属性将包括它的宽度。
          this.canvas.height = window.innerHeight
          const canvas = this.canvas
          // 創建渲染器
          // new WebGLRenderer会在body里面生成一个canvas标签,当然如果你想在某个位置插入canvas可以在指定的dom元素appendChild(renderer.domElement)
          this.renderer = new THREE.WebGLRenderer({
            canvas,
            alpha: true, // alpha:true/false是否可以设置背景色透明
            antialias: true // antialias:true/false是否开启反锯齿
          })
          this.renderer.setPixelRatio(window.devicePixelRatio) // setPixelRatio是为了兼容高清屏幕,Window 接口的devicePixelRatio返回当前显示设备的物理像素分辨率与CSS像素分辨率之比
          this.renderer.shadowMap.enabled = true // 輔助線
          this.renderer.shadowMap.type = THREE.PCFSoftShadowMap // 柔化边缘的软阴影映射
          this.renderer.setClearColor(0xffffff, 0) // 设置canvas背景色(clearColor)和背景色透明度(clearAlpha)
        },
        initScene() {
          this.scene = new THREE.Scene() // 創建場景
        },
        initCamera() {
          // 創建透視相機,
          this.camera = new THREE.PerspectiveCamera(
            45, // 视野角fov
            window.innerWidth / window.innerHeight, // 纵横比:aspect
            1, // 相机离视体积最近的距离:near
            3000 // 相机离视体积最远的距离:far
          )
          this.camera.position.set(-200, 50, 0) // 设置相机的位置坐标xyz
          this.camera.lookAt(0, 0, 0) // 设置视野的中心坐标
          this.scene.add(this.camera) // 添加相機到場景裡
        },
        initAxesHelper() {
          const axesHelper = new THREE.AxesHelper(500)
          this.scene.add(axesHelper)
        },
        initLabelRender() {
          console.log('canvas.clientWidth, canvas.clientHeight')
          console.log(this.canvas.clientWidth, this.canvas.clientHeight)
          this.labelRenderer.setSize(
            this.canvas.clientWidth,
            this.canvas.clientHeight
          )
          this.labelRenderer.domElement.style.position = 'absolute'
          this.labelRenderer.domElement.style.top = '0px'
          this.$refs.myBody.appendChild(this.labelRenderer.domElement) // 追加 【canvas】 元素到 【myBody】 元素中。
        },
        initLight() {
          this.light = new THREE.DirectionalLight(0xff0000, 1.0, 0) // 设置平行光源
          this.light.position.set(200, 200, 200) // 设置光源向量
          this.scene.add(this.light) // 追加光源到场景
        },
        initObject() {
          // Orbit controls(轨道控制器)可以使得相机围绕目标进行轨道运动
          this.orbitcontrols = new OrbitControls(
            this.camera,
            this.labelRenderer.domElement
          )
          console.log('軌道控制器')
          this.orbitcontrols.update() // false 更新控件,在手动改变了摄像机的钻换后必须调用。在设置了autoRotate或enableDamping时也要在循环中调用
    
          this.loader = new THREE.TextureLoader() // 纹理加载器
          // Object3D似乎是Three.js框架中最重要的类,相当一部分其他的类都是继承自Object3D类,比如场景类、几何形体类、相机类、光照类等等:他们都是3D空间中的对象,所以称为Object3D类
          this.sunSystem = new THREE.Object3D()
          this.scene.add(this.sunSystem) // 向场景中添加对象
          // sun
          // 网格基础材质
          const sunMaterial = new THREE.MeshBasicMaterial({
            map: this.loader.load(require('./webgl-assets/img/sun_bg.jpg'))
          })
          // 网孔对象的基类,MESH就是一系列的多边形组成的,三角形或者四边形,网格一般由顶点来描绘,我们看见的三维开发的模型就是由一系列的点组成的。Mesh( geometry几何模型, material材料 )
          this.sun = new THREE.Mesh(
            new THREE.SphereGeometry(14, 30, 30), // 一个 几何模型(Geometry) 实例,用来定义对象的结构。可以创建一个半径为14,经度划分成30份,纬度划分成30份的球体
            sunMaterial // 一个 材料(Material) 实例,用来定义对象的外观
          )
          this.sun.name = 'SUN'
          this.sunSystem.add(this.sun) // 将对象添加为该对象的子对象。可以添加任意数量的对象
        },
        initPlanet() {
          const planetDiv = document.createElement('div')
          planetDiv.className = 'label'
          planetDiv.textContent = '太陽'
          planetDiv.style.color = 'white'
          planetDiv.style.marginTop = '-0.3em'
          const planetaLabel = new CSS2DObject(planetDiv) // 把上述div对象转化为一个CSS2DObject对象
          planetaLabel.position.set(0, 14, 0)
          this.sun.add(planetaLabel) // 在球體模型中加入该CSS2DObject对象
        },
        addPlanets() {
          // 添加水星
          const Mercury = this.loadPlanet('mercury', 2, 20, 0.02)
          this.planets.push(Mercury)
          // 添加金星
          const Venus = this.loadPlanet('venus', 4, 30, 0.012)
          this.planets.push(Venus)
          // 添加地球
          const Earth = this.loadPlanet('earth', 5, 40, 0.01)
          this.planets.push(Earth)
          // 添加火星
          const Mars = this.loadPlanet('mars', 4, 50, 0.008)
          this.planets.push(Mars)
          // 添加木星
          const Jupiter = this.loadPlanet('jupiter', 9, 70, 0.006)
          this.planets.push(Jupiter)
          // 添加土星
          const Saturn = this.loadPlanet('saturn', 7, 100, 0.005)
          this.planets.push(Saturn)
          // 添加天王星
          const Uranus = this.loadPlanet('uranus', 4, 120, 0.003)
          this.planets.push(Uranus)
          // 添加海王星
          const Neptune = this.loadPlanet('neptune', 3, 150, 0.002)
          this.planets.push(Neptune)
          // 添加冥王星
          const Pluto = this.loadPlanet('pluto', 4, 160, 0.0016)
          this.planets.push(Pluto)
    
          const particleSystem = this.initParticle()
          this.scene.add(particleSystem)
        },
        initParticle() {
          // /*背景星星*/
          const particles = 20000 // 星星数量
          // /*buffer做星星*/
          const bufferGeometry = new THREE.BufferGeometry()
    
          const positions = new Float32Array(particles * 3)
          const colors = new Float32Array(particles * 3)
    
          const color = new THREE.Color()
    
          const gap = 900 // 定义星星的最近出现位置
    
          for (let i = 0; i < positions.length; i += 3) {
            // positions
    
            // /*-2gap < x < 2gap */
            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)
    
            // /*找出x,y,z中绝对值最大的一个数*/
            const biggest =
              Math.abs(x) > Math.abs(y) ? Math.abs(x) > Math.abs(z) ? 'x' : 'z' : Math.abs(y) > Math.abs(z) ? 'y' : 'z'
            const pos = { x, y, z }
    
            // /*如果最大值比n要小(因为要在一个距离之外才出现星星)则赋值为n(-n)*/
            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
    
            // colors
    
            // /*70%星星有颜色*/
            const hasColor = Math.random() > 0.3
            let vx, 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()
    
          // /*星星的material*/
          const material = new THREE.PointsMaterial({
            size: 6,
            vertexColors: THREE.VertexColors
          })
          const particleSystem = new THREE.Points(bufferGeometry, material)
    
          return particleSystem
        },
        loadPlanet(name, radius, position, speed) {
          const planetSystem = new THREE.Mesh(
            new THREE.SphereGeometry(1, 1, 1), // 一个 几何模型(Geometry) 实例,用来定义对象的结构。可以创建一个半径为1,经度划分成1份,纬度划分成1份的球体
            new THREE.MeshLambertMaterial() // 材料實例,用來定義對象外觀
          ) // 材质设定
          planetSystem.speed = speed
          // 動態設置材質
          const material = new THREE.MeshBasicMaterial({
            map: this.loader.load(require(`./webgl-assets/img/${name}_bg.jpg`))
          })
          const planet = new THREE.Mesh(
            new THREE.SphereGeometry(radius, 30, 30), // 動態創建球體
            material // 使用動態貼圖渲染
          )
          planet.position.z = -position // 設置z坐標位置
          // planet.rotateOnAxis(new THREE.Vector3(1, 0, 0).normalize(), -23.36 * Math.PI / 180)
          planetSystem.add(planet)
    
          if (name === 'saturn') {
            const ringMaterial = new THREE.MeshBasicMaterial({
              map: this.loader.load(`./webgl-assets/img/${name}_ring.jpg`), // 皮膚貼圖
              side: THREE.DoubleSide // 雙面材質
            })
            const ring = new THREE.Mesh(
              new THREE.RingGeometry(radius * 1.2, radius * 1.5, 64, 1),// RingGeometry用来在三维空间内创建一个二维圆环面对象.
              ringMaterial
            )
            ring.rotation.x = -Math.PI / 2
            planet.add(ring)
          }
    
          const track = new THREE.Mesh(
            new THREE.RingGeometry(position, position + 0.05, 64, 1), // 二维圆环面对象(內徑,外徑,分段數,面細分)
            new THREE.MeshBasicMaterial({
              side: THREE.DoubleSide // 雙面材質
            })
          )
          track.rotation.x = -Math.PI / 2
          this.scene.add(track)
          // Three.js中的div标签跟随(模型弹框)
          const planetDiv = document.createElement('div') // 把div存为变量
          planetDiv.className = 'label'
          planetDiv.style.color = 'white'
          planetDiv.textContent = name
          planetDiv.style.marginTop = '-0.3em'
          const planetLabel = new CSS2DObject(planetDiv) // 把上述div对象转化为一个CSS2DObject对象
          planetLabel.position.set(0, radius, 0) // 前两个参数是对于屏幕xy坐标,可以取负数  第三个不清楚,按道理应该是z轴坐标,不知道怎么体现
          planet.add(planetLabel) // 在模型中加入该CSS2DObject对象
    
          this.sunSystem.add(planetSystem)
          // 注意事项:上面的代码放在camera / OrbitControls之后, 否则相机控制不能用
          return planetSystem
        },
        resizeRendererToDisplaySize(renderer) {
          const canvas = renderer.domElement
          const width = canvas.clientWidth
          const height = canvas.clientHeight
          const needResize = canvas.width !== width || canvas.height !== height
          if (needResize) {
            renderer.setSize(width, height, false) // 指定渲染器的高宽
          }
          return needResize
        },
        render(time) {
          time *= 0.0005
    
          if (this.resizeRendererToDisplaySize(this.renderer)) {
            const canvas = this.renderer.domElement
            this.camera.aspect = canvas.clientWidth / canvas.clientHeight // aspect属性:设置摄像机视口比例,实际窗口的纵横比,即宽度除以高度,这个值越大,说明你宽度越大,那么你可能看的是宽银幕电影了,如果这个值小于1,则为竖屏。
            this.camera.updateProjectionMatrix() // 如果相机对象与投影矩阵相关的属性发生了变化,就需要手动更新相机的投影矩阵,更新相机对象的投影矩阵属性
          }
    
          this.sunSystem.rotation.y = -time // 在three.js你可以使用rotation来设置object3D的旋转。
          for (var i = 0; i < this.planets.length; i++) {
            this.planets[i].rotation.y -= this.planets[i].speed
            const planet = this.planets[i].children[0]
            planet.rotation.y -= 0.1
          }
    
          this.orbitcontrols.update() // Orbit controls(轨道控制器)可以使得相机围绕目标进行轨道运动
    
          this.renderer.render(this.scene, this.camera)
          this.labelRenderer.render(this.scene, this.camera)
    
          requestAnimationFrame(this.render)
        },
        animation() {},
        threeStart() {
          this.initThree()
          this.initScene()
          this.initCamera()
          this.initAxesHelper()
          this.initLabelRender()
          this.initObject()
          this.initPlanet()
          this.addPlanets()
          requestAnimationFrame(this.render)
        },
        consoleObj() {
          console.log(THREE.REVISION)
          console.log(OBJLoader)
          console.log(MTLLoader)
          console.log(CSS2DRenderer)
          console.log(CSS2DObject)
        }
      }
    }
    </script>
    <style lang="less" scoped>
    .my_div {
      background: #000 url('./webgl-assets/img/starry_sky_bg.jpg') no-repeat center
        center;
      margin: 0;
      padding: 0;
      overflow: hidden;
      .label {
        color: #fff;
        font-family: sans-serif;
        font-size: xx-small;
        padding: 2px;
      }
    
      .label:hover {
        color: red;
      }
    }
    
    #main {
      position: relative;
      /* makes this the origin of its children */
       100%;
      height: 100%;
      overflow: hidden;
    }
    </style>
    

    参考原版地址:
    原github大佬有详细介绍代码如何一步一步写出来,可以去看看哟~
    原版是用的H5,我用的vue,和原版不太一样
    https://github.com/iWun/solar-system

  • 相关阅读:
    UML序列图总结
    数据库水平切分的实现原理解析
    oracle imp file data
    putty的设置
    run java jar command
    forex website
    forex tables
    ubuntu set defult jdk
    友情连接
    jstl tag
  • 原文地址:https://www.cnblogs.com/sugartang/p/13605718.html
Copyright © 2011-2022 走看看