# 3d-test1 **Repository Path**: programmer_luosx/3d-test1 ## Basic Information - **Project Name**: 3d-test1 - **Description**: Three.js学习 - **Primary Language**: JavaScript - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-02-27 - **Last Updated**: 2024-03-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: Threejs ## README # web3D笔记 ``` // Project setup npm install // Compiles and hot-reloads for development npm run dev // Compiles and minifies for production npm run build ``` # 第一章 初步掌握 ## 一、Three.js的三要素 场景Scene、相机Camera、渲染器Renderer ``` // 创建3D场景对象Scene const scene = new THREE.Scene(); // 实例化一个透视投影相机对象,模拟人眼 const camera = new THREE.PerspectiveCamera(30, 1, 1, 3000); //视角(范围),画布宽高比,近裁截面,远裁截面 camera.position.set(200, 200, 200); //相机位置 camera.lookAt(0, 0, 0); //相机观察目标 // 创建渲染器对象 const renderer = new THREE.WebGLRenderer(); renderer.setSize(width, height); //设置three.js渲染区域的尺寸(像素px) renderer.render(scene, camera); //执行渲染操作 document.getElementById('xxId').appendChild(renderer.domElement); //画布插入到任意HTML元素中 ``` ## 二、光源对物体表面影响 ### 1、网格材质 * 不受光照影响 MeshBasicMaterial * 受光照影响 MeshLambertMaterial 漫反射 MeshPhongMaterial 高光反射,有镜面反射效果 MeshStandardMaterial 物理 MeshPhysicalMaterial 物理 ### 2、光源 * 环境光 AmbientLight * 点光源 PointLight * 聚光灯 SpotLight * 平行光 DirectionalLight ``` const pointLight = new THREE.PointLight(0xffffff); // 白光 pointLight.intensity = 1.0;//光照强度 pointLight.decay = 0.0;//设置光源不随距离衰减 pointLight.position.set(400, 200, 300); //光照位置 ``` 光源辅助观察,例xxxxLightHelper ## 三、轨道控制器OrbitControls使用 通过相机控件OrbitControls实现旋转缩放预览效果。旋转:拖动鼠标左键,缩放:滚动鼠标中键,平移:拖动鼠标右键 ``` // 引入轨道控制器扩展库OrbitControls.js import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; const controls = new OrbitControls(camera, renderer.domElement); // 设置相机控件轨道控制器 // 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景 controls.addEventListener('change', function () { renderer.render(scene, camera); }); ``` ## 四、动画渲染 threejs可以借助HTML5的API请求动画帧window.requestAnimationFrame实现动画渲染 ``` function render() { renderer.render(scene, camera); //执行渲染操作 requestAnimationFrame(render);//请求再次执行函数render,无限循环 } ``` 设置了渲染循环,相机控件OrbitControls就不用再通过事件change执行renderer.render(scene, camera);因为渲染循环一直在执行renderer.render(scene, camera);。 ``` // 轨道控制器 const controls = new OrbitControls(camera, renderer.domElement); controls.update(); // 渲染循环 const render3D = () => { renderer.render(scene, camera) requestAnimationFrame(render3D) } ``` ## 五、canvas画布宽高动态变化 threejs渲染输出的结果就是一个Cavnas画布,所以可以插入到web页面上任何一个元素中 ``` // onresize 事件会在窗口被调整大小时发生 window.onresize = function () { // 重置渲染器输出画布canvas尺寸 renderer.setSize(window.innerWidth, window.innerHeight); // 全屏情况下:设置观察范围长宽比aspect为窗口宽高比 camera.aspect = window.innerWidth / window.innerHeight; // 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix // 但是不会每渲染一帧,就通过相机的属性计算投影矩阵(节约计算资源) // 如果相机的一些属性发生了变化,需要执行updateProjectionMatrix ()方法更新相机的投影矩阵 camera.updateProjectionMatrix(); }; ``` ## 六、性能检测stats stats.js库可以查看three.js当前的渲染性能,计算three.js的渲染帧率(FPS)。简单说就是three.js每秒钟完成的渲染次数,一般渲染达到每秒钟60次为最佳状态。 ``` //引入性能监视器stats.js import Stats from 'three/addons/libs/stats.module.js'; const stats = new Stats(); //创建stats对象 document.body.appendChild(stats.domElement); //web页面上输出计算结果 function render() { stats.update(); // 循环调用方法update(),来刷新时间 renderer.render(scene, camera); requestAnimationFrame(render); } render(); ``` 可以通过stats.setMode()方法的参数mode的数值设置首次打开页面,测试结果的显示模式。鼠标单击可以更换为不同的显示模式。 ## 七、常见几何体 ``` //BoxGeometry:长方体 const geometry = new THREE.BoxGeometry(100, 100, 100); // SphereGeometry:球体 const geometry = new THREE.SphereGeometry(50); // CylinderGeometry:圆柱 const geometry = new THREE.CylinderGeometry(50,50,100); // PlaneGeometry:矩形平面 const geometry = new THREE.PlaneGeometry(100,50); // CircleGeometry:圆形平面 const geometry = new THREE.CircleGeometry(50); ``` ## 八、双面可见 Three.js的材质默认正面可见,反面不可见 ``` new THREE.MeshBasicMaterial({ // side: THREE.FrontSide, //默认只有正面可见 // side: THREE.BackSide, //设置只有背面可见 side: THREE.DoubleSide, //两面可见 }); ``` ## 九、WebGL渲染器设置 1、抗锯齿 antialias ``` const renderer =new Three.WebGLRenderer({ antialias: true // 默认false,开启抗锯齿图像展示更平滑 }) ``` 2、设备像素比 .setPixelRatio() ``` // 获取你屏幕对应的设备像素比.devicePixelRatio告诉threejs,以免渲染模糊问题 renderer.setPixelRatio(window.devicePixelRatio); // 必写 ``` ## 十、可视化改变三维场景(gui.js库) 能够可视化的动态地调整设置的图像参数,便于观察变化。 1、通过.add()增加交互界面,改变obj对应属性值。 * 参数3和参数4分别是数字,交互界面是拖动条 * 参数3是数组,生成交互界面是下拉菜单 * 参数3是对象,生成交互界面是下拉菜单,key为选项名,value为值 * 改变属性的对应的数据类型是布尔值,交互界面是单选框 ``` // 引入dat.gui.js的一个类GUI import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; const gui = new GUI(); // 实例化一个gui对象 const obj = {x: 30, y: 60, z: 300}; gui.add(obj, 'x', 0, 100); // 属性值范围(0-100) gui.add(obj, 'y', 0, 50); // 属性值范围(0-50) gui.add(obj, 'z', 0, 60); gui.add(ambient, 'intensity', 0, 2.0); // 设置灯光强度 gui.add(mesh.position, 'x', 0, 180); // 模型位置 gui.add(mesh.position, 'y', [0,10,20,30,40]); gui.add(mesh.position, 'z', {left: -10,center: 0,right: 10}); ``` 2、通过.name('自定义名称'),对属性进行命名 ``` gui.add(ambient, 'intensity', 0, 2.0).name('设置光强度'); ``` 3、.step()设置调节步长 ``` gui.add(ambient, 'intensity', 0, 2.0).name('设置光强度').step(0.5) ``` 4、.onChange()属性改变时触发对应事件 ``` gui.add(mesh.position, 'x', 0, 180).onChange(function(value){ console.log('mesh的x坐标:',value) } ``` 5、.addColor()颜色值改变 ``` gui.addColor(light, 'color').name('设置灯光颜色') ``` 6、.addFolder()分组,方便管理,可套娃 ``` const posFolder = gui.addFolder('坐标') posFolder.add(mesh.position, 'y', [0,10,20,30,40]) posFolder.add(mesh.position, 'x', 0, 50) const lightFolder = gui.addFolder('光照') lightFolder.add(light, 'intensity', 0, 10) lightFolder.addColor(light, 'color') lightFolder.close() // 折叠组 posFolder.open() // 打开组 ``` # 第二章 几何体BufferGeometry ## 一、几何体顶点位置数据、点模型 ### 1、缓冲类型几何体BufferGeometry 长方体BoxGeometry、球体SphereGeometry等几何体都是基于BufferGeometry (opens new window)类构建的; BufferGeometry是一个没有任何形状的空几何体,你可以通过定义顶点数据,BufferGeometry展示出任何几何形状。 ``` //创建一个空的几何体对象 const geometry = new THREE.BufferGeometry(); //创建顶点数据,类型化数组 const vertices = new Float32Array([ 0, 0, 0, //顶点1坐标 50, 0, 0, //顶点2坐标 0, 100, 0, //顶点3坐标 0, 0, 10, //顶点4坐标 0, 0, 100, //顶点5坐标 50, 0, 10, //顶点6坐标 ]); // 创建属性缓冲区对象。3个为一组,表示一个顶点的xyz坐标 const attribue = new THREE.BufferAttribute(vertices, 3); // .attributes.position 设置几何体attributes属性的位置属性 geometry.attributes.position = attribue; ``` ### 2、点模型对象Points 点模型Points和网格模型Mesh一样,只是大部分情况都是用Mesh表示物体。点模型Points会把几何体渲染为点,网格模型Mesh会把几何体渲染为面。 ``` // 点渲染模式 const material = new THREE.PointsMaterial({ color: 0xffff00, size: 10.0 //点对象像素尺寸 }); const points = new THREE.Points(geometry, material); //点模型对象 ``` ## 二、线模型对象 渲染效果是从第一个点开始到最后一个点,依次连成线。 ``` // 线材质对象 const material = new THREE.LineBasicMaterial({ color: 0xff0000 //线条颜色 }); const line = new THREE.Line(geometry, material); // 创建线模型对象 const line = new THREE.LineLoop(geometry, material); // 闭合线条 const line = new THREE.LineSegments(geometry, material); //非连续的线条 ``` ## 三、网格模型 ### 三角形面 网格模型Mesh其实就一个一个三角形(面)拼接构成。使用网格模型Mesh渲染几何体geometry,就是几何体所有顶点坐标三个为一组,构成一个三角形,多组顶点构成多个三角形,就可以用来模拟表示物体的表面。 眼睛(相机)对着三角形的一个面,如果三个顶点的顺序是逆时针方向,该面视为正面;反之,为反面。 * 正面:逆时针 * 反面:顺时针 ### 构建一个矩形平面几何体 一个矩形平面,可以至少通过两个三角形拼接而成。而且两个三角形有两个顶点的坐标是重合的。 注意三角形的正反面问题:保证矩形平面两个三角形的正面是一样的,也就是从一个方向观察,两个三角形都是逆时针或顺时针。 ``` const vertices = new Float32Array([ 0, 0, 0, //顶点1坐标 80, 0, 0, //顶点2坐标 80, 80, 0, //顶点3坐标 0, 0, 0, //顶点4坐标 和顶点1位置相同 80, 80, 0, //顶点5坐标 和顶点3位置相同 0, 80, 0, //顶点6坐标 ]); ``` ## 四、几何体顶点索引数据 ``` // Uint16Array类型数组创建顶点索引数据 const indexes = new Uint16Array([ // 下面索引值对应顶点位置数据中的顶点坐标 0, 1, 2, 0, 2, 3, ]) // 索引数据赋值给几何体的index属性 geometry.index = new THREE.BufferAttribute(indexes, 1); //1个为一组 ``` ## 五、顶点法线数据 ## 六、查看threejs自带几何体顶点 ### 查看几何体顶点位置和索引数据 可以用顶点索引index数据构建几何体,也可以不用,threejs默认的大部分几何体都有三角形的顶点索引数据,具体可以通过浏览器控制台打印几何体数据查看。 ``` const geometry = new THREE.PlaneGeometry(100,50); //矩形平面几何体 // const geometry = new THREE.BoxGeometry(50,50,50); //长方体 console.log('几何体',geometry); console.log('顶点位置数据',geometry.attributes.position); console.log('顶点索引数据',geometry.index); ``` ### 材质属性.wireframe 线条模式渲染,查看几何体三角形结构 ``` const material = new THREE.MeshLambertMaterial({ color: 0x00ffff, wireframe:true, // 线条模式渲染mesh对应的三角形数据 }); ``` ### 几何体细分数 Three.js很多几何体都提供了细分数相关的参数。例:矩形平面几何体至少需要两个三角形拼接而成。 ``` // 矩形几何体PlaneGeometry的参数3,4表示细分数,默认是1,1 const geometry = new THREE.PlaneGeometry(100,50,1,1); // 把一个矩形分为2份,每个矩形2个三角形,总共就是4个三角形 const geometry = new THREE.PlaneGeometry(100,50,2,1); // 把一个矩形分为4份,每个矩形2个三角形,总共就是8个三角形 const geometry = new THREE.PlaneGeometry(100,50,2,2); ``` ### 三角形数量与性能 对于一个曲面而言,细分数越大,表面越光滑,但是三角形和顶点数量却越多。 几何体三角形数量或者说顶点数量直接影响Three.js的渲染性能,在不影响渲染效果的情况下,一般尽量越少越好。 ## 七、旋转、缩放、平移 BufferGeometry的旋转、缩放、平移等方法,本质上就是改变顶点的位置坐标 ### 缩放.scale() ``` // 几何体xyz三个方向都放大2倍 geometry.scale(2, 2, 2); // 几何体旋转、缩放或平移之后,查看几何体顶点位置坐标的变化 console.log('顶点位置数据', geometry.attributes.position); ``` ### 平移.translate() ``` // 几何体沿着x轴平移50 geometry.translate(50, 0, 0); ``` ### 旋转.rotateX() .rotateY() .rotateZ() ``` // 几何体绕着x轴旋转45度 geometry.rotateX(Math.PI / 4); // Math.PI = 3.1415926 ``` ### 居中.center() ``` geometry.translate(50, 0, 0);//偏移 // 居中:已经偏移的几何体居中,执行.center(),你可以看到几何体重新与坐标原点重合 geometry.center(); ``` # 第三章 模型、材质 # 第四章 层级模型 # 第五章 顶点UV坐标、纹理贴图 ## 纹理贴图 通过纹理贴图加载器TextureLoader的load()方法加载一张图片可以返回一个纹理对象Texture,纹理对象Texture可以作为模型材质颜色贴图.map属性的值。 ``` //纹理贴图加载器TextureLoader, .load()方法加载图像,返回一个纹理对象Texture const texLoader = new THREE.TextureLoader(); const texture = texLoader.load('./earth.jpg'); const material = new THREE.MeshLambertMaterial({ map: texture, //map表示材质的颜色贴图属性 }); // material.map = texture; // 也可设置贴图 ``` .map颜色贴图和.color属性颜色值会混合 ## 自定义顶点UV坐标 颜色纹理贴图映射到不同的几何体上,映射效果不同。其实和UV坐标相关。 顶点UV坐标的作用是从纹理贴图上提取像素映射到网格模型Mesh的几何体表面上。 ``` const geometry = new THREE.PlaneGeometry(200, 100); //矩形平面 // const geometry = new THREE.BoxGeometry(100, 100, 100); //长方体 // const geometry = new THREE.SphereGeometry(100, 30, 30);//球体 console.log('uv',geometry.attributes.uv); // 查看几何体默认UV坐标 ``` ### 自定义顶点UV 顶点UV坐标geometry.attributes.uv和顶点位置坐标geometry.attributes.position是一一对应的,即position有几个顶点,UV也需要设置几个顶点。 UV顶点坐标你可以根据需要在0~1之间任意设置 ``` /**纹理坐标0~1之间随意定义*/ const uvs = new Float32Array([ 0, 0, //图片左下角 1, 0, //图片右下角 1, 1, //图片右上角 0, 1, //图片左上角 ]); // 设置几何体attributes属性的位置normal属性 geometry.attributes.uv = new THREE.BufferAttribute(uvs, 2); //2个为一组,表示一个顶点的纹理坐标 ``` ``` const vertices = new Float32Array([ 0,0,0, 10,0,0, 10,10,0, 0,0,0, 10,10,0, 0,10,0 ]) const uvs = new Float32Array([ 0, 0, //图片左下角 1, 0, //图片右下角 1, 1, //图片右上角 0, 0, 1, 1, 0, 1 //图片左上角 ]); const geometry = new Three.BufferGeometry() geometry.setAttribute('uv',new Three.BufferAttribute(uvs, 2)) geometry.setAttribute('position',new Three.BufferAttribute(vertices, 3)) // 通过材质的map添加纹理 const material = new Three.MeshBasicMaterial({map: texture, side: Three.DoubleSide}) const mesh = new Three.Mesh(geometry, material) mesh.position.set(10,10,0) scene.add(mesh) ``` ## 圆形平台设置纹理图 通过圆形几何体CircleGeometry创建一个网格模型Mesh,把一张图片作为圆形Mesh材质的颜色贴图,可以把一张方形图片剪裁四角,渲染为圆形效果。 CircleGeometry的UV坐标会对颜色纹理贴图.map进行提取,CircleGeometry的UV坐标默认提取的就是一个圆形轮廓。 ## 纹理对象Texture阵列 使用纹理对象Texture的阵列功能+矩形平面几何体PlaneGeometry实现一个地面瓷砖效果。 设置了阵列,重复渲染纹理才会均匀分布 ``` const geometry = new THREE.PlaneGeometry(2000, 2000); const texLoader = new THREE.TextureLoader(); const texture = texLoader.load('./瓷砖.jpg'); //设置阵列 texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; // 设置uv两个方向纹理重复数量 texture.repeat.set(10, 10) const material = new THREE.MeshLambertMaterial({ map: texture, }); const mesh = new THREE.Mesh(geometry, material); ``` ## 矩形Mesh+背景透明png贴图 把一个背景透明的.png图像作为平面矩形网格模型Mesh的颜色贴图是一个非常有用的功能,可以对三维场景进行标注。 整体思路:创建一个矩形平面,设置颜色贴图.map,注意选择背景透明的.png图像作为颜色贴图,同时材质设置transparent: true,这样png图片背景完全透明的部分不显示。 ``` const geometry = new THREE.PlaneGeometry(60, 60); //默认在XOY平面上 const textureLoader = new THREE.TextureLoader(); const material = new THREE.MeshBasicMaterial({ map: textureLoader.load('./left.png'), transparent: true, //开启透明 }); const mesh = new THREE.Mesh(geometry, material); mesh.rotateX(-Math.PI / 2); ``` ## UV动画 纹理对象Texture的.offset的功能是偏移贴图在Mesh上位置,本质上相当于修改了UV顶点坐标。 ``` function render() { texture.offset.x +=0.01; //设置纹理动画:偏移量根据纹理和动画需要,设置合适的值 // mesh.position.x -= 0.2 renderer.render(scene, camera); requestAnimationFrame(render); } ``` # 第六章 加载三维模型(gltf) ## 一、建模软件绘制3D场景(Blender) 3D美术常用的三维建模软件,比如Blender、3dmax、C4D、maya等等 分工和流程: * 3D美术:使用三维建模软件绘制3D模型,导出gltf等常见格式 * 程序:加载解析三维软件导出的三维模型 比如使用Blender三维建模软件导出gltf格式模型,然后再通过threejs加载三维模型。 ### gltf(Web3D的jpg) GLTF格式是新2015发布的三维模型格式,随着物联网、WebGL、5G的进一步发展,会有越来越多的互联网项目Web端引入3D元素,你可以把GLTF格式的三维模型理解为.jpg、.png格式的图片一样是标配。在2017年又发布了GLTF2.0的版本。 GLTF文件通过JSON的键值对方式来表示模型信息。.gltf格式文件几乎可以包含所有的三维模型相关信息的数据,比如模型层级关系、PBR材质、纹理贴图、骨骼动画、变形动画... 不仅three.js,其它的WebGL三维引擎cesium、babylonjs都对gltf格式有良好的的支持。 ### .bin文件、.glb文件 glTF文件会关联一个或多个.bin文件,.bin文件以二进制形式存储了模型的顶点数据等信息。 .glb是gltf格式的二进制文件。.gltf模型和贴图信息.bin全部合成得到一个.glb文件。.glb文件相对.gltf文件体积更小,网络传输自然更快。 ## 二、加载.gltf文件(模型加载全流程) 在实现基础场景、光源、渲染器、相机后,三步: * gltf模型加载器GLTFLoader.js * 相机参数根据需要设置 * 加载gltf的时候,webgl渲染器编码方式设置 ``` // 引入gltf模型加载库GLTFLoader.js import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; // 创建GLTF加载器对象 const loader = new GLTFLoader(); // gltf对象包含模型、动画等信息,gltf.scene属性包含的是模型信息 loader.load( 'gltf模型.gltf', function ( gltf ) { scene.add( gltf.scene ); // 返回的场景对象gltf.scene插入到threejs场景中 }) ``` ### 尺寸 一般通过三维建模软件可以轻松测量模型尺寸,用三维建模软件blender打开gltf模型,测量尺寸。 three.js的世界没有任何单位,只有数字大小的运算。obj、gltf格式的模型信息只有尺寸,并不含单位信息。 不过实际项目开发的时候会约定单位,如果单位不统一,可以用.scale缩放 ### 相机注意 * 相机的位置合理设置.position * 某位在居中,camera.lookAt()指向该坐标(OrbitControls会影响lookAt,orbitControls.target应与lookAt相同) * PerspectiveCamera(fov, aspect, near, far)近远截面合理设置,影响可视范围 ### 纹理贴图颜色偏差解决 three.js加载gltf模型的时候,可能会渲染结果颜色偏差,对于这种情况,只需要修改WebGL渲染器默认的编码方式.outputColorSpace 即可 ``` // 设置为SRGB颜色空间 renderer.outputColorSpace = THREE.sRGBEncoding; // 旧版用.outputEncoding ``` ### 递归模型修改材质 加载一个外部模型,比如gltf模型,如果你想批量修改每个Mesh的材质,一个一个设置比较麻烦,可以通过递归遍历方法.traverse()批量操作更加方便 ``` // 递归遍历所有模型节点批量修改材质 gltf.scene.traverse(function(obj) { if (obj.isMesh) { //判断是否是网格模型 console.log('模型节点名字',obj.name); } }); ``` threejs解析gltf模型默认材质一般是MeshStandardMaterial或MeshPhysicalMaterial,这两个材质属于PBR物理材质,可以提供更加真实的材质效果 ``` gltf.scene.traverse(function(obj) { if (obj.isMesh) { // 重新设置材质 obj.material = new THREE.MeshLambertMaterial({ color:0xffffff, }); } }); ``` ### 外部模型材质共享的问题 美术通过三维建模软件绘制好一个三维场景以后,一些外观一样的Mesh,可能会共享一个材质对象。因此出现改变一个模型颜色其它模型跟着变化 解决问题方向 * 三维建模软件中设置,需要代码改变材质的Mesh不要共享材质,要独享材质。 * 代码批量更改:克隆材质对象,重新赋值给mesh的材质属性 ``` //用代码方式解决mesh共享材质问题 gltf.scene.getObjectByName("小区房子").traverse(function (obj) { if (obj.isMesh) { // .material.clone()返回一个新材质对象,和原来一样,重新赋值给.material属性 obj.material = obj.material.clone(); } }); mesh1.material.color.set(0xffff00); mesh2.material.color.set(0x00ff00); ``` # 第七章 PBR材质与纹理贴图 ## PBR材质简介 PBR基于物理的渲染(physically-based rendering)。 Three.js提供了两个PBR材质相关的APIMeshStandardMaterial和MeshPhysicalMaterial,MeshPhysicalMaterial是MeshStandardMaterial扩展的子类,提供了更多功能属性。他们用的光照模型不同,反射光照的代码算法不同,算法不同,自然模拟光照的真实程度也不同。 * MeshLambertMaterial: Lambert光照模型(漫反射) * MeshPhongMaterial:Phong光照模型(漫反射、高光反射) * MeshStandardMaterial和MeshPhysicalMaterial:基于物理的光照模型(微平面理论、能量守恒、菲涅尔反射...) ## PBR材质金属度和粗糙度 金属度属性.metalness表示材质像金属的程度, 非金属材料,如木材或石材,使用0.0,金属使用1.0。 threejs的PBR材质,.metalness默认是0.5,0.0到1.0之间的值可用于生锈的金属外观 粗糙度roughness表示模型表面的光滑或者说粗糙程度,越光滑镜面反射能力越强,越粗糙,表面镜面反射能力越弱,更多地表现为漫反射。 粗糙度roughness,0.0表示平滑的镜面反射,1.0表示完全漫反射,默认0.5。 ``` new THREE.MeshStandardMaterial({ metalness: 1.0,//金属度属性 roughness: 0.5,//表面粗糙度 }) ``` ## 环境贴图.envMap(金属效果) 环境贴图对PBR材质渲染效果影响还是比较大,一般渲染PBR材质的模型,最好设置一个合适的环境贴图 # Shader着色器 Shader可以做什么: * 建筑流光效果 * 智慧城市特效 * 地球、地图可视化飞线Shader * 网页波浪背景 需要了解WebGL的着色器GLSL ES语言 ## ShaderMaterial着色器材质 与MeshBasicMaterial、MeshLambertMaterial这些材质通过color、map设置外观不同 ShaderMaterial是通过着色器GLSL ES语言 (opens new window)自定义材质效果,比如颜色。 * .vertexShader:顶点着色器 * .fragmentShader:片元着色器 ### 顶点着色器vertexShader 顶点着色器属性vertexShader的值是字符串,字符串的内容是着色器GLSL ES语言写的代码。 为了方便预览顶点着色器代码,咱们用模板字符串``的形式去写 先按照着色器GLSL ES语言的语法,给顶点着色器代码设置一个主函数main,函数main无返回值,前面加上关键字void即可。 #### gl_Position关键字 gl_Position是着色器GLSL ES语言的内置变量,数据类型是四维向量vec4,前面三个参数表示xyz坐标,第四个参数一般为1.0。 ``` const vertexShader = ` void main(){ gl_Position = vec4( x, y ,z ,1.0 ); } ` const material = new THREE.ShaderMaterial({ vertexShader: vertexShader,// 顶点着色器 }); ``` #### attribute关键字 attribute关键字一般用来声明与顶点数据有关变量。 attribute vec3 pos表示用attribute声明了一个变量pos,attribute的作用就是指明pos是顶点相关变量,pos的数类型是三维向量vec3,分别是x、y、z三个分量 ``` const vertexShader = ` attribute vec3 pos; void main(){ gl_Position = vec4(pos,1.0 ); }` ``` 调用shader材质ShaderMaterial的时候,threejs默认插入了一行代码attribute vec3 position;,相当于帮你声明了一个变量position,position表示顶点的位置数据 ``` const vertexShader = ` // attribute vec3 position;//默认提供,不用自己写 void main(){ gl_Position = vec4(position,1.0 ); }` ``` #### uniform关键字 用来声明非顶点的变量(顶点变量用atribute声明),比如模型的矩阵、光源位置等等。 执行uniform mat4 mT。意味着通过关键字uniform声明一个变量mT,变量mT的数据类型是mat4(4x4的矩阵) 调用shader材质ShaderMaterial的时候,threejs默认插入了一行代码uniform mat4 modelMatrix;。可以使用modelMatrix对几何体顶点位置坐标进行旋转、缩放、平移。 视图矩阵viewMatrix、投影矩阵projectionMatrix也是内置,通过viewMatrix和projectionMatrix来表示相机对场景模型的旋转、缩放、平移变换。 ``` const vertexShader = ` // uniform mat4 modelMatrix;//默认提供,不用自己写 // uniform mat4 viewMatrix;//默认提供,不用自己写 // uniform mat4 projectionMatrix;//默认提供,不用自己写 void main(){ // 投影矩阵 * 视图矩阵 * 模型矩阵 * 顶点坐标 // 注意矩阵乘法前后顺序不要写错 gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position,1.0 ); } ` ``` ### 片元着色器代码fragmentShader gl_FragColor是着色器GLSL ES语言的内置变量。 gl_FragColor数据类型是四维向量vec4(r,g,b,a),第四个参数表示透明度,不透明就是1.0。 ``` // 片元着色器代码 const fragmentShader = ` void main() { // RGB 0.0,1.0,1.0对应16进制颜色是0x00ffff gl_FragColor = vec4(0.0,1.0,1.0,1.0); }` const material = new THREE.ShaderMaterial({ vertexShader: vertexShader,// 顶点着色器 fragmentShader: fragmentShader,// 片元着色器 }); ``` ### ShaderMaterial半透明、双面显示 side: THREE.DoubleSide设置双面显示 ``` const material = new THREE.ShaderMaterial({ vertexShader: vertexShader, fragmentShader: fragmentShader, side: THREE.DoubleSide // 双面显示 }); ``` 半透明通过片元着色器代码设置透明度,更改gl_FragColor的第四个分量。材质ShaderMaterial设置transparent:true,才会生效 ``` // 片元着色器代码 const fragmentShader = ` void main() { //透明度设置0.3,在0~1之间,半透明 gl_FragColor = vec4(0.0,1.0,1.0,0.3); }` const geometry = new THREE.PlaneGeometry(100, 50); const material = new THREE.ShaderMaterial({ vertexShader: vertexShader,// 顶点着色器 fragmentShader: fragmentShader,// 片元着色器 transparent:true //开启透明 }); ``` ### uniform声明变量 ``` const fragmentShader = ` uniform float opacity; //uniform声明透明度变量opacity uniform vec3 color; //声明一个颜色变量color void main() { gl_FragColor = vec4(color, opacity); }` const material = new THREE.ShaderMaterial({ uniforms: { // 给透明度uniform变量opacity传值 opacity: {value:0.3} // 给uniform同名color变量传值 color:{value:new THREE.Color(0x00ffff)} }, vertexShader: vertexShader, fragmentShader: fragmentShader, // 片元着色器 transparent: true, //允许透明 }); ``` ## WebGL渲染管线执行 地址http://www.webgl3d.cn/pages/21c48e/ 顶点着色器到片元着色器的处理可成: 1、顶点缓冲区:顶点数据 2、顶点着色器:顶点变换 3、图元装配:比如渲染Mesh,Mesh几何体有多个三角形拼接,三个点为一组生成一个三角形 4、光栅化:比如在上一步三角形轮廓中,生成填充一个一个片元(像素) 5、片元着色器:给片元(像素)着色器 ### gl_FragCoord.xy片元屏幕坐标 gl_FragCoord.xy坐标系的坐标原点,位于threejs canvas画布的左下角,x轴水平向右,y轴竖直向上,单位是像素px。 #### 根据片元屏幕坐标设置颜色 canvas画布总宽800px的情况下,以中间作为分界点,左半部分片元红色,右半部分片元蓝色。 ``` if(gl_FragCoord.x < 400.0){ gl_FragColor = vec4(1.0,0.0,0.0,1.0); }else { gl_FragColor = vec4(0.0,0.0,1.0,1.0); } ``` 根据片元x坐标,设置一个渐变色。 ``` gl_FragColor = vec4(gl_FragCoord.x/800.0*1.0,0.0,0.0,1.0); ``` discard是着色器语言GLSL ES的一个关键字,用来控制片元着色器功能单元,舍弃某个片元。 ``` if(gl_FragCoord.x < 400.0){ // 符合条件片元保留,并设置颜色 gl_FragColor = vec4(1.0,0.0,0.0,1.0); }else { discard;//不符合条件片元直接舍弃掉 } ``` ### 顶点颜色varying插值计算,顶点位置插值(实现渐变色) ShaderMaterial和原来的网格材质一样,设置vertexColors:true,允许设置使用顶点颜色渲染 ShaderMaterial还有一个内置变量color,color变量表示插值之前的顶点颜色数据,varying关键字声明一个插值计算后的顶点颜色变量vColor。 ``` // 顶点着色器代码 const vertexShader = ` // attribute vec3 color;//默认提供不用手写 varying vec3 vColor; //表示顶点插值后位置数据,与片元数量相同,一一对应 void main(){ vColor = color;// 顶点颜色数据进行插值计算 // 投影矩阵 * 模型视图矩阵 * 模型顶点坐标 gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }` // 片元着色器代码 const fragmentShader = ` varying vec3 vColor; //获取顶点着色器插值数据vPosition void main() { // gl_FragColor = vec4(0.0,1.0,1.0,1.0); gl_FragColor = vec4(vColor,1.0); }` ``` ### 颜色贴图.map ShaderMaterial内置了顶点UV坐标变量uv(vec2),查看Uniform的文档,能看到着色器语言数据类型sampler2D ``` const vertexShader = ` varying vec2 vUv; void main(){ vUv = uv;// UV坐标插值计算 gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }` const fragmentShader = ` uniform sampler2D map; //颜色贴图变量 varying vec2 vUv; void main() { // 通过几何体的UV坐标从颜色贴图获取像素值 gl_FragColor = texture2D( map, vUv ); }` const texture = new THREE.TextureLoader().load('./Earth.png'); const material = new THREE.ShaderMaterial({ uniforms: { // 给着色器中同名uniform变量map传值 map: {value: texture}, }, vertexShader: vertexShader, fragmentShader: fragmentShader, }); ```