ThreeJS学习笔记(一)

84 阅读4分钟

一、Basic

  1. dat.GUI的使用
 <!doctype html>
 <html lang="en">
   <head>
     <meta charset="UTF-8">
     <meta name="viewport"
       content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
     <meta http-equiv="X-UA-Compatible" content="ie=edge">
     <title>Test - dat.gui</title>
     <link rel="stylesheet" href="../node_modules/normalize.css/normalize.css">
     <link rel="stylesheet" href="../style/global.css">
   </head>
   <body>
     <div class="three-dom" id="threeDom"></div>
     <script type="importmap">
       {
         "imports": {
           "three": "../node_modules/three/build/three.module.js",
           "three/addons/": "../node_modules/three/examples/jsm/",
           "datGui":"../node_modules/dat.gui/build/dat.gui.module.js"
         }
       }
     </script>
     <script type="module">
       import {
         AxesHelper,
         Color,
         FogExp2, Mesh,
         MeshBasicMaterial,
         PerspectiveCamera,
         PlaneGeometry,
         Scene, SphereGeometry,
         WebGLRenderer
       } from 'three'
       import {OrbitControls} from 'three/addons/controls/OrbitControls.js'
       import datGui from 'datGui'
 ​
       function init(threeDom) {
         const scene = new Scene()
         scene.background = new Color(0x333333)
         scene.fog = new FogExp2(0xcccccc, 0.002)
 ​
         const camera = new PerspectiveCamera(120, threeDom.clientWidth / threeDom.clientHeight, 0.1, 1000)
         camera.position.set(-30, 40, 30)
         camera.lookAt(scene.position)
 ​
         const renderer = new WebGLRenderer()
         renderer.setSize(threeDom.clientWidth, threeDom.clientHeight)
           renderer.setPixelRatio(window.devicePixelRatio)
           renderer.setClearColor(new Color(0xdddddd, 1))
           threeDom.appendChild(renderer.domElement)
 ​
           const planeGeometry = new PlaneGeometry(60, 40, 1, 1)
           const planeMaterial = new MeshBasicMaterial({
           wireframe: false,
           color: 0xcccccc
           })
           const plane = new Mesh(planeGeometry, planeMaterial)
           plane.position.set(10, 0, 0)
           plane.rotation.x = -0.5 * Math.PI
           scene.add(plane)
 ​
           const orbitControls = new OrbitControls(camera, renderer.domElement)
           orbitControls.listenToKeyEvents(window)
           orbitControls.enableDamping = true
           orbitControls.dampingFactor = 0.05
           orbitControls.screenSpacePanning = false
           orbitControls.minDistance = 1
           orbitControls.maxDistance = 50
           orbitControls.maxPolarAngle = Math.PI / 2
 ​
           const axes = new AxesHelper(10)
           scene.add(axes)
 ​
           return [scene, camera, renderer, orbitControls, planeGeometry]
           }
 ​
 ​
           window.onload = function () {
           const threeDom = document.getElementById('threeDom')
           const [scene, camera, renderer, orbitControls, planeGeometry] = init(threeDom)
           window.addEventListener('resize', () => {
           camera.aspect = threeDom.clientWidth / threeDom.clientHeight
           camera.updateProjectionMatrix()
           renderer.setSize(threeDom.clientWidth, threeDom.clientHeight)
           })
 ​
         function render() {
             orbitControls.update()
             renderer.render(scene, camera)
             requestAnimationFrame(render)
         }
 ​
         const controls = new function () {
             this.numberOfObjects = scene.children.length
             this.addCube = function () {
                 const sphereSize = Math.ceil((Math.random() * 3))
                 const sphereGeometry = new SphereGeometry(sphereSize)
                 const sphereMaterial = new MeshBasicMaterial({
                     wireframe: true,
                     color:Math.random()* 0xffffff
                 })
                 const sphere = new Mesh(sphereGeometry, sphereMaterial)
                 sphere.position.x = -30 + Math.round((Math.random() * planeGeometry.parameters.width));
                 sphere.position.y = Math.round((Math.random() * 5));
                 sphere.position.z = -20 + Math.round((Math.random() * planeGeometry.parameters.height));
                 sphere.name = 'sphere-' + scene.children.length
                 scene.add(sphere)
 ​
                 this.numberOfObjects = scene.children.length;
             }
             this.removeCube = function () {
                 const allChildren = scene.children
                 const lastObject = allChildren[allChildren.length - 1]
                 if (lastObject instanceof Mesh) {
                     scene.remove(lastObject)
                     this.numberOfObjects = scene.children.length
                 }
             }
             this.outputObjects = function () {
                 console.log(scene.children);
             }
         }
         const gui = new datGui.GUI()
         gui.add(controls, 'addCube')
         gui.add(controls, 'removeCube');
         gui.add(controls, 'outputObjects');
         gui.add(controls, 'numberOfObjects').listen();
         render()
     }
 </script>
 </body>
 </html>
  1. 全屏显示
 window.addEventListener("dblclick", () => {
     console.log('dbclick')
     const fullScreen = document.fullscreenElement
     if (fullScreen) {
         document.exitFullscreen()
     } else {
         renderer.domElement.requestFullscreen()
     }
 })
  1. 自适应全屏
 window.addEventListener('resize', () => {
   camera.aspect = threeDom.clientWidth / threeDom.clientHeight
   camera.updateProjectionMatrix()
   renderer.setSize(threeDom.clientWidth, threeDom.clientHeight)
 })
  1. 使用多种材质创建几何体

SceneUtils 是Scene对象的一个工具类 位于three/examples/jsm/utils/SceneUtils.jsSceneUtils.createMultiMaterialObject(cubeGeometry, materials) 方法返回为materials数组中指定的材质创建一个实例,并把实例保存在组里。可以像使用Mesh对象那样使用组。

 <script type="importmap">
   {
     "imports": {
       "three": "../node_modules/three/build/three.module.js",
       "three/addons/": "../node_modules/three/examples/jsm/",
       "datGui":"../node_modules/dat.gui/build/dat.gui.module.js",
       "SceneUtils":"../node_modules/three/examples/jsm/utils/SceneUtils.js"
     }
   }
 </script>
 ​
 <script type="module">
 import * as  SceneUtils  from 'SceneUtils'
 import { Mesh, MeshBasicMaterial, BoxGeometry, MeshLambertMaterial } from 'three'
   const controls = new function () {
     this.addComplexCube = function () {
       const cubeGeometry = new BoxGeometry(6, 6, 6)
       const materials = [
         new MeshBasicMaterial({color: 0x000000, wireframe: true}),
         new MeshLambertMaterial({opacity: 0.6, color: 0x44ff55, transparent: true})
       ]
       const cube = new SceneUtils.createMultiMaterialObject(cubeGeometry, materials)
       scene.add(cube)
     }
   } 
 </script>
 ​
 ​
  1. clone函数
 this.clone = function () {
     const geom = new BoxGeometry(2, 2, 2)
     const materials = [
         new MeshBasicMaterial({color: 0x000000, wireframe: true}),
         new MeshLambertMaterial({opacity: 0.6, color: 0x44ff44, transparent: true})
     ]
     const mesh = SceneUtils.createMultiMaterialObject(geom, materials)
     mesh.name = 'mesh'
     scene.add(mesh)
     this.numberOfObjects += 1
     // 复制几何体网格
     console.log(mesh.children[0])
     console.log(mesh instanceof  Group)  // true
     const cloneGeometry = mesh.children[0].geometry.clone()
     const materialsOther = [
         new MeshLambertMaterial({opacity: 0.8, color: 0xff44ff, transparent: true}),
         new MeshBasicMaterial({color: 0x000000, wireframe: true})
     ]
   
     const meshOther = SceneUtils.createMultiMaterialObject(cloneGeometry, materialsOther)
     meshOther.translateX(5)
     meshOther.translateZ(5)
     meshOther.name = 'cloneBox'
     setTimeout(() => {
       scene.remove(scene.getObjectByName("mesh"))
       this.numberOfObjects -= 1
     }, 1000)
     scene.add(meshOther)
     this.numberOfObjects += 1
 ​
 }
  1. PerspectiveCamera( fov?: number, aspect?: number, near?: number, far?: number)函数

fov:表示视场。这是在摄像机中能看到的那部分场景。 aspect:渲染结果的横向尺寸和纵向尺寸的比值。 near:定义了从相机多进的距离开始渲染。 far:定义了摄像机从它所处的位置能够看多远。 zoom:属性可以放大和缩小场景。

  1. OrthographicCamera.constructor( left?: number, right?: number, top?: number, bottom?: number, near?: number, far?: number)正交投影相机

left:可是范围的左平面 right:可是范围的右平面 top:可是范围的上平面 bottom:可是范围的下平面 near:基于摄像机所处的位置,从这一点开始渲染场景 far:基于摄像机所有的位置,渲染到场景到这一点为止 orthographicCamera.png

  1. 将摄像机聚焦在指定点

通常摄像机会指向场景中心position(0,0,0);我们可以改变摄像机指向的位置 camera.lookAt(new Vector(x,y,z))

二、Light

  1. Threejs中的光源的分类

所有光源的基类都是Light对象的扩展。分为两类: 一、简单光源; AmbientLight、PointLight、SpotLight、DirectionalLight 二、特殊用途的光源和效果 HemisphereLight、AreaLight、LensFlare。

  1. AmbientLight

AmbientLight光源的特性:会应用到全局、该光源没有特别的来源方向、不会产生阴影。 用法:不能将AmbientLight作为唯一的光源;在使用其他光源时使用它。

使用AmbientLight 和SpotLight小案例

 <!doctype html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <meta name="viewport"
           content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
     <meta http-equiv="X-UA-Compatible" content="ie=edge">
     <title>chapter three</title>
     <link rel="stylesheet" href="../node_modules/normalize.css/normalize.css">
     <link rel="stylesheet" href="../style/global.css">
 </head>
 <body>
 <div class="three-dom" id="threeDom"></div>
 <script type="importmap">
     {
         "imports": {
             "three": "../node_modules/three/build/three.module.js",
             "three/addons/": "../node_modules/three/examples/jsm/",
             "datGui":"../node_modules/dat.gui/build/dat.gui.module.js",
             "SceneUtils":"../node_modules/three/examples/jsm/utils/SceneUtils.js",
             "utils":"../utils/index.js",
             "stats":"../node_modules/three/examples/jsm/libs/stats.module.js"
         }
     }
 </script>
 <script type="module">
     import {initRenderer, initCamera, initStats,initOrbitControls} from 'utils'
     import {
         AmbientLight,
         Color,
         Mesh,
         MeshBasicMaterial,
         Scene,
         SpotLight,
         PlaneGeometry,
         MeshLambertMaterial, SphereGeometry, BoxGeometry
     } from "three";
 ​
     import datGui from 'datGui'
 ​
     const init = (threeDom) => {
         const stats = initStats(undefined, threeDom)
         const renderer = initRenderer(undefined, threeDom)
         const camera = initCamera(undefined, threeDom)
         const scene = new Scene()
         const orbicontrols = initOrbitControls(camera,renderer)
 ​
 ​
         const ambientLight = new AmbientLight('#606008', 1)
         scene.add(ambientLight)
 ​
         const spotLight = new SpotLight(0xffffff, 1, 180, Math.PI / 4);
         spotLight.shadow.mapSize.set(2048, 2048);
         spotLight.position.set(-30, 40, -10);
         spotLight.castShadow = true;
 ​
         scene.add(spotLight);
 ​
         initObject(scene)
         setUpControls(ambientLight, spotLight)
         return {
             stats,
             renderer,
             camera,
             scene,
             orbicontrols
         }
     }
 ​
     function setUpControls(ambientLight, spotLight) {
         const controls = new function () {
             this.intensity = ambientLight.intensity
             this.ambientColor = ambientLight.color.getStyle()
             this.diabledSpotLight = false
         }
         const gui = new datGui.GUI()
         gui.add(controls, 'intensity', 0, 3, 0.1).onChange(function (e) {
             ambientLight.color = new Color(e)
             ambientLight.intensity = controls.intensity
         })
         gui.add(controls, 'ambientColor').onChange(function (e) {
             ambientLight.color = new Color(controls.ambientColor)
             ambientLight.intensity = controls.intensity
         })
         gui.add(controls, 'diabledSpotLight').onChange(function (e) {
             spotLight.visible = !e
         })
         return controls
     }
 ​
     function initObject(scene) {
         const planeGeometry = new PlaneGeometry(60, 20, 1, 1)
         const planeMaterial = new MeshBasicMaterial({
             color: 0xaaaaaa
         })
         const plane = new Mesh(planeGeometry, planeMaterial)
         plane.position.set(15, 0, 0)
         plane.rotation.x = -0.5 * Math.PI
         scene.add(plane)
 ​
         const sphereGeometry = new SphereGeometry(4,20,20)
         const sphereMaterial = new MeshLambertMaterial({
             color:0x7777ffff,
             wireframe:true
         })
         const sphere = new Mesh(sphereGeometry,sphereMaterial)
         sphere.position.set(20,4,2)
         scene.add(sphere)
 ​
         const boxGeometry = new BoxGeometry(4,4,4)
         const boxMaterial = new MeshLambertMaterial({
             color:0xff0000,
             wireframe: true
         })
         const  box = new Mesh(boxGeometry,boxMaterial)
         box.position.set(-4,3,0)
         scene.add(box)
     }
 ​
     window.onload = function () {
         const threeDom = document.getElementById('threeDom')
         const {stats, renderer, camera, scene,orbicontrols} = init(threeDom)
         window.addEventListener('resize', () => {
             camera.aspect = threeDom.clientWidth / threeDom.clientHeight
             camera.updateProjectionMatrix()
             renderer.setPixelRatio(window.devicePixelRatio)
             renderer.setSize(threeDom.clientWidth, threeDom.clientHeight)
         })
 ​
         function render() {
             stats.update()
             orbicontrols.update()
             requestAnimationFrame(render)
             renderer.render(scene, camera)
         }
 ​
         render()
     }
 </script>
 </body>
 </html>

utils.js文件内容

import {PerspectiveCamera, WebGLRenderer, PCFSoftShadowMap, Color, Vector3} from 'three'
import Stats from 'stats'
import {OrbitControls} from 'three/addons/controls/OrbitControls.js'
function initStats(type, threeDom) {
    const panelType = (typeof type !== 'undefined' && type) && (!isNaN(type)) ? parseInt(type) : 0
    const stats = new Stats()
    stats.showPanel(panelType)
    threeDom.appendChild(stats.dom)
    return stats
}

function initRenderer(additionalProperties, threeDom) {
    const props = (typeof additionalProperties !== 'undefined' && additionalProperties) ? additionalProperties : {};
    const renderer = new WebGLRenderer(props);
    renderer.shadowMap.enabled = true;
    renderer.shadowMapSoft = true;
    renderer.shadowMap.type = PCFSoftShadowMap;

    renderer.setClearColor(new Color(0x000000));
    renderer.setSize(threeDom.clientWidth, threeDom.clientHeight);
    renderer.shadowMap.enabled = true;
    threeDom.appendChild(renderer.domElement);

    return renderer;
}

function initCamera(initialPosition, threeDom) {
    const position = (initialPosition !== undefined) ? initialPosition : new Vector3(-30, 40, 30);

    const camera = new PerspectiveCamera(45, threeDom.clientWidth / threeDom.clientHeight, 0.1, 1000);
    camera.position.copy(position);
    camera.lookAt(new Vector3(0, 0, 0));

    return camera;
}

function initOrbitControls (camera,renderer){
    const orbitControls = new OrbitControls(camera, renderer.domElement)
    orbitControls.listenToKeyEvents(window)
    orbitControls.enableDamping = true
    orbitControls.dampingFactor = 0.05
    orbitControls.screenSpacePanning = false
    orbitControls.minDistance = 1
    orbitControls.maxDistance = 50
    orbitControls.maxPolarAngle = Math.PI / 2
    return orbitControls
}

export {
    initStats,
    initCamera,
    initRenderer,
    initOrbitControls
}