基础 材质 基类:Material 定义:材质描述了对象objects的外光,它们的定义方式与渲染器无关,如果使用不同的渲染器,则不必重写材质。
整体上来看,就是渲染表现能力越强,占用的计算机硬件资源更多。
占用渲染资源 MeshBasicMaterial < MeshLambertMaterial < MeshPhongMaterial < MeshStandardMaterial < MeshPhysicalMaterial
渲染表现能力 MeshBasicMaterial < MeshLambertMaterial < MeshPhongMaterial < MeshStandardMaterial < MeshPhysicalMaterial
材质或集合体共享 我们可以直接访问并修改物体的材质对象,因为材质事共享的如果对其进行修改该用到该材质的物体都会发生改变
我们同时也可以直接访问并修改物体的集合体对象,若有其他物体也是用到这个集合体 修改后也会造成影响。
三维变量的克隆和复制方法 let v1 = new three.Vector(1,2,3)
letv2 = v1.clone() //此时会克隆一个与v1一样属性与值得对象
let v3 = {}
v3.copy(v1)可将v1得值赋值给v3
若是物体得克隆 他的材质与集合体不单独进行克隆得话 那么材质和集合体还是存在共享问题
顶点: 顶点的每个坐标对应一个顶点法线 ,法线关系光照后阴影的角度。
物体的属性有一些列的属性:position,rotation,scalr等属性 还有相关的方法,其都会继承父级构造函数的属性和方法
向量:normalize 将向量转为单位向量 长度为1
组对象group
子对象:group得children属性下可以查看到这个组对象下面得所有子对象。
子对象任何变化始终都是根据着父节点进行的。我们也可以直接使用Object3D创建一个节点对象,也可以给网格添加子对象。
通过设置name 可以给相应的物体设置指定的名字。
Object3D.traverse遍历所有的子节点
Object3D.getObjectByName() 获取指定名称的节点
本地坐标:当前物体的的坐标就是position的位置
Object3D.postion 表示物体的局部位置
世界坐标:本地坐标加上所有上级的坐标累加坐标,是为子集物体始终是相对于父级物体进行偏移的
Object3D.getWordPosition(v3):将当前物体的三维坐标存在一个三维向量中,可以获取当这个物体的世界坐标
当我们通过translate修改了集合体顶点坐标的时候,该物体的局部坐标系并没有发生改变,所有我们对这个物体(object3d)的一些列操作(如rotate)这个物体模型会相对于坐标原点进行
物体隐藏visible:物体object3d 隐藏或显示一个模型。 隐藏父级物体 他的子集物体也会被隐藏
材质隐藏visible:材质materail 通过该属性可以控制是否隐藏该材质对应的模型对象。 相同材质的隐藏的话 所有使用到此材质的物体的模型都会被隐藏
object3d.remove: scene , mesh ,都有删除这个方法 与add添加模型对立,可以删除他的子代模型 如“senec.remove(light)删除环境光,因为光照也是通过add添加的 ,场景模型多的话可以遍历子代模型条件删除一些模型。在进行删除尝试中发现了一个微妙的问题,就是在遍历traverse中不建议直接删除当前的属性。会出现一些奇怪的问题
模型数的遍历 object3D.traverse() 遍历了下面的所有子子节点。
object3D.getObjectByName() 根据Object.name来查找对应的名字的模型
object3D.visible 控制模型的显示与隐藏
模型:gltf 包含内容:几乎包含了该模型的所有数据 模型的层级关系,rgb材质,纹理材质,骨骼动画,变形动画…
格式信息:
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 { "asset" : { "version" : "2.0" , } , ... "materials" : [ { "pbrMetallicRoughness" : { "baseColorFactor" : [ 1 , 1 , 0 , 1 ] , "metallicFactor" : 0.5 , "roughnessFactor" : 1 } } ] , "meshes" : ... "images" : [ { "uri" : "贴图名称.png" } ] , "buffers" : [ { "byteLength" : 840 , "uri" : "data:application/octet-stream;base64,AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/....... } ], }
gltf格式文件不一定就是以扩展名.gltf结尾,.glb就是gltf格式的二进制文件。比如你可以把.gltf模型和贴图信息全部合成得到一个.glb文件中,.glb文件相对.gltf文件体积更小,网络传输自然更快。
load(’模型地址’,加载成功后的函数,加载时的相关信息) :我们可以在加载函数中再次处理这个模型,如修改模型的相关属性,可以通过第三个参数来设置相关进度条…
查询模型节点 :getObjectName() 根据模型的name属性来查询谋和模型对象
遍历模型traverse :
查看gltf的默认材质 :
材质相同问题:模型材质共享的问题判断材质的name 是否相同,
解决方案:1,三维建模软件中设置,需要代码改变材质不要共享,要独享材质。2,代码批量更改,克隆材质对象,重新赋值给mesh的材质属性.
1 2 3 4 5 6 gltf.scne .getObjectByName ('汽车' ).traverse (function (item ){ if (item.isMesh ){ item.material = obj.material .clone () } })
纹理 如果没有特殊的需求,一般为了正常渲染,避免色差,three.js中贴图的颜色空间编码属性encoding要与webGL渲染器的outputEncoding属性(最新版本中属性名已改为 outputColorSpace )保持一致。
纹理对象的encoding有多个属性:THREE.LinearEncoding(线性颜色空间:3000 ),THREE.sRGBEncoding (sRGB颜色空间:3001)
如果webGL的渲染器的色彩空间手动设置了后面的添加的纹理也需要设置成相关的色彩空间(最新版中纹理对象的色彩空间属性名已由encoding改为colorSpace)
给gltf模型更换颜色贴图:TextureLoader()加载新的模型数据 如果直接给gltf的模型材质map设置新的纹理会出现错位的情况,这和贴图的texture的翻转flipY属性有关。
纹理对象的flipY默认值为trun ,gltf的贴图flipY默认值为false,跟换模型的贴图时需要设置为false.
纹理的列阵wrapS wrapT 分别是在UV轴上的如何进行包裹。repeat在uv轴上重复的数量
环境贴图:
立方体纹理加载器cubetextureLoader
这个加载器加载六个图片作为空间的上下左右前后,这个用来加载环境贴图,
1 2 3 4 5 6 7 8 9 10 const textureCube = new THREE .CubeTextureLoader () .setPath ('./环境贴图/环境贴图0/' ) .load (['px.jpg' , 'nx.jpg' , 'py.jpg' , 'ny.jpg' , 'pz.jpg' , 'nz.jpg' ]);
材质MeshStandardMaterial的envMap环境贴图属性,通过pbr材质的贴图属性反射周围的景物。
envMapIntensity属性控制了反光率 roughness:控制粗糙度
环境贴图的色彩空间编码应该与渲染器的保持一致
//pbr材质的相关属性:
clearcoat:可以给物体表面设置一层透明膜,就像一层透明的漆,范围在0-1.
clearcoatRoughness:0-1 ;清漆层的粗糙层度。
metalness: 0.9,//车外壳金属度 roughness: 0.5,//车外壳粗糙度
transmission 玻璃透明度
Ior:折光率
场景的背景: 透明度:new Three.WebGLRenderer({alpha:true})或 render.setColorAlpha:0-1
背景色:render.setClearColor(颜色,透明度)
导出图片: 1.创建a标签
2.设置a标签的herf的 为场景的canvas元素,可以通过render.domElement获取
3.渲染器设置preserveDrawingBuffer为true ,开启缓存。
4.通过canvas的toDataURL(‘image/png’)获取话不上的像素信息,将以png的格式保存。
mesh重叠时候会出现闪缩的情况叫做深度冲突(z-fighting) 自定义划线:顶点数据来画 几何体setFromPoints(v3)将v3顶点数据设为几何体的attribute.position的顶点位置属性。除了可以是三维向量数组还可以是二位向量数组。
曲线:基类cuver
管道几何体 1 TubeGeometry(path, tubularSegments, radius, radiusSegments, closed)
旋转成型几何体 LatheGeometry :通过一个圆形轮廓坐标点 旋转得到一个几何体曲面
1 2 LatheGeometry(points, segments, phiStart, phiLength) points为一个二维矢量组成的一个线段 segments为细分数, phiStart开始角度 phiLength旋转的角度默认值2π
形状几何体 ShapeGeometry: 通过一个集合的轮廓得到一个多边形几何体平面。
先通过 Shape 将这些点生成一个二维平面,shape([二维矢量点数组…])
用ShapeGeometry 将这个二位平面装维平面几何体 ShapeGeometry (shape)
多边形轮廓shape(重点) 使用一些列路径传入shape生成一个多边形的形状,它可以和ExtrudeGeometry 、ShapeGeometry 一起使用,获取点,或者获取三角,也可以获得一个该形状的几何体,非常Nice.
1 2 父类为Path,所以也是继承了父类的相关方法与属性,如画线,画圆,画椭圆,等等获取当前点等属性。 传入点组成opints,可构造一个自定任意形状的多边形
若线与圆弧组合的时候,第一次的结束点自动衍生至第二个圆弧的起点。当前一次线画完时会采取以当前的currentPoint为中心点。
shape的线与圆共同使用的时候,他们的起点会相连接
内孔:holes 在创建的shape内挖出对应的孔洞,这些孔洞的坐标位置相对原点。
边缘几何体 EdgesGeometry :给指定几何体添加外边框线查看其边缘。
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 const geometryModle = new THREE .CylinderGeometry (2 ,2 ,32 ) const materialModle = new THREE .MeshLambertMaterial ({ color : 0x004444 , transparent :true , opacity :0.5 , }) const mesh2 = new THREE .Mesh (geometryModle,materialModle) scene.add (mesh2) const edgesGeometry = new THREE .EdgesGeometry (geometryModle,30 ) const edgesMaterial = new THREE .LineBasicMaterial ({ color : 0x00ffff , }) const lineModle = new THREE .LineSegments (edgesGeometry,edgesMaterial) scene.add (lineModle) modelLoader.load ('./bwm/scene.gltf' ,(gltf )=> { gltf.scene .position .set (5 ,0 ,5 ) gltf.scene .traverse ((item )=> { if (item.isMesh ){ item.material = new THREE .MeshLambertMaterial ({ color : 0x004444 , transparent : true , opacity : 0.5 , }) const itemEdge = new THREE .EdgesGeometry (item.geometry ) const itemLineMaterial = new THREE .LineBasicMaterial ({ color : 0x00ffff , }) const itemLine = new THREE .Line (itemEdge,itemLineMaterial) item.add (itemLine) } }) scene.add (gltf.scene )
拉伸几何体 **ExtrudeGeometry :**也是通过一个平面坐标拉成一个几何体,
参数:
1 2 3 4 5 6 depth : 20 , bevelThickness : 5 , bevelSize : 5 , bevelSegments : 20 , extrudePath :cuverpath steps :100
弯曲水管流动效果: 贴图版本:创建一个贴图,然后将贴图设置一个阵列模式。再render函数中给贴图的x轴的点进行+1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const pointsV = new THREE .CatmullRomCurve3 ([ new THREE .Vector3 (20 ,10 ,5 ), new THREE .Vector3 (0 ,0 ,0 ), new THREE .Vector3 (-20 ,-15 ,-50 ), ]) const pipeBackgroundImage = new THREE .TextureLoader ().load (img) pipeBackgroundImage.wrapS = THREE .RepeatWrapping ; pipeBackgroundImage.wrapT = THREE .RepeatWrapping ; pipeBackgroundImage.repeat .set (1 ,1 ); pipeBackgroundImage.encoding = THREE .SRGBColorSpace console .log (pipeBackgroundImage,'tuoian' ); const pipeline = new THREE .TubeGeometry (pointsV,65 , 2 , 25 ) const pipeMaterial = new THREE .MeshPhysicalMaterial ({emissive :'#3f7b9d' ,side :THREE .DoubleSide ,map :pipeBackgroundImage,clearcoat :1 }) const pipeMesh = new THREE .Mesh (pipeline,pipeMaterial) scene.add (pipeMesh) function animation ( ){ pipeBackgroundImage.offset .x += 0.0001 ; renderer.render (scene,camera) requestAnimationFrame (animation) } animation ( ){
几何体的顶点属性 顶点位置 数据geometry.attributes.position
顶点法向量 数据geometry.attributes.normal
顶点UV 数据geometry.attributes.uv
顶点颜色 数据geometry.attributes.color
如何使用顶点颜色: 创建相关几何体时,通过设置起这个几何体顶点数量的颜色。
*若要添加顶点颜色需要在材质中开启vertexColors ,材质的color属性可以不用设置了
*rgb 范围1-0
1 2 3 4 5 6 7 const colors = new Float32Array([ 1, 0, 0, //顶点1颜色 0, 0, 1, //顶点2颜色 0, 1, 0, //顶点3颜色 ]) geotry.attributes.color = new three.BufferAttribute(colors,3) //三个一组
颜色的rgb范围如下图 ↓
绘制彩色曲线
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 const peakCover = new three.CubicBezierCurve3 ( new THREE .Vector3 (4 ,4 ,4 ), new THREE .Vector3 (4 ,5 ,6 ), new THREE .Vector3 (7 ,6 ,6 ), ) const peackPoints = peackCover.getpoints (20 )let peackColors = []for (let i =0 ;i<peackPoints.length ;i++){ const itemValue = i / peackPoints.length peackColors.push (itemValue,0 ,1 ) } const peackCoverGeotry = new three.BufferGeometry ().setFromPoints (new Float32Array (peackColors),3 )const peackCoverMaterial = new three.LineBasicMaterial ({ vertexColors :true , }) const coverLine = new three.Line (peackCoverGeotry,peackCoverMaterial)scene.add (coverLine)
Color的使用 THREE.Color(r,g,b) 当所有参数被定义时,r是红色分量,g是绿色分量,b是蓝色分量。 当只有 r 被定义时:
它可用一个十六进制 hexadecimal triplet 值表示颜色(推荐)。
它可以是一个另一个颜色实例。
它可以是另外一个CSS样式。例如:
‘rgb(250, 0,0)’
‘rgb(100%,0%,0%)’
‘hsl(0, 100%, 50%)’
‘#ff0000’
‘#f00’
‘red’
1 const color = new THREE.Color(r,g,b)
color的插值 1 2 3 4 5 6 7 8 9 10 11 const c1 = new THREE .Color (0xff0000 ); const c2 = new THREE .Color (0x0000ff ); const c = new THREE .Color ();c = c.lerpColors (c1,c2,占比Value ) c1.lerp (c2,1 ) let c3 = c1.clone ().lerColor (c1,c2,占比)
颜色插值的应用:不用自己用相关逻辑来写颜色的属性数组,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let peakCoverPoint = peakCover.getPoints(20)//获取曲线上的点 let colorArr = [] for(let i=0;i< peakCoverPoint.length ;i++){ const percent = i / peakCoverPoint.length; //点索引值相对所有点数量的百分比 //根据顶点位置顺序大小设置颜色渐变 // 红色分量从0到1变化,蓝色分量从1到0变化 colorArr.push(percent, 0, 1); //蓝色到红色渐变色 } // 优化↓ let c1 = new three.Color(0x00ffff) let c2 = new three.Color(0xffff00) for(let i=0;i< peakCoverPoint.length ;i++){ const percent = i / peakCoverPoint.length; //点索引值相对所有点数量的百分比 //根据顶点位置顺序大小设置颜色渐变 let c = c1.clone(c2,percent) colorArr.push(c.r, c.g, c.b); //颜色插值计算 }
模型的顶点及数据处理 引入的模型有些在返回的模型json对象的scne.children[0]中有geometry属性。有的会在其下面继续拓展多个物体。此时我们就需要遍历这个对象,去获取他的顶点数据。
BufferAttribute具有getX(), getY() ,getZ() 方法获取x(1维) y(2维) z(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 40 41 42 modelLoader.load ('shan2.glb' ,(gltf )=> { gltf.scene .position .set (5 ,0 ,5 ) console .log ( gltf.scene ); gltf.scene .scale .set (25 ,25 ,25 ) const zArr = [] gltf.scene .traverse (item => { let itemAttributes = item.geometry .attributes let itemPosition = item.geometry .attributes .position for (let i = 0 ;i<itemPosition.length ;i++){ zArr.push (itemPosition.getZ (i)) } }) zArr.sort () let minHeight = zArr[0 ] let maxHeight = zArr[zArr.length - 1 ] let height = maxHeight - minHeight let minColor = new THREE .Color (0x0000ff ) let maxColor = new THREE .Color (0xff0000 ) gltf.scene .traverse (item => { let itemAttributes = item.geometry .attributes let itemPosition = item.geometry .attributes .position let colorArr = [] for (let i = 0 ;i<itemPosition.count ;i++){ let colorVal = (itemPosition.getZ (I) - minHeight) / height let c = minColor.clone ().lerp (maxColor,colorVal) colorArr.push (c.r ,c.g ,c.b ) } let float32Arr = new three.Float32Array (colorArr) itemAttributes.geometry .color = new three.BufferAttribute (float32Arr,3 ) item.material = new three.MeshLambertMaterial ({ vertexColors :true }) }) })
相机camera 相机种类 :正投影相机 OrthographicCamera ,透视投影相机 PerspectiveCamera ,立方体相机CubeCamera
正投影相机 :相机所看到的是一个四边形衍生后的空间。 正投影没有透视效果,也就是不会模拟人眼观察世界的效果。
1 2 3 4 5 6 7 8 9 OrthographicCamera ( left, right, top, bottom, near, far )left 渲染空间的左边界 right 渲染空间的右边界 top 渲染空间的上边界 bottom 渲染空间的下边界 near near属性表示的是从距离相机多远的位置开始渲染,一般情况会设置一个很小的值。 默认值0.1 far far属性表示的是距离相机多远的位置截止渲染,如果设置的值偏小小,会有部分场景看不到。 默认值2000 left 与 top 为负值
相机的position位置若在物体位置左边范围里面 ,会渲染范围外的 ,涉及到范围的坐标的物体这回被切割一般
透视投影相机 :具有一定角度的锥型视野空间。特点:模拟人眼所看到的物体特征
视觉差异 :同样渲染一个物体,正投影渲染的没有透视相机看着的自然。
相机选择: 对于大部分需要模拟人眼观察效果的场景,需要使用透视投影相机,比如人在场景中漫游 ,或是在高处俯瞰 整个园区或工厂。
正投影没有透视效果,也就是不会模拟人眼观察世界的效果。在一些不需要透视的场景你可以选择使用正投影相机,比如整体预览一个中国地图的效果,或者一个2D可视化的效果。
WebGL渲染器更新Canvas画布尺寸 1 2 3 4 5 6 7 解释 window .onresize = function ( ) { const width = window .innerWidth ; const height = window .innerHeight ; renderer.setSize (width, height); };
也可以在创建的时候就设置render.setSize(window.innerWidth,window.innerHeight)
透视相机需要更新一下宽高比
1 2 3 4 5 6 7 window .onresize = function ( ) { const width = canvas.clientWidth const height = canvas.clientHeight camera.aspect = canvas.clientWidth /canvas.clientHeight camera.updateProjectionMatrix (); };
正投相机受到宽高影响left 和 right的影响。
1 2 3 4 const k = width / height; const s = 50 ; const camera = new THREE .OrthographicCamera (-s * k, s * k, s, -s, 1 , 8000 );
1 2 3 4 5 6 7 8 9 10 11 12 13 window .onresize = function ( ) { const width = window .innerWidth ; const height = window .innerHeight ; renderer.setSize (width, height); const k = width / height; camera.left = -s*k; camera.right = s*k; camera.updateProjectionMatrix (); };
包围盒 包围一个物体外层包围的几何体,就如手办的透明收纳盒。
1 const box3 = new THREE .Box3 ();
expandByObject(mesh):方法获取这个物体的最小包围盒.
getSize(target:vector3):获取到这个包围盒的大小并赋值给一个v3.
getCenter(target:vector3):获取到这个包围盒的中心点,并赋给一个v3.
包围盒地图案例: 在这个案例中涉及到几个点
1.我们如何让地图居中,也就是摄像机如何对着地图,。
2.轨道控制器中心点对象。
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 69 70 import cq from 'map.json' const width = canvas.clientWidth ; const height = canvas.clientHeight ; const k = width / height; const s = 5 ; console .log (k*s,'ks' ); const camera = new THREE .OrthographicCamera (-k*s,k*s,s,-s,0.001 ,8000 ) camera.position .set (10 ,10 ,10 ) camera.lookAt (0 ,0 ,0 ) scene.add (camera) let depth = 0.2 cq.map (item => { let v2 = [] let v3 = [] item.path .forEach (el => { v2.push (new THREE .Vector2 (el[0 ],el[1 ])) v3.push (new THREE .Vector3 (el[0 ],el[1 ],depth+0.0001 )) }); let shape = new THREE .Shape (v2) let ShapeGeo = new THREE .ExtrudeGeometry (shape,{depth :depth,curveSegments :v3.length ,bevelEnabled :false }) let shapeMaterial = new THREE .MeshPhongMaterial ({color :'#0a4287' ,opacity :1 }) let shapeMaterial2 = new THREE .MeshPhongMaterial ({color :'#1b6ef3' ,opacity :1 }) let shaeMesh = new THREE .Mesh (ShapeGeo ,[shapeMaterial,shapeMaterial2]) shaeMesh.position .z = 0 const areaLineGeo = new THREE .BufferGeometry ().setFromPoints (v3) const areaLineMaterial = new THREE .MeshPhongMaterial ({color :'#fff' ,}) const areaLine = new THREE .Line (areaLineGeo,areaLineMaterial) console .log (shaeMesh); scene.add (areaLine) scene.add (shaeMesh) }) console .log (cqoutLine); let outLine = [] cqoutLine.features [0 ].geometry .coordinates .forEach (item => { console .log (item); item[0 ].forEach (val => { outLine.push (new THREE .Vector3 (val[0 ],val[1 ],depth+0.0001 )) }) }) console .log (outLine,'新' ); const outLineGeo = new THREE .BufferGeometry ().setFromPoints (outLine) const outLIneMaterial = new THREE .MeshBasicMaterial ({color :'red' }) const outLineMesh = new THREE .Line (outLineGeo,outLIneMaterial) camera.lookAt (0 ,0 ,0 ) camera.position .set (300 ,300 ,300 ) const mapBox = new THREE .Box3 () let outLineBox = mapBox.expandByObject (outLineMesh) const center = new THREE .Vector3 () outLineBox.getCenter (center) console .log (center,'center' ); camera.lookAt (center.x ,center.y ,center.z ) camera.position .set (center.x ,center.y ,50 ) Controls .target .set (center.x ,center.y ,center.z ); Controls .update (); let mapBoxSize = new THREE .Vector3 () outLineBox.getSize (mapBoxSize) const obj = new THREE .Object3D () obj.position .set (center.x ,center.y ,center.z ) cqLight.target = obj
相机的动画效果:
主要是通过移动相机的position 完成的。
如在animation中不断更新相机的位置,相机观察的中心点始终为某个点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function animation(){ ridius+=0.001 camera.position.z = r * Math.sin(ridius) camera.position.x = r * Math.cos(ridius) camera.position.x += 0.003 camera.position.z -= 0.003 camera.lookAt(5,1.2,-5) renderer.render(scene,camera) requestAnimationFrame(animation) } animation()
相机的**.up**属性控制哪个坐标轴朝上:
x轴朝上:camera.up.set(1,0,0)
y轴朝上:camera.up.set(0,1,0)
z轴朝上:camera.up.set(0,0,1)
管道穿梭案例: 思路创建一个曲线,生成对应的管道模型。获取这个曲线上的点,相机位置设置在第当前点位上 然后相机的朝向为当前点位的下一个点的位置。
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 cosnt coverLine = new three.CubicBezierCurve3 ( new THREE .Vector3 ( -10 , 0 , 0 ), new THREE .Vector3 ( -5 , 15 , 0 ), new THREE .Vector3 ( 20 , 15 , 0 ), new THREE .Vector3 ( 10 , 0 , 0 ) ) const textrue = new three.TextrueLoader ().load ('water.png' )textrue.warpS = three.RepeatWrapping textrue.repeate .x = 1 const coverMaterial = new three.MeshBasicMaterial ({ map :textrue, side : THREE .DoubleSide , }) const coverPoints = coverLine.getPoints (500 )let i = 0 function animation ( ){ i+=1 if (i < coverPoints.length - 1 ){ camera.position .copy (coverPoints[i]) camera.lookAt (coverPoints[i + 1 ]) }else { i = 0 } renderer.render (scene,camera) requestAnimationFrame (animation) } animation ()
相机控件OrbitControls
旋转:拖动鼠标左键。enableRotate:true 开启旋转
缩放:滚动鼠标中键。 enableZoom :true 开启缩放
平移:拖动鼠标右键 。enablePan:true 开启平移
设置控件的平移,缩放,旋转就是控制相机的位置变化。
如果修改了空间的target的目标点 则也修改了相机的camera。修改控件的属性时也需要更新控件update()
控制缩放范围:
1 2 3 4 5 6 透视投影相机看见为物体效果问近大远小,控件提供了两个属性minDistance 与 maxDistance, controls.minDistance = 5 controls.maxDistance = 15 正投影相机则是minZoom,maxZoom
控制相机的旋转范围:
1 2 3 4 5 6 7 8 控制相机旋转范围: minPolarAngle / maxPolarAngle 控制相机的上下旋转范围 controls.minPolarAngle = 0 controls.maxPolarAngle = Math.PI / 4 minAzimuthAngle / maxAzimuthAngle 控制相机左右旋转范围 controls.minAzimuthAngle = 0 controls.maxAzimuthAngle = Math.PI /2
除了OrbitControls控件外 还有一个MapOrbitControls 模拟地图的拖拽。
光源:light 光源基类light:平行光:DirectionalLight 点光源:PointLight 聚光灯:SpotLight ;这光源三种都能产生阴影。
环境光源:AmbientLight 。不会产生阴影
调试注意: 调试阴影的时候记得开启相机的辅助工具 以便快速找到相机的范围; 以及将光源可视化确定光源的具体位置。
光源如何产生阴影:
1.光源需要设置.castShadow 为true
2.产生阴影的模型需要设置.castShadow 为true
3.渲染器的允许渲染 renderder. shadowMap.enabled 为true
4.设置渲染范围.shadow.camera 属性值始终为正投影相机OrthographicCamera , 限制阴影的范围,如果给的不合适看不到或者看不全,模糊不清等
光源的shadow.mapSize
提高阴影的渲染效果,值越大越清楚。默认值512
注意:
在能覆盖包含阴影渲染范围的情况下, .shadow.camera的尺寸尽量小。
如果你增加.shadow.camera的长方体视野尺寸范围,阴影模糊齿感,可以适当提升.shadow.mapSize的大小。
光源的shadow.radius
改变阴影边缘的过度效果。
可以适当的提升.shadow.radius让边缘的过度效果更明显。或者说边缘逐渐弱化或者模糊化,没有明显的边界干。
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 69 70 71 72 73 74 75 76 77 renderer.shadowMap .enabled = true ; const light = new THREE .AmbientLight ( 0x404040 ,10 ); const gui = new GUI (); gui.add (light, 'intensity' , 0 , 100 ).name ('环境光.intensity' ); scene.add ( light ); const directionalLight = new THREE .DirectionalLight ( 0xffffff ,5 ) directionalLight.position .set (100 ,100 ,100 ), directionalLight.target .position .set (0 ,0 ,0 ) directionalLight.castShadow = true directionalLight.receiveShadow = true ; console .log ('阴影相机属性' ,directionalLight.shadow .camera ); const directionalLightHelp = new THREE .DirectionalLightHelper (directionalLight,0xffffff ) directionalLight.shadow .mapSize .set (2540 ,2540 ) directionalLight.shadow .radius = 5 directionalLight.shadow .camera .left = -100 ; directionalLight.shadow .camera .right = 100 ; directionalLight.shadow .camera .top = 100 ; directionalLight.shadow .camera .bottom = -100 ; directionalLight.shadow .camera .near = 0.01 ; directionalLight.shadow .camera .far = 5000 ; const cameraHelper = new THREE .CameraHelper (directionalLight.shadow .camera ); scene.add (cameraHelper); const dirHelper = new THREE .DirectionalLightHelper ( directionalLight, 5 ); scene.add ( dirHelper ); const load = new GLTFLoader ('模型1.gltf' ,gltf => { gltf.scene .castShadow = true scene.add (gltf.scene ) }) const shadowFolder = gui.addFolder ('平行光阴影' ); const cam = directionalLight.shadow .camera ; shadowFolder.add (cam,'left' ,-500 ,0 ).onChange (function (v ){ cam.updateProjectionMatrix (); cameraHelper.update (); }); shadowFolder.add (cam,'right' ,0 ,500 ).onChange (function (v ){ cam.updateProjectionMatrix (); cameraHelper.update (); }); shadowFolder.add (cam,'top' ,0 ,500 ).onChange (function (v ){ cam.updateProjectionMatrix (); cameraHelper.update (); }); shadowFolder.add (cam,'bottom' ,-500 ,0 ).onChange (function (v ){ cam.updateProjectionMatrix (); cameraHelper.update (); }); shadowFolder.add (cam,'far' ,0 ,1000 ).onChange (function (v ){ cam.updateProjectionMatrix (); cameraHelper.update (); }); let r = 100 let angle = 0 function animation ( ){ angle+=0.01 directionalLight.position .y = r * Math .sin (angle) directionalLight.position .x = r * Math .cos (angle) renderer.render (scene,camera) requestAnimationFrame (animation) }
精灵:sprite spriteMaterial:材质与普通网格材质一样可以设置相关的.map envMap 啥的。
Sprite :始终朝向摄像机,旋转轨道也不会让精灵图旋转 ,不会产生阴影 。即使设置了castShadow为true也不会生效。与其他材质不同的是精灵不需要设置几何体 Geometry ,精灵图的默认尺寸都是 1 ,
透视相机中:也遵循远小近大的原理
正投影相机如何验证精灵的尺寸:如果精灵尺寸为1, 给相机top , buttom设置为0.5 则整个精灵回撑满画布。
精灵与矩形mesh区别 :他们都是一个矩形,区别在于精灵始终平行于画布,而mesh的矩形则始终跟着旋转。在尺寸上,mesh可以通过geometry和mesh.scale来定义,然而精灵则只能通过sprite.scale来定义。
模拟下雨和下雪的效果: 如果要做下雨效果3d的曲面几何体,一个曲面几何体需要4个三角形的话,如果有3千个水滴,则需要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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 const texture = new THREE .TextureLoader ().load ('img/xue.png' )const spriteMaterial = new THREE .SpriteMaterial ({ map :texture, transparent :true , }) const group = new THREE .Object3D ()for (let i = 0 ;i < 6000 ;i++){ const sprite = new THREE .Sprite (spriteMaterial) sprite.scale .set (5 , 5 , 1 ) sprite.position .x = 500 * (Math .random () - 0.5 ) sprite.position .y = 250 * (Math .random ()) sprite.position .z = 500 * (Math .random () - 0.5 ) group.add (sprite) } scene.add (group) const clock = new THREE .Clock ()function loop ( ){ group.children .forEach (item => { item.position .y -= 0.1 item.material .rotation += 0.000001 if (item.position .y < 0 ){ item.position .y = 250 } }) requestAnimationFrame (loop) } loop ()function animation ( ){ renderer.render (scene,camera) requestAnimationFrame (animation) } animation ()
后处理: EffectComposer 定义: 可以理解为处理照片后期处理一个 ‘ps’。
这是一个拓展库需要单独引入EffectComposer.js 和相关的后期处理通道。
后期处理库:
OutlinePass.js:高亮发光描边
UnrealBloomPass.js:Bloom发光
GlitchPass.js:画面抖动效果
GammaCorrectionShader 伽马矫正
…等,通道可以组合使用:组合使用的话,先处理一个通道再处理另外一个。
threejs本地部署的服务文件包examples文件目录,全文检索关键词EffectComposer,可以找到后处理的很多案例。
设置后期处理后,模型会出现色差, renderer.outputEncoding色彩空间不会生效,此时我们需要设置伽马校正通道。GammaCorrectionShader.js 提供了伽马校正的对象,可以处理模型后处理后颜色的色彩。没有伽马矫正的通道 还需要引入一个shaderPass.js库 配合使用 将伽马校正的对象作为参数创建校验通道。
模型添加后会出现锯齿:后处理器多种抗锯齿配置, FXAAShader 和 SMAAShader。SMAA效果比FXAA的效果好写
如何使用呢:
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 69 70 71 72 import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';//渲染器通道RenderPass import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';//物体模型外描边通道RenderPass import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';// 引入UnrealBloomPass通道 // 引入GlitchPass通道 import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js'; // 伽马校正后处理Shader import {GammaCorrectionShader} from 'three/addons/shaders/GammaCorrectionShader.js'; // ShaderPass功能:使用后处理Shader创建后处理通道 import {ShaderPass} from 'three/addons/postprocessing/ShaderPass.js'; // FXAA抗锯齿Shader import { FXAAShader } from 'three/addons/shaders/FXAAShader.js'; // SMAA抗锯齿通道 import {SMAAPass} from 'three/addons/postprocessing/SMAAPass.js'; //首先需要使用后期处理器 const composer = new EffectComposer(renderder) //参数为需要处理的webgl渲染器 //创建渲染器通道。这个必须要有不然后面可能不会出现效果 const renderPass = new RenderPass(scene,camera)//参数为场景与相机 comporser.addPass(renderPass)//给后期处理器添加渲染器通道 //例子 物体的外边缘发光通道 //创建一个二维矢量 大小与画布一直 let v2 = new three.Vector2(canvas.innerWidth,canvas.innerHeight) const outLinePass = new OutlinePass(v2,scene,camera) outLinePass.selectedObjects = [mesh1] //selectedObjects属性为添加物体 值为数组所以可以添加多个 composer.addPass(outLinePass) //outLinePass的相关属性 outLinePass.visibleEdgeColor.set(0xffff00) //控制描边的颜色 outLinePass.edgeThickness = 4 //描边的厚度 outLinePass.edgeStrength = 6 //描边的亮度 outLinePass.pulsPeriod = 2 //描边的闪烁频率 const bloom = new UnrealBloomPass(v2) //控制物体本身发光 bloom.strength = 2 //发光强度 // composer.addPass(bloom) const glitchPass = new GlitchPass()//闪屏效果,类型以前黑白电视信号不好的闪屏 composer.addPass(glitchPass) // 创建伽马校正通道 const gammaPass= new ShaderPass(GammaCorrectionShader); composer.addPass(gammaPass); //首先需要设置像素比,避免canvas画布数据模糊 renderer.setPixelRatio(window.devicePixelRatio); const pixelRatio = renderer.getPixelRatio() console.log(pixelRatio,'像素比'); //FXAA抗锯齿 const fxaa = new ShaderPass(FXAAShader) fxaa.uniforms.resolution.value.x = 1/(canvas.clientWidth*pixelRatio) fxaa.uniforms.resolution.value.y = 1/(canvas.clientHeight*pixelRatio) // composer.addPass(fxaa) //smaa抗锯齿 const smaa = new SMAAPass(canvas.clientWidth*pixelRatio,canvas.clientHeight*pixelRatio) composer.addPass(smaa) //还需要设循环渲染中调用一下后期渲染器 后期渲染器执行时也可以不用执行webgl渲染器的渲染了 function animation(){ //renderer.render(scene,camera) composer.render() requestAnimationFrame(animation) } animation()
射线Ray(重点) 射线起点:origin为一个三维向量
射线方向:direction 是一个三维向量单位,向量长度始终为1,在设置了三维向量后我们一般使用 .normalize() 将其向量进行归一化。归一化就是将这个线长度变为1。
射线的真实方向: 射线方向归一后,得到的方向就是射线的防线,简单点来说我们将射线方向起点移动到射线起点就会得到射线的真实方向了。
intersectObjects(meshArr):与哪些物体相交。注意点:我们在执行这个方法前先更新一下坐标,因为修改了相关位置信息后会在下一次render时才生效,所以我们得updateMatrixWorld()手动更新.
屏幕坐标转three.js设备坐标: 我们可以用offset与client来进行转换,两种坐标点转换逻辑其实都差不多。设备作标 以canvas中心点为坐标原点,xy坐标的范围在[-1,1]。
计算逻辑: 设备坐标范围[-1,1],所以我们需要一个范围。首先求出每个方向上的占比,此时得到的就是一个小于1
的小数。因为-1 到 1范围是2 ,所以得到的值乘2,再-1 结果永远都在 - 1 跟 1 之间了。唯一注意点是y轴方向相反 ,占比值取负*2 + 1。
offset转换 :建议
1 2 3 //获取canvas画布的宽高 width,height let x = (width/e.offsetX) * 2 - 1 let y = -(height/e.offsetY)*2 + 1
client转换 :需要注意的是,client坐标是相对浏览器的,所以canvas画布距离浏览器上边与左边的距离需要自己删减一波,如果开全屏的画就不需要了。
1 2 3 4 5 6 7 8 9 let canvasWidth = canvas.clientWidth let canvasHeight = canvas.clientHeight let cX = ((e.clientX - 8 )/canvasWidth) * 2 - 1 let cY = -((e.clientY -20.8 -8 )/canvasHeight) * 2 +1 console .log (cX,cY,'client' ); let cX = (e.clientX /canvasWidth) * 2 - 1 let cY = -(e.clientY /canvasHeight) * 2 + 1
射线拾取器Raycaster: 创建Raycaster实例。他的ray属性就是射线,设置ray的origin,direction就是改变射线拾取器的起点与方向,也可以通过拾取器的Set方法来进行修改。
.intersectObject( mesh ):方法 判断是否包含某个物体
.intersectObjects( [ mesh1, mesh2 ] ):检索是否经过这些物体,将经过的物体返回成一个数组,没有就是空数组
.setFromCamera( v2 , camera) : v2作为方向, 在标准化设备坐标中鼠标的二维坐标 —— X分量与Y分量应当在-1到1之间。 射线所来源的摄像机。
.set(v3,direction): v3 作为起点,是个三维坐标。direction是一个方向也是三维坐标。
如果画布容器canvas尺寸发生变化,需要更新的设置 :相机的宽高比需要更新camera.aspect=w/h ; 渲染器更新画布尺寸render.setSize(w,h) ; 相机参数变化,相机必须更新camera.updateProjectionMatrix();
精灵图也可以进行拾取,操作方法与mesh一样的。
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 const ray = new THREE .Ray () ray.origin = new THREE .Vector3 (1 ,0 ,0 ) ray.direction = new THREE .Vector3 (5 ,5 ,5 ).normalize (); const raycaster = new Raycaster () document .body .addEventListener ('click' ,e => { let canvasWidth = canvas.clientWidth let canvasHeight = canvas.clientHeight let x = (width/e.offsetX ) * 2 - 1 let y = -(height/e.offsetY )*2 + 1 let v2 = new three.Vector2 (x,y) raycaster.setFromCamera (v2,camera) const arr = rayCaster.intersectObjects ([mesh3,mesh1,mesh2]) if (arr.length > 0 ){ arr[0 ].object .material .color .set ('red' ) } document .querySelector ('#change' ).addEventListener ('click' ,()=> { vue.flog = !vue.flog if (vue.flog ){ vue.dom .style .width = '500px' vue.dom .style .height = '500px' camera.aspect = canvas.clientWidth /canvas.clientHeight renderer.setSize (canvas.clientWidth ,canvas.clientHeight ) camera.updateProjectionMatrix () }else { vue.dom .style .width = '400px' vue.dom .style .height = '400px' camera.aspect = canvas.clientWidth /canvas.clientHeight renderer.setSize (canvas.clientWidth ,canvas.clientHeight ) camera.updateProjectionMatrix () } }) })
html2D标注CSS2DRender/CSS2DObject 在某些场景中我们需要标注一些场景中的模块信息,此时我们需要自己设置信息的排版。CSS2DRender及CSS2DObject就可以实现。
原理 :因为创建的始终为html,所以我们创建的css2dRender是一个html元素,但是这个元素要与three的画布的位置始终保持一致,这样通过CSS2DObject所创建的标注才不会偏移。
CSS2DRender :场景标注的渲染器,跟webgl渲染器的用法一致,但是需要注意位置与three画布保持一致
CSS2DObject : 将html转为网格模型的类,转化这个标注也可以设置相关的属性,如position等,因为基类是object3D
标签的位置设置: 需要将标注添加到某个物体附近。1.将标注添加到该模型的子级;2.获取该模型的世界坐标也是可以的; 3. 标签模型对象作为需要标注mesh的子对象,然后获取mesh几何体某个顶点的坐标,作为标签模型对象局部坐标position;
标签偏移: 可以通过设置标签的css定位属性来设置属性的偏移。
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 const btn = document .querySelector ('.tip2D' ) const btn2 = document .querySelector ('.tip3D' ) const btn2D = new CSS2DObject (btn) let v3 = new THREE .Vector3 (50 ,30 ,30 ) console .log ( boxPosition.getX (0 ),'某个位置' ); btn2D.position .copy (v3) const glbLoader = new GLTFLoader () let n = 5 const rayCaster = new Raycaster () const renderPass = new RenderPass (scene,camera) const composer = new EffectComposer (renderer) composer.addPass (renderPass) const outLinePass = new OutlinePass (new THREE .Vector2 (canvas.clientWidth ,canvas.clientHeight ),scene,camera) outLinePass.visibleEdgeColor .set (0xffff00 ) outLinePass.edgeThickness = 4 outLinePass.edgeStrength = 6 composer.addPass (outLinePass) glbLoader.load ('/零件.glb' ,gltf => { console .log (gltf,'模型' ); gltf.scene .traverse (val => { if (val.isMesh ||val.isLine ){ n+=30 if (n==185 ){ camera.position .set (n+10 ,n+10 ,n+10 ) camera.lookAt (n,n,n) controls.target .set (n,n,n) controls.update () console .log ('相机的位置' ,camera); } val.position .set (n,n,n) console .log (val.position ,'???' ); } }) const tag2D = new CSS2DObject (btn) console .log (tag2D,tag3D,'css对象' ); let changeObj = null document .body .addEventListener ('click' ,e => { let x = (e.offsetX /canvas.clientWidth )*2 - 1 let y = -(e.offsetY /canvas.clientHeight )*2 + 1 console .log (x,y); let v2 = new THREE .Vector2 (x,y) rayCaster.setFromCamera (v2,camera) let obj = rayCaster.intersectObjects (gltf.scene .children [0 ].children [0 ].children ) console .log (obj); if (obj.length >0 ){ let a = obj[0 ].object outLinePass.selectedObjects = [a] changeObj = a if (a.isLine ) a.add (tag2D) }else { } }) document .querySelector ('.close1' ).style .pointerEvents = 'auto' ; document .querySelector ('.close1' ).addEventListener ('click' ,()=> { if (changeObj) changeObj.remove (tag2D) outLinePass.selectedObjects = [] }) scene.add (gltf.scene ) }) const css3DRender = new CSS3DRenderer () css3DRender.domElement .style .position = 'absolute' css3DRender.domElement .style .top = '0px' css3DRender.domElement .style .pointerEvents = 'none' css3DRender.setSize (canvas.clientWidth ,canvas.clientHeight ) document .querySelector ('.screen' ).appendChild (css3DRender.domElement ) window .addEventListener ('resize' ,()=> { css3DRender.setSize (canvas.clientWidth ,canvas.clientHeight ) }) const css2Drender = new CSS2DRenderer () css2Drender.domElement .style .position = 'absolute' css2Drender.domElement .style .top = '0px' css2Drender.domElement .style .pointerEvents = 'none' css2Drender.setSize (canvas.clientWidth ,canvas.clientHeight ) document .querySelector ('.screen' ).appendChild (css2Drender.domElement ) window .addEventListener ('resize' ,()=> { css2Drender.setSize (canvas.clientWidth ,canvas.clientHeight ) }) function animation ( ){ css2Drender.render (scene,camera) css3DRender.render (scene,camera) composer.render () requestAnimationFrame (animation) } animation ()
html3D标注CSS3DRender/CSS3DObject/CSS3DSprite CSS3DRender与2D的使用基本上一致。不论是css2d还是3d 所创建的标注都不会被物体压住,因为他在一个canvas画布的一个html里面。
**2d与3d的区别:**2d始终朝上 就是普通的Html. 3d是可以跟着旋转与缩放的就如Mesh物体一样,css3d精灵则跟three精灵一样始终始终朝向相机, CSS3精灵模型CSS3DSprite尺寸、位置、缩放等渲染规律和CSS3对象模型CSS3DObject基本一致。
CSS2渲染的标签和CSS3渲染的标签偏移方式不同,CSS3标签,直接按照threejs模型尺寸修改方式改变,比用HTML像素方式更方便准确。
1 2 3 4 5 6 7 8 css2采取的是设置css定位属性,而css3采取的是网格的模型位置 css2: const div = document.getElementById('tag'); // id="tag"元素高度322px,默认标签中心与标注点 div.style.top = '-161px'; //平移-161px,指示线端点和标注点重合 css3: tag.scale.set(0.5,0.5,1);//缩放标签尺寸 tag.position.y += 10;//累加标签高度一半,标签底部和圆锥顶部标注位置重合
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 const btn2 = document .querySelector ('.tip3D' )const glbLoader = new GLTFLoader ()let n = 5 const rayCaster = new Raycaster ()const renderPass = new RenderPass (scene,camera)const composer = new EffectComposer (renderer)composer.addPass (renderPass) const outLinePass = new OutlinePass (new THREE .Vector2 (canvas.clientWidth ,canvas.clientHeight ),scene,camera)outLinePass.visibleEdgeColor .set (0xffff00 ) outLinePass.edgeThickness = 4 outLinePass.edgeStrength = 6 composer.addPass (outLinePass) glbLoader.load ('/零件.glb' ,gltf => { console .log (gltf,'模型' ); gltf.scene .traverse (val => { if (val.isMesh ||val.isLine ){ n+=30 if (n==185 ){ camera.position .set (n+10 ,n+10 ,n+10 ) camera.lookAt (n,n,n) controls.target .set (n,n,n) controls.update () console .log ('相机的位置' ,camera); } val.position .set (n,n,n) console .log (val.position ,'???' ); } }) const tag3D = new CSS3DObject (btn2) btn2.style .backface -visibility = 'hidden' const tagSprite = new CSS3DSprite (btn2) tag3D.scale .set (0.5 ,0.5 ,1 ); tag3D.position .x += 60 console .log (tag2D,tag3D,'css对象' ); let changeObj = null document .body .addEventListener ('click' ,e => { let x = (e.offsetX /canvas.clientWidth )*2 - 1 let y = -(e.offsetY /canvas.clientHeight )*2 + 1 console .log (x,y); let v2 = new THREE .Vector2 (x,y) rayCaster.setFromCamera (v2,camera) let obj = rayCaster.intersectObjects (gltf.scene .children [0 ].children [0 ].children ) console .log (obj); if (obj.length >0 ){ let a = obj[0 ].object outLinePass.selectedObjects = [a] changeObj = a if (a.isLine ) a.add (tag2D) if (a.isMesh ) a.add (tagSprite) }else { } }) document .querySelector ('.close1' ).style .pointerEvents = 'auto' ; document .querySelector ('.close1' ).addEventListener ('click' ,()=> { if (changeObj) changeObj.remove (tag2D) outLinePass.selectedObjects = [] }) document .querySelector ('.close2' ).style .pointerEvents = 'auto' ; document .querySelector ('.close2' ).addEventListener ('click' ,()=> { if (changeObj) changeObj.remove (tagSprite) outLinePass.selectedObjects = [] }) scene.add (gltf.scene ) }) const css3DRender = new CSS3DRenderer ()css3DRender.domElement .style .position = 'absolute' css3DRender.domElement .style .top = '0px' css3DRender.domElement .style .pointerEvents = 'none' css3DRender.setSize (canvas.clientWidth ,canvas.clientHeight ) document .querySelector ('.screen' ).appendChild (css3DRender.domElement )window .addEventListener ('resize' ,()=> { css3DRender.setSize (canvas.clientWidth ,canvas.clientHeight ) }) const css2Drender = new CSS2DRenderer ()css2Drender.domElement .style .position = 'absolute' css2Drender.domElement .style .top = '0px' css2Drender.domElement .style .pointerEvents = 'none' css2Drender.setSize (canvas.clientWidth ,canvas.clientHeight ) document .querySelector ('.screen' ).appendChild (css2Drender.domElement )window .addEventListener ('resize' ,()=> { css2Drender.setSize (canvas.clientWidth ,canvas.clientHeight ) }) function animation ( ){ css2Drender.render (scene,camera) css3DRender.render (scene,camera) composer.render () requestAnimationFrame (animation) } animation ()
添加多i个标注
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 let num = 0 for(let i=0;i<5;i++){ glbLoader.load('tree2.glb',glb=>{ console.log(glb,'shu'); glb.scene.traverse(item=>{ if(item.isMesh){ console.log(item,'shuju '); // item.material.emissive.set('red') } }) glb.scene.scale.set(0.1,0.1,0.1) glb.scene.position.set(num,0,num) // 创建树的标签 css2D 如果需要将标签的位置设置在某个物体顶上的话 如果只设置css定位,缩放时标签会偏移;建议还是用精准定位标签的position属性 const treeDom = document.querySelector('.treeName').cloneNode() treeDom.innerHTML = '第'+(i+1)+'棵树' const treeTip = new CSS2DObject(treeDom) //方案1:将标签添加到该模型中 单独设置y的坐标 // treeTip.position.y = 500 // glb.scene.add(treeTip) //方案2:获取模型的世界坐标,在修改y的坐标 // let adr = new THREE.Vector3() // glb.scene.getWorldPosition(adr) // adr.y += 50 // treeTip.position.copy(adr) // // treeTip.position.x = 50 // console.log(treeTip); // scene.add(treeTip) // 添加css3d精灵 css3d的精灵不能不能被物体遮挡 // const iconDom = document.querySelector('.icon').cloneNode() // const iconSprite = new CSS3DSprite(iconDom) // iconSprite.position.y = 400 // glb.scene.add(iconSprite) // 添加精灵模型 精灵模型可以被物体遮挡。 // const textureLoad = new THREE.TextureLoader() // let spriteImg = textureLoad.load('/警戒.png') // const spriteM = new THREE.SpriteMaterial({map:spriteImg}) // const sprite = new THREE.Sprite(spriteM) // sprite.position.y = 450 // sprite.scale.set(100,100,100) // glb.scene.add(sprite) //通过canvas来作为精灵的贴图绘制标注 let tipcanvas = createCanvas() const canvasTexture = new THREE.CanvasTexture(tipcanvas) const spriteM2 = new THREE.SpriteMaterial({map:canvasTexture}) const sprite2 = new THREE.Sprite(spriteM2) sprite2.position.y = 450 sprite2.scale.set(canvas.width/canvas.height*100,100,100) glb.scene.add(sprite2) scene.add(glb.scene) num+=20 }) } function createCanvas(src='house/nz (1).png'){ const img = new Image() img.src = src //通过canvas来绘制精灵模型的贴图 const tipCan = document.createElement('canvas') let str = '设备A' let arr = str.split('') const arc = /[\u4e00-\u9fa5]/ let strLength = 0 //字符串的长度 for(let i = 0;i<arr.length;i++){ if(arc.test(arr[0])){//判断的是否为汉字 是汉字+1 strLength+=1 }else{ strLength+=0.0 //是应为或者其他符号 则+0.5 } } // 根据字体符号类型和数量,文字font-size大小来设置canvas画布的宽度 const h = 80 const w = h + strLength*32 tipCan.width = w tipCan.height = h const h1 = h*0.8 const c = tipCan.getContext('2d')//获取canvas的上下文 才能绘制 const r = h1/2 c.arc(r,r,r,-Math.PI/2,Math.PI/2,true)//顺势针旋转半圆 c.arc(w-r,r,r,Math.PI/2,-Math.PI/2,true)//顺势针旋转半圆 // c.fillStyle = "rgba(0,0,0,0.0)"; //背景透明 // c.fillRect(0, 0, w, h); c.fillStyle = "rgba(255,255,255,1)"; c.fill() // c.drawImage(img, 0, 0, w, h);//图片绘制到canvas画布上 // 箭头 c.beginPath() const h2 = h-h1 c.moveTo(w/2-h2*0.6,h1) c.lineTo(w/2+h2*0.6,h1) c.lineTo(w/2,h) c.fill() // 文字 c.beginPath() c.translate(w/2,h1/2) c.fillStyle = '#000000'//文本填充色 c.font = 'normal 32px 宋体'//文本的字体 c.textBaseline = 'middle'//文本与fillText定义的纵坐标,对齐线 c.textAlign = "center"; //文本居中(以fillText定义的横坐标) c.fillText(str,0,0) return tipCan }
着色器材质(重要) 着色器是 WebGL 的重要组件之一, shader 它是一种使用 GLSL 语言编写的运行在 GPU 上的小程序。顾名思义,着色器用于定位几何体的每个顶点,并为几何体的每个可见像素进行着色 。着色器是屏幕上呈现画面之前的最后一步,用它可以实现对先前渲染结果进行修改,如颜色、位置等,也可以对先前渲染的结果做后处理,实现高级的渲染效果。
资源网站