背景 公司要做3D大屏和3D组态,首先要做几个3D大屏对外演示
基本使用 以下记录我的工作中需求的基本实现,方便以后使用。官方的代码中有示例,大部分效果都能在示例中找到。
安装 工程项目引入可以使用npm,html文件使用,直接下载官方提供的代码即可。这里使用0.148.0版本,threejs官方更新频繁,注意版本,160的版本按148的语法,材质反光就不一样,具体未深入研究。
1 2 3 4 5 6 7 8 <script type="importmap" > { "imports" : { "three" : "./node_modules/three/build/three.module.js" , "three/addons/" : "./node_modules/three/examples/jsm/" } } </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <script type="module" > import * as THREE from 'three' ; import { OrbitControls } from 'three/addons/controls/OrbitControls.js' ; import { DragControls } from 'three/addons/controls/DragControls.js' ; import { GUI } from 'three/addons/libs/lil-gui.module.min.js' ; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js' ; import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js' ; import { FBXLoader } from 'three/addons/loaders/FBXLoader.js' ; import { OBJLoader } from 'three/addons/loaders/OBJLoader.js' ; import { MTLLoader } from 'three/addons/loaders/MTLLoader.js' ; import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js' ; import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js' ; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js' ; import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js" import { FXAAShader } from "three/addons/shaders/FXAAShader.js" import { CSS3DRenderer , CSS3DObject , CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js' </script>
场景 1.新建一个场景,以后的光源、相机、模型等都需要添加到场景中。 2.给场景添加材质,这里用一个天空的360全景分成的前后上下左右6个面做天空盒的6个方位的面。也可以用球体和360全景图做,后面会写个文档记录一下全景图 3.将相机放在天空盒内,就有了置身场景内的效果。更多功能看官方文档
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 const scene = new THREE .Scene ()var urls = [ './models/new0713/yun4/px.png' , './models/new0713/yun4/nx.png' , './models/new0713/yun4/py.png' , './models/new0713/yun4/ny.png' , './models/new0713/yun4/pz.png' , './models/new0713/yun4/nz.png' ]; var cubeLoader = new THREE .CubeTextureLoader ();scene.background = cubeLoader.load (urls); scene.fog = new THREE .Fog (0xD3DBE7 , 1500 , 2000 ) let texture1 = textureLoader.load ('./models/new0713/dimian.jpg' );var textureNormal1 = textureLoader.load ('./models/new0713/dimian.jpg' )texture1.wrapS = texture1.wrapT = THREE .RepeatWrapping ; texture1.repeat .set (25 , 25 ); texture1.anisotropy = 16 ; const planeGeometry = new THREE .PlaneGeometry (5000 , 5000 , 320 , 320 );const planeMaterial = new THREE .MeshStandardMaterial ({ color : 0x707070 , map : texture1, roughness : 0.3 , lightMap : textureNormal1, bumpScale : 3 }) const plane = new THREE .Mesh (planeGeometry, planeMaterial);plane.name = '地面' plane.rotation .x = - 0.5 * Math .PI ; plane.position .set (0 , 0 , 0 ) plane.receiveShadow = true ; scene.add (plane);
光源 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 const pointLight = new THREE .PointLight (0xffffff , 2.0 )pointLight.position .set (-1000 , 1000 , -1000 ) pointLight.castShadow = true pointLight.shadow .mapSize .width = 2048 ; pointLight.shadow .mapSize .height = 2048 ; pointLight.shadow .camera .near = 0.5 ; pointLight.shadow .camera .far = 10000 scene.add (pointLight) const pointLightHelper = new THREE .PointLightHelper (pointLight, 5.0 , 'yellow' )scene.add (pointLightHelper) let ambient = new THREE .AmbientLight (0x404040 );scene.add (ambient); const directionalLight = new THREE .DirectionalLight (0xffffff , 0.5 );scene.add (directionalLight); const hemiLight = new THREE .HemisphereLight (0xddeeff , 0x0f0e0d , 0.02 );hemiLight.intensity = 1 ; scene.add (hemiLight);
相机 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const width = window .innerWidth const height = window .innerHeight const camera = new THREE .PerspectiveCamera (75 , width / height, 1 , 3000 )camera.setViewOffset (width, height, 0 , 0 , width, height); camera.position .set (-800 , 100 , 80 ) camera.lookAt (0 , 0 , 0 ) const controls = new OrbitControls (camera, renderer.domElement )controls.enablePan = false controls.addEventListener ('change' , function ( ) { renderer.render (scene, camera) })
模型 glb/gltf自带材质 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 const loader = new GLTFLoader ()var model = null loader.load ( './models/house.glb' , function (gltf ) { model = gltf.scene ; const div = document .createElement ('div' ); div.setAttribute ('id' , 'tag' ) div.innerHTML = 'glb模型' div.style .color = '#fff' div.style .background = 'red' div.style .width = '120px' div.style .height = '50px' const tag = new CSS3DObject (div) tag.rotation .y = - 0.5 * Math .PI tag.position .set (-8 , 7 , 0 ) tag.scale .set (0.05 , 0.05 , 0.05 ) model.add (tag); model.castShadow = true model.children .forEach (item => { item.name = 'glb模型:楼房' item.castShadow = true }) model.name = '建筑' model.position .set (-100 , 20 , -200 ); model.scale .set (20 , 20 , 20 ); scene.add (model); }, function (xhr ) { console .log ((xhr.loaded / xhr.total * 100 ) + '% loaded' ); }, function (error ) { console .log ('An error happened' ); } );
obj,需要单独引入材质 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 43 44 45 46 const objLoader = new OBJLoader ()mtlLoader.load ('./models/new0711/medieval-house.3dcool.net.mtl' , (mtl ) => { mtl.preload (); for (const material of Object .values (mtl.materials )) { material.side = THREE .DoubleSide ; } objLoader.setMaterials (mtl); objLoader.load ( './models/new0711/medieval-house.3dcool.net.obj' , function (object ) { const div1 = document .createElement ('div' ); div1.setAttribute ('id' , 'tag1' ) div1.innerHTML = 'obj模型+材质' div1.style .color = '#fff' div1.style .background = 'red' div1.style .width = '120px' div1.style .height = '50px' const tag1 = new CSS3DObject (div1) tag1.position .set (200 , 1300 , -200 ) tag1.scale .set (10 , 10 , 10 ) object.add (tag1) object.scale .set (0.1 , 0.1 , 0.1 ) object.rotation .y = - 0.5 * Math .PI ; object.children .forEach (item => { item.castShadow = true item.name = 'obj模型:别墅' }) scene.add (object); }, function (xhr ) { console .log ((xhr.loaded / xhr.total * 100 ) + '% loaded' ); }, function (error ) { console .log ('An error happened' ); } ); });
glb/gltf自带材质 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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 const fbxLoader = new FBXLoader ()fbxLoader.load ( './models/robot092504.fbx' , function (object ) { console .log ('object' , object) console .log ('animation' , object.animations ) const box = new THREE .Box3 ().setFromObject (object); const size = box.getSize (new THREE .Vector3 ()); console .log ('size' , size) mixer = new THREE .AnimationMixer ( object ); var action = mixer.clipAction ( object.animations [0 ] ); action.play (); const box1 = new THREE .Box3 ().setFromObject (object); const center = new THREE .Vector3 (); box1.getCenter (center); object.position .sub (center); object.traverse ( function ( child ) { if ( child.isMesh ) { if (child.material ) { child.frustumCulled = false } child.castShadow = true ; child.receiveShadow = true ; console .error ('child.material' , child.material ) if (child.material .isMaterial ) { console .log ('name' , child.name ) let meshMaterial = new THREE .MeshPhysicalMaterial ({ color : 0xffffff , metalness : 0.8 , roughness : 0.5 }); meshMaterial.needsUpdate = true child.material = meshMaterial } } } ); scene.add (object); }, function (xhr ) { console .log ((xhr.loaded / xhr.total * 100 ) + '% loaded' ); setTimeout (() => { render () }, 10 ); }, function (error ) { console .log ('An error happened' , error); } );
渲染 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 43 44 const renderer = new THREE .WebGLRenderer ()renderer.setSize (width, height) renderer.shadowMap .enabled = true ; renderer.shadowMap .type = THREE .PCFSoftShadowMap ; document .body .appendChild (renderer.domElement )const composer = new EffectComposer (renderer);const v2 = new THREE .Vector2 (window .innerWidth , window .innerWidth );const outlinePass = new OutlinePass (v2, scene, camera);outlinePass.renderToScreen = false ; outlinePass.edgeStrength = 1 outlinePass.edgeGlow = 2 outlinePass.edgeThickness = 2 outlinePass.pulsePeriod = 1 outlinePass.usePatternTexture = false outlinePass.visibleEdgeColor .set ('yellow' ); outlinePass.hiddenEdgeColor .set ('white' ); outlinePass.clear = true const renderPass = new RenderPass (scene, camera)composer.addPass (renderPass) function render ( ) { renderer.render (scene, camera) r.render (scene, camera) controls.update () requestAnimationFrame (render) if (composer) composer.render () } const controls = new OrbitControls (camera, renderer.domElement )controls.addEventListener ('change' , function ( ) { renderer.render (scene, camera) composer.render () r.render (scene, camera) }) render ()
事件 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 43 44 45 46 47 48 49 50 document .onclick = onDocumentMouseDownvar mouse = {}function onDocumentMouseDown (e ) { e.preventDefault (); screenToWorld (e.clientX , e.clientY ) mouse.x = (e.clientX / window .innerWidth ) * 2 - 1 ; mouse.y = -(e.clientY / window .innerHeight ) * 2 + 1 ; var vector = new THREE .Vector3 (mouse.x , mouse.y , 0.5 ).unproject (camera); const pointer = new THREE .Vector2 (); pointer.x = mouse.x pointer.y = mouse.y var raycaster = new THREE .Raycaster (camera.position , vector.sub (camera.position ).normalize ()); var intersects = raycaster.intersectObjects (scene.children ); if (intersects.length > 0 ) { var intersected = intersects[0 ].object ; var worldPosition = new THREE .Vector3 (); outlinePass.selectedObjects = [intersected]; composer.addPass (outlinePass) var effectFXAA = new ShaderPass (FXAAShader ) effectFXAA.uniforms .resolution .value .set (1 / window .innerWidth , 1 / window .innerHeight ) effectFXAA.renderToScreen = true composer.addPass (effectFXAA) if (document .querySelector ('.tip' )) { document .querySelector ('.tip' ).remove () } const doc = document .querySelector ('body' ) const dom = document .createElement ('div' ) dom.setAttribute ('class' , 'tip' ) let aaa = intersects[0 ].object .aaa || '' dom.innerHTML = `<span>${intersects[0 ].object.name} </span>` dom.style .background = 'rgba(0,0,0,0.8)' dom.style .padding = '10px 10px' dom.style .border = '1px solid #fff' dom.style .color = '#fff' dom.style .position = 'absolute' dom.style .top = e.clientY + 'px' dom.style .left = e.clientX + 'px' doc.appendChild (dom) } }