基于 Viewer 库封装的一些记录
基础封装初始化包括以下:
# 一、ThreeJS-ES6 库引入
引入部分除了 ThreeJS 核心库的东西外,还有标签渲染器,用于后期在场景中添加标签,还有控制器何和帧率显示器。
| import { | |
|   Cache, | |
|   WebGLRenderer, | |
|   ACESFilmicToneMapping, | |
|   PCFSoftShadowMap, | |
|   sRGBEncoding, | |
|   PerspectiveCamera, | |
|   Scene, | |
| Color | |
| } from 'three' | |
| // 二维标签渲染器 | |
| import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer' | |
| import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer' | |
| import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' | |
| import Stats from 'three/examples/jsm/libs/stats.module.js' | 
# 二、初始化渲染器
初始化渲染器部分主要是获取渲染画布的 dom,然后初始化场景渲染器,初始化二维标签渲染器,和三维标签渲染器。
这一部分代码我们设置渲染器的大小,这个放到了后面会讲。
| _initRenderer () { | |
|     // 获取画布 dom | |
| this.viewerDom = document.getElementById(this.id) | |
|     // 初始化渲染器 | |
| this.renderer = new WebGLRenderer({ | |
| logarithmicDepthBuffer: true, | |
| antialias: true, //true/false 表示是否开启反锯齿 | |
| alpha: true, //true/false 表示是否可以设置背景色透明 | |
| precision: 'highp', //highp/mediump/lowp 表示着色精度选择 | |
| premultipliedAlpha: true, //true/false 表示是否可以设置像素深度(用来度量图像的分辨率) | |
| preserveDrawingBuffer: true //true/false 表示是否保存绘图缓冲 | |
| }) | |
| this.renderer.domElement.style.zIndex = 1 | |
|     // 默认情况下,js 的光强数值不真实。为了使得光强更趋于真实值,应该把渲染器的 physicallyCorrectLights 属性设为 true | |
| this.renderer.physicallyCorrectLights = true | |
|     // 尽管我们的贴图不是 HDR,但使用 tone mapping 可以塑造更真实的效果。 | |
| this.renderer.toneMapping = ACESFilmicToneMapping | |
|     // 场景中的阴影自动更新 | |
| this.renderer.shadowMap.enabled = true | |
|     // 设置渲染器开启阴影贴图,并将类型设为 PCFSoftShadowMap | |
| this.renderer.shadowMap.type = PCFSoftShadowMap | |
|     // 这下我们可以看到更亮的材质,同时这也影响到环境贴图。 | |
| this.renderer.outputEncoding = sRGBEncoding | |
|     // 一个 canvas,渲染器在其上绘制输出。 | |
| this.viewerDom.appendChild(this.renderer.domElement) | |
|     // 网页标签 | |
| this.labelRenderer = new CSS2DRenderer() | |
| this.labelRenderer.domElement.style.zIndex = 2 | |
| this.labelRenderer.domElement.style.position = 'absolute' | |
| this.labelRenderer.domElement.style.top = '0px' | |
| this.labelRenderer.domElement.style.left = '0px' | |
|     // 避免 HTML 标签遮挡三维场景的鼠标事件 | |
| this.labelRenderer.domElement.style.pointerEvents = 'none' | |
| this.viewerDom.appendChild(this.labelRenderer.domElement) | |
|     // 三维标签 | |
| this.css3DRenderer = new CSS3DRenderer() | |
| this.css3DRenderer.domElement.style.zIndex = 0 | |
| this.css3DRenderer.domElement.style.position = 'absolute' | |
| this.css3DRenderer.domElement.style.top = '0px' | |
| this.css3DRenderer.domElement.style.left = '0px' | |
|     // 避免 HTML 标签遮挡三维场景的鼠标事件 | |
| this.css3DRenderer.domElement.style.pointerEvents = 'none' | |
| this.viewerDom.appendChild(this.css3DRenderer.domElement) | |
|   } | 
# 三、初始化相机
相机参数里面的 aspect(摄像机视锥体长宽比),其实应该是画布 dom 的长宽比,而不是我们浏览器 windows 的长宽比,请你仔细品品这句话。
参数
构造器 PerspectiveCamera (fov : Number, aspect : Number, near : Number,> far : Number )
fov — 摄像机视锥体垂直视野角度
aspect — 摄像机视锥体长宽比
near — 摄像机视锥体近端面
far — 摄像机视锥体远端面
| _initCamera () { | |
|     // 渲染相机 | |
| this.camera = new PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 5000) | |
| this.camera.position.set(50, 0, 50) | |
| this.camera.lookAt(0, 0, 0) | |
|   } | 
# 四、初始化场景
| _initScene () { | |
|     // 渲染场景 | |
| this.scene = new Scene() | |
| this.scene.background = new Color('rgb(5,24,38)') | |
|   } | 
# 五、初始化鼠标控制器
控制器主要是用来控制通过鼠标漫游场景的。
参数
OrbitControls(object:Camera,domElement:HTMLDOMElement)
object:(必需)要控制的摄像机。相机不得是其他对象的子对象,除非该对象是场景本身。
domElement:用于事件侦听器的 HTML 元素。
| _initControl (option) { | |
| this.controls = new OrbitControls(this.camera, this.renderer.domElement) | |
| this.controls.enableDamping = false | |
| this.controls.maxPolarAngle = Math.PI * 0.46// 垂直轨道多远 上限 | |
| this.controls.minPolarAngle = Math.PI * 0.3// 你可以垂直轨道多远,下限 | |
| this.controls.screenSpacePanning = false // 定义平移时如何平移相机的位置 控制不上下移动 | |
|   } | 
# 六、灯光初始化
没有灯光的话,就会一片漆黑,所以需要添加灯光。
| // 全局光不需要 | |
| const ambientLight = new DS.THREE.AmbientLight(0xffffff, 0.2) | |
| viewer.scene.add(ambientLight) | 
# 七、全局渲染的函数 - 逐帧获取页面大小
这里是全局渲染的函数,通过 requestAnimationFrame 函数,可以逐帧去渲染场景。
摄像机视锥体的长宽比,设置渲染器的长宽比,都是这里一直去获取传入的 div,而不是 window 的大小,设置参数(虽然这样写会很耗费性能,但是我也没有找到更好的方法去监听页面的大小变化)。
| function animate () { | |
| requestAnimationFrame(animate) | |
| that._undateDom() | |
| that._readerDom() | |
|       // 全局的公共动画函数,添加函数可同步执行 | |
| that.animateEventList.forEach(event => { | |
| event.fun && event.content && event.fun(event.content) | |
| }) | |
|     } | |
| animate() | |
|   // 更新 dom 大小 | |
| _undateDom () { | |
| const that = this | |
| that.controls.update() | |
|     // 摄像机视锥体的长宽比,通常是使用画布的宽 / 画布的高,所以这里的画布指的是 dom,就是一个 div,而不是 window | |
| that.camera.aspect = that.viewerDom.clientWidth / that.viewerDom.clientHeight | |
|     // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用,来使得这些改变生效 | |
| that.camera.updateProjectionMatrix() | |
|     // 设置渲染器的长宽比,所以就不需要在代码里面去写 window 的页面监听,来改变页面大小【】 | |
| that.renderer.setSize(that.viewerDom.clientWidth, that.viewerDom.clientHeight) | |
|     //that.renderer.setPixelRatio (window.devicePixelRatio) // 设置设备像素比 | |
| that.labelRenderer.setSize(that.viewerDom.clientWidth, that.viewerDom.clientHeight) | |
| that.css3DRenderer.setSize(that.viewerDom.clientWidth, that.viewerDom.clientHeight) | |
|   } | |
|   // 渲染 dom | |
| _readerDom () { | |
| this.renderer.render(this.scene, this.camera) | |
| this.labelRenderer.render(this.scene, this.camera) | |
| this.css3DRenderer.render(this.css3dScene, this.camera) | |
|   } | 
# 八、全局动画函数
这里我做了一个全局所有动画的管理器,页面上所有的需要动画的函数都可以传入运行,包括模型的动画、加载水面的运动、贴图的 UV 动画。
动画函数数组用来存储所有的动画函数
| // 动画函数数组 | |
| this.animateEventList = [] | 
对动画函数的添加
| /** | |
| * 添加全局的动画事件 | |
| * @param animate 函数加参数对象 | |
|    * 传入对象 = { | |
| fun: 函数名称, | |
| content: 函数参数 | |
| } | |
| */ | |
| addAnimate (animate) { | |
| this.animateEventList.push(animate) | |
|   } | 
这里以状态监视器为例,创建函数,并且添加到全局的动画函数数组里面去
| /** | |
| * 状态更新 | |
| * @param statsControls | |
| */ | |
| _statsUpdate (statsControls) { | |
| statsControls.update() | |
|   } | |
|   /** | |
| * 添加状态监测 | |
| */ | |
| addStats () { | |
| if (!this.statsControls) this.statsControls = new Stats() | |
| this.statsControls.dom.style.position = 'absolute' | |
| this.viewerDom.appendChild(this.statsControls.dom) | |
|     // 添加到动画 | |
| this.statsUpdateObject = { | |
| fun: this._statsUpdate, // 函数名称,函数在上面 | |
| content: this.statsControls // 绑定传入的参数 | |
|     } | |
| this.addAnimate(this.statsUpdateObject) | |
|   } | 
对于函数的移除
| /** | |
| * 移除全局的动画事件 | |
| * @param animate 函数加参数对象 | |
|    * 传入对象 = { | |
| fun: 函数名称, | |
| content: 函数参数 | |
| } | |
| */ | |
| removeAnimate (animate) { | |
| this.animateEventList.map((val, i) => { | |
| if (val === animate) this.animateEventList.splice(i, 1) | |
| }) | |
|   } | 
以移除状态监视器为例
| /** | |
| * 移除状态检测 | |
| */ | |
| removeStats () { | |
| if (this.statsControls) this.viewerDom.removeChild(this.statsControls.dom) | |
|     // 添加到动画 | |
| this.statsUpdateObject = { | |
| fun: this._statsUpdate, | |
| content: this.statsControls | |
|     } | |
| this.removeAnimate(this.statsUpdateObject) | |
|   } | 
# 九、销毁页面
| beforeDestroy () { | |
| this.scene.traverse((child) => { | |
| if (child.material) { | |
| child.material.dispose() | |
|       } | |
| if (child.geometry) { | |
| child.geometry.dispose() | |
|       } | |
| child = null | |
| }) | |
| this.renderer.forceContextLoss() | |
| this.renderer.dispose() | |
| this.scene.clear() | |
|   } | 
