相关技术简介 Canvas: 作为html元素存在.提供渲染上下文用于手动绘制需要展示的内容.
OpenGL 一套规范,定义了一系列操作和显示图形的API.显卡厂家通过驱动的方式,来将对应的API调用转化为GPU指令.
WebGL 一种3D绘图协议.浏览器支持后,可以通过JavaScript调用本地OpenGL接口,从而在浏览器提供硬件3D加速渲染.
https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext
ThreeJs 基于WebGL封装运行的三维引擎,封装了一些3D渲染需求中重要的工具方法与渲染循环.http://www.webgl3d.cn/Three.js/
3D渲染常用概念 场景 虚拟的三维世界,包含需要绘制的所有元素和参与绘制的工具.
模型 待绘制的元素以模型存在于场景中. 每一个模型由几何体和材质组成.
相机 三维场景在二维的投影会受到视角影响. 通过相机位置和方向的定义,来确定最终渲染的结果.
渲染器 根据场景和相机,将二维结果渲染到浏览器canvas上.
代码实操 最简单的场景实现: 1 2 3 <div id ="threejs_root" style="height: 100%;width: 100%" > <canvas id ="cell_canvas" class ="canvas" ></canvas> </div >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const root = document.getElementById('threejs_root ') const canvas = document.getElementById('cell_canvas ') var scene = new THREE.Scene() ; var width = root.clientWidth; var height = root.clientHeight; const camera = new THREE.PerspectiveCamera(45, width / height , 1, 5000) ; camera.position.set(0 , 0 , 500 ); camera.lookAt(scene .position ) ; const renderer = new THREE.WebGLRenderer({canvas , antialias : true }) ; renderer.setSize(width , height ) ; renderer.setClearColor(0xb9d3ff, 1) ; renderer.render(scene, camera);
增加坐标轴 1 2 3 const axes = new THREE.AxesHelper(20000 ) axes.setColors('red' , 'green' , 'blue' ) scene.add (axes)
支持鼠标联动 将鼠标拖动,滚动转化为相机数据,实时更新渲染结果.
1 const controls = new OrbitControls(camera , canvas ) ;
放置模型 简单的长方体. 1 2 3 4 5 const geometry = new THREE.BoxGeometry( 100 , 200 , 300 );const material = new THREE.MeshLambertMaterial({ color :0xff0000 });const cube = new THREE.Mesh( geometry, material ); scene.add (cube);
加载现有的obj模型. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 loadObj ( ) { return new Promise ((resolve,reject ) => { if (cellObj){ resolve(cellObj) }else { const loader = new OBJLoader(); loader.load('/cell2.obj' , function (obj ) { console .log("obj:" , obj) cellObj = obj.children[0 ] resolve(cellObj) }) } }) },
设置光源 点光源: 1 2 3 4 5 6 const point = new THREE.PointLight(0xffffff );point .position.set (100 , 200 , 0 ); scene.add (point );const pointLightHelper=new THREE.PointLightHelper(point ,10 ,0xff00ff ); scene.add (pointLightHelper);
平行光: 1 2 3 4 5 6 const directionalLight = new THREE.DirectionalLight(0xffffff , 1 );directionalLight .position.set (400 , 200 , 300 ); scene.add (directionalLight );const directionalLightHelper=new THREE.DirectionalLightHelper(directionalLight ,50 ,0x00ff00 ); scene.add (directionalLightHelper);
环境光:用于上述光源照射不到的面.会与材质颜色做混合运算. 1 2 const ambient = new THREE.AmbientLight(0x666666 ); scene.add (ambient );
点击事件 点击事件本质上是将相机位置和屏幕点击位置连起来画射线,穿过的模型就是被点击的物体. ThreeJs提供了射线类Raycaster 来完成这个功能.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const raycaster = new THREE.Raycaster();onMouseDown ( ) { mouse.x = (event.clientX / window .innerWidth) * 2 - 1 ; mouse.y = -(event.clientY / window .innerHeight) * 2 + 1 ; raycaster.setFromCamera(mouse, this .camera); var raycasters = raycaster.intersectObjects(this .scene.children); if (raycasters.length > 0 ) { const object = raycasters[0 ].object console .log('命中的第一个模型:' , object ) } },
简单的点击特效:发光外壳 使用效果组合器接管渲染器.
1 2 3 var renderPass = new RenderPass(scene , camera ) ; var composer = new EffectComposer(renderer ) ; composer.addPass(renderPass ) ;
将
1 this .renderer.render(this .scene, this .camera);
替换为
1 this .composer.render(this .scene, this .camera);
在效果组合器中增加一个轮廓处理器.
1 2 3 4 5 6 7 var outlinePass = new OutlinePass(new THREE.Vector2(window .innerWidth , window .innerHeight ) , scene, camera); outlinePass.visibleEdgeColor = new THREE.Color(1, 1, 1) ; outlinePass.hiddenEdgeColor = new THREE.Color(1, 1, 1) ; outlinePass.edgeThickness = 3.0 ; outlinePass.pulsePeriod = 3 var composer = new EffectComposer(renderer ) ; composer.addPass(renderPass ) ;
点击事件中,为轮廓处理器设置目标对象.
1 2 const object = raycasters[0 ].object this .outlinePass.selectedObjects = [object ];
增加2D弹窗 简单的2D弹窗通过单独的渲染器CSS2DRenderer配合CSS2DObject实现.
直接在canvas同级添加一个element,并和Render绑定.
1 2 3 const labelRenderer = new CSS2DRenderer() ; labelRenderer.setSize(root .clientWidth , root .clientHeight ) ; canvas.parentElement.appendChild(labelRenderer .domElement ) ;
使用dom element创建CSS2DObject,像添加3D模型一样添加到场景中.
1 2 3 4 5 6 7 8 const div = labelLayout.cloneNode(true ); div.style .visibility = "visible" ; div.childNodes[0 ].childNodes[0 ].textContent = item.vol + 'V\n#21 '; const label = new CSS2DObject(div);label .name = item.namelabel .position .copy (copyObj.position );//这里直接使用对应3D模型的坐标.label .translateZ(40 )//沿Z轴上移,显示效果为悬浮在对应模型上方.scene .add(label )
渲染时,将CSS2DRenderer一起执行即可.
1 this .labelRenderer.render(this .scene, this .camera);
动画效果的实现. 屏幕显示的内容本质上是镜头和场景共同作用的结果. 只需要以固定间隔修改两者之一,即可做出动画效果.
简单的视角改变,可以用通过镜头的位移和角度实现.
模型动画,需要单独修改场景甚至指定模型.
示例: 指定几个变量,在render方法中开启动画对应的镜头计算.
1 2 3 4 5 6 7 8 9 10 11 12 render() { this .composer.render(this .scene, this .camera); this .labelRenderer.render(this .scene, this .camera); requestAnimationFrame(this .render); if (animating1) { this .calAnimatPositions1() } else if (animating2) { this .calAnimatPositions2() }else if (animating3) { this .calAnimatPositions3() } },
三段动画:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 calAnimatPositions1() { this .camera.position.x -= 2 ; this .camera.position.z -= 1 ; this .camera.lookAt(0 , 0 , 0 ); if (this .camera.position.z < 25 ) { this .camera.position.z = 25 } if (this .camera.position.x <= 0 ) { animating1 = false animating2 = true } }, calAnimatPositions2() { this .camera.position.y -= 2 ; this .camera.position.z += 1 ; this .camera.lookAt(0 , 0 , 0 ); if (this .camera.position.z > 100 ) { this .camera.position.z = 100 } if (this .camera.position.y <= -300 ) { animating2 = false animating3 = true } }, calAnimatPositions3() { this .camera.position.y += 2 ; if (this .camera.position.y <= 0 ) { this .camera.position.x += 2 ; } else { this .camera.position.x -= 2 ; } this .camera.lookAt(0 , 0 , 0 ); if (this .camera.position.y >= 300 ) { animating3 = false } },
总结 ThreeJS提供的功能还远不止于此.纹理/精灵/骨骼等高级应用可以完成更酷炫和实用的需求. 参考资料:https://threejs.org/docs/index.html#manual/zh/introduction/Creating-a-scene http://www.webgl3d.cn/Three.js/ https://sogrey.top/Three.js-start/start/#top