关于three.js:DataGear制作基于threejs的3D数据可视化看板

<article class=“article fmt article-content”><h3>DataGear专业版 1.0.0 已公布,欢送试用! http://datagear.tech/pro/</h3><p>DataGear 反对采纳原生的HTML、JavaScript、CSS制作数据可视化看板,也反对导入由<code>npm</code>、<code>vite</code>等前端工具构建的前端程序包。得益于这一个性,能够很容易制作基于three.js的3D数据可视化看板。</p><p>首先,参考three.js的官网教程 https://threejs.org/docs/index.html#manual/en/introduction/Installation 编写3D前端源码包。</p><p>源码包中蕴含两个文件:<code>index.html</code>、<code>main.js</code>,如下所示:</p><p>index.html</p><pre><code class=“html”><!DOCTYPE html><html><head><meta charset=“utf-8”></head><body><script type=“module” src="/main.js"></script><script type=“module”>import { ThreeRenderer } from “/main.js”;window.ThreeRenderer = ThreeRenderer;</script><div style=“padding:1rem;"> <button onclick=“threeRender()">渲染</button> <button onclick=“threeUpdate()">更新</button> <p></p> <div id=“threeChart” style=“width:300px;height:300px;"></div></div><script>var renderer;function threeRender(){ renderer = new ThreeRenderer(document.getElementById(“threeChart”)); renderer.render();}function threeUpdate(){ renderer.update(0xff0000);}</script></body></html></code></pre><blockquote><code>index.html</code>是下述<code>main.js</code>中定义3D组件的调试页面,点击【渲染】、【更新】按钮可调试3D组件成果。</blockquote><p>main.js</p><pre><code class=“javascript”>import * as THREE from ’three’;export function ThreeRenderer(dom){ this.dom = dom; this.render = function() { const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 75, this.dom.clientWidth / this.dom.clientHeight, 0.1, 1000 ); const renderer = new THREE.WebGLRenderer(); renderer.setSize( this.dom.clientWidth, this.dom.clientHeight ); this.dom.appendChild( renderer.domElement ); const geometry = new THREE.BoxGeometry( 1, 1, 1 ); const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); const cube = new THREE.Mesh( geometry, material ); scene.add( cube ); camera.position.z = 5; function animate() { requestAnimationFrame( animate ); cube.rotation.x += 0.01; cube.rotation.y += 0.01; renderer.render( scene, camera ); } animate(); this.cube = cube; }; this.update = function(hexColor) { const cube = this.cube; const material = cube.material; const color = material.color; color.setHex(hexColor); };}</code></pre><blockquote><code>main.js</code>定义了一个<code>ThreeRenderer</code>3D组件类,大部分代码由three.js官网教程拷贝,它的<code>render</code>函数绘制一个简略的3D立方体, <code>update</code>函数能够更新这个3D立方体的色彩。</blockquote><p>调试:</p><pre><code>npm install –save threenpm install –save-dev vitenpx vite</code></pre><p>执行<code>npx vite build</code>将它们构建为前端程序包:</p><pre><code>index.htmlassets/index-.js</code></pre><p>先将上述前端程序包压缩为<code>ZIP</code>包后导入为DataGear看板,而后将<code>index.html</code>中的</p><pre><code><script type=“module” crossorigin src="/assets/index-.js”></script></code></pre><p>批改为采纳相对路径引入形式:</p><pre><code><script type=“module” crossorigin src=“assets/index-.js”></script></code></pre><p>此时,点击【保留并展现】看板后,关上展现页面,点击【渲染】、【更新】按钮,将能够看到3D成果,如下所示:</p><p></p><p>上面,咱们将上述3D组件制作为DataGear自定义图表,能够依据数据集返回的数值,更新其色彩。</p><p>首先,新建SQL数据集:</p><p>名称:<code>最新指标值</code></p><p>SQL:</p><pre><code>SELECT D_VALUE AS VALUEFROM t_date_valueORDER BY d_date DESCLIMIT 0, 1</code></pre><blockquote>上述SQL从<code>t_date_value</code>表中查问最新日期的指标值</blockquote><p>而后,新建一个关联上述数据集的<code>自定义</code>类型的图表;</p><p>名称:<code>最新指标值</code></p><p>图表类型:<code>自定义</code></p><p>数据集:<code>最新指标值</code></p><p>更新距离:<code>2000毫秒</code></p><blockquote>上述图表每隔2秒更新一次数据</blockquote><p>最初,批改方才导入看板的<code>index.html</code>,增加自定义图表渲染器,当<code>t_date_value</code>表中最新指标值大于等于<code>80</code>时,将3D模型渲染为红色,否则,渲染为绿色。</p><p>批改后的<code>index.html</code>如下所示:</p><pre><code class=“html”><!DOCTYPE html><html><head><meta charset=“utf-8”><script src=“assets/index-.js”></script><script>//自定义图表渲染器var chartRenderer ={ render: function(chart) { var internal = new ThreeRenderer(chart.element()); internal.render(); chart.internal(internal); }, update: function(chart, results) { var chartDataSet = chart.chartDataSetMain(); var result = chart.resultOf(results, chartDataSet); var value = chart.resultCell(result, “VALUE”, 0); var color = (value >= 80 ? 0xff0000 : 0x00ff00); var internal = chart.internal(); internal.update(color); }};</script></head><body><div style=“padding:1rem;text-align:center;"> <h1>DataGear制作3D图表</h1> <h5>http://www.datagear.tech</h5> <div style=“display:inline-block;width:300px;height:300px;margin:1rem;” dg-chart-renderer=“chartRenderer” dg-chart-widget="【图表ID】"></div> <div style=“display:inline-block;width:300px;height:300px;margin:1rem;” dg-chart-renderer=“chartRenderer” dg-chart-widget="【图表ID】"></div></div></body></html></code></pre><blockquote>上述<code>assets/index-*.js</code>应替换为理论的JS文件名,<code>【图表ID】</code>应替换为理论的<code>最新指标值</code>图表的ID</blockquote><p>点击【保留并展现】,即可看到3D图表成果,如下图所示:</p><p></p><p>官网地址:http://www.datagear.tech</p><p>源码地址:</p><p>Gitee:https://gitee.com/datagear/datagear</p><p>Github:https://github.com/datageartech/datagear</p></article> ...

February 29, 2024 · 2 min · jiezi

关于three.js:Threejs教程光源对物体表面影响

举荐:将NSDT场景编辑器退出你的3D工具链其余系列工具:NSDT简石数字孪生 光源对物体外表影响理论生存中物体外表的明暗成果是会受到光照的影响,threejs中同样也要模仿光照Light对网格模型Mesh外表的影响。 你能够关上课件中案例源码,比照有光照和无光照两种状况,网格模型Mesh外表的差别。 受光照影响材质threejs提供的网格材质,有的受光照影响,有的不受光照影响。根底网格材质MeshBasicMaterial (opens new window)不会受到光照影响。 //MeshBasicMaterial不受光照影响const material = new THREE.MeshBasicMaterial(); 漫反射网格材质MeshLambertMaterial (opens new window)会受到光照影响,该材质也能够称为Lambert网格材质,音译为兰伯特网格材质。 一个立方体长方体应用MeshLambertMaterial材质,不同面和光线夹角不同,立方体不同面就会出现进去不同的明暗成果。 //MeshLambertMaterial受光照影响const material = new THREE.MeshLambertMaterial(); 光源简介Three.js提供了多种模仿生存中光源的API,文档搜寻关键词light就能够看到。点光源点光源PointLight (opens new window)能够类比为一个发光点,就像生存中一个灯泡以灯泡为核心向周围发射光线。 //点光源:两个参数别离示意光源色彩和光照强度// 参数1:0xffffff是纯白光,示意光源色彩// 参数2:1.0,示意光照强度,能够依据须要调整const pointLight = new THREE.PointLight(0xffffff, 1.0);光源地位你把点光源设想为一个电灯泡,你在3D空间中,放的地位不同,模型的渲染成果就不一样。 留神光源地位尺寸大小:如果你心愿光源照在模型的外外表,那你就须要把光源放在模型的里面。 //点光源地位pointLight.position.set(400, 0, 0);//点光源放在x轴上扭转光源地位,察看网格模型外表的明暗变动。 directionalLight.position.set(100, 60, 50); 光源增加到场景光源和网格模型Mesh对应一样是三维场景的一部分,天然须要增加到三维场景中能力起作用。 scene.add(directionalLight); //点光源增加到场景中 3D建模学习工作室

July 4, 2023 · 1 min · jiezi

关于three.js:Threejs教程三维坐标系

举荐:将NSDT场景编辑器退出你的3D工具链其余系列工具:NSDT简石数字孪生 三维坐标系本节课的目标就是为了增强大家对threejs三维空间的意识。 辅助察看坐标系THREE.AxesHelper()的参数示意坐标系坐标轴线段尺寸大小,你能够依据须要扭转尺寸。 // AxesHelper:辅助察看的坐标系const axesHelper = new THREE.AxesHelper(150);scene.add(axesHelper);材质半透明设置设置材质半透明,这样能够看到坐标系的坐标原点。 const material = new THREE.MeshBasicMaterial({ color: 0x0000ff, //设置材质色彩 transparent:true,//开启通明 opacity:0.5,//设置透明度});AxesHelper的xyz轴three.js坐标轴色彩红R、绿G、蓝B别离对应坐标系的x、y、z轴,对于three.js的3D坐标系默认y轴朝上。 设置模型在坐标系中的地位或尺寸通过模型的地位、尺寸设置,加深3D坐标系的概念。测试:设置长方体xyz不同方向尺寸 // 设置几何体长宽高,也就是x、y、z三个方向的尺寸//比照三个参数别离对应xyz轴哪个方向new THREE.BoxGeometry(100, 60, 20);测试:扭转地位 // 设置模型mesh的xyz坐标mesh.position.set(100,0,0);扭转相机参数——预览新的渲染成果你能够尝试源码中扭转相机的参数,看看场景中的物体渲染成果怎么变动。 相机放在x轴负半轴,指标观察点是坐标原点,这样相当于相机的眼帘是沿着x轴正方向,只能看到长方体的一个矩形立体。 camera.position.set(-1000, 0, 0);camera.lookAt(0, 0, 0);// 相机眼帘沿着x轴负半轴,mesh位于相机前面,天然看不到camera.position.set(-1000, 0, 0);camera.lookAt(-2000, 0, 0);相机far偏小,mesh位于far之外,物体不会显示在画布上。 // const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);// 你能够进行上面测试,扭转相机参数,把mesh放在视锥体之外,看看是否显示// 3000改为300,使mesh位于far之外,mesh不在视锥体内,被剪裁掉const camera = new THREE.PerspectiveCamera(30, width / height, 1, 300); 3D建模学习工作室整顿翻译

July 4, 2023 · 1 min · jiezi

关于three.js:Threejs教程渲染器

举荐:将NSDT场景编辑器退出你的3D工具链。其余系列工具:NSDT简石数字孪生 渲染器生存中如果有了风物和相机,那么如果想取得一张照片,就须要你拿着相机,按一下,咔,实现拍照。对于threejs而言,如果实现“咔”这个拍照动作,就须要一个新的对象,也就是WebGL渲染器WebGLRenderer (opens new window)。 WebGL渲染器WebGLRenderer通过WebGL渲染器WebGLRenderer (opens new window)能够实例化一个WebGL渲染器对象。 设置Canvas画布尺寸.setSize()// 定义threejs输入画布的尺寸(单位:像素px)const width = 800; //宽度const height = 500; //高度renderer.setSize(width, height); //设置three.js渲染区域的尺寸(像素px)渲染器渲染办法.render()渲染器WebGLRenderer执行渲染办法.render()就能够生成一个Canvas画布(照片),并把三维场景Scene出现在canvas画布下面,你能够把.render()了解为相机的拍照动作“咔”。 renderer.render(scene, camera); //执行渲染操作 渲染器Canvas画布属性.domElement渲染器WebGLRenderer通过属性.domElement能够取得渲染办法.render()生成的Canvas画布,.domElement实质上就是一个HTML元素:Canvas画布。 document.body.appendChild(renderer.domElement);Canvas画布插入到任意HTML元素中<div id="webgl" style="margin-top: 200px;margin-left: 100px;"></div>document.getElementById('webgl').appendChild(renderer.domElement);3D建模学习工作室

June 30, 2023 · 1 min · jiezi

关于three.js:Threejs教程模型对象旋转平移缩放变换

举荐:将NSDT场景编辑器退出你的3D工具链其余系列工具:NSDT简石数字孪生 模型对象旋转平移缩放变换点模型Points、线模型Line、网格网格模型Mesh等模型对象的基类都是Object3D,如果想对这些模型进行旋转、缩放、平移等操作,如何实现,能够查问Threejs文档Object3D对相干属性和办法的介绍。 缩放网格模型Mesh的属性.scale示意模型对象的缩放比例,默认值是THREE.Vector3(1.0,1.0,1.0),.scale的属性值是一个三维向量对象Vector3,查看three.js文档你能够晓得Vector3对象具备属性.x、.y、.z,Vector3对象还具备办法.set(),.set办法有三个示意xyz方向缩放比例的参数。 网格模型xyz方向别离缩放0.5,1.5,2倍 mesh.scale.set(0.5, 1.5, 2) x轴方向放大2倍 mesh.scale.x = 2.0; 地位属性.position模型地位.position属性和.scale属性的属性值一样也是三维向量对象Vector3,通过模型地位属性.position能够设置模型在场景Scene中的地位。模型地位.position的默认值是THREE.Vector3(0.0,0.0,0.0)。 设置网格模型y坐标 mesh.position.y = 80; 设置模型xyz坐标 mesh.position.set(80,2,10); 平移网格模型沿着x轴正方向平移100,能够屡次执行该语句,每次执行都是绝对上一次的地位进行平移变换。 // 等价于mesh.position = mesh.position + 100;mesh.translateX(100);//沿着x轴正方向平移间隔100沿着Z轴负方向平移间隔50。 mesh.translateZ(-50); 沿着自定义的方向挪动。 //向量Vector3对象示意方向var axis = new THREE.Vector3(1, 1, 1);axis.normalize(); //向量归一化//沿着axis轴示意方向平移100mesh.translateOnAxis(axis, 100);执行.translateX()、.translateY()、.translateOnAxis()等办法实质上扭转的都是模型的地位属性.position。 旋转立方体网格模型绕立方体的x轴旋转/4,能够屡次执行该语句,每次执行都是绝对上一次的角度进行旋转变动 mesh.rotateX(Math.PI/4);//绕x轴旋转/4 网格模型绕(0,1,0)向量示意的轴旋转/8 var axis = new THREE.Vector3(0,1,0);//向量axismesh.rotateOnAxis(axis,Math.PI/8);//绕axis轴旋转/8执行旋转.rotateX()等办法和执行平移.translateY()等办法一样都是对模型状态属性的扭转,区别在于执行平移办法扭转的是模型的地位属性.position,执行模型的旋转办法扭转的是示意模型角度状态的角度属性.rotation或者四元数属性.quaternion。 模型的角度属性.rotation和四元数属性.quaternion都是示意模型的角度状态,只是示意办法不同,.rotation属性值是欧拉对象Euler,.quaternion属性值是是四元数对象Quaternion // 绕着Y轴旋转90度mesh.rotateY(Math.PI / 2);//控制台查看:旋转办法,扭转了rotation属性console.log(mesh.rotation);3D建模学习工作室

June 29, 2023 · 1 min · jiezi

关于three.js:Threejs教程对象克隆复制

举荐:将NSDT场景编辑器退出你的3D工具链其余系列工具:NSDT简石数字孪生 对象克隆.clone()和复制.copy()Threejs大多数对象都有克隆.clone()和复制.copy()两个办法,点模型Points、线模型Line、网格网格模型Mesh一样具备这两个办法。 复制办法.copy()A.copy(B)示意B属性的值赋值给A对应属性。var p1 = new THREE.Vector3(1.2,2.6,3.2);var p2 = new THREE.Vector3(0.0,0.0,0.0);p2.copy(p1)// p2向量的xyz变为p1的xyz值console.log(p2);克隆办法.clone()N = M.clone()示意返回一个和M雷同的对象赋值给N。 var p1 = new THREE.Vector3(1.2,2.6,3.2);var p2 = p1.clone();// p2对象和p1对象xyz属性雷同console.log(p2);网格模型复制和克隆网格模型复制克隆和三维向量根本逻辑是雷同,然而留神三维向量Vector3的.x、.y、.z属性值是数字,也就是说是根本类型的数据,对于网格模型而言,网格模型对象的几何体属性mesh.geometry和材质属性mesh.material的属性值都是对象的索引值。 var box=new THREE.BoxGeometry(10,10,10);//创立一个立方体几何对象var material=new THREE.MeshLambertMaterial({color:0x0000ff});//材质对象var mesh=new THREE.Mesh(box,material);//网格模型对象var mesh2 = mesh.clone();//克隆网格模型mesh.translateX(20);//网格模型mesh平移scene.add(mesh,mesh2);//网格模型增加到场景中缩放几何体box,你能够发现下面代码中的两个网格模型的大小都产生了变动,因为网格模型克隆的时候,mesh对象的几何体对象mesh.geometry属性值是box对象的索引值,返回的新对象mesh2几何体属性mesh.geometry的值同样是box对象的索引值。 box.scale(1.5,1.5,1.5);//几何体缩放 留神通过本节课的学习,对Threejs不同对象的克隆.clone()和复制.copy()办法有一个大抵印象即可。 理论开发的时候,留神不同对象的复制或克隆办法可能稍有区别,应用的时候最好通过代码测试,或者间接查看threejs源码某个类对.clone()和.copy()封装,这样更为直观分明。 几何体复制和克隆几何体克隆或复制和网格模型在属性值深拷贝、浅拷贝方面有些不同,比方几何体的顶点属性Geometry.vertices,Geometry.vertices的属性值是一个数组对象,然而复制或克隆的时候,不是取得对象的索引值,而是深拷贝属性的值,能够在threejs源码Geometry.js全文检索copy: function关键词,找到该类对copy办法的封装细节。 3D建模学习工作室

June 29, 2023 · 1 min · jiezi

关于three.js:整个宇宙将为你闪烁-除了三体人前端也可以-哈哈

整个宇宙将为你闪动 除了三体人前端也能够 哈哈整个宇宙将为你闪动,这句话出自《三体》,过后就被吸引住了,而后就想着把它实现进去。我的项目次要应用 three.js,设计灵感及贴图来源于 Solar System Scope 。 视频展现: Bilibili 哔哩哔哩 我的项目源码: GitHub 前置项在开始之前心愿你对 three.js 有肯定的理解,这里举荐一个教程,Discover threejs, three.js 主创之一编写的适宜入门的教程。 我的项目只用原生 js 并没有引入 Vue 或 React 等框架,因为我想让我的项目更纯正些,也心愿本人不要来到框架就不会写代码了。 基础设施创立场景// scene.jsimport { Scene } from 'three'function createScene() { const scene = new Scene() return scene}export { createScene }创立相机// camera.jsimport { PerspectiveCamera } from 'three'function createCamera() { const camera = new PerspectiveCamera(45, 1, 0.1, 2000) camera.position.set(0, 0, 20) return camera}export { createCamera }渲染场景// renderer.jsimport { WebGLRenderer } from 'three'function createRenderer() { const renderer = new WebGLRenderer() return renderer}export { createRenderer } 增加物体到场景中实现下面的基本操作,咱们就能够往场景中增加物体了。 ...

June 21, 2023 · 5 min · jiezi

关于three.js:Threejs教程常见光源类型

举荐:将NSDT场景编辑器退出你的3D工具链其余系列工具:NSDT简石数字孪生 常见光源类型Threejs虚构光源是对自然界光照的模仿,threejs搭建虚构场景的时候,为了更好的渲染场景,往往须要设置不同的光源,设置不同的光照强度,就像摄影师给你拍照要设置各种辅助灯光一样。 环境光AmbientLight环境光是没有特定方向的光源,次要是平均整体扭转Threejs物体外表的明暗成果,这一点和具备方向的光源不同,比方点光源能够让物体外表不同区域明暗水平不同。 //环境光:环境光色彩RGB成分别离和物体材质色彩RGB成分别离相乘var ambient = new THREE.AmbientLight(0x444444);scene.add(ambient);//环境光对象增加到scene场景中你能够把光源色彩从0x444444更改为0x888888,能够看到threejs场景中的网格模型外表变的更亮。 点光源PointLight点光源就像生存中的白炽灯,光线沿着发光外围向外发散,同一立体的不同地位与点光源光线入射角是不同的,点光源照耀下,同一个立体不同区域是呈现出不同的明暗成果。 和环境光不同,环境光不须要设置光源地位,而点光源须要设置地位属性.position,光源地位不同,物体外表被照亮的面不同,远近不同因为衰减明暗水平不同。 你能够把案例源码中点光源地位从(400, 200, 300)地位扭转到(-400, -200, -300),你会发现网格模型被照亮的地位从后面变到了前面,这很失常,光源只能照亮面对着光源的面,背对着光源的无奈照射到,色彩会比拟暗。 //点光源var point = new THREE.PointLight(0xffffff);//设置点光源地位,扭转光源的地位point.position.set(400, 200, 300);scene.add(point);平行光DirectionalLight平行光顾名思义光线平行,对于一个立体而言,立体不同区域接管到平行光的入射角一样。 点光源因为是向周围发散,所以设置好地位属性.position就能够确定光线和物体外表的夹角,对于平行光而言,次要是确定光线的方向,光线方向设定好了,光线的与物体外表入射角就确定了,仅仅设置光线地位是不起作用的。 在三维空间中为了确定一条直线的方向只须要确定直线上两个点的坐标即可,所以Threejs平行光提供了地位.position和指标.target两个属性来一起确定平行光方向。指标.target的属性值能够是Threejs场景中任何一个三维模型对象,比方一个网格模型Mesh,这样Threejs计算平行光照耀方向的时候,会通过本身地位属性.position和.target示意的物体的地位属性.position计算出来。 // 平行光var directionalLight = new THREE.DirectionalLight(0xffffff, 1);// 设置光源的方向:通过光源position属性和指标指向对象的position属性计算directionalLight.position.set(80, 100, 50);// 方向光指向对象网格模型mesh2,能够不设置,默认的地位是0,0,0directionalLight.target = mesh2;scene.add(directionalLight);平行光如果不设置.position和.target属性,光线默认从上往下照耀,也就是能够认为(0,1,0)和(0,0,0)两个坐标确定的光线方向。 留神一点平行光光源的地位属性.position并不示意平行光从这个地位向远处照耀,.position属性只是用来确定平行光的照耀方向,平行光你能够了解为太阳光,从有限远处照耀过去。 聚光源SpotLight聚光源能够认为是一个沿着特定方会逐步发散的光源,照耀范畴在三维空间中形成一个圆锥体。通过属性.angle能够设置聚光源发散角度,聚光源照耀方向设置和平行光光源一样是通过地位.position和指标.target两个属性来实现。 // 聚光光源var spotLight = new THREE.SpotLight(0xffffff);// 设置聚光光源地位spotLight.position.set(200, 200, 200);// 聚光灯光源指向网格模型mesh2spotLight.target = mesh2;// 设置聚光光源发散角度spotLight.angle = Math.PI / 6scene.add(spotLight);//光对象增加到scene场景中光源辅助对象Threejs提供了一些光源辅助对象,就像AxesHelper可视化显示三维坐标轴一样显示光源对象,通过这些辅助对象能够不便调试代码,查看地位、方向。 辅助对象构造函数名聚光源辅助对象SpotLightHelper点光源辅助对象PointLightHelper平行光光源辅助对象DirectionalLightHelper光照计算算法Three.js渲染的时候光照计算还是比较复杂的,这里不进行深刻介绍,只给大家说下光源色彩和网格模型Mesh色彩相乘的常识,如果你有趣味能够学习计算机图形学或者WebGL教程。 Threejs在渲染的时候网格模型材质的色彩值mesh.material.color和光源的色彩值light.color会进行相乘,简略说就是RGB三个重量别离相乘。 平行光漫反射简略数学模型:漫反射光的色彩 = 网格模型材质色彩值 x 光线色彩 x 光线入射角余弦值漫反射数学模型RGB重量示意:(R2,G2,B2) = (R1,G1,B1) x (R0,G0,B0) x ...

June 9, 2023 · 1 min · jiezi

关于three.js:网易Threejs可视化企业实战WEBGL课2023全面升级版

download:网易Three.js可视化企业实战WEBGL课-2023全面升级版Three.js可视化:在浏览器中创立交互式3D图形Three.js是一个开源的JavaScript库,用于在Web浏览器中创立和渲染3D图形。应用Three.js,您能够轻松地在浏览器中出现简单的3D场景,并与用户进行互动。本文将探讨Three.js的基本功能以及如何应用它来创立各种类型的3D图形。 什么是Three.js?Three.js是一款十分风行的JavaScript库,由Mr.doob(Ricardo Cabello)开发,用于在Web浏览器中创立和渲染3D图形。它是基于WebGL技术构建的,这使得它能够在任何反对WebGL的浏览器上运行,包含Chrome、Firefox、Safari和Opera等。Three.js提供了丰盛的性能,包含灯光、暗影、纹理贴图、几何体、线条、粒子等,能够很容易地实现各种类型的3D图形。 基本概念在开始应用Three.js之前,咱们须要理解一些基本概念。首先,咱们须要一个场景(Scene),用于寄存所有的3D对象。其次,咱们须要一个相机(Camera),用于定义观察者的视角和地位。最初,咱们须要一个渲染器(Renderer),用于将场景和相机中的对象渲染为2D图像,以便在浏览器中显示。 创立一个根本场景让咱们看一下如何应用Three.js创立一个根本场景。首先,咱们须要创立一个场景: javascriptvar scene = new THREE.Scene();接下来,咱们须要一个相机。在这个例子中,咱们将应用透视相机(PerspectiveCamera): javascriptvar camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);这里的75示意相机的视线角度,window.innerWidth / window.innerHeight是相机的宽高比,0.1是近截面间隔,1000是远截面间隔。 当初,咱们须要一个渲染器。在这个例子中,咱们将应用WebGLRenderer: javascriptvar renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);这里的setSize()办法设置了渲染器的大小,而appendChild()办法则将渲染器增加到HTML文档中的body元素中。 最初,咱们须要将相机和场景增加到渲染器中: javascriptrenderer.render(scene, camera);当初,咱们曾经创立了一个根本的场景,并将其渲染到浏览器中。 创立一个立方体让咱们当初尝试创立一个简略的3D物体。在这个例子中,咱们将创立一个立方体: javascriptvar geometry = new THREE.BoxGeometry();var material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });var cube = new THREE.Mesh(geometry, material);scene.add(cube);这里的BoxGeometry()办法创立了一个立方体的几何体,而MeshBasicMaterial()办法则创立了一个材质,它应用了绿色(0x00ff00)来渲染立方体。最初,咱们将立方体增加到场景中。 创立动画成果当初,让咱们为立方体创立一些动画成果。在这个例子中,咱们将使立方体绕着Y轴旋转: javascriptfunction animate() { requestAnimationFrame(animate);cube.rotation.y += 0.01;renderer.render(scene, camera);}animate();这里的requestAnimationFrame()办法用于循环调用animate()函数,而cube.rotation.y属性

May 30, 2023 · 1 min · jiezi

关于three.js:Threejs可视化示例让你的数据可视化变得更加生动

Three.js可视化示例:让你的数据可视化变得更加活泼如果你是一个数据科学家或者是一位数据分析师,你可能曾经意识到了一个重要的问题:如何将咱们的数据转换成有意义的信息并展现给其他人看。在过来,咱们会应用图表、报告或幻灯片等形式来进行数据展现。然而这些形式都有一个独特的毛病,那就是不足互动性和视觉效果。 最近几年,随着WebGL技术的倒退,Three.js成为了可视化畛域的热门工具之一。Three.js是一个基于WebGL的JavaScript库,它能够创立高性能的3D图形和动画。在这篇文章中,我将向大家介绍Three.js的基础知识,并提供一个简略的示例来展现如何将你的数据可视化变得更加活泼。 Three.js简介Three.js是一个基于WebGL的JavaScript库,它能够让咱们创立高性能的3D场景和动画。WebGL是一种用于在浏览器中渲染3D图形的API(利用程序接口),它利用GPU的并行处理能力来实现高性能渲染。 Three.js能够被用于很多不同的场景,比方游戏开发、建筑设计、医学图像等等。它反对各种不同的根本图形,包含立方体、球体、圆柱体、立体等等,并且还能够加载3D模型和纹理图片。 Three.js的应用装置首先,你须要将Three.js库引入到你的我的项目中。你能够从最新版本的库文件,或者应用CDN链接引入库文件。 <script src="https://cdn.jsdelivr.net/npm/three@0.135.0/build/three.min.js"></script>创立场景在应用Three.js之前,咱们须要先创立一个场景。场景是所有3D对象的容器,咱们能够在场景中增加相机、灯光、3D模型等。 // 创立场景 var scene = new THREE.Scene();创立相机相机用于管制察看场景的角度和地位。Three.js提供了多种不同类型的相机,比方透视相机、正交相机等。 // 创立透视相机var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);camera.position.z = 5;创立渲染器渲染器将场景中的3D对象渲染到屏幕上。Three.js提供了多种不同类型的渲染器,比方WebGLRenderer、CanvasRenderer等。 // 创立WebGLRenderervar renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);增加3D对象当初咱们曾经创立了场景、相机和渲染器,接下来咱们须要增加3D对象到场景中。 // 创立立方体var geometry = new THREE.BoxGeometry(1, 1, 1);var material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });var cube = new THREE.Mesh(geometry, material);scene.add(cube);渲染场景最初一步是将场景中的3D对象渲染到屏幕上。这个过程通常是在一个循环中进行的,每次循环都会从新渲染一遍场景。 function animate() { requestAnimationFrame(animate); cube.rotation.x += 0.01; cube.rotation.y += 0.01; renderer.render(scene, camera);}animate();总结以上就是有对于Three.js可视化示例:让你的数据可视化变得更加活泼的示例文章,欢送点评。

May 26, 2023 · 1 min · jiezi

关于three.js:Threejs-进阶之旅滚动控制模型动画和相机动画-

申明:本文波及图文和模型素材仅用于集体学习、钻研和观赏,请勿二次批改、非法流传、转载、出版、商用、及进行其余获利行为。 摘要专栏上篇文章《Three.js 进阶之旅:页面平滑滚动-王国之泪》 解说并实现了如何应用 R3F 进行页面图片平滑滚动,本文内容在上节的根底上,学习如何应用滚动管制 ScrollControls 来管制模型的的动画播放和相机动画,通过滚动鼠标滚轮或者高低挪动触摸板,来管制模型的动画播放进度或者相机的方位视角,从而呈现出惊艳的视觉效果。这种乏味的成果大家在平时浏览一些网页的时候应该常常见到,如一些 3D产品 介绍页向下滑动鼠标滚轮时产品同时旋转并依据产品的不同视角加载不同文案、或者 3D数字地球 依据滚轮的挪动间隔转到某个国家或地区、还有一些 个人简历 页面或时间轴页面也常常用到这种成果。通过本文的浏览和案例页面的实现,你将学习到的常识包含:R3F 生态中的 ScrollControls、Html、useScroll、useGLTF、useAnimations 等组件和办法的根本用法、在 R3F 中加载模型并播放模型骨骼动画、通过滚动管制模型动画播放过程和相机参数、页面元素的一些 CSS 动画及页面整体丝滑滚动动画实现等。 成果本文案例的实现成果如下图所示,页面主体元素由一个三维模型 、及底部的 5 页 HTML 页面形成,页面初始加载时模型是静止的,当咱们应用鼠标或触控板或间接拖动页面滚动条时 ,相机镜头 从侧面近处平滑过渡到模型侧面远处,模型开始自动播放自带骨骼动画,模型动作依据页面滚动间隔和滚动时速率的大小而不同。当咱们点击页面顶部菜单时,页面会平滑滚动到对应地位,模型也会播放动画。 当页面逆向 滚动时,相机和模型动画也会逆向变动和播放。 文章应用 GIF 可能会造成丢帧或卡顿,能够亲自关上预览链接试试,大屏拜访成果更佳。 在线预览地址:https://dragonir.github.io/dancingDuck/本专栏系列代码托管在 Github 仓库【threejs-odessey】,后续所有目录也都将在此仓库中更新。 代码仓库地址:git@github.com:dragonir/threejs-odessey.git原理如果用原生 JavaScript 实现滚动动画成果,就须要监听滚动事件和计算滚动间隔。本文还是和上篇文章《hree.js 进阶之旅:页面平滑滚动-王国之泪》一样,间接应用封装好的组件 ScrollControls 和 Scroll 来实现,它们的具体用法和原理可返回上篇文章查看。本文中最终实现的页面须要加载模型并播放它自带的骨骼动画,因而用到了以下几个 @react-three/drei 中的组件和 hooks。 Html容许咱们将 HTML 内容绑定到场景中的任意对象,它将主动投影到对象上。 <Html as='div' // 包裹元素,默认为 'div' wrapperClass // 包裹元素的类名,默认为 undefined prepend // 画布前面的元素,默认为 false center // 增加 -50%/-50% css变换,默认为 false fullscreen // 与左上角对齐并填满屏幕,默认为 false distanceFactor={10} // 如果设置该值,子元素将按与相机的间隔进行缩放,默认为 undefined zIndexRange={[100, 0]} // Z阶范畴,默认为 [16777271, 0] portal={domnodeRef} // 对指标容器的援用,默认为 undefined transform // 若设置 true,将进行 3d 矩阵转换,默认为 false sprite // 渲染为 sprite,仅在转换模式下失效,默认为 false occlude // 遮挡模式,默认为 false,当设置为 blending 时将开启真正混合遮挡 castShadow // 产生暗影 receiveShadow // 接管暗影 // 像 Mesh 一样设置材质 material={<meshPhysicalMaterial side={DoubleSide} opacity={0.1} />} // 笼罩默认定位性能 calculatePosition={(el: Object3D, camera: Camera, size: { width: number; height: number }) => number[]} occlude={[ref]} // 能够为真或 Ref<Object3D>,当为 true 时遮挡整个场景,默认为 undefined onOcclude={(visible) => null} // 可见性批改时的回调,默认为 undefined {...groupProps} // 反对所有 THREE.Group 属性 {...divProps} // 反对所有 HTML DIV 元素属性> <h1>hello</h1> <p>world</p></Html>Html 能够通过配置 occlude 属性暗藏在几何体前面。当 Html 组件暗藏时,它将在最外部的 div 上设置 opacity 属性,如果须要增加动画成果或者管制过渡成果,能够应用 onOcclude 自定义办法。 ...

May 25, 2023 · 3 min · jiezi

关于three.js:Threejs教程材质共有属性私有属性

举荐:将NSDT场景编辑器退出你的3D工具链其余系列工具:NSDT简石数字孪生 材质共有属性、公有属性如果你的javascript语言根底还能够,应该明确类、基类、子类、父类等概念。如果你有这些类的概念,那么在学习Threejs的过程中,如何查找Threejs文档将会比较顺利。 点材质PointsMaterial、根底线材质LineBasicMaterial、根底网格材质MeshBasicMaterial、高光网格材质MeshPhongMaterial等材质都是父类Material的子类。 各种各样的材质子类都有本人的特定属性,比方点材质特有的尺寸属性.size、高光网格材质特有的高光色彩属性.specular等等这些属性能够成为子类材质的公有属性。 所有子类的材质都会从父类材质Material继承透明度opacity、面side等属性,这些来自父类的属性都是子类共有的属性。 .side属性在Three.js开发过程中你可能会遇到上面的问题,比方three.js矩形立体planegeometry的网格模型插入场景看不到,一个球体或立方体网格模型如何反面显示贴图,侧面不显示...,对于这些问题能够通过Three.js材质对象.side属性来设置。 材质.side属性的具体介绍能够查看Threejs文档中所有材质对象的基类Material。 .side属性的属性值定义面的渲染形式后面前面 或 双面. 属性的默认值是THREE.FrontSide,示意后面. 也能够设置为前面THREE.BackSide 或 双面THREE.DoubleSide. var material = new THREE.MeshBasicMaterial({ color: 0xdd00ff, // 后面FrontSide 反面:BackSide 双面:DoubleSide side:THREE.DoubleSide,});材质透明度.opacity通过材质的透明度属性.opacity能够设置材质的通明水平,.opacity属性值的范畴是0.0~1.0,0.0值示意齐全通明, 1.0示意齐全不通明,.opacity默认值1.0。 当设置.opacity属性值的时候,须要设置材质属性transparent值为true,如果材质的transparent属性没设置为true, 材质会放弃齐全不通明状态。 在结构函数参数中设置transparent和.opacity的属性值 var material = new THREE.MeshPhongMaterial({ color: 0x220000, // transparent设置为true,开启通明,否则opacity不起作用 transparent: true, // 设置材质透明度 opacity: 0.4,});通过拜访材质对象属性模式设置transparent和.opacity的属性值 // transparent设置为true,开启通明,否则opacity不起作用material.transparent = true; // 设置材质透明度material.opacity = 0.4;3D建模学习工作室

May 23, 2023 · 1 min · jiezi

关于three.js:Threejs教程常用材质介绍

举荐:将NSDT场景编辑器退出你的3D工具链其余系列工具:NSDT简石数字孪生 罕用材质介绍为了不便开发Threejs提供了一系列的材质,所有材质就是对WebGL着色器代码的封装,如果你不理解WebGL,会通过查阅Threejs文档应用相干材质类即可。 点材质PointsMaterial点材质比较简单,只有PointsMaterial,通常应用点模型的时候会应用点材质PointsMaterial。点材质PointsMaterial的.size属性能够每个顶点渲染的方形区域尺寸像素大小。 var geometry = new THREE.SphereGeometry(100, 25, 25); //创立一个球体几何对象// 创立一个点材质对象var material = new THREE.PointsMaterial({ color: 0x0000ff, //色彩 size: 3, //点渲染尺寸});//点模型对象 参数:几何体 点材质var point = new THREE.Points(geometry, material);scene.add(point); //网格模型增加到场景中线材质线材质有根底线材质LineBasicMaterial和虚线材质LineDashedMaterial两个,通常应用应用Line等线模型才会用到线材质。 根底线材质LineBasicMaterialvar geometry = new THREE.SphereGeometry(100, 25, 25);//球体// 直线根底材质对象var material = new THREE.LineBasicMaterial({ color: 0x0000ff});var line = new THREE.Line(geometry, material); //线模型对象scene.add(line); //点模型增加到场景中虚线材质LineDashedMaterial// 虚线材质对象:产生虚线成果var material = new THREE.LineDashedMaterial({ color: 0x0000ff, dashSize: 10,//显示线段的大小。默认为3。 gapSize: 5,//间隙的大小。默认为1});var line = new THREE.Line(geometry, material); //线模型对象// computeLineDistances办法 计算LineDashedMaterial所需的间隔数组line.computeLineDistances();网格模型Threejs提供的网格类材质比拟多,网格材质波及的材质品种和材质属性也比拟多,一节课也无奈解说完,本节课先有一个理性的认知。网格材质顾名思义,网格类模型才会应用的材质对象。根底网格材质对象MeshBasicMaterial,不受带有方向光源影响,没有棱角感。 ...

May 10, 2023 · 1 min · jiezi

关于three.js:Threejs教程访问几何体对象的数据

举荐:将NSDT场景编辑器退出你的3D工具链其余系列工具:NSDT简石数字孪生 拜访几何体对象的数据理论开发我的项目的时候,可能会加载内部模型,有些时候须要获取模型几何体的顶点数据,如果想获取几何体的顶点数据首先要相熟three.js几何体BoxGeometry和BufferGeometry的构造。拜访几何体顶点数据其实很简略,刚开始学习不必刻意记忆,间接查看threejs文档,就像拜访javascript对象的属性一样。 测试BoxGeometry调用BoxGeometry创立一个立方体,执行THREE.BoxGeometry构造函数会主动生成几何体对象的顶点地位坐标、顶点法向量等数据。你能够通过执行上面代码,而后查看浏览器控制台打印的数据 var geometry = new THREE.BoxGeometry(100, 100, 100); //创立一个立方体几何对象Geometryconsole.log(geometry);console.log('几何体顶点地位数据',geometry.vertices);console.log('三角行面数据',geometry.faces);BoxGeometry、PlaneGeometry、SphereGeometry等几何体类的基类是Geometry,所以拜访这些几何体的顶点数据,不晓得具体属性名称,能够查问threejs文档Geometry。 测试PlaneBufferGeometryPlaneBufferGeometry示意一个矩形平面几何体,执行上面代码,你能够查看该几何体的相干顶点数据。 //创立一个矩形平面几何体var geometry = new THREE.PlaneBufferGeometry(100, 100);console.log(geometry);console.log('几何体顶点地位数据',geometry.attributes.position);console.log('几何体索引数据',geometry.index);BoxBufferGeometry、PlaneBufferGeometry、SphereBufferGeometry等几何体类的基类是BufferGeometry,所以拜访这些几何体的顶点数据,不晓得具体属性名称,能够查问threejs文档BufferGeometry。 案例通过上面代码批改BoxGeometry的三角形顶点色彩的数据,能够渲染进去如下成果。 var geometry = new THREE.BoxGeometry(100, 100, 100); //创立一个立方体几何对象Geometry// 遍历几何体的face属性geometry.faces.forEach(face => { // 设置三角面face三个顶点的色彩 face.vertexColors = [ new THREE.Color(0xffff00), new THREE.Color(0xff00ff), new THREE.Color(0x00ffff), ]});var material = new THREE.MeshBasicMaterial({ // color: 0x0000ff, vertexColors: THREE.FaceColors, // wireframe:true,//线框模式渲染}); //材质对象Material案例你能够通过上面代码删除立方体局部三角形面,测试删除成果。 var geometry = new THREE.BoxGeometry(100, 100, 100); //创立一个立方体几何对象Geometry// pop():删除数组的最初一个元素 shift:删除数组的第一个元素geometry.faces.pop();geometry.faces.pop();geometry.faces.shift();geometry.faces.shift();var material = new THREE.MeshLambertMaterial({ color: 0x0000ff, side: THREE.DoubleSide, //两面可见}); //材质对象Material拜访内部模型几何体顶点数据Threejs加载内部模型的时候,会把几何体解析为缓冲类型几何体BufferGeometry,所以拜访内部模型几何体顶点数据,能够查看文档BufferGeometry。 ...

May 6, 2023 · 1 min · jiezi

关于three.js:Threejs-进阶之旅页面平滑滚动王国之泪-

申明:本文波及图文和模型素材仅用于集体学习、钻研和观赏,请勿二次批改、非法流传、转载、出版、商用、及进行其余获利行为。 摘要浏览网页时,常被一些基于鼠标滚轮管制的页面动画所惊艳到,比方greensock 官网这些 showcase 案例页面就十分优良,它们大多数都是应用 Tween.js、gasp 及 greensock 提供的一些动画扩大库实现的。应用 Three.js 也能很容易实现丝滑的滚动成果,本文应用 React + Three.js + React Three Fiber 技术栈,实现一个《塞尔达传说:王国之泪》主题格调基于滚动管制的平滑滚动图片展现页面。通过本文的浏览,你将学习到的知识点包含:理解 R3F 中 useFrame hook 及 useThree hook 基本原理及用法;理解 @react-three/drei 库的根本组成,学习应用它提供的 Preload、useIntersect、ScrollControls、Scroll、及 Image 等组件和办法;用 CSS 生成简略的循环悬浮动画等。 成果本文案例的实现成果如下图所示,当页面每个模块滚动进入视区时,每个模块会具备平滑向上挪动的视差成果,并且随同着由大到小的缩放动画,当鼠标悬浮到以后模块时,模块会产生高亮 ✨ 成果。除此之外,页面还有一些其余的装璜,比方塞尔达格调的页面背景和边框、具备缓动动画成果的希卡之石以及同样具备平滑滚动成果的文字装璜王国之泪四个字。 页面的整体布局是这样的,总共有 7 页,即高度为 700vh,每一页都具备不同的布局格调款式,滚动时都会具备缓动成果。 关上以下链接,在线预览成果,本文中的 gif 造成丢帧和画质损失,大屏拜访成果更佳。 在线预览地址:https://dragonir.github.io/tearsOfTheKingdom/本专栏系列代码托管在 Github 仓库【threejs-odessey】,后续所有目录也都将在此仓库中更新。 代码仓库地址:git@github.com:dragonir/threejs-odessey.git原理本文是应用 React Three Fiber 实现的,它不仅能够非常容易实现丑陋的三维图形,在二维立体页面开发中也能大放异彩。在开始实现本文案例之前,咱们先来汇总下本文中须要利用到的知识点。把握这些原理和办法,能够帮忙咱们迅速构建一个交互体验极佳的平滑滚动页面。 useFrame此 hook 容许在页面每一帧渲染的时候运行代码,比方更新渲染成果、控件等,与 Three.js 中调用 requestAnimationFrame 履行重绘动画成果是一样的。你将接管到状态值 state 和时钟增量 delta。回调函数将在渲染帧之前被调用,当组件卸载时,它会主动从渲染循环中登记。· useFrame((state, delta, xrFrame) => { // 此函数在共享渲染循环内以本机刷新率运行}); 留神,在 useFrame 中不能应用 setState 更新状态值!管制渲染循序如果你须要更多的管制,你能够传递一个数字渲染优先级值。这将导致 React Three Fiber 齐全禁用主动渲染。当初,渲染程序将由咱们本人管制,这在前期渲染通道解决以及在多个视图渲染的场景下十分有用。 ...

May 6, 2023 · 6 min · jiezi

关于three.js:Threejs教程顶点索引复用顶点数据

举荐:将NSDT场景编辑器退出你3D工具链其余工具系列:NSDT简石数字孪生 顶点索引复用顶点数据通过几何体BufferGeometry的顶点索引属性BufferGeometry.index能够设置几何体顶点索引数据,如果你有WebGL根底很容易了解顶点索引的概念,如果没有也没有关系,上面会通过一个简略的例子形象阐明。比方绘制一个矩形网格模型,至多须要两个三角形拼接而成,两个三角形,每个三角形有三个顶点,也就是说须要定义6个顶点地位数据。对于矩形网格模型而言,两个三角形有两个顶点地位是重合的。也就是说能够反复的地位能够定义一次,而后通过通过顶点数组的索引值获取这些顶点地位数据。 不应用顶点索引上面通过几何体六个顶点定义了两个三角形,几何体的顶点地位数据、顶点法向量数据都是6个。 var geometry = new THREE.BufferGeometry(); //申明一个空几何体对象//类型数组创立顶点地位position数据var 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坐标]);// 创立属性缓冲区对象var attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组// 设置几何体attributes属性的地位position属性geometry.attributes.position = attribuevar normals = new Float32Array([ 0, 0, 1, //顶点1法向量 0, 0, 1, //顶点2法向量 0, 0, 1, //顶点3法向量 0, 0, 1, //顶点4法向量 0, 0, 1, //顶点5法向量 0, 0, 1, //顶点6法向量]);// 设置几何体attributes属性的地位normal属性geometry.attributes.normal = new THREE.BufferAttribute(normals, 3); //3个为一组,示意一个顶点的xyz坐标顶点索引上面代码通过几何体BufferGeometry的顶点索引BufferGeometry.index定义了一个矩形。通过顶点索引组织网格模型三角形的绘制,因为矩形的两个三角形有两个顶点地位反复,所以顶点地位数据、顶点法向量数据都只须要定义4个就能够。 ...

April 26, 2023 · 1 min · jiezi

关于three.js:Threejs教程顶点位置数据解析渲染

举荐:将NSDT场景编辑器退出你3D工具链其余工具系列:NSDT简石数字孪生 顶点地位数据解析渲染如果你没有WebGL根底,能够先不必记忆每个的threejs 具体内容,有一个大抵印象即可,学习本节课的重点是建设顶点的概念。如果你建设了顶点的概念,那么对于你深刻了解学习hree.js很有帮忙。如果你曾经有WebGL根底或者说图形学根底,阐明你必定有顶点的概念,本节课重点能够放在学习threejs的API应用细节,threejs引擎是如何封装webgl的。 JavaScript类型化数组本节课会用到javascript的类型化数组,你如果不理解,可查看MDN对于javascript类型化数组的介绍,也能够查看文章类型化数组。 自定义几何体你能够间接调用BoxGeometry间接创立一个立方体几何体,调用SphereGeometry创立一个球体几何体。不过为了大家更好的建设顶点概念,通过上面的代码自定义了一个几何体,通过网格模型能够渲染进去两个三角形成果。上面代码通过Threejs引擎的BufferGeometry和BufferAttribute两个API自定义了一个具备六个顶点数据的几何体。var geometry = new THREE.BufferGeometry(); //创立一个Buffer类型几何体对象 //类型数组创立顶点数据var 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坐标]);// 创立属性缓冲区对象var attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组,示意一个顶点的xyz坐标// 设置几何体attributes属性的地位属性geometry.attributes.position = attribue;通过自定义的几何体创立一个网格模型。对于网格模型而言,几何体所有顶点每三个顶点为一组能够确定一个三角形,几何体是六个顶点,也就是说能够绘制两个三角形,当然了你能够本人再减少三个顶点地位坐标数据,测试下渲染成果。 // 三角面(网格)渲染模式var material = new THREE.MeshBasicMaterial({ color: 0x0000ff, //三角面色彩 side: THREE.DoubleSide //两面可见}); //材质对象var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh点模型Points为了更好的了解几何体是由顶点形成的,能够把几何体geometry作为点模型Points而不是网格模型Mesh的参数,你会发现下面的六个点坐标会渲染为六个方形的点区域,能够用上面代码代替下面的网格模型局部代码测试成果。对于网格模型Mesh而言,几何体geometry三个顶点为一组渲染进去一个三角形,对于点模型Points而言,几何体的每个顶点对应地位都会渲染进去一个方形的点区域,该区域能够设置大小。 // 点渲染模式var material = new THREE.PointsMaterial({ color: 0xff0000, size: 10.0 //点对象像素尺寸}); //材质对象var points = new THREE.Points(geometry, material); //点模型对象scene.add(points); //点对象增加到场景中 ...

April 21, 2023 · 1 min · jiezi

关于three.js:Threejs-进阶之旅全景漫游高阶版在线看房-

申明:本文波及图文和模型素材仅用于集体学习、钻研和观赏,请勿二次批改、非法流传、转载、出版、商用、及进行其余获利行为。 摘要专栏上篇文章《Three.js 进阶之旅:全景漫游-初阶挪动相机版》中通过创立多个球体全景场景并挪动相机和控制器的形式实现了多个场景之间的穿梭漫游。这种形式的毛病也是不言而喻的,随着全景场景的减少来创立对应数量的球体,使得空间关系计算难度晋升,并且大幅升高浏览器渲染行性能。在上一篇文章的根底上,本文通过以下几点对全景性能加以优化,最初实现一个能够利用到理论我的项目中的在线看房案例。通过浏览本文和实际案例,你将学到的常识包含:应用 Three.js 用新的技术思路实现多个场景的加载和场景间的丝滑过渡切换、随着空间始终和角度实时变动的房源小地图、在全景场景中增加如高空指引、空间物体展现、房间标注等多种类型的交互热点等。 成果咱们先来看看本文在线看房案例的最终实现成果,页面主体由代表多个房间的全景图 、全景空间中的用于标识物体的交互热点 、显示房间名称的空间热点 ️ 、用于高空后退指引的交互热点 、固定在侧边的房间切换按钮 ◻️ 以及右上侧的房间小地图 形成。左右拖动页面能够进行以后房间的全景预览,同时小地图上的锚点旋转角度和地位也依据以后房间的地位和旋转角度的变动而变动,应用鼠标滚轮或触摸板放大放大页面能够查看房间全景图的整体和部分细节。 点击高空后退指引标记热点或空间中的房间名标签热点、以及固定在左边的房间名按钮时能够丝滑切换到对应的房间,固定在右侧的按钮和空间中的房间名标签之间互相联动,当页面视区无奈看到空间中的房间名标签时,它会主动固定到右侧按钮处。点击房间中的物体标识热点能够与之产生交互。 关上以下链接,在线预览成果,gif 造成丢帧和画质损失,在线大屏拜访成果更佳。 在线预览地址:https://dragonir.github.io/panorama-advanced/本专栏系列代码托管在 Github 仓库【threejs-odessey】,后续所有目录也都将在此仓库中更新。 代码仓库地址:git@github.com:dragonir/threejs-odessey.git原理比照上篇有哪些优化点看完本文在线看房案例,咱们先来总结下本文在上篇文章示例的根底上,做了哪些优化?下图几个标注点对应本文实现的一些新的性能和优化,通过以下几点的实现能够晋升多个全景场景漫游我的项目的加载渲染性能和用户体验。 ①:是指应用新的技术思路加载多个全景图场景,并应用着色器实现多个场景之间的优雅过渡。②:是用于标注室内物体的空间交互热点,点击能够实现交互。③:是固定于页面侧边的切换空间按钮,只有悬浮在空间中的房间标签④看不见的时候,对应的房间切换按钮才会显示。④:是悬浮在空间中的房间标签,当旋转场景看不见它的时候,它会固定到③处,造成空间的标签和固定在侧边的标签之间是互相联动的视觉效果。⑤:是高空场景切换指引热点,点击高空的热点能够切换到下个场景,视觉上造成在空间中后退的成果。⑥:是示意整个房子的小地图,在全景空间中旋转或者全景漫游时,图中的锚点的方向和地位会对应扭转。 房间场景切换原理上篇文章示例咱们通过创立多个球体挪动相机和控制器的形式实现多个全景场景之间的漫游,而这篇文章中,咱们将通过创立多个场景的形式,来实现两个全景场景之间的过渡漫游成果。 实现原理示意图如下所示,页面总共将创立 3 个场景,origin 示意以后场景,destination 示意指标场景,利用以后场景和指标场景合成用于展现过渡成果的 transition 过渡场景,当点击切换房间按钮时,三个场景的加载顺便别离为 origin -> transition -> destiontion,由此在视觉上造成从上个房间切换到下个房间并且随同突变过渡的场景漫游成果。 实现对应下面几个优化点及多个场景的切换原理,咱们当初来一步步实现本文中的案例: 〇 场景初始化<canvas class="webgl"></canvas>咱们先来看看页面应用了哪些资源,OrbitControls 是镜头轨道控制器,能够在全景图场景中应用鼠标进行旋转和放大放大;TWEEN 和 Animations 用于实现一些镜头补间动画成果;rooms, markers, roomLabels 是自定义的数据,别离示意房间信息、房间高空后退指引标记物和房间名称热点标记物;fragment 和 vertex 是用于实现原始场景和指标场景切换的动画过渡成果;TinyMap 是和以后房间地位和镜头旋转方向同步的小地图组件。 import * as THREE from 'three';import { OrbitControls } from '@/utils/OrbitControls.js';import { TWEEN } from 'three/examples/jsm/libs/tween.module.min.js';import { rooms, markers, roomLabels } from '@/views/home/data';import fragment from '@/shaders/cross/fragment.js';import vertex from '@/shaders/cross/vertex.js';import Animations from '@/utils/animations';import TinyMap from '@/components/TinyMap.vue';初始化 Three.js 构建三维场景的渲染器、场景、相机、控制器等根本元素,须要留神的是这次把场景命名为原场景 sceneOrigin,因为后续还要场景多个场景,通过多个场景间的动画过渡成果,实现各个房间全景图之间的穿梭漫游成果。 ...

April 10, 2023 · 5 min · jiezi

关于three.js:Threejs-进阶之旅全景漫游初阶移动相机版

申明:本文波及图文和模型素材仅用于集体学习、钻研和观赏,请勿二次批改、非法流传、转载、出版、商用、及进行其余获利行为。 摘要3D 全景技术能够实现日常生活中的很多性能需要,比方地图的街景全景模式、数字展厅、在线看房、社交媒体的全景图预览、短视频直播平台的全景直播等。Three.js 实现全景性能也是非常不便的,当然了目前曾经有很多相干内容的文章,我之前就写过一篇《Three.js 实现3D全景侦探小游戏》。因而本文内容及此专栏下一篇文章探讨的重点不是如何实现 3D 全景图性能,而是如何一步步优雅实现在多个3D全景中穿梭漫游,达到如在真实世界中后退后退的视觉效果。 全景漫游系列文章将分为高低两篇,本篇内容咱们先介绍如何通过挪动相机的办法来达到场景切换的目标。通过本文的学习,你将学到的知识点包含:在 Three.js 中创立全景图的几种形式、在 3D 全景图中增加交互热点、利用 Tween.js 实现相机切换动画、多个全景图之间的切换等。 成果本文最终将实现如下的成果,左右管制鼠标旋转屏幕能够预览室内三维全景图,同时全景图内有多个交互热点,它们标识着三维场景内的一些物体,比方沙发 、电视机 等,交互热点会随着场景的旋转而旋转,点击热点 能够弹出交互反馈提示框。 点击屏幕上有其余场景名称的按钮比方 客厅、卧室、书房 时,能够从以后场景切换到指标场景全景图,交互热点也会同时切换。 关上以下链接,在线预览成果,大屏拜访成果更佳。 在线预览地址:https://dragonir.github.io/panorama-basic/本专栏系列代码托管在 Github 仓库【threejs-odessey】,后续所有目录也都将在此仓库中更新。 代码仓库地址:git@github.com:dragonir/threejs-odessey.git原理咱们先来简略总结下在 Three.js 中实现三维全景性能的有哪些形式: 球体在球体内增加 HDR 全景照片能够实现三维全景性能,全景照片是一张用球形相机拍摄的图片,如下图所示: const geometry = new THREE.SphereGeometry(500, 60, 40);geometry.scale(- 1, 1, 1);const texture = new THREE.TextureLoader().load( 'textures/hdr.jpg');const material = new THREE.MeshBasicMaterial({ map: texture });const mesh = new THREE.Mesh(geometry, material);scene.add(mesh); 球体全景图 Three.js 官网示例立方体在立方体内增加全景图贴图的形式也能够实现三维全景图性能,此时须要对 HDR 全景照片进行裁切,宰割成 6 张来别离对应立方体的 6 个面。 ...

March 28, 2023 · 5 min · jiezi

关于three.js:-和我一起学Threejs初级篇5-掌握材质

本文为《和我一起学 Three.js 系列》「高级篇」的第六篇文章,文中的示例代码基于往期文章的代码进行扩大,在入手实际前,请确保您曾经在本地创立好了引入 three.js 的古代前端开发环境。工夫过的真快!明天(2023.3.27),我终于依照当初每周更新一篇文章的打算,实现了《和我一起学 Three.js 系列》「高级篇」的 6 篇「无条件公布」的文章。每篇文章我都花了大量的工夫收集材料,从 0 开始编写代码(以保障您操作的后果和我雷同)以及思考如何让文章更易于了解,截止目前,您应该曾经能够在 3D 场景中增加各种平面物品并与之交互,您还应该理解到纹理的不同品种,以及它是让简略的 3D 模型变为事实物品的一把要害钥匙 。 而本章节,咱们不仅会让上一章所学没有徒劳,还会更进一步,向您展现 3D 世界又一个激动人心的概念:「材质(Materail)」,通过学习本章节的内容,您将有能力串联起来之前所有的常识,打造一个充斥趣味的 3D 世界!话不多说,让咱们开始吧 ! 0. 系列文章合集《 和我一起学【Three.js】「高级篇」:0. 总论》《 和我一起学【Three.js】「高级篇」:1. 搭建 3D 场景》《 和我一起学【Three.js】「高级篇」:2. 把握几何体》《 和我一起学【Three.js】「高级篇」:3. 把握摄影机》《 和我一起学【Three.js】「高级篇」:4. 把握纹理》您以后在这里 《 和我一起学【Three.js】「高级篇」:5. 把握材质》《 和我一起学【Three.js】「高级篇」:6. 把握光照》( 本篇文章将于 2023.4.3 更新并反对公众号内付费观看,将在全系列文章总赞数 >= 500 时解锁公布)《 和我一起学【Three.js】「高级篇」:7. 把握暗影》( 本篇文章将于 2023.4.10 更新并反对公众号内付费观看,将在全系列文章总赞数 >= 1000 时解锁公布)《 和我一起学【Three.js】「高级篇」:8. 死记硬背,神功小成》( 本篇文章将于 2023.4.17 更新并反对公众号内付费观看,将在第 7,8 章节解锁后公布)1. 什么是材质(material)?在 Three.js 中,「材质(material)」是用来定义物体外观的属性。它蕴含如何渲染物体的信息,如色彩,光照,反射等等。材质能够被赋予不同的属性,以便实现各种不同的外观成果。 咱们上一章所提到的「纹理(Texture)」是材质的一种属性,它能够被用来在物体外表增加图像,图案,色彩等。纹理能够用来模仿各种不同的外表个性,例如木头、金属、石头等等。 为了区别「材质」和「纹理」的概念,咱们能够设想一件木质家具。该家具的「材质」是木头,但它的外观可能会因为在木外表上利用不同的纹理而产生不同的成果。例如,一个家具制造商可能会在木外表上利用一层润滑的漆,使其看起来更加润滑;另一个家具制造商则可能会在木外表上利用一个毛糙的纹理,使其看起来更加人造。同样地,在 Three.js 中,咱们能够通过利用不同的纹理来扭转物体的外观,以实现咱们想要的成果。 在 Three.js 中,材质被用于为几何体的每个可见像素着色,这一过程的算法实现被称为「着色器(shader)」(一种在 GPU 上运行的程序,它用于计算几何体的每个像素的色彩和外观,能够对材质进行定制化的着色和光照计算,以及其余各种成果)。至此,咱们能够将之前一张形容 Three.js 中创立 3D 对象的类关系图更新为上面这样: ...

March 27, 2023 · 3 min · jiezi

关于three.js:-和我一起学Threejs初级篇4-掌握纹理

本文为《和我一起学 Three.js 系列》高级篇的第五篇文章,文中的示例代码基于上一篇文章中的代码进行相应的扩大,请确保您曾经浏览上一篇文章,并和我一起应用 vite 搭建了古代前端开发环境。感谢您一路追随我来到这里!截止目前为止,咱们应该有能力搭建一个 3D 场景,在其中增加各种官网提供的几何体,并通过应用控制器,调整摄影机地位与几何体交互。这所有看起来都还不错,但未免有些枯燥。所幸本章节以及下一节的内容将让咱们的 3D 世界变得丰富多彩。 本章节咱们谈判及 Web 3D 世界一个十分常见的概念:「纹理」(Texture),它将会和下一章节的「材质」(Materials)概念一起使咱们简略的几何体变成真实世界中咱们相熟的物体!让咱们一起开展这趟旅途吧! 1. 什么是纹理(Textures)兴许应用纹理(Textures)的另一个名称能更加直观的反映其本质:「贴图」。没错,纹理自身就是一些图片,用来笼罩在咱们的几何体或模型的外表,从而使物体具备拟真的成果。 尽管听起来简略,然而别忘了,咱们不只是想让物体「看起来」像是某个事实物体,咱们还心愿它合乎物体「在事实中的样子」,我是指在面对不同的光照环境时,物体须要有不同的反馈,以及物体须要有其对应的「质感」。这就不是一张图片能够解决的问题了,为此,咱们须要有不同类型的图片表白物体的不同属性(例如:哪里通明,哪里应该有金属光泽等等)。还须要理解在 Three.js 的世界中,如何为一个对象加载多张纹理贴图。 让咱们先从意识不同的纹理开始: 2. 纹理的品种 在本章节中,我并不会向您介绍具体「贴图」的办法,因为这波及「材质」和「光照」等稍后咱们要提及的内容。本章节的用意在于让您明确纹理有哪些品种,它们长什么样子,以及在物体上会出现什么样的成果。如果您可能胜利拜访:https://3dtextures.me/ 这个网站,并下载其提供的收费纹理资源,您会发现,其中的一些图片会令人不明所以,它们是不同的纹理类别,有特定的用处: 图片来源于:https://drive.google.com/drive/folders/1tLmUWv9WocFh88XMqsexdTkMDE6R2ABg 本章节我会应用该纹理进行演示,后续将不再额定表明资源出处。 2.1 反照率纹理(Albedo Texture)反照率纹理(Albedo Texture)又称「色彩(Color)纹理」,是一种根本的纹理类型,用于模仿物体外表的色彩和反射个性。它是最罕用的纹理类型之一,也是创立真切 3D 场景必不可少的一部分。 在 3D 渲染中,反照率是指物体外表对于不同色彩光线的反射率,通常用一张 RGB 图片来示意。反照率纹理中的每个像素都对应着物体外表上的一个点,这个点的色彩和反射个性能够由纹理像素的色彩值来确定。例如,纹理中的红色像素示意该点外表对所有色彩的光线反射率都很高,彩色像素示意该点外表对所有色彩的光线反射率都很低。 上面的图片是一张反照率纹理: 将反照率纹理利用于一个球体时,会取得这样的成果: 为了演示不便,我设置了强度为 2 的红色环境光以及一束蓝色直射光,并且将粗糙度与金属度设置为 0.8,我想您走漏这些只是为了未来您可能实现和我一样的成果,但当初您并不需要晓得如何设置。2.2 高度纹理(Height Texture)高度纹理(Height Texture)又称为「深度纹理」。在该图像中,每个像素的灰度值被用于示意相应地位的高度或深度。它通常被用于创立几何体的外表细节,例如山峰、岩石、河流等。 通过应用高度纹理,能够在几何体外表增加细节,并模仿光的反射和折射等视觉效果。 上面的图片是一张高度纹理: 在增加高度纹理后,咱们的物体将会有显著的凹凸成果: 2.3 透明度纹理(Alpha Texture)透明度纹理(Alpha Texture)也称为「Alpha 通道纹理」,它是一种非凡的纹理,其中蕴含了用于管制材质透明度的 Alpha 通道数据。 Alpha 通道是图像中的第四个通道,它示意每个像素的透明度值。在 Alpha 纹理中,Alpha 通道的值被用于管制每个像素的透明度。应用 Alpha 纹理,您能够轻松地创立通明的材质成果。例如,您能够应用 Alpha 纹理来管制几何体外表的通明区域,以实现相似玻璃、水、烟雾等材质的成果。 上面的图片是一张透明度纹理: ...

March 20, 2023 · 2 min · jiezi

关于three.js:Threejs-进阶之旅物理效果3D乒乓球小游戏-

申明:本文波及图文和模型素材仅用于集体学习、钻研和观赏,请勿二次批改、非法流传、转载、出版、商用、及进行其余获利行为。 摘要本文在专栏上一篇内容《Three.js 进阶之旅:物理成果-碰撞和声音》的根底上,将应用新的技术栈 React Three Fiber 和 Cannon.js 来实现一个具备物理个性的小游戏,通过本文的浏览,你将学习到的知识点包含:理解什么是 React Three Fiber 及它的相干生态、应用 React Three Fiber 搭建根底三维场景、如何应用新技术栈给场景中对象的增加物理个性等,最初利用上述知识点,将开发一个简略的乒乓球小游戏。 成果在正式学习之前,咱们先来看看本文示例最终实现成果:页面主体内容是一个手握乒乓球拍的模型和一个乒乓球 ,对球拍像现实生活中一样进行颠球施力操作,乒乓球能够在球拍上弹起,乒乓球弹起的高度随着施加在球拍上的力的大小的变动而变动,球拍地方显示的是间断颠球次数 5️,当乒乓球从球拍掉落时一局游戏完结,球拍上的数字归零 0️ 。快来试试你一次能够颠多少个球吧 。 关上以下链接,在线预览成果,大屏拜访成果更佳。 在线预览地址:https://dragonir.github.io/physics-pingpong/本专栏系列代码托管在 Github 仓库【threejs-odessey】,后续所有目录也都将在此仓库中更新。 代码仓库地址:git@github.com:dragonir/threejs-odessey.git原理React-Three-FiberReact Three Fiber 是一个基于 Three.js 的 React 渲染器,简称 R3F。它像是一个配置器,把 Three.js 的对象映射为 R3F 中的组件。以下是一些相干链接: 仓库: https://github.com/pmndrs/react-three-fiber官网: https://docs.pmnd.rs/react-three-fiber/getting-started/introduction示例: https://docs.pmnd.rs/react-three-fiber/getting-started/examples 特点应用可重用的组件以申明形式构建动静场景图,使 Three.js 的解决变得更加轻松,并使代码库更加整洁。这些组件对状态变动做出反馈,具备开箱即用的交互性。Three.js 中所有内容都能在这里运行。它不针对特定的 Three.js 版本,也不须要更新以批改,增加或删除上游性能。渲染性能与 Three.js 和 GPU 相仿。组件参加 React 之外的 render loop 时,没有任何额定开销。写 React Three Fiber 比拟繁琐,咱们能够写成 R3F 或简称为 Fiber。让咱们从当初开始应用 R3F 吧。 ...

March 20, 2023 · 6 min · jiezi

关于three.js:-和我一起学Threejs初级篇3-掌握摄影机

本文为《和我一起学 Three.js 系列》高级篇的第四篇文章,文中的示例代码基于上一篇文章中的代码进行相应的扩大,请确保您曾经浏览上一篇文章,并和我一起应用 vite 搭建了古代前端开发环境。1. 什么是摄影机?在 Three.js 中,摄影机(Camera)用来定义场景中的「视角」以及「可见范畴」(_为了晋升渲染性能,超出可见范畴的物体将不会被渲染,这被称为「视锥体剔除(frustum culling)」技术_)。除此之外,摄影机实际上还管制着场景中物体的「地位」和「大小」,通过挪动摄影机,GPU 会渲染出合乎直觉的物体透视成果,从而让咱们有优良的 3D 场景体验。 因而,把握 Three.js 提供的各种摄影机以及管制摄影机的各种办法,可能使咱们的 3D 场景和用户进行互动并极大的丰盛场景的出现形式。 把握本章节的概念和内容十分重要,请您务必放弃急躁,入手实际。2. 摄影机的品种在 Three.js 中,所有摄影机都封装为特定的子类,它们对立继承自一个抽象类:[Camera](https://threejs.org/docs/?q=camera#api/en/cameras/Camera)。Three.js 提供的摄影机有: 数组摄影机([ArrayCamera](https://threejs.org/docs/?q=camera#api/en/cameras/ArrayCamera)):次要用于须要渲染多个视角的场景,例如 VR,AR,多屏幕游戏等。它通过将多个摄影机实例放入数组并传入 ArrayCamera 中,GPU 会渲染各个摄影机视角下的场景,并将它们合并在画布中,例如这个示例展现了不同角度的摄影机察看同一个物体的成果: 平面摄影机([StereoCamera](https://threejs.org/docs/?q=StereoCamera#api/en/cameras/StereoCamera)):能够同时生成两个相机对象,一个渲染左眼图像,一个用于渲染右眼图像。这就很适宜在 VR,AR,3D 视频等利用中产生更加实在的 3D 成果(_由 StereoCamera 生成的 VR 成果能够通过 Oculus Rift,HTC Vive 等 VR 头显设施观看_)。 立方体摄影机([CubeCamera](https://threejs.org/docs/?q=CubeCamera#api/en/cameras/CubeCamera)):用于生成「环境贴图(Environment Map)」,它会生成一个立方体场景,捕获场景内的环境信息,别离在 6 个面各渲染一次,而后将渲染后果用于物体的反射贴图,折射贴图,从而加强场景的真实感和细节成果。「环境贴图」是一种罕用于加强场景真实感的技术,它通过将场景中周围环境信息捕获下来,并将其利用于物体外表的材质中,从而使物体看起来更加实在和具备反射性。具体来说,环境贴图是一张蕴含了场景中周围环境的图像,通常为立方体贴图。它能够捕捉到场景中的反射、折射、漫反射、全局光照等信息,使得物体的外表看起来更加实在,同时也能够加强场景的光照成果。 正交摄影机([OrthographicCamera](https://threejs.org/docs/?q=Ortho#api/en/cameras/OrthographicCamera)):将会渲染一个没有透视成果的场景,无论物体和摄影机的间隔如何,物体的大小都不会放生变动。能够通过该摄影机创立 RTS 类游戏,例如帝国时代。 透视摄影机([PerspectiveCamera](https://threejs.org/docs/?q=Camera#api/en/cameras/PerspectiveCamera)):这是 Three.js 中最罕用的摄影机,它能够模仿人眼察看物体时近大远小的透视成果。 请留神透视摄影机与正交摄影机在渲染立方体时的不同,在正交摄影机中,所有立方体的尺寸是类似的。3. 创立摄影机 上面将进入代码实际环节,请务必确保您已浏览之前的文章,和我领有雷同的开发环境! 本节咱们不会涵盖「立方体摄影机」和「平面摄影机」,因为前者波及咱们下一章的内容,我会放在一起介绍。而后者因为须要额定设施,对于大多数人难以察看后果,因而略去不提。 3.1 透视摄像机让咱们首先介绍最罕用的透视摄像机,创立一个透视摄像机非常简单,只须要实例化 PerspectiveCamera 类,并传入对应的参数: const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 1, 100)PerspectiveCamera 函数接管四个参数,从前到后顺次为: ...

March 13, 2023 · 2 min · jiezi

关于three.js:-和我一起学Threejs初级篇0-总论

「和我一起学 XXX」是我 2023 年的一个新企划,目标是向读者(也包含将来的本人)介绍我正在学习的某项新技术。文章会通过长期重复迭代的形式放弃其内容的新鲜度。文章有较大内容更新时,会在文章结尾进行更新工夫阐明(因为工夫精力有限,更新的内容只能保障少数几个平台的同步,请见谅)。1. 什么是 Three.jsThree.js 是一个基于 WebGL 的 3D JavaScript 开源库(遵循 MIT 协定),它使 JavaScript 开发者可能更不便地在 Web 利用中创立 3D 场景。 请留神该定义的如下局部: 基于 WebGL:WebGL 是一种 3D 绘图协定,对于开发者而言,它是一组更底层的绘图 API,它负责绘制点,线与三角形,应用 WebGL 绘制简单的 3D 场景,须要十分多的代码;3D JavaScript 开源库:Three.js 基于十分宽松的 MIT 协定,这意味着您能够自在应用,批改 Three.js 代码创立商业利用;更不便地:就像 jQuery 基于 JavaScript 提供了更敌对地 API 使开发者可能轻松地操作 DOM 一样,Three.js 也封装出更敌对地 API 供开发者绘制 3D 场景,相较于应用 WebGL,应用 Three.js 绘制 3D 场景须要的代码量要少的多得多。3D 场景:它蕴含: 3D 游戏;建筑设计和数据可视化看板;AR,VR;虚构展厅,虚构商品展现;交互式展览,培训等;您能够在 Three.js 官网发现丰盛的案例,它们从不同方面展现了 Three.js 的魅力和弱小! 2. (我)为什么要学习 Three.js?在理解 Three.js 是什么后,若抉择持续学习,想必您有本人的理由。对于我而言,学习 Three.js 的次要动机是「好玩」(Just for fun!)。 ...

February 20, 2023 · 1 min · jiezi

关于three.js:Threejs-进阶之旅物理效果碰撞和声音-

申明:本文波及图文和模型素材仅用于集体学习、钻研和观赏,请勿二次批改、非法流传、转载、出版、商用、及进行其余获利行为。 摘要本文内容次要汇总如何在 Three.js 创立的 3D 世界中增加物理成果,使其更加实在。所谓物理成果指的是对象会有重力,它们能够互相碰撞,施加力之后能够挪动,而且通过铰链和滑块还能够在挪动过程中在对象上施加束缚。 通过本文的浏览,你将学习到如何应用 Cannon.js 在 Three.js 中创立一个 3D 物理世界,并在物理世界更新对象、分割材质、施加外力、解决多个物体中增加物体之间的碰撞成果,通过检测碰撞强烈水平来增加撞击声音等。 成果本文最终将实现如下所示的成果,点击 DAT.GUI 中创立立方体 和球体 的按钮,对应的物体将在领有重力的三维世界中坠落,物体与高空及物体与物体之间产生碰撞时能够产生与碰撞强度匹配的撞击音频 ,点击重置按钮,创立的物体将被革除。 关上以下链接,在线预览成果,大屏拜访成果更佳。 在线预览地址:https://dragonir.github.io/physics-cannon本专栏系列代码托管在 Github 仓库【threejs-odessey】,后续所有目录也都将在此仓库中更新。 代码仓库地址:git@github.com:dragonir/threejs-odessey.git原理在专栏之前的原理和示例学习中,咱们曾经能够应用光照、暗影、Raycaster 等个性生成一些简略的物理成果,然而如果须要实现像物体张力、摩擦力、拉伸、反弹等物理成果时,咱们能够应用一些业余的物理个性开源库来实现。 为了实现物理成果,咱们将在 Three.js 中创立一个物理世界,它纯正是实践性质的,咱们无奈间接看到它,然而在其中,三维物体将产生掉落、碰撞、摩擦、滑动等物理个性。具体原理是当咱们在 Three.js 中创立一个网格模型时,同时会将其增加到物理世界中,在每一帧渲染任何内容之前咱们会通知物理世界如何自行更新,而后咱们将获取物理世界中更新的位移和旋转坐标数据,将其利用到 Three.js 三维网格中。 库曾经有很多性能齐备的物理个性库,咱们就没必要反复造轮子了。物理个性库能够分为 2D 库和 3D 库,尽管咱们是应用 Three.js 开发三维性能,然而有些 2D库 在三维世界中同样是实用的而且它们的性能会更好,如果咱们须要开发的物理性能是碰撞类的,则能够应用 2D 库,比方Ouigo Let's play就是一个应用 2D 库开发的优良示例。上面是一些罕用的物理个性库。 对于 3D 物理库,次要有以下三个: Ammo.js 官网:http://schteppe.github.io/ammo.js-demos/仓库:https://github.com/kripken/ammo.js/文档:https://github.com/kripken/ammo.js/#readmeBullet 一个应用 C++ 编写的物理引擎的 JavaScript 间接移植包比拟重量级,以后依然由社区更新保护Cannon.js 官网:https://schteppe.github.io/cannon.js/仓库:https://github.com/schteppe/cannon.js文档:http://schteppe.github.io/cannon.js/docs/比 Ammo.js 更加轻量级,应用起来更难受次要由一个开发者保护,曾经多年未更新,有一个保护的 fork 是 cannon-esOimo.js ...

February 15, 2023 · 6 min · jiezi

关于three.js:threejs起步学习之创建旋转立方体并随时停止和控制旋转

间接看成品 能够看到点击开始旋转的时候立方体开始旋转,点击进行旋转的时候立方体进行旋转。 先放官网文档threejs官网 点击en切换为中文,而后点击创立第一个场景,页面就全副变成中文啦。 先应用原生js编写代码,首先就是下载threejs而后引入 <script src="./three.js"></script>而后就能够应用THREE这个对象了 要创立一个立方体,首先咱们须要创立一个场景,一个摄像机,一个渲染器,这样咱们能力透过摄像机看到渲染出的场景 var width = 500var height = 500const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75,width/height,0.1,1000)const renderer = new THREE.WebGLRenderer();renderer.setSize(width,height);document.body.appendChild(renderer.domElement);首先我定义了整体的宽高,如果间接用window.innerWidth和innerHeight的话,就占满了整个页面,所以我抉择固定一个小一点的宽高,不便察看和后续增加元素。 threejs有几种不同的相机,这里用的是PerspectiveCamera(透视相机)。第一个参数是视线角度(FOV),就是你能在显示器上看到的场景范畴,单位是角度(与弧度不同)常常玩游戏的人必定晓得这个参数的作用,比方射击游戏里,FOV越大,能看到的场景越多这里举荐一个FOV无关的材料https://zhuanlan.zhihu.com/p/... 第二个参数是长宽比,即画面的比例,比方4:3、16:9这种。比例设置不对,那一个物体看起来就会被压扁或者拉伸。 接下来两个参数是近截面和远截面。当一个物体的某个局部离摄像机的近截面更近或者远截面更远的时候,这部分就不会被渲染到场景里。 上面就是渲染器了,这里应用的是WebGLRenderer渲染器,其余的我临时没有用过,也不懂。 创立完渲染器实例当前,咱们还须要设置渲染尺寸,如果整个页面都须要渲染,那就把setSize里的参数改为浏览器的宽高,如果性能不行,能够尝试调小这个值,比方都除以2。 最初一步就是把渲染器renderer的dom元素renderer.domElement增加到咱们的HTML中,也就是在页面里减少了一个<canvas> 当然,当初页面上还是一片白,因为咱们没有真正的渲染canvas外面的货色 加上这一步 function animate() { renderer.render( scene, camera );}animate();就能够看到页面上渲染进去了一个彩色的区域 第二步,摆个盒子下来 先上代码 const geometry = new THREE.BoxGeometry(1,1,1);const material = new THREE.MeshBasicMaterial({color:0x00ff00});const cube = new THREE.Mesh(geometry,material);scene.add(cube);camera.position.z = 5;要创立一个立方体,咱们须要一个BoxGeometry对象,这个对象蕴含了一个立方体中所有的顶点和面。 上面,咱们须要给这个立方体一个材质,让它有色彩。Threejs自带了几种材质,这里应用的是MeshBasicMaterial。所有的材质都存有利用于他们属性的对象。这里简单化,只设置一个color属性,值为0x00ff00 留神,和CSS中的十六进制不同。 第三步,咱们须要一个Mesh(网格)。网格蕴含一个几何体以及作用在该几何体上的材质。间接讲该网格对象放入到场景中。 个别状况下,当咱们调用scene.add()办法的时候,物体将被间接增加到(0,0,0)坐标,摄像机默认地位也在这里,所以为了防止物体和摄像机重叠,咱们须要将摄像机往外挪动一点,即设置camera的position中的z轴值为5就行 保留当前,页面上就变成了这样 第三步,让立方体动起来 首先弄两个按钮下来 <div> <div class="btn" onclick="btnfunc()">点我开始旋转</div> <div class="btn" onclick="cancel()">点我进行旋转</div></div> ...

February 9, 2023 · 1 min · jiezi

关于three.js:Threejs-进阶之旅新春特典Rabbit-craft-go-

申明:本文波及图文和模型素材仅用于集体学习、钻研和观赏,请勿二次批改、非法流传、转载、出版、商用、及进行其余获利行为。 摘要兔年到了,祝大家身材健,康万事顺利。本文内容作为兔年新春留念页面,将应用 Three.js 及 其余前端开发常识,创立一个以兔子为主题的 3D 简略的趣味页面 Rabbit craft go。本文内容包含应用纯代码创立三维浮岛、小河、树木、兔子、胡萝卜以及兔子的静止交互、浮岛的动画成果等。本文蕴含的知识点绝对比较简单,次要包含 应用 Three.js 网格立方体搭建三维卡通场景、键盘事件的监听与三维场景动画的联合等,如果仔细阅读并实际过本专栏《Three.js 进阶之旅》的话,非常容易把握。 兔子造型来源于 Three.js开源论坛,页面整体造型灵感来源于《我的世界》,页面名称灵感来源于游戏《Lara Craft Go》。成果咱们先来看看实现成果,页面加载实现后是一个游戏操作提醒界面,能够通过键盘 ⌨ 空格键 及 W、 A、 S、 D 或方向键操作小兔子静止。点击开始按钮后,游戏提醒界面隐没,能够看到倒三角造型的天空浮岛及浮岛上方的树木 、河流 ⛵、桥 、胡萝卜 、兔子 等元素,接着摄像机镜头 主动拉近并聚焦到兔子上。 依照操作提醒界面的按键,能够操作兔子进行后退、转向、跳跃等静止,当兔子的静止地位触碰到胡萝卜时,胡萝卜会隐没同时兔子会进行跳跃静止。当兔子静止到小河或者超出浮岛范畴时,兔子则会坠落到下方。 关上以下链接,在线预览成果,大屏拜访成果更佳。 在线预览地址:https://dragonir.github.io/rabbit-craft-go本专栏系列代码托管在 Github 仓库【threejs-odessey】,后续所有目录也都将在此仓库中更新。 代码仓库地址:git@github.com:dragonir/threejs-odessey.git实现文章篇幅无限,因而删减了三维模型的地位信息等细节调整代码,只提供构建三维模型的整体思路逻辑,想理解该局部内容的具体介绍能够浏览本专栏前几篇文章及浏览本文配套源码。当初,咱们来看看整个页面的实现具体步骤: 页面构造Rabbit Craft Go 页面的整体构造如下,其中 canvas.webgl 是用于渲染场景的容器、残余标签都是一些装璜元素或提醒语。 <canvas class="webgl"></canvas><div class="mask" id="mask"> <div class="box"> <div class="keyboard"> <div class="row"><span class="key">W/↑</span></div> <div class="row"><span class="key">A/←</span><span class="key">S/↓</span><span class="key">D/→</span></div> <div class="row"><span class="key space">space</span></div> </div> <p class="tips"><b>W</b>: 行走&emsp;<b>S</b>: 进行&emsp;<b>A</b>: 向左转&emsp;<b>D</b>: 向右转&emsp;<b>空格键</b>: 跳跃</p> <p class="start"><button class="button" id="start_button">开始</button></p> </div></div><a class='github' href='https://github.com/dragonir/threejs-odessey' target='_blank' rel='noreferrer'> <span class='author'>three.js odessey</span></a><h1 class="title">RABBIT CRAFT GO!</h1><div class="banner"><i></i></div>场景初始化场景初始化过程中,咱们引入必须的开发资源,并初始化渲染场景、相机、控制器、光照、页面缩放适配等。其中内部资源的引入,其中 OrbitControls 用于页面镜头缩放及挪动管制;TWEEN 和 Animations 用于生成镜头补间动画,也就是刚开始时浮岛由远及近的镜头切换动画中成果;Island、Carrot、Rabbit、Waterfall 等是用来构建三维世界的类。为了使场景更加卡通化,应用了 THREE.sRGBEncoding 渲染成果。场景中增加了两种光源,其中 THREE.DirectionalLight 用来生成暗影成果。 ...

January 22, 2023 · 7 min · jiezi

关于three.js:Threejs开发3D展馆-大帅老猿threejs特训

本文将介绍如果应用Threejs开发制作一个3D展馆,成果如图: 首先,咱们须要一个展馆模型。我是通过Blender做进去的,在Blender中将模型导出为glb格局。 接下来,咱们通过代码将模型加载到网页中。 在3D图形开发中,最根本的概念就是场景、相机和光源。这三个是形成3D世界的基本要素。所以,咱们须要先创立一个场景、设置相机和光源。 const scene = new THREE.Scene(); //设置相机const camera = new THREE.PerspectiveCamera(45, container.offsetWidth / container.offsetHeight, 10, 10000);camera.position.set(5, 10, -40);//设置相机地位camera.lookAt(0,0,0);//让相机看相原点//光源,平行光var light2 = new THREE.DirectionalLight(0xffffff, 1);scene.add(light2);而后,咱们应用Threejs提供的GLTFLoader将模型加载进来。 const loader = new GLTFLoader();loader.load('../resources/models/zhanguan2.glb',function (gltf) {scene.add(gltf.scene);//将模型增加到场景中})这时候咱们会发现,整个场馆有点暗,而且只有场馆外面是亮的,围墙是黑乎乎的。因为咱们用的是平行光,有些中央会照不到,所以就是黑的了。接下来,咱们用环境贴图的形式,照亮整个场馆。咱们应用这张图片: new RGBELoader().load('../resources/sky.hdr', function (texture) { texture.mapping = THREE.EquirectangularReflectionMapping scene.environment = texture renderer.outputEncoding = THREE.sRGBEncoding renderer.render(scene, camera) }) 接下来,咱们将人物角色加载进来,同样应用的是GLTFLoader. new GLTFLoader().load('../resources/models/player.glb', (gltf) => { playerMesh = gltf.scene scene.add(playerMesh)}角色加载进来后,如何让她挪动呢?应用playerMesh.translateZ()办法,让其在Z轴方向挪动,对应到场景中,就是在高空上挪动了。那如果碰到墙壁,是不是就不应该让角色继续前进了呢?所以要实现碰撞检测性能,这里应用Threejs中的Raycaster射线检测来实现。 window.addEventListener('keydown', (e) => { if (e.key === 'w') { const curPos = playerMesh.position.clone(); //获取角色以后地位 playerMesh.translateZ(1);//让角色往前走一步 const frontPos = playerMesh.position.clone();//在获取当初的地位 playerMesh.translateZ(-1 );//退回原来的地位 const frontVector3 = frontPos.sub(curPos).normalize()//让后面地位的向量减去前面地位的向量 const raycasterFront = new THREE.Raycaster(playerMesh.position.clone().add(playerHalfHeight), frontVector3); const collisionResultsFrontObjs = raycasterFront.intersectObjects(scene.children); if (collisionResultsFrontObjs && collisionResultsFrontObjs[0] && collisionResultsFrontObjs[0].distance > 1) { // 间隔大于1才能够继续前进 playerMesh.translateZ(0.1); } }if (e.key === 's') {//后退playerMesh.translateZ(-0.1);}})到这里,展馆的基本功能就实现完了。 ...

January 15, 2023 · 1 min · jiezi

关于three.js:threejs初识-大帅老猿threejs特训

threejs初识 | 大帅老猿threejs特训筹备工作要装置three 的 npm 模块,请在你的我的项目文件夹里关上终端窗口,并运行: npm install three引入头文件 import * as THREE from 'three';//相机轨道控制器import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";//gltf资源加载器import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader";//纹理贴图加载器import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';利用创立场景const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.01, 10);camera.position.set(0.3, 0.3, 0.5);//渲染器const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);const controls = new OrbitControls(camera, renderer.domElement);//增加灯光const directionLight = new THREE.DirectionalLight(0xffffff, 0.4);scene.add(directionLight);加载资源let donuts;new GLTFLoader().load('../resources/models/donuts.glb', (gltf) => { scene.add(gltf.scene); donuts = gltf.scene; mixer = new THREE.AnimationMixer(gltf.scene); const clips = gltf.animations; // 播放所有动画 clips.forEach(function (clip) { const action = mixer.clipAction(clip); action.loop = THREE.LoopOnce; // 停在最初一帧 action.clampWhenFinished = true; action.play(); });})加载场景贴图new RGBELoader() .load('../resources/sky.hdr', function (texture) { scene.background = texture; texture.mapping = THREE.EquirectangularReflectionMapping; scene.environment = texture; renderer.outputEncoding = THREE.sRGBEncoding; renderer.render(scene, camera);});动画办法实现function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); controls.update(); if (donuts){ donuts.rotation.y += 0.01; } if (mixer) { mixer.update(0.02); }}animate();成果截图 ...

January 15, 2023 · 1 min · jiezi

关于three.js:ThreeJS一盘魔性的甜甜圈-大帅老猿threejs特训

收场魔镜魔镜谁的代码写的最好,最烂的是你,最好的不晓得。。。。。 恍惚间进入了梦幻,收场就是Blender咣当掉下一些超两米的甜甜圈,大喊连忙实现工作不然下一个掉头上 素材解决我丢这是哪个过程出了bug关上Blender一顿拖拽,分和,在磕磕盼盼中管制的了外星来的甜甜圈 看着这甜甜圈仿佛能够吃,离气氛还毛病场景,没有香槟还不能来点烛光 跳动的甜圈刚好在搭建的three场景中减少一束光,照亮不起眼的床 const directionLight = new THREE.DirectionalLight(0xffffff, 0.4);scene.add(directionLight);甜甜圈来吧,到照相机前站着别动,我给你个片段特写GLTFLoader押着怪异的甜甜圈来了 const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.01, 10);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);camera.position.set(0.3, 0.3, 0.5);const controls = new OrbitControls(camera, renderer.domElement);new GLTFLoader().load('../resources/models/donuts.glb', (gltf) => { console.log(gltf); scene.add(gltf.scene); donuts = gltf.scene; // gltf.scene.traverse((child)=>{ // console.log(child.name); // }) mixer = new THREE.AnimationMixer(gltf.scene); const clips = gltf.animations; // 播放所有动画 clips.forEach(function (clip) { const action = mixer.clipAction(clip); action.loop = THREE.LoopOnce; // 停在最初一帧 action.clampWhenFinished = true; action.play(); });})new RGBELoader() .load('../resources/sky.hdr', function (texture) { scene.background = texture; texture.mapping = THREE.EquirectangularReflectionMapping; scene.environment = texture; renderer.outputEncoding = THREE.sRGBEncoding; renderer.render(scene, camera);});![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d7938bd3127f467f9c3e50fe70cc0f01~tplv-k3u1fbpfcp-watermark.image?)甜甜圈还在不安分的掉落着,那就洗脑循环吧,不爽也打不着我 ...

January 14, 2023 · 1 min · jiezi

关于three.js:八叉树空间-初学

学了 three 元宇宙开发后发现人物只能在立体上行走,于是我人物上楼梯 跳跃就特地的好奇……对于常识的好奇不下于我爱喝啤酒,于是我全世界的去找材料和在技术群里问同学,终于让我晓得three 有一个货色叫 八叉树 接下来记录一下我用 八叉树 的一些小心得…… 必须的引入的库import { Octree } from 'three/examples/jsm/math/Octree.js';import { Capsule } from 'three/examples/jsm/math/Capsule.js';这两个库必须引入,在 八叉树空间 的所有撞检和重力都靠这两个库实现 申明 Capsule 和 Octreeconst worldOctree = new Octree();const playerCollider = new Capsule( new Vector3(0,0.35,0), new Vector3(0,1,0), 0.35 );场景模型引入增加入八叉树空间new GLTFLoader().setPath("./model/test/").load("collision-world.glb", (gltf) => { ... worldOctree.fromGraphNode( gltf.scene ); // 把场景模型退出到八叉树空间 ...});整体的来说就是 整个八叉树空间 就是 Capsule 和 Octree 运算 全副代码<!-- --><template> <div id="three" ref="three"> <div ref="le" @mousedown="ddmousedown"></div> </div></template><script setup lang="ts">import { ref, reactive, onMounted, onUnmounted } from "vue";import * as dat from "dat.gui";import { ACESFilmicToneMapping, AmbientLight, AnimationAction, AnimationMixer, AnimationUtils, AxesHelper, BackSide, CameraHelper, Clock, Color, DirectionalLight, DirectionalLightHelper, Fog, Group, HemisphereLight, IcosahedronGeometry, Mesh, MeshBasicMaterial, MeshLambertMaterial, PerspectiveCamera, PlaneGeometry, PointLight, Raycaster, Scene, Sphere, SpotLight, sRGBEncoding, TextureLoader, Vector3, VideoTexture, VSMShadowMap, WebGLRenderer,} from "three";import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";import Stats from 'three/examples/jsm/libs/stats.module.js';import { Octree } from 'three/examples/jsm/math/Octree.js';import { OctreeHelper } from 'three/examples/jsm/helpers/OctreeHelper.js';import { Capsule } from 'three/examples/jsm/math/Capsule.js';/** * 配置 */// MARK: 配置=======const le = ref();const three = ref();const donuts = ref();const scene = new Scene(); // 场景对象Sceneconst axes = new AxesHelper(50); // 轴长度const th = reactive({ ctrl: new dat.GUI({ width: 200 }), renderer: new WebGLRenderer({ antialias: true, }), // 渲染器对象 ambienLight: new AmbientLight(0xffffff, 0.1), // 自然光 planeGeometry: new PlaneGeometry(100, 100), // 高空 spotLight: new SpotLight(0xffffff), // 聚光灯 pointlight: new PointLight(0xffffff, 6, 60), // 点光源 directionalLight: new DirectionalLight(0xffffff, 0.2), // 平行光源 hemisphereLight: new HemisphereLight(0xffffff, 0x00ff00, 1), // 半球光光源});scene.background = new Color(0x88ccee);scene.fog = new Fog( 0x88ccee, 0, 50 );// scene.add(axes); // 增加轴const clock =ref<any>(new Clock());const stats = ref<any>(Stats());let helper:any = null;const GRAVITY = ref<number>(30);const NUM_SPHERES = ref<number>(100);const SPHERE_RADIUS = ref<number>(0.2);const STEPS_PER_FRAME = ref<number>(5);const worldOctree = new Octree();const playerCollider = new Capsule( new Vector3(0,0.35,0), new Vector3(0,1,0), 0.35 );const playerVelocity = new Vector3();const playerDirection = new Vector3();let playerOnFloor = false;let mouseTime = 0;const keyStates:any = {};/** * 影相机 */// MARK: 影相机=======let camera = new PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000); //透视相机camera.rotation.order = 'YXZ';/** * 灯光 */// MARK: 灯光=======scene.add(th.ambienLight); // 自然光th.directionalLight.position.set( - 5, 25, - 1 );th.directionalLight.castShadow = true;th.directionalLight.shadow.camera.near = 0.01;th.directionalLight.shadow.camera.far = 500;th.directionalLight.shadow.camera.right = 30;th.directionalLight.shadow.camera.left = - 30;th.directionalLight.shadow.camera.top = 30;th.directionalLight.shadow.camera.bottom = - 30;th.directionalLight.shadow.mapSize.width = 1024;th.directionalLight.shadow.mapSize.height = 1024;th.directionalLight.shadow.radius = 4;th.directionalLight.shadow.bias = - 0.00006;scene.add( th.directionalLight );// const helper = new DirectionalLightHelper(th.directionalLight, 5);// scene.add(helper);const dirCameraHelper = new CameraHelper(th.directionalLight.shadow.camera);// scene.add(dirCameraHelper);const hemisphereLight = new HemisphereLight( 0x4488bb, 0x002244, 0.5 );hemisphereLight.position.set( 2, 1, 1 );scene.add( hemisphereLight );/** * 渲染 场景 */// MARK: 渲染 场景=======th.renderer.shadowMap.enabled = true;th.renderer.setPixelRatio( window.devicePixelRatio );th.renderer.setSize( window.innerWidth, window.innerHeight );th.renderer.shadowMap.enabled = true;th.renderer.shadowMap.type = VSMShadowMap;th.renderer.outputEncoding = sRGBEncoding;th.renderer.toneMapping = ACESFilmicToneMapping;/** * Stats 性能检测 */// MARK: 性能检测=======stats.value.domElement.style.position = 'absolute';stats.value.domElement.style.top = '0px';/** * 高空 */// MARK: 高空=======/** * OrbitControls 轨道控制器 */// MARK: OrbitControls 轨道控制器=======/** * 导入模型 */// MARK: 导入模型=======let playerMesh:any= null;let playerMixer= ref<AnimationMixer>();let actionWalk= ref<AnimationAction>();let actionIdle= ref<AnimationAction>();const lookTarget = new Vector3(0, 2, 0);new GLTFLoader().setPath("./model/test/").load("player2.glb", gltf => { // 角色 playerMesh = gltf.scene; gltf.scene.position.set(0,0,0); gltf.scene.rotateY(Math.PI); gltf.scene.traverse((child)=>{ child.receiveShadow = true; child.castShadow = true; }) camera.position.set(0, 2.5, -5); // camera.lookAt(playMesh.value.position); camera.lookAt(lookTarget); const pointLight: PointLight = new PointLight(0xffffff, 1); pointLight.position.set(0, 2, -1); playerMixer.value = new AnimationMixer(gltf.scene); const clipWalk = AnimationUtils.subclip(gltf.animations[0], 'walk', 0, 30); actionWalk.value = playerMixer.value.clipAction(clipWalk); const clipIdle = AnimationUtils.subclip(gltf.animations[0], 'idle', 31, 281); actionIdle.value = playerMixer.value.clipAction(clipIdle); actionIdle.value.play(); gltf.scene.add(pointLight); gltf.scene.add(camera); scene.add(gltf.scene);});new GLTFLoader().setPath("./model/test/").load("collision-world.glb", (gltf) => { // console.log(gltf); scene.add(gltf.scene); donuts.value = gltf.scene; worldOctree.fromGraphNode( gltf.scene ); gltf.scene.traverse( (child:any) => { if ( child.isMesh ) { child.castShadow = true; child.receiveShadow = true; if ( child.material.map ) { child.material.map.anisotropy = 4; } } } ); helper = new OctreeHelper( worldOctree ); helper.visible = false; scene.add( helper ); animate();});/** * 建盘管制人物 */// MARK: 建盘管制人物=======let isWalk = ref<boolean>(false);const playerHalfHeight = new Vector3(0,0.3, 0);window.addEventListener("keydown", event => { keyStates[event.code] = true; if (event.key == "w") { if (!isWalk.value) { if( actionIdle.value && actionWalk.value){ crossPlay(actionIdle.value,actionWalk.value); } isWalk.value = true; } }});window.addEventListener("keyup", event => { keyStates[ event.code ] = false; if (event.key == "w") { if( actionIdle.value && actionWalk.value){ crossPlay(actionWalk.value,actionIdle.value); } isWalk.value = false; }});let ddmousedown =()=>{ document.body.requestPointerLock(); mouseTime = performance.now();}document.addEventListener( 'mouseup', () => { // if ( document.pointerLockElement !== null ) throwBall();});let preClientX:number;window.addEventListener("mousemove", event => { if(document.pointerLockElement === document.body){ playerMesh.rotation.y -= -(event.movementX / 300); // playerMesh.rotation.x -= -(event.movementY / 2000); }});let playerCollisions = ()=> { const result = worldOctree.capsuleIntersect( playerCollider ); playerOnFloor = false; if ( result ) { playerOnFloor = result.normal.y > 0; if ( ! playerOnFloor ) { playerVelocity.addScaledVector( result.normal, - result.normal.dot( playerVelocity ) ); } playerCollider.translate( result.normal.multiplyScalar( result.depth ) ); }}let updatePlayer = ( deltaTime:number )=> { let damping = Math.exp(-4*deltaTime)-1; if ( ! playerOnFloor ) { playerVelocity.y-=GRAVITY.value*deltaTime; // small air resistance damping *= 0.1; } playerVelocity.addScaledVector( playerVelocity, damping ); const deltaPosition = playerVelocity.clone().multiplyScalar( deltaTime ); playerCollider.translate( deltaPosition ); playerCollisions(); if(playerMesh) playerMesh.position.copy( playerCollider.end );}let getForwardVector = ()=>{ playerMesh.getWorldDirection( playerDirection ); playerDirection.y = 0; playerDirection.normalize(); return playerDirection;}let getSideVector = ()=>{ playerMesh.getWorldDirection( playerDirection ); playerDirection.y = 0; playerDirection.normalize(); playerDirection.cross( playerMesh.up ); return playerDirection;}let controls=( deltaTime:number )=>{ // gives a bit of air control const speedDelta = deltaTime * ( playerOnFloor ? 25 : 8 ); if ( keyStates[ 'KeyW' ] ) { playerVelocity.add( getForwardVector().multiplyScalar(speedDelta*0.6)); } if ( keyStates[ 'KeyS' ] ) { // playerVelocity.add( getForwardVector().multiplyScalar(-speedDelta)); } if ( keyStates[ 'KeyA' ] ) { // playerVelocity.add( getSideVector().multiplyScalar(-speedDelta)); } if ( keyStates[ 'KeyD' ] ) { // playerVelocity.add( getSideVector().multiplyScalar(speedDelta)); } if ( playerOnFloor ) { if ( keyStates[ 'Space' ] ) { playerVelocity.y = 15; } }}let teleportPlayerIfOob=()=>{ if(!playerMesh) return; if ( playerMesh.position.y <= - 25 ) { playerCollider.start.set( 0, 0.35, 0 ); playerCollider.end.set( 0, 1, 0 ); playerCollider.radius = 0.35; playerMesh.position.copy( playerCollider.end ); playerMesh.rotation.set( 0, 0, 0 ); }}let crossPlay = (curAction:AnimationAction, newAction:AnimationAction)=>{ curAction.fadeOut(0.3); newAction.reset(); newAction.setEffectiveWeight(1); newAction.play(); newAction.fadeIn(0.3);}let render = () => { th.renderer.render(scene, camera);};//============================================场景搭建end==================================onMounted(() => { le.value.appendChild(th.renderer.domElement); le.value.appendChild(stats.value.domElement); window.addEventListener( "resize", () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); th.renderer.setSize(window.innerWidth, window.innerHeight); render(); }, false );});//============================================构建场景 start==================================// MARK: 构建场景=======//============================================构建场景 end==================================//============================================gui start==================================/** * dat.gui 插件 */// MARK: dat.gui 插件=======let ctrlObj2 = {};let basicfolder = th.ctrl.addFolder("MeshBasicMaterial");basicfolder.add( { debug: false }, 'debug' ).onChange(value=>{ helper.visible = value;});basicfolder.open();//============================================gui end==================================let animate=()=> { const deltaTime = Math.min(0.05, clock.value.getDelta())/STEPS_PER_FRAME.value; //咱们在子步骤中寻找碰撞,以升高危险 //一个物体疾速穿过另一个物体而无奈检测。 for ( let i = 0; i < STEPS_PER_FRAME.value; i ++ ) { controls( deltaTime ); updatePlayer( deltaTime ); teleportPlayerIfOob(); } th.renderer.render( scene, camera ); stats.value.update(); if (playerMixer.value) { playerMixer.value.update(0.045); } requestAnimationFrame( animate );}onUnmounted(() => { th.ctrl.destroy(); // 销毁dat.GUI});</script><style scoped lang="less">@import "~@/common/less/index.less";</style>运行成果如下 ...

January 14, 2023 · 5 min · jiezi

关于three.js:threejs碰撞检测前进后退上下楼梯一口气搞定

原文参考我的公众号文章 # threejs碰撞检测-后退后退,高低楼梯一口气搞定! 物体挪动 - 前后碰撞检测次要还是依附 Raycaster(origin, direction) 射线检测。在物体前后挪动时,实时获取物体的地位origin=target.position.clone()作为「射线发射点」,并通过player.getWorldDirection(dir)实时获取物体的方向作为射线「发射方向」,当物体挪动的 forward>0,示意后退,否则示意后退。如果是后退,则dir.negate();方向反转。效果图GIF 图只有 4fps,20 品质,所以很卡。否则图片太大了 次要代码,正文具体阐明了过程/** * JoyStick控制器-角色碰撞检测【前|后】 * @param {Object3D} target 须要碰撞检测的挪动物体 * @param {THREE.Raycaster} raycaster 挪动物体的碰撞检测射线 * @param {Number} intersectDistance 检测间隔,小于此间隔示意碰撞了 * @param {Boolean} recursive 是否递归检测场景中的物体 * @returns Boolean */function checkCollide( target, raycaster, intersectDistance = 2, recursive = true) { // 是否在【后退】 let isForward = this.js.forward >= 0; // 获取target以后地位,Y轴加一个固定量,代表纵轴射线发射(检测碰撞的)地位 let origin = target.position.clone().add(new THREE.Vector3(0, 1, 0)); // 获取target以后朝向 let direction = new THREE.Vector3(); //定义一个方向向量 this.player.getWorldDirection(direction); direction.normalize(); this.playerLight.position.x = this.player.position.x; this.playerLight.position.y = this.player.position.y + 2; this.playerLight.position.z = this.player.position.z; // 如果在【后退】,检测方向direction取反 if (!isForward) { direction.negate(); // console.log("move fallback:", direction); } else { // console.log("move forward:", direction); } // 设置射线发射地位 raycaster.ray.origin.copy(origin); // 设置射线发射方向 raycaster.ray.direction.copy(direction); // 开始【前、后】检测:对于blender制作的模型,须要递归遍历所有child,否则无奈实现射线碰撞检测{[childs], true} let ins = raycaster.intersectObjects(this.objects, recursive); if (ins.length) { let { distance, object } = ins[0]; if (object.name == "stair") { return false; } if (distance < intersectDistance) { console.warn("碰撞了:", object); // 主动回退1米,给接下来小角度后退留余地,否则会继续触发碰撞条件而无奈挪动 if (isForward) { console.log("后退过程碰撞了,主动后退1米"); this.player.translateZ(-1); } else { console.log("后退过程碰撞了,主动后退1米"); this.player.translateZ(+1); } return true; } } return false;}一些优化产生碰撞时,依据此前挪动方向,向相同的方向回退 1m(this.player.translateZ(±1);),这样能够避免转弯时继续碰撞物体TODO 还未解决在楼梯下后退后退对楼梯的碰撞检测物体挪动 - 高低楼梯次要还是依附 THREE.Raycaster(origin, dir) 射线检测。从挪动物体的顶部的中央收回射线,方向朝下,检测楼梯,进行间隔差值判断。须要留神的点在于要躲避人「走在楼梯下」的场景,不要误判为「上楼」。效果图GIF 图只有 4fps,20 品质,所以很卡。否则图片太大了 ...

January 13, 2023 · 2 min · jiezi

关于three.js:threejs入门一些基础理论|大帅老猿threejs特训

前言加入了胖达老师的threejs直播课。有须要的视频私信取。本篇文章为入门实践局部。 能够学到什么:一、软能力1. 零碎全流程了解web3D 利用/数字孪生/元宇宙,程序开发与3D美术资源制作2. 建设与3D美术团队良好沟通合作能力3. 良好把控3D画面成果和性能均衡4. 造就程序和美术联合思维,最简省的形式实现最好的成果5. 相熟罕用3D软件,获得web3D程序员根本应用能力6. 独立开发Web3D利用(程序、2D/3D美术)7. Three.js和3D美术实战经验二、硬件能力1. Three.js根底API(本身想法通过Three.js失去简略实现)2. 3D美术工具的应用3. 甜甜圈3D模型及调整4. Three.js掉落的甜甜圈案例5. Blender 软件根底6. 全流程演示从展厅图片建设展厅3D模型7. PBR材质介绍8. 贴图解决,材质设置9. 实现残缺展厅模型10. 加载展厅模型11. 角色模型12. 角色骨骼动作13. 角色动作合并14. 角色动作管制15. 环境照明16. 自在展览的展厅三、web3D的次要展现形式1.浏览器间接渲染:电脑浏览器、挪动端浏览器(包含微信)以及微信小程序2.服务端渲染(服务端运行:成果好,然而经营老本高)将3D画面像素推流到前端/小程序展现四、相干概念数字孪生:三维实景模型 + 多系统监控/告警数据,实现近程监管(监控、治理) AR:人进入虚拟世界VR:虚构进入事实世界五、浏览器运行3D的计划ActiveX插件:已过期Flash:进行保护webGL: 浏览器原生反对(ie11根本反对)webGPU:性能高,目前未失去操作系统和浏览器的广泛支持六、可实现公布webGL到浏览器运行的计划(从重到轻)##### unity引擎: wasm、webGL;空包:20m+;不反对ie11/低版本chrome及firefox及国产化 ##### CocosCreator: webGL; 空包:6m+; 不反对ie11/低版本chrome及firefox ##### threejs: webGL; 库大小:1m-;开源;ie11均反对 ##### babylonjs:webGL;库大小:4m+;微软开元,中文材料绝对较少 Three.js根底概念 根底场景三大件- scene 场景 new THREE Scene():- camera 相机 new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );- renderer 渲染器 new THREE.WebGLRenderer();灯光 材质(重点)MeshBasicMaterial 根底材质(测试调试用的多)MeshStandardMaterial PBR材质(写实我的项目标配材质) ...

January 11, 2023 · 1 min · jiezi

关于three.js:threejs-入门用例

这是一个底层基于 WebGL 开发的3D渲染引擎( 当然,后续是否会正式公布基于 WebGPU 的版本,也是可能的)。 和间接应用WebGL相比,比方 着色器 ,大部分状况下你无需本人开发,不过,状况并不总是这样,如果你的需要太过非凡,咱们仍旧能够用更靠近原生的形式来绘制,这是一个十分敌对的设计。 绘制流程个别最通用的绘制流程大抵如下: 你能够提前看看咱们最终要绘制的成果(一个旋转的立方体): 点击在线例子能够查看代码。 场景所谓的场景,也就是空间的属性,就是以后空间外面有什么货色;比方:有什么物体(物体的材质、形态、尺寸)、有没有光(点光源还是环境光、或者平行光)等。 对咱们这里而言,很显著,空间里有一个正六面体,而且,如同还有光照在下面。 那么,咱们首先创立好场景,后续再补充场景中的内容: var scene = new THREE.Scene();网格模型当初,咱们来创立一个立方体: var geometry = new THREE.BoxGeometry(100, 100, 100);立方体是红色的,所以,还须要创立一个材质对象: var material = new THREE.MeshLambertMaterial({ color: "red"});而后,把立方体和材质对象关联起来,就取得了示意这个残缺立方体信息的模型对象了: var mesh = new THREE.Mesh(geometry, material);最初,把这个立方体放到之前创立的空间中去: scene.add(mesh);光照光个别有多种,比方环境光,其实咱们就能够认为是漫反射,在原生代码中,咱们须要本人设计光叠加的算法,而在这里,你只须要调用api设置参数即可: var ambient = new THREE.AmbientLight("green");同样的,也须要被增加到以后空间中去: scene.add(ambient);别的类型的光也相似,只是设置的参数不一样,这里就不再赘述了。 相机其实就相当于你的眼睛的可视区域。空间中有什么,不代表你就应该看见什么,通过相机来确定你看的地位、方向、范畴等。 还有一点须要特地阐明,因为你看见的其实是立体,空间自身是3D的,那就存在一个投影算法,不同的投影算法最终你空间的内容也是不一样的(咱们这里抉择的是正射投影),整体来说比拟好了解,间接看代码: var width = window.innerWidth; //窗口宽度var height = window.innerHeight; //窗口高度var k = width / height; //窗口宽高比var s = 100; //三维场景显示范畴管制系数,系数越大,显示的范畴越大var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);camera.position.set(200, 300, 200); //设置相机地位camera.lookAt(scene.position); //设置相机方向(指向的场景对象)渲染器好了,空间筹备好了,相机也筹备好了,接下来就是渲染进去了。 ...

January 11, 2023 · 1 min · jiezi

关于three.js:THREEJS-相机控制旋转

自行实现相机管制中的旋转性能. 相机管制旋转对于相机旋转比拟好的是应用球坐标系,绝对于直角坐标系而言,球坐标系用来形容旋转更加适合直观且对于方位角、极角而言属于线性扭转。对于线性函数咱们是十分喜爱的,因为线性代表简略,能用越简略的函数来形容关系的就越适宜。 再想一下如果应用笛卡尔坐标系来解决角度问题,那么肯定是个三角函数,三角函数复杂度大于线性函数,这是咱们不想看到的。 下图所示是球坐标系示意图: 代码解释// 获取二维坐标 将本来在左上角的原点挪动到屏幕核心,当初取值范畴是[-1,1]const offsetX = ((e.offsetX - mouseDownX) / dom.clientWidth) * 2;const offsetY = ((e.offsetY - mouseDownY) / dom.clientHeight) * 2;const matrix3 = new THREE.Matrix3();mouseDownX = e.offsetX;mouseDownY = e.offsetY;// 记住本来相机的地位,咱们只挪动视角不挪动地位cameraPosition.copy(camera.position);let rotation = new THREE.Vector3(0, 0, -1);// 获得相机的世界坐标的归一化坐标rotation.applyNormalMatrix(matrix3.setFromMatrix4(camera.matrixWorld));let spherical = new THREE.Spherical();// 从归一化坐标设置球,能够说是将相机坐标映射到了半径为1的球坐标系上// setFromVector3用于设置theta和phi, 等于是将笛卡尔坐标系换算成球坐标系spherical.setFromVector3(rotation);// 上一步将笛卡尔坐标系换算成球坐标系后,theta和phi都是线性扭转的了,对theta和phi相加减即可spherical.theta -= offsetX;spherical.phi += offsetY;spherical.makeSafe();rotation.setFromSpherical(spherical);// 相机挪动到原点 因为球坐标系在原点camera.position.set(0, 0, 0);camera.lookAt(rotation.x, rotation.y, rotation.z);// 视角扭转后挪动到原地位,深藏功与名camera.position.copy(cameraPosition);camera.updateMatrixWorld();

January 9, 2023 · 1 min · jiezi

关于three.js:THREEJS-将构件缩放至视野中的方法

概述只将某个物体缩放至整个屏幕, 非常常见的利用场景,这和相机视线无关,须要分透视相机和正交相机来别离阐明。 透视相机首先假设物体被相机视线笼罩,有如下图 首先必须晓得物体所在的突围球半径 r, 其次相机角度是已知的,就是 camera.fov, fov 是角度制数据,简略依据三角函数能够得出球心到相机的间隔 s。 s = r/sin(fov)应用代码来表白就是 const dir = new THREE.Vector3(0, 1, 0); // 方向const dist = Math.abs(sphere.radius / Math.sin(((fov / 360) * Math.PI) / 2));const temp = new THREE.Vector3();temp.addVectors(sphere.center, dir.multiplyScalar(dist));正交相机正交相机和剖切很类似,除了 position、rotation 外,应用 left、top、right、bottom 来规定视线范畴 这个更加容易了解,对于屏幕都是矩形的电脑来说,这个缩放将会恰达益处(精确度高,透视相机因为用到了突围球其实不是很准确)。 const left = -(boundingBox.max.x - boundingBox.min.x) / 2;const right = (boundingBox.max.x - boundingBox.min.x) / 2;const top = (boundingBox.max.z - boundingBox.min.z) / 2;const bottom = -(boundingBox.max.z - boundingBox.min.z) / 2;const camera = new THREE.OrthographicCamera(left, right, top, bottom, 1, 1000);camera.lookAt(new THREE.Vector3(0, -100, 0));用处: 制作小地图 ...

January 9, 2023 · 1 min · jiezi

关于three.js:使用-threejs-加载基本三维模型-大帅老猿threejs特训

如果你是初学者,那么学习三维动画可能是一项比拟艰难的工作。然而,应用 three.js 这个弱小的 JavaScript 库,能够让你轻松地制作出精美的三维动画。 首先,咱们须要导入 three.js 库,并创立场景、相机和渲染器。场景是三维空间中所有物体的容器,而相机则决定了咱们所看到的视角。渲染器则负责将场景出现在屏幕上。 接下来,咱们能够应用 OrbitControls 插件来管制相机的地位和视角,并应用 GLTFLoader 插件来加载三维模型文件。这里咱们应用的是 donuts.glb 文件,它蕴含了一个甜甜圈的三维模型。 为了让咱们的场景更加真切,咱们还能够应用 RGBELoader 插件来加载环境贴图,并将它设置为场景的背景。这样,咱们就能够在咱们的三维模型中感触到周围环境的光照成果了。 最初,咱们还能够在动画函数中增加代码来主动旋转模型,并播放它的动画。这样,咱们就能够轻松地制作出精彩的三维动画了。 总的来说,应用 three.js 制作三维动画十分不便,它提供了丰盛的插件和 API,让咱们可能疾速上手。如果你也想学习 three.js,无妨退出猿创营(v:dashuailaoyuan),一起交流学习。 import * as THREE from 'three';import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';// 三D三大件// 场景const scene = new THREE.Scene();// 相机const camera = new THREE.PerspectiveCamera(75, window.innerWidth/innerHeight, 0.1, 10)// 渲染器const renderer = new THREE.WebGLRenderer({antialias: true});renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);// 设置相机地位camera.position.set(0.3,0.3,0.5);// 增加 控制器,能够拖拽,缩放const controls = new OrbitControls(camera, renderer.domElement);// 增加灯光,DirectionLight方向光const directionLight = new THREE.DirectionalLight(0xffffff, 0.4);// scene.add(directionLight);let donuts;let mixer;// 加载 glb 模型new GLTFLoader().load('../resources/models/donuts.glb', (gltf) => { // 模型增加到场景 scene.add(gltf.scene); donuts = gltf.scene; // mixer = new THREE.AnimationMixer(gltf.scene); const clips = gltf.animations; clips.forEach((clip) => { const action = mixer.clipAction(clip); action.loop = THREE.LoopOnce; action.clampWhenFinished = true; action.play(); })})// 加载环境贴图 360全景图new RGBELoader() .load('../resources/sky.hdr', (texture) => { // 设置为场景背景 scene.background = texture; texture.mapping = THREE.EquirectangularReflectionMapping; // 设置环境光 scene.environment = texture; renderer.outputEncoding = THREE.sRGBEncoding; renderer.render(scene, camera); });function animate() { requestAnimationFrame(animate); // 渲染场景 renderer.render(scene, camera); // 更新控制器 controls.update(); // 主动旋转 if (donuts) { donuts.rotation.y += 0.001; } // 播放动画 if (mixer) { mixer.update(0.003); }}animate();

December 31, 2022 · 1 min · jiezi

关于three.js:绝配周杰伦bgm阿瑟爱心代码-酷炫播放器

前言最近电视剧《点燃我,和煦你》中的一个桥段,李峋写的爱心代码火了 ❤️❤️❤️,相似的表白神器每年都会在特定的工夫(这些日期你都晓得吗)出圈几个。 当各路大佬开始晒各种语言各种类型的爱心代码,我一个顶级CV工程师,在这塌实的环境下天然也想着夸耀一番,于是在这片汪洋大海中通过我的层层筛选,这款爱心代码被我挑中并对他进行了小小的革新。 插件地址 对于插件: 这款插件的作者从代码来看曾经无从讲究了,感激大佬栽树。 作为前端实现爱心代码的酷炫成果,尤其是3d成果应用的技术栈根本都是以three.js+canvas为主 这款插件也是应用three.js + canvas 开发,具体的开发过程不做过多的剖析,cv工程师不太会three.js,见笑了 重点认识一下Web Audio API: Web Audio API 为管制 Web 上的音频提供了一个弱小且通用的零碎,容许开发人员抉择音频源、为音频增加成果、创立音频可视化、利用空间成果(如平移)等等。 声音远近,大小,触动等等这些成果的可视化都须要Web Audio API的帮忙 接口:这里简略列举了几个插件中用到的接口,更多接口可返回MDN查看 AudioListenerAudioListener 用一个虚构的listener示意在场景中所有的地位和非地位相干的音效. 一个three.js程序通常创立一个AudioListener. 它是音频实体构造函数的必须参数,比方 Audio and PositionalAudio. 大多数状况下, listener对象是camera的子对象. Camera的3D变换示意了listener的3D变换. AudioLoader用来加载 AudioBuffer的一个类。 外部默认应用FileLoader> > 来加载文件。(我的项目中用来加载音频文件) AudioAnalyser创立AudioAnalyser对象, 应用AnalyserNode 去剖析音频数据. 开始 插件动画成果曾经完满了,合乎装要求。 去掉边框更显大气; bgm像铃声换成我周董的更能俘获人心; 周董咬字不清加个歌词显示更加人性化(开玩笑的啦,喜爱的就是这个味)。 动画追随旋律的成果插件曾经有了,配上歌词那不就是一个现成的播放器吗 思路一: 实现辨认音频显示歌词,相似视频播放显示字幕的成果。 一通百度失去的后果次要分两种 上传语音到服务端,服务端利用解析语音的API,返回文字;利用原生api或各种库调取浏览器麦克风,辨认语音转文字办法一在我这个捣鼓着玩的我的项目里显著不实用,办法二延时太久,辨认精确度太低 思路二:√ 获取歌词,实现歌词与音频的同步,这种计划是可行的 实现获取歌词网上找到LRC格局的歌词,肯定要是LRC格局,放到一个文本域中,设置为暗藏 <textarea id="lrc_content" cols="30" style="display: none"> [ti:花海] [ar:周杰伦] [00:03.63]花海 [00:06.35]周杰伦 [00:14.49]... [00:18.56]... [00:26.90]静止了 所有的花开 [00:33.31]边远了 清晰了爱 [00:39.53]情侣们 爱却更喜爱 [00:45.99]那时候 我不懂 这场爱 [00:51.55] [00:52.50]你喜爱 站在那窗台 [00:58.47]你良久 都没再来 [01:05.12]黑白的 世界染上空白 [01:12.18]是你流的泪晕开 [01:16.14] [01:17.26]不要你来到 间隔隔不开 [01:23.65]怀念变成海 在窗外进不来 [01:29.82]原谅说太快 爱成了妨碍 [01:36.24]手中的风筝放太快回不来 [01:42.22] [01:42.74]不要你来到 泪已化不开 [01:48.96]经验的妨碍 我在期待重来 [01:55.51]天空仍璀璨 她爱着大海 [02:01.79]情歌被战胜 爱已不存在 [02:09.83] [02:34.80]你喜爱 站在那窗台 [02:41.14]你良久 都没再来 [02:47.40]黑白的 世界染上空白 [02:54.66]是你流的泪晕开 [02:59.87] [03:00.24]不要你来到 间隔隔不开 [03:05.75]怀念变成海 在窗外进不来 [03:12.16]原谅说太快 爱成了妨碍 [03:18.56]手中的风筝放太快回不来 [03:24.53] [03:25.07]不要你来到 泪已化不开 [03:31.37]经验的妨碍 我在期待重来 [03:37.81]天空仍璀璨 她爱着大海 [03:44.12]情歌被战胜 爱已不存在 [03:54.85]</textarea>js读取歌词 ...

December 15, 2022 · 2 min · jiezi

关于three.js:使用Threejs实现炫酷的赛博朋克风格3D数字地球大屏

背景近期工作有波及到数字大屏的需要,于是利用业余时间,联合 Three.js 和 CSS实现赛博朋克2077格调视觉效果 实现炫酷 3D 数字地球大屏页面。页面应用 React + Three.js + Echarts + stylus 技术栈,本文波及到的次要知识点包含:THREE.Spherical 球体坐标系的利用、Shader 联合 TWEEN 实现飞线和冲击波动画成果、dat.GUI 调试工具库的应用、clip-path 创立不规则图形、Echarts 的根本应用办法、radial-gradient 创立雷达图形及动画、GlitchPass 增加故障格调前期、Raycaster 网格点击事件等。 成果 如下图 所示,页面次要头部、两侧卡片、底部仪表盘以及主体 3D 地球 形成,地球外围有 飞线 动画和 冲击波 动画成果 ,通过 鼠标能够旋转和放大地球。点击第一张卡片的 START ⬜ 按钮会给页面增加故障格调前期 ⚡,双击地球会弹出随机提醒语弹窗。 实现 资源引入引入开发必备的资源,其中除了根底的 React 和样式表之外,dat.gui 用于动态控制页面参数,其余残余的次要分为两局部:Three.js相干, OrbitControls 用于镜头轨道控制、TWEEN 用于补间动画管制、mergeBufferGeometries 用户合并模型、EffectComposer RenderPass GlitchPass 用于生成前期故障成果动画、 lineFragmentShader 是飞线的 Shader、Echarts相干按需引入须要的组件,最初应用 echarts.use 使其失效。 import './index.styl';import React from 'react';import * as dat from 'dat.gui';// three.js 相干import * as THREE from 'three';import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';import { TWEEN } from 'three/examples/jsm/libs/tween.module.min.js';import { mergeBufferGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils';import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';import { GlitchPass } from 'three/examples/jsm/postprocessing/GlitchPass.js';import lineFragmentShader from '@/containers/EarthDigital/shaders/line/fragment.glsl';// echarts 相干import * as echarts from 'echarts/core';import { BarChart /*...*/ } from 'echarts/charts';import { GridComponent /*...*/ } from 'echarts/components';import { LabelLayout /*...*/ } from 'echarts/features';import { CanvasRenderer } from 'echarts/renderers';echarts.use([BarChart, GridComponent, /* ...*/ ]); 页面构造页面次要构造如以下代码所示,.webgl 用于渲染 3D 数字地球;.header 是页面顶部,外面包含工夫、日期、星际坐标、Cyberpunk 2077 Logo、自己 Github 仓库地址等;.aside 是左右两侧的图表展现区域;.footer 是底部的仪表盘,展现一些雷达动画和文本信息;如果仔细观察,能够看出背景有噪点成果,.bg 就是用于生成噪点背景成果。 ...

July 31, 2022 · 6 min · jiezi

关于three.js:关于Threejs的基本使用和概念场景相机渲染器等

这是一个底层基于WebGL开发的3D渲染引擎( 当然,后续是否会正式公布基于WebGPU的版本,也是可能的)。 和间接应用WebGL相比,比方着色器,大部分状况下你无需本人开发,不过,状况并不总是这样,如果你的需要太过非凡,咱们仍旧能够用更靠近原生的形式来绘制,这是一个十分敌对的设计。 绘制流程个别最通用的绘制流程大抵如下: 筹备好场景→筹备好相机→应用渲染器渲染进去 你能够提前看看咱们最终要绘制的成果: 这是一个旋转的立方体。 你能够点击此处进行查看运行成果。 场景所谓的场景,也就是空间的属性,就是以后空间外面有什么货色;比方:有什么物体(物体的材质、形态、尺寸)、有没有光(点光源还是环境光、或者平行光)等。 对咱们这里而言,很显著,空间里有一个正六面体,而且,如同还有光照在下面。 那么,咱们首先创立好场景,后续再补充场景中的内容: var scene = new THREE.Scene();网格模型当初,咱们来创立一个立方体: var geometry = new THREE.BoxGeometry(100, 100, 100);立方体是红色的,所以,还须要创立一个材质对象: var material = new THREE.MeshLambertMaterial({ color: "red"});而后,把立方体和材质对象关联起来,就取得了示意这个残缺立方体信息的模型对象了: var mesh = new THREE.Mesh(geometry, material);最初,把这个立方体放到之前创立的空间中去: scene.add(mesh);光照光个别有多种,比方环境光,其实咱们就能够认为是漫反射,在原生代码中,咱们须要本人设计光叠加的算法,而在这里,你只须要调用api设置参数即可: var ambient = new THREE.AmbientLight("green");同样的,也须要被增加到以后空间中去: scene.add(ambient);别的类型的光也相似,只是设置的参数不一样,这里就不再赘述了。 相机其实就相当于你的眼睛的可视区域。空间中有什么,不代表你就应该看见什么,通过相机来确定你看的地位、方向、范畴等。 还有一点须要特地阐明,因为你看见的其实是立体,空间自身是3D的,那就存在一个投影算法,不同的投影算法最终你空间的内容也是不一样的(咱们这里抉择的是正射投影),整体来说比拟好了解,间接看代码: var width = window.innerWidth; //窗口宽度var height = window.innerHeight; //窗口高度var k = width / height; //窗口宽高比var s = 100; //三维场景显示范畴管制系数,系数越大,显示的范畴越大var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);camera.position.set(200, 300, 200); //设置相机地位camera.lookAt(scene.position); //设置相机方向(指向的场景对象)渲染器好了,空间筹备好了,相机也筹备好了,接下来就是渲染进去了。 ...

July 25, 2022 · 2 min · jiezi

关于three.js:用threejs写一个下雨动画

最近看了《Three.js开发指南》,粗浅地意识到光看不练跟没看差不多,所以就练习写了这个小动画。 我的项目地址: https://github.com/alasolala/... 前置常识WebGL让咱们能在浏览器开发3D利用,然而间接应用WebGL编程还是挺简单的,开发者须要晓得WebGL的底层细节,并且学习简单的着色语言来取得WebGL的大部分性能。Three.js提供了一系列很简略的对于WebGL个性的JavaScript API,使开发者能够很不便地创作出难看的3D图形。在Three.js官网,就有很多酷炫3D成果。 应用Three.js开发3D利用,通常要包含渲染器(Renderer)、场景(Scene)、照相机(Camera),以及你在场景中创立的物体,光照。 构想一下照相的状况,咱们须要一个场景(Scene),在这个场景中摆好要拍摄的物体,设置光照环境,摆放好照相机(Camera)的地位和朝向,而后就能够拍照了。渲染器(Renderer)可能和摄影师比拟像吧,负责下命令拍摄,并且生成图像(照片)。 将上面的代码的复制并运行,就能够失去一个很简略的3D场景。 <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>room</title></head><body> <div id="webgl-output"></div> <script src="https://unpkg.com/three@0.119.0/build/three.js"></script> <script> function init () { const scene = new THREE.Scene() const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000 ) camera.position.set(-30, 40, 30) camera.lookAt(0,0,0) scene.add(camera) const planeGeometry = new THREE.PlaneGeometry(60,20) const planeMaterial = new THREE.MeshLambertMaterial({ color: 0xAAAAAA }) const plane = new THREE.Mesh(planeGeometry, planeMaterial) plane.rotation.x = -Math.PI / 2 plane.position.set(15, 0, 0) scene.add(plane) const sphereGeometry = new THREE.SphereGeometry(4, 20, 20) const sphereMaterial = new THREE.MeshLambertMaterial({ color: 0xffff00 }) const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial) sphere.position.set(20, 4, 2) scene.add(sphere) const spotLight = new THREE.SpotLight(0xffffff) spotLight.position.set(-20, 30, -15) scene.add(spotLight) const renderer = new THREE.WebGLRenderer() renderer.setClearColor(new THREE.Color(0x000000)) renderer.setSize(window.innerWidth, window.innerHeight) document.getElementById('webgl-output').appendChild(renderer.domElement) renderer.render(scene, camera) } init()</script></body></html>场景(Scene)THREE.Scene对象是所有不同对象的容器,但这个对象自身没有很简单的操作,咱们通常在程序最开始的时候实例化一个场景,而后将照相机、物体、光源增加到场景中。 ...

July 22, 2022 · 4 min · jiezi

关于three.js:threeJs相机

此篇将threeJs相机的应用做一下记录,简略的封装为vue组件,以备参考。之后缓缓欠缺此文。 一、立方相机cubeCameracubeCamera,结构一个蕴含6个PerspectiveCameras(透视摄像机)的立方摄像机,并将其拍摄的场景渲染到一个WebGLCubeRenderTarget上,生成指标纹理(WebGLCubeRenderTarget.texture)。render target是一个缓存,缓存显卡为正在后盾渲染的场景绘制的像素。被用于不同的成果,例如用在一个图像渲染到屏幕上之前先做一些前期解决。此处,将WebGLCubeRenderTarget.texture作为某个具备envMap属性材质的模型的环境贴图,即此模型反射的环境图案。 CubeCamera.vue<template> <div> <div id="canvasContainer" style="width: 900px; height: 800px"></div> </div></template><script setup>import { onMounted } from 'vue'import * as THREE from 'three'import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'let scene, camera, renderer, width, height, controlsonMounted(() => { init()})function init() { const canvasContainer = document.querySelector('#canvasContainer') width = canvasContainer.clientWidth height = canvasContainer.clientHeight // 1、创立场景 scene = new THREE.Scene() // 2、创立渲染器 renderer = new THREE.WebGLRenderer() // renderer.setClearColor('#FFFFFF') // 设置渲染器的背景色 renderer.setClearColor('#88b4e1') renderer.setSize(width, height) // 设置渲染区域尺寸 canvasContainer.appendChild(renderer.domElement) // 将渲染器创立的canvas元素增加到容器元素 // 3、创立透视相机,用于拍摄主场景 camera = new THREE.PerspectiveCamera(fov, width / height, 0.1, 1000) camera.position.set(100, 50, 200) // 设置相机地位 camera.lookAt(scene.position) // 设置相机方向(指向的场景对象) // 4、创立立方相机 cubeCamera() // 为具备镜面反射成果的模型A拍摄反射的图形,而后作为A的环境贴图 // 要先创立renderer, 因为cubeCamera.update(renderer, scene) // 5、其余 pointLight() // 点灯光 initControls() // 用鼠标管制透视相机的拍摄角度 axisHelper() // 坐标辅助器 animate() }// 循环动画渲染function animate() { requestAnimationFrame(animate) renderer.render(scene, camera)}// 轨道控制器,使得相机围绕指标进行轨道静止function initControls() { controls = new OrbitControls(camera, renderer.domElement)}// 辅助三维坐标系function axisHelper() { const axis = new THREE.AxesHelper(250) scene.add(axis)}// 创立一个虚构的球形网格Mesh的辅助对象来模仿点光源PointLightfunction pointLight() { const pointLight = new THREE.PointLight(0xffffff, 1, 1000) pointLight.position.set(100, 150, 100) //点光源地位 scene.add(pointLight) // 创立点光源辅助对象模仿点光源 const pointLightHelper = new THREE.PointLightHelper(pointLight, 1) scene.add(pointLightHelper)}let sphere, box// 创立立方相机function cubeCamera() { // 创立cube相机的渲染指标 const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(128, { format: THREE.RGBAFormat, generateMipmaps: true, minFilter: THREE.LinearMipmapLinearFilter }); // 创立cube相机 const cubeCamera = new THREE.CubeCamera(1, 100000, cubeRenderTarget) scene.add(cubeCamera) // 创立具能反射环境的模型 const sphereGeometry = new THREE.SphereGeometry(50) const sphereMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000, envMap: cubeRenderTarget.texture }); sphere = new THREE.Mesh(sphereGeometry, sphereMaterial) sphere.position.set(80, -50, 0) scene.add(sphere) // 创立被反射的模型 const boxGeometry = new THREE.BoxGeometry(20, 20, 20) const boxMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00 }) box = new THREE.Mesh(boxGeometry, boxMaterial) box.position.set(80, 100, 0) scene.add(box) // 更新渲染指标对象 sphere.visible = false cubeCamera.position.copy(sphere.position) cubeCamera.update(renderer, scene) // 能够渲染场景了 sphere.visible = true}</script>效果图: ...

July 19, 2022 · 2 min · jiezi

关于three.js:利用vue3dloader同时加载多个3D模型

家喻户晓ThreeJS是web端用于加载3D模型的JS库,广泛应用于物联网及局部游戏行业中,其弱小就弱小在能够加载大多数类型的3D模型,如:.dae/.fbx/.gltf(.glb)/.obj/.ply/.stl/.json的模型。 然而在工作中,常常会遇到独立的模型会比拟大,建模师会将一个大型的模型拆分成多个小模型,因而就须要threeJS加载多个模型;另一种状况是须要加载多种不同品种的模型。 我将以上两种状况辨别为以下两个需要: 加载多个3D模型加载多个不同品种的3D模型因为加载多个3D模型,大多数人会想到间接应用for循环对模型进行加载,然而理论利用中,当同时加载.fbx模型大小超过60M左右,浏览器就会变得十分卡顿。由此我将其再次宰割为两种加载形式: 并行加载(即for循环)按程序一一加载面对以上的性能需要,因而我开发了一款可能满足以上需要的vue组件,vue-3d-loader(github.com) 性能反对列表加载单个3D模型同时加载多个3D模型同时加载多个不同类型3D模型设置场景宽高设置材质及纹理交互管制鼠标事件灯光相机地位及旋转增加标注点演示自动播放模型内置动画 加载多个模型 图片材质 灯光 在你的我的项目中装置vue-3d-loader留神:vue2版本应用1.x.x版本,vue3版本应用2.0.0+版本 npm i vue-3d-loader@2.0.0 -S应用办法全局应用,在入口文件中全局装置,代码如下: /* vue2 */import vue3dLoader from "vue-3d-loader";Vue.use(vue3dLoader)/* vue3 */import vue3dLoader from "vue-3d-loader";createApp(App).use(vue3dLoader).mount('#app')非全局应用,在Vue文件中应用如下代码: import { vue3dLoader } from "vue-3d-loader"; // 留神 vue3dLoader 写在 {...} 内在组件中应用标签<vue3dLoader></vue3dLoader> <vue3dLoader :height="200" :showFps="true" :filePath="['/fbx/1.fbx', '/obj/2.obj', '/gltf/3.gltf']" :mtlPath="[null, '/obj/2.mtl', null]" :backgroundColor="0xff00ff"></vue3dLoader>filePath为必填,并反对字符串与数组两种形式传入 反对的事件eventdescriptionmousedown(event, intersects)鼠标按下, intersect:以后相交最近的物体mousemove(event, intersects)鼠标挪动, intersect:以后相交最近的物体mouseup(event, intersects)鼠标放开, intersect:以后相交最近的物体click(event, intersects)点击, intersect:以后相交最近的物体load加载模型事件process(event, fileIndex)加载进度, fileIndex:以后加载模型的索引error(event)谬误事件同一个场景中加载多个模型<!-- 可同时加载多个不同品种的模型 --><template> <vue3dLoader :filePath="filePath" :scale="{ x: 0.4, y: 0.4, z: 0.4 }" :cameraPosition="{ x: 100, y: 200, z: 30 }" ></vue3dLoader></template><script>export default { data() { return { filePath: [ "/models/fbx/Samba Dancing.fbx", "models/collada/pump/pump.dae", ], }; },};</script>材质及纹理加载<!-- obj加载mtl材质 --><vue3dLoader filePath="/obj/1.obj" mtlPath="/obj/1.mtl" ></vue3dLoader><!-- fbx图片纹理加载 --><vue3dLoader filePath="/fbx/1.fbx" textureImage="/fbx/1.png" ></vue3dLoader>更多演示3D演示(因为网络问题可能速度会比较慢) ...

July 15, 2022 · 1 min · jiezi

关于three.js:ThreeJS学习记录三监听鼠标点击和移动

监听鼠标挪动,几何体变大变小监听鼠标点击,弹出信息 应用办法:Raycaster import * as THREE from 'three'import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'import Stats from 'three/examples/jsm/libs/stats.module'let container, statslet camera, scene, raycaster, rendererlet INTERSECTEDconst pointer = new THREE.Vector2()let clickPosX = 0, clickPosY = 0function onPointerMove(event) { pointer.x = (event.clientX / window.innerWidth) * 2 - 1 pointer.y = -(event.clientY / window.innerHeight) * 2 + 1 if (INTERSECTED) { INTERSECTED.scale.set(3, 3, 3) } render()}function onPointerClick(event) { pointer.x = (event.clientX / window.innerWidth) * 2 - 1 pointer.y = -(event.clientY / window.innerHeight) * 2 + 1 if (INTERSECTED) { alert('点击了' + INTERSECTED.name) } render()}function calcMousePosition(event, x, y) { // 决定信息弹窗的定位 var event = event || window.event if (event.pageX || event.pageY) { x = event.pageX y = event.pageY } else if (event.clientX || event.clientY) { x = event.clientX + document.documentElement.scrollLeft + document.body.scrollLeft y = event.clientY + document.documentElement.scrollTop + document.body.scrollTop } return { x: x, y: y }}function createDom() { const geometry = new THREE.BoxBufferGeometry(10, 10, 10) const material = new THREE.MeshBasicMaterial({ color: 0x00fa9a }) const mesh = new THREE.Mesh(geometry, material) mesh.name = '几何体1' scene.add(mesh) mesh.position.set(0, -5, 0) const geometry1 = new THREE.BoxBufferGeometry(10, 10, 10) const mesh1 = new THREE.Mesh(geometry1, material) mesh1.name = '几何体2' scene.add(mesh1) mesh1.position.set(0, 20, 0) const ambient = new THREE.AmbientLight(0xffffff, 1) scene.add(ambient) // var axesHelper = new THREE.AxesHelper(250) // scene.add(axesHelper)}function init() { scene = new THREE.Scene() scene.background = new THREE.Color(0xffffff) camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 0.1, 1000 ) camera.position.set(18, 20, 100) renderer = new THREE.WebGLRenderer() renderer.setPixelRatio(window.devicePixelRatio) renderer.setSize(window.innerWidth, window.innerHeight) document.body.appendChild(renderer.domElement) controls = new OrbitControls(camera, renderer.domElement) controls.addEventListener('change', render) window.addEventListener('resize', onWindowResize) document.addEventListener('mousemove', onPointerMove) document.addEventListener('click', onPointerClick) stats = new Stats() document.body.appendChild(stats.dom) raycaster = new THREE.Raycaster() createDom()}function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight camera.updateProjectionMatrix() renderer.setSize(window.innerWidth, window.innerHeight)}function render() { stats.update() camera.updateMatrixWorld() raycaster.setFromCamera(pointer, camera) const intersects = raycaster.intersectObjects(scene.children, false) if (intersects.length > 0) { if (INTERSECTED != intersects[0].object) { if (INTERSECTED) { INTERSECTED.scale.set(1, 1, 1) INTERSECTED = null } INTERSECTED = intersects[0].object } } else { if (INTERSECTED) { INTERSECTED.scale.set(1, 1, 1) INTERSECTED = null } } renderer.render(scene, camera)}init()render()

July 12, 2022 · 2 min · jiezi

关于three.js:ThreeJS学习记录二立方体不同面不同图片待补充

学习点一:material数组在设置Mesh的时候,第一个参数是几何体,第二个参数就是材质。依据官网文档看,第二个参数能够是一个数组。也就是说能够设置几何体不同面的材质图片。 学习点二:程序大家能够看下哪一个面会对应哪个图片,nx\ny\nz\px\py\pz。 学习点三:红色是透明度为0,彩色是透明度为1设置material时,图片红色的中央会透出材质的色彩,彩色中央不会。比照下,上面是图片,material的color设置青色,附在几何体上的成果: 次要应用module: OrbitControls 几何体:BoxBufferGeometry、SphereGeometry、CircleGeometry、CylinderGeometry、IcosahedronGeometry 材质加载:TextureLoader 材质:MeshPhongMaterial 环境光:AmbientLight 具体代码import * as THREE from 'three'// 轨道控制器import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'let scene, camera, controls, rendererlet axesHelper// 设置加载管理器let event = {}event.onLoad = () => { render() //当材质图片加载实现,从新渲染一下}function init() { renderer = new THREE.WebGLRenderer({ antialias: true }) // 创立渲染器对象 renderer.setPixelRatio(window.devicePixelRatio) renderer.setSize(window.innerWidth, window.innerHeight) //设置渲染区域尺寸 document.body.appendChild(renderer.domElement) //body元素中插入canvas对象 scene = new THREE.Scene() camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 1, 1000 ) camera.position.set(0, 5, 100) controls = new OrbitControls(camera, renderer.domElement) controls.addEventListener('change', render) //监听鼠标、键盘事件 // 环境光 const ambient = new THREE.AmbientLight(0xffffff, 1) scene.add(ambient) // 几何体 const box = new THREE.BoxBufferGeometry(10, 10, 10) //正方体六面 const ball = new THREE.SphereGeometry(5, 16, 8) //球一面 const circle = new THREE.CircleGeometry(5, 32) //圆形一面 const cylinder = new THREE.CylinderGeometry(5, 5, 20, 32) //圆柱 const twelve = new THREE.IcosahedronGeometry(10, 0) //十二面 // 材质 let texture = new THREE.TextureLoader() let t1 = texture.load('./textures/六面/nx.jpg', event.onLoad) let t2 = texture.load('./textures/六面/px.jpg', event.onLoad) let t3 = texture.load('./textures/六面/ny.jpg', event.onLoad) let t4 = texture.load('./textures/六面/py.jpg', event.onLoad) let t5 = texture.load('./textures/六面/nz.jpg', event.onLoad) let t6 = texture.load('./textures/六面/pz.jpg', event.onLoad) let t1Material = new THREE.MeshPhongMaterial({ color: 0x00fa9a, map: t1, side: THREE.DoubleSide, //双面渲染 }) let t2Material = new THREE.MeshPhongMaterial({ color: 0x00fa9a, map: t2 }) let t3Material = new THREE.MeshPhongMaterial({ color: 0x00fa9a, map: t3 }) let t4Material = new THREE.MeshPhongMaterial({ color: 0x00fa9a, map: t4 }) let t5Material = new THREE.MeshPhongMaterial({ color: 0x00fa9a, map: t5 }) let t6Material = new THREE.MeshPhongMaterial({ color: 0x00fa9a, map: t6 }) // 六面材质数组 let materialArr = [ t1Material, t2Material, t3Material, t4Material, t5Material, t6Material, ] let material = new THREE.MeshPhongMaterial({ color: 0x00fa9a }) let mesh_box = new THREE.Mesh(box, materialArr) let mesh_ball = new THREE.Mesh(ball, material) //球只有一个面 let mesh_circle = new THREE.Mesh(circle, material) let mesh_cylinder = new THREE.Mesh(cylinder, materialArr) let mesh_twelve = new THREE.Mesh(twelve, material) mesh_box.position.set(-32, 0, 0) mesh_ball.position.set(-16, 0, 0) mesh_circle.position.set(0, 0, 0) mesh_cylinder.position.set(16, 0, 0) mesh_twelve.position.set(32, 0, 0) scene.add(mesh_box) scene.add(mesh_ball) scene.add(mesh_circle) scene.add(mesh_cylinder) scene.add(mesh_twelve) render() window.addEventListener('resize', onWindowResize)}function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight camera.updateProjectionMatrix() render.setSize(window.innerWidth, window.innerHeight)}function render() { renderer.render(scene, camera) //执行渲染操作 // requestAnimationFrame(render)}function buildHelper() { // 辅助坐标系 参数250示意坐标系大小,能够依据场景大小去设置 axesHelper = new THREE.AxesHelper(250) scene.add(axesHelper)}init()buildHelper()render()

July 11, 2022 · 2 min · jiezi

关于three.js:Threejs-实现3D开放世界小游戏阿狸的多元宇宙-

申明:本文波及图文和模型素材仅用于集体学习、钻研和观赏,请勿二次批改、非法流传、转载、出版、商用、及进行其余获利行为。背景2545光年之外的开普勒1028星系,有一颗色彩斑斓的宜居星球 ,星际移民 必须穿戴基地发放的防辐射服能力生存。阿狸 驾驶星际飞行器 来临此地,快帮它在限定工夫内应用轮盘挪动找到基地获取防辐射服吧! 本文应用 Three.js + React + CANNON 技术栈,实现通过滑动屏幕管制模型在 3D 世界里静止的 Low Poly 低多边形格调小游戏。本文次要波及到的知识点包含:Three.js 暗影类型、创立粒子系统、cannon.js 根本用法、应用 cannon.js 高度场 Heightfield 创立地形、通过轮盘挪动管制模型动画等。 成果游戏玩法:点击开始游戏按钮,通过操作屏幕底部轮盘来挪动阿狸,在倒计时限定工夫内找到基地。主线工作:限定工夫内找到庇护所。干线工作:自在摸索凋谢世界。 在线预览: 地址1:https://3d-eosin.vercel.app/#... 地址2:https://dragonir.github.io/3d...已适配: PC 端 挪动端 小提示:站得越高看得越远,模摸糊糊据说基地位于初始地位的西面,开始时应该向左前方后退哦。设计游戏流程如下图所示:页面加载实现后玩家 点击开始按钮,而后在限定工夫内通过管制页面底部轮盘 挪动模型,找到指标基地所在的地位。寻找胜利或失败都会显示后果页 ,后果下面有两个按钮再试一次和自在摸索,点击再试一次工夫会重置,而后从新回到终点开始倒计时。点击自在摸索则不在计时,玩家能够在 3D 凋谢世界里操作模型自在摸索。同时,游戏内页面也提供一个时光倒流按钮,它的作用是玩家 能够在失败前本人手动重置倒计时 ⏳,从新回到终点开始游戏。 实现加载资源加载开发所需的必备资源:GLTFLoader 用于加载狐狸 和基地 模型、CANNON 是用于创立 3D 世界的物理引擎;CannonHelper 是对 CANNON 一些应用办法的封装;JoyStick 用于创立通过监听鼠标挪动地位或触碰屏幕产生的位移来管制模型挪动的轮盘 。 import * as THREE from 'three';import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';import CANNON from 'cannon';import CannonHelper from './scripts/CannonHelper';import JoyStick from './scripts/JoyStick';页面构造页面构造比较简单,.webgl 用于渲染 WEBGL;.tool 是游戏内的工具栏,用于重置游戏和显示一些提醒语;.loading 是游戏加载页面,用来显示游戏加载进度、介绍游戏规则、显示游戏开始按钮;.result 是游戏后果页面,用于显示游戏胜利或失败后果,并提供再试一次和自在摸索两个按钮 。 ...

April 1, 2022 · 6 min · jiezi

关于three.js:Threejs系列-写一个第一三人称视角小游戏

大家好,我是秋风,在上一篇 中说到了 Three.js 系列的指标以及宝可梦游戏,那么明天就来通过 Three.js 来谈谈对于游戏中的视角追随问题。置信我的读者都或多或少玩一些游戏,例如王者光荣、绝地求生、宝可梦、塞尔达、原神之类的游戏。那么你晓得他们别离是什么视角的游戏么?你晓得第一人称视角和第三人称视角的差别么?通过代码咱们怎么能实现这样的成果呢?如果你对以上问题好奇,并且不能齐全答复。那么请跟随着我一起往下看吧。 视角解说首先咱们先来看看第一人称视角、第三人称视角的概念。其实对于咱们而言 第一人称 和 第三人称,是十分相熟的,第一人称就是以本人的口气讲述一件事,例如自传都是以这种模式抒写,第三人称则是以旁观者,例如很多小说,都是以他(xxx)来展开式将的,观众则是以上帝视角看着这整个故事。对应的第一人称视角、第三人称视角也是雷同的概念,只不过是视觉下面。那么他们各自有下面区别呢?第一人称视角的有点是能够给玩家带来最大限度的沉迷感,从第一人称视角“我”去察看场景和画面,能够让玩家更加粗疏地感触到其中的细节,最常见的就是相似绝地求生、极品飞车之类的。 而第一人称视角也有他的局限性。玩家的视线受限,无奈看到更广大的的视线。另一个就是第一人称视角会给玩家带来“3D眩晕感”。当反应速度更不上镜头速度的时候会造成眩晕感。那么第三人称视角呢?他的劣势就是自在,视线宽阔,人物挪动和视角是离开的,一个用来操作人物前进方向,另一个则是用来操控视线方向。 它的劣势就是无奈很好的聚焦部分,容易错过细节。然而总的来说,目前大多数游戏都提供了两种视角的切换来满足不同的情景。例如相对求生中平时走路用第三人称视角追随挪动,开枪的时候个别用第一人称视角。好了,到目前为主咱们曾经晓得了第一人称视角、第三人称视角各自概念、区别。那么咱们接下来以第三人称视角为例,开展剖析咱们该如何实现这样的一个成果呢?(第三人称的编写好后,稍加批改就能够变成第一人称,因而以更加简单的第三人称为例)把大象放入冰箱须要几步?三步!关上冰箱,把大象放进冰箱,关上冰箱。显然如果真的要把大象放进冰箱是很难的事件,然而从宏观角度来看,就是三个步骤。因而咱们也将实现第三人称视角这个性能分成三步: 步骤拆分以下的步骤拆分不会蕴含任何代码,请放心使用:1.人物如何静止咱们都晓得在物理实在的世界中,咱们静止起来是靠咱们双腿,迈开就动起来了。那这个过程从更宏观的角度来看是怎么样的呢?其实如果从地球外,从一个更远的角度来看,咱们做静止更像是一个个平移变动。雷同地,咱们在计算机中来示意静止也就是使用了平移变动。平移变动具体大家以前都比拟相熟,如果当初不相熟了呢,也没有什么关系,先看上面的坐标轴。(小方块的边长是1) 小方块从A1地位挪动到地位A2就是平移变动,如果用数学表达式来示意的话就是 下面是什么意思呢?就是说咱们让小方块中所有的小点的 x 值都加2,而 y 的值不变。咱们随便取一些值来验证一下。例如A1地位小方块,左下角是 (0,0), 通过以上变动,就变成了 (2, 0),咱们来A2中看小方块新的地位就是 (2, 0);再用右上角的 (1,1) 代入,发现就变成了(3,1),和咱们实在挪动到的地位也是一样的。所以下面的式子没有什么问题。然而起初呢,大家感觉像下面那样的式子用来示意略微有点不够通用。至于这里为什么说不够通用,在前面的系列文章中会具体解说,因为还波及到了其余变动,例如旋转、缩放,他们都能够用一个矩阵来进行形容,因而如果平移也可能用矩阵的形式来表白,那么整个问题就变得简略了,也就是说:静止变动 = 矩阵变动咱们来看看把最开始的式子变成矩阵是什么样子的: 能够简略解说一下左边这个矩阵是怎么来的 左上角的这个局部称为单位矩阵,前面的 2 0 则就是咱们须要的平移变动,至于为什么从2维变成了3维,则是因为引入了一个齐次矩阵的概念。同样的原理,类比到 3维,咱们就须要用到4维矩阵。所以说,咱们通过一系列的例子,最终想要失去的一个论断就是,所有的静止都是矩阵变动。 2.镜头朝向人物咱们都晓得,在事实世界中咱们眼睛看进来的视线是无限的,在电脑中也是一样的。假如在电脑中咱们的视线是  3 * 3 的方格,咱们还是以之前坐标轴举例子,黄色区域是咱们的视线可见区域: 当初咱们让小块往右挪动3个单位,再网上挪动1个单位。 这个时候咱们会发现,咱们的视线内曾经看不到这个小块了。试想一下,咱们正在玩一个射击游戏,敌人在眼前挪动,咱们为了找到它会在怎么办?没错,咱们会旋转咱们的脑袋,从而使得敌人裸露在咱们的视线内。就像这样: 这下就把敌人锁定住了,可能始终让人物呈现在咱们的视线内并且放弃绝对静止。3.镜头与人物同距光有镜头朝向人物还不够,咱们还得让咱们的镜头和人物同距。为什么这么说呢,首先还是咱们坐标轴的例子,然而这次咱们将裁减一个z轴:而后咱们看看失常下的立体截图 截图: 当初咱们将咱们的小块往-Z 挪动1个单位: 截图: 这个时候咱们发现这个小方块变小了,并且随着小方块往 -z方向挪动的越多,咱们看到的小块会越来越小。这个时候咱们明明没有扭转咱们的视角,然而还是无奈很好的跟踪小块。因而咱们须要挪动为咱们视角的地位,当咱们看不清一个远处的路标的时候,咱们会怎么办?没错,凑近点! 截图: 完满!当初咱们通过三个方向的解说,将如果实现一个第三人称视角的性能从实践下面实现了! 搞代码接下来咱们只须要依照咱们的以上的实践,来实现代码就好了,代码无奈就是咱们用另一种语言的实现形式,晓得了原理都是非常简单的。1.初始化画布场景 ...

March 2, 2022 · 1 min · jiezi

关于three.js:threejs中出现条纹阴影和网格阴影解决办法

ThreeJS 开启暗影正确做法: 渲染器启用暗影 renderer.shadowMap.enabled = true;灯光产生暗影 light.castShadow = true;物体遮挡暗影 obj.castShadow = true;高空显示暗影 obj.receiveShadow = true;无暗影成果: 有暗影成果,产生了许多条纹: 产生起因 将四方块设置为 不接管暗影 obj.receiveShadow = false; 可防止条纹,但四方块上就没有投影成果了。将材质的 material.side = 0; side属性设置为0也能够失常。或者将材质的投影面设置为反面也可解决 material.shadowSide = THREE.BackSide;正确暗影成果: 以上为产生条纹暗影解决办法。 而模型上呈现了网格暗影或投射出的暗影比拟含糊: 则是暗影不够精密导致投射在本身或其余五提倡的暗影呈现锯齿,解决办法:light.shadow.mapSize.width=2048; //暗影贴图宽度设置为2048像素light.shadow.mapSize.height=2048; //暗影贴图高度设置为2048像素(light为你本人定义的可投射暗影灯光) 值的大小能够本人跟据理论状况设置,设置得小一点性能会更加好,设置得大一点成果会更好,不过会占用更多的图像运算资源。 以上。

February 21, 2022 · 1 min · jiezi

关于three.js:THREEjs渲染顺序

本文将会讲述THREE.js渲染程序,这里的渲染程序指的是处于前后地位的物体,是如何渲染进去物体之间的遮挡关系的。 次要的讲述内容包含: 不通明物体的默认渲染程序是怎么的;通明物体的默认渲染程序是怎么的;不通明物体和通明物体一起渲染的时候,默认渲染程序是怎么的;如何扭转物体的默认渲染程序。不通明物体的默认渲染程序是怎么的首先,咱们通过一个简略的例子介绍下咱们要实现的成果: 一个根本场景,场景中搁置两个4X4正方形,正方形与XOY立体平行,红色正方形放在(0, 0, 0)的地位,绿色正方形放在(2, 0, -2)的地位。 相机应用THREE.PerspectiveCamera,这种相机会有近大远小的成果,相机搁置在(0, 0, 6)的地位,此时,相机看向Z轴的负方向。 相机和两个立体的空间地位如下图1、图2所示:图1 图2 通过相机和两个立体的空间地位关系,咱们晓得 红色立体在绿色立体的后面显示;红色立体会遮挡绿色立体的一部分;最初出现成果如下图3所示:图3 此时,你能够先本人思考一下,如果让你本人实现,你会如何实现? 我过后本人想了一下,首先咱们是晓得这两个物体和相机之间的间隔的,咱们能够依照物体间隔相机的远近给物体排个序,而后依照由远及近的程序渲染物体。也就是先渲染绿色的立体,而后渲染红色的立体。下面这个简略的例子没有问题,那对其余简单的场景是否实用呢? 比方,这两个立体间隔相机的地位是一样的?如下图4所示:图4 依照咱们方才的构想,对于上述情景,咱们渲染进去的是要么绿色齐全在下面,要么红色齐全在下面,如下图5或图6所示:图5图6 然而理论的渲染后果应该如下图7所示:图7 所以上述依照由远及近的程序渲染的计划只能满足局部应用场景。那么,THREE.js是如何实现的呢? THREE.js次要是应用WebGL构建3D场景的开源库,是对WebGL提供的能力的一个易用性封装。所以在探讨THREE.js中物体的渲染程序之前就得先看下WebGL是如何渲染不同地位的物体的。而WebGL是基于OpenGL的,所以这个问题就变成了OpenGL是如何渲染不同地位的物体的。 OpenGL是应用深度测试(depth testing)来保障物体渲染进去正确的遮挡关系的。深度测试应用了深度缓冲区(depth buffer)。和色彩缓冲区(color buffer)相似,只不过色彩缓冲区中存储的是每个像素点的色值,而深度缓冲区存储的是色彩缓冲区以后像素点的色值所在的深度。深度缓冲区和色彩缓冲区具备雷同的宽度和高度。 那么,在渲染过程中,对于每一个像素点,咱们存储的数据包含: 以后像素的色值(通过色彩缓冲区获取);以后像素对应的物体片元在空间中的深度(通过深度缓冲区获取)。上面,咱们通过上述图4的例子阐明一下,OpenGL是如何通过深度测试(depth testing)实现物体正确的遮挡关系的。 假如物体先绘制绿色的立体,物体在绘制的时候是逐像素绘制的,此时咱们已知的信息包含像素的色值和深度信息。对于绿色立体投影到的每一个像素,咱们在色彩缓冲区中该像素的地位写入色值,在深度缓冲区中该像素的地位写入深度信息。 而后,咱们开始绘制红色的物体。当绘制红色物体的每一个像素时,咱们晓得了该像素的色值和深度信息D2。而后,咱们依据该像素的坐标,获取深度缓冲区中已绘制像素对应的深度值D1,而后比拟D1和D2的值。有以下三种状况: 如果D2小于D1,那就是该物体的以后像素在后面,也就是该像素应该取该物体的以后像素的色值。此时,更新色彩缓冲区以后像素的色值为红色,更新深度缓冲区以后像素的深度为D2;如果D2大于D1,那就是该物体的以后像素在前面,也就是该像素不会显示进去,所以以后像素的色彩缓冲区和深度缓冲区的值都不必扭转;如果D2等于D1,行为和D2小于D1统一。总结来说,就是咱们有一个判断该像素点是否渲染的函数。该函数的输出是 以后期待渲染的像素点的深度值;深度缓冲区中以后像素点的深度值。输入是一个布尔值表明以后像素是否应用新的色值渲染。 THREE.js中该函数的默认值是LessEqualDepth,也就是上述D2和D1比拟的三种状况。该函数的所有取值能够参考THREE.js官网Depth Mode。 所以,还是下面那个例子: 对于红色物体右边的每一个像素,该深度值D2小于深度缓冲区中的深度值D1,所以色彩缓冲区更新为红色。对于红色物体左边的每一个像素,该深度值D2大于深度缓冲区中的深度值D1,所以放弃原来的色彩。最终的后果就是左半边是红色,右半边是绿色。 咱们下面剖析了先绘制绿色立体,再绘制红色立体的状况。你能够本人尝试剖析先绘制红色立体,再渲染绿色立体的状况。 最初的后果就是渲染后果的遮挡关系根本和绘制的先后顺序无关。 通明物体的默认渲染程序是怎么的后面讲述了不通明物体的渲染程序,那么,如果场景中的物体都是通明物体的时候,又是如何渲染的呢? 还是拿后面的例子举例,此时咱们把两个立体都设置成半透明的。如下图8所示:图8 如果还是用后面那个逻辑,每个像素点的色彩要么不变,要么应用新物体的色彩,加上深度测试的逻辑之后,渲染进去的成果如下图9所示:图9 在现实生活中,透过通明的物体咱们应该是能够看到该通明物体前面的物体的。显然,图9并没有实现这样的成果。那么,问题出在什么中央呢? 咱们后面在深度测试的时候,在往色彩缓冲区中写入色值的时候,要么写入以后物体的色值,要么抛弃以后物体的色值。而对于通明物体来说,最终显示的色值并不是单个物体的色彩,而是多个可见物体色彩的一个混合(blend)。 那么,在后面步骤中,当咱们判断以后物体是在后面时,能够从简略粗犷的间接应用该色值变为依据以后物体的色值和在色彩缓冲区中的色值依照透明度进行一个混合,而后应用混合后的色值更新色彩缓冲区。 THREE.js提供了多种blend办法,默认是NormalBlending。NormalBlending的计算公式如下:color(RGB) = (sourceColor * sourceAlpha) + (destinationColor * (1 - sourceAlpha))color(A) = (sourceAlpha * 1) + (destinationAlpha * (1 - sourceAlpha)) 增加上混合逻辑,最初实现进去的成果如下图10所示,这个成果也是合乎咱们心理预期的一个成果:图10 在渲染不通明物体的时候,咱们发现最终的实现成果和物体绘制的前后程序没有关系。那么,对于通明物体呢?咱们试验一下: 先渲染红色立体,再渲染绿色立体渲染成果如下图11所示:图11 ...

January 4, 2022 · 1 min · jiezi

关于three.js:小程序中开发Threejs教程及踩坑记录

一、后期调研因小程序环境下,没有浏览器相干的运行环境如window, document等,所以在小程序中应用three.js须要应用相应的移植版本。调研了以下三种:①微信官网给出的版本:https://github.com/wechat-min...加载不带贴图的glb文件没问题。但加载带贴图的glb文件报错,对loader文件没有做移植,并且看官网不怎么保护了。②three平台化产品,然而感觉上手不是特地敌对,并且比拟大。https://github.com/deepkolos/...③yannliao微信官网社区里比拟热门的版本https://github.com/yannliao/t... 最终应用了第三种,对glb带材质的文件也能失常加载。 二、应用教程1、在示例仓库(https://github.com/yannliao/t...)中,下载 libs及jsm文件夹,放入本人我的项目根目录下2、在index.js页面的js文件下引入three import * as THREE from '../../libs/three.weapp.min.js'import { OrbitControls } from '../../jsm/loaders/OrbitControls'Page({ data: {}, onLoad: function () { wx.createSelectorQuery() .select('#c') .node() .exec((res) => { const canvas = THREE.global.registerCanvas(res[0].node) this.setData({ canvasId: canvas._canvasId }) const camera = new THREE.PerspectiveCamera(70, canvas.width / canvas.height, 1, 1000); camera.position.z = 500; const scene = new THREE.Scene(); scene.background = new THREE.Color(0xAAAAAA); const renderer = new THREE.WebGLRenderer({ antialias: true }); const controls = new OrbitControls(camera, renderer.domElement); // controls.enableDamping = true; // controls.dampingFactor = 0.25; // controls.enableZoom = false; camera.position.set(200, 200, 500); controls.update(); const geometry = new THREE.BoxBufferGeometry(200, 200, 200); const texture = new THREE.TextureLoader().load('./pikachu.png'); const material = new THREE.MeshBasicMaterial({ map: texture }); // const material = new THREE.MeshBasicMaterial({ color: 0x44aa88 }); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh); // renderer.setPixelRatio(wx.getSystemInfoSync().pixelRatio); // renderer.setSize(canvas.width, canvas.height); function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(canvas.width, canvas.height); } function render() { canvas.requestAnimationFrame(render); // mesh.rotation.x += 0.005; // mesh.rotation.y += 0.01; controls.update(); renderer.render(scene, camera); } render() }) }, onUnload: function () { THREE.global.unregisterCanvas(this.data.canvasId) }, touchStart(e) { console.log('canvas', e) THREE.global.touchEventHandlerFactory('canvas', 'touchstart')(e) }, touchMove(e) { console.log('canvas', e) THREE.global.touchEventHandlerFactory('canvas', 'touchmove')(e) }, touchEnd(e) { console.log('canvas', e) THREE.global.touchEventHandlerFactory('canvas', 'touchend')(e) }, touchCancel(e) { // console.log('canvas', e) }, longTap(e) { // console.log('canvas', e) }, tap(e) { // console.log('canvas', e) }, documentTouchStart(e) { // console.log('document',e) }, documentTouchMove(e) { // console.log('document',e) }, documentTouchEnd(e) { // console.log('document',e) },})3、 在index.wxml中退出canvas组件, 其中须要手动绑定相应的事件,用于手势管制。 ...

December 20, 2021 · 2 min · jiezi

关于three.js:Threejs-新旧版本创建几何对象差异

昨天遇到个小问题,也搞了挺长时间:用Three.js在webgl中绘制三维对象,从网上抄的一段代码,运行报错: THREE.Geometry is not a constructor 纳尼,找不到Geometry对象? function createLine(){ let start = new THREE.Vector3(7, 1.8, -2); let end = new THREE.Vector3(1.6,1.8, -2); // 就是这里创立的对象找不到 ***** let geometry = new THREE.Geometry(); // **************************** let lineMaterial = new THREE.LineBasicMaterial({color: 0x00ff00}); geometry.vertices.push(start); geometry.vertices.push(end); let line = new THREE.Line(lineGeometry, lineMaterial); scene.add(line); return line;}一开始还认为是模块导入问题,折腾了一阵才发现,THREE模块中居然真的就没有Geometry!难道网上的代码是愚人节贴的?遇事不决问google,终于确认人家是降级了。新版的Three.js不再提供Geometry对象,代之以BufferGeometry,代码批改如下: function createLine(){ let start = new THREE.Vector3(7, 1.8, -2); let end = new THREE.Vector3(1.6,1.8, -2); let lineMaterial = new THREE.LineBasicMaterial({color: 0x00ff00}); let pointsArray = [start, end]; let lineGeometry = new THREE.BufferGeometry().setFromPoints(pointsArray); let line = new THREE.Line(lineGeometry, lineMaterial); scene.add(line); return line;}

December 16, 2021 · 1 min · jiezi

关于three.js:Threejs学习-创建立方体

Three.js 零根底入门教程(郭隆邦)Three.js 中文文档First Scene几何体 GeometryTHREE.BoxGeometry(宽, 高, 长) 立方体THREE.SphereGeometry(半径, number, number) 球体var geometry = new THREE.BoxGeometry(100, 100, 100);var geometry = new THREE.SphereGeometry(60, 40, 40); 资料 Material对立方体的色彩、透明度等等属性进行设置 var material = new THREE.MeshLambertMaterial({ color: 0x0000ff });光源 Light创立一个点光源,参数定义光照强度,光源强度变低时,物体也随之变黯淡 var point = new THREE.PointLight(0xffffff);相机 CameraTHREE.OrthographicCamera()创立一个正射投影相机对象,参数扭转拍照窗口的大小。取景范畴变小,物体变大,反之变小。 var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);整体构造 过程THREE.Scene() 创立场景对象发明物体对象 new THREE.BoxGeometry 创立物体new THREE.MeshLambertMaterial 应用资料new THREE.Mesh(box,material) 创立网格,增加物体new THREE.PointLight(0xffffff) 创立光源new THREE.AmbientLight(0x444444) 创立环境光scene.add(mesg/point/ambient) 向场景中增加对象THREE.OrthographicCamera() 创立相机对象THREE.WebGLRenderer() 创立渲染对象renderer.render(scene,camera) 投影插入多个几何体几何体//长方体 参数:长,宽,高var geometry = new THREE.BoxGeometry(100, 100, 100);// 球体 参数:半径60 经纬度细分数40,40var geometry = new THREE.SphereGeometry(60, 40, 40);// 圆柱 参数:圆柱面顶部、底部直径50,50 高度100 圆周分段数var geometry = new THREE.CylinderGeometry(50, 50, 100, 25);// 正八面体var geometry = new THREE.OctahedronGeometry(50);// 正十二面体var geometry = new THREE.DodecahedronGeometry(50);// 正二十面体var geometry = new THREE.IcosahedronGeometry(50);辅助三维坐标系 AxisHelper// 辅助坐标系 参数250示意坐标系大小,能够依据场景大小去设置var axisHelper = new THREE.AxisHelper(250);scene.add(axisHelper);同时绘制多个几何体代码var geometry1 = new THREE.BoxGeometry(100, 100, 100);var material1 = new THREE.MeshLambertMaterial({ color: 0x0000ff,});var mesh1 = new THREE.Mesh(geometry1, material1);scene.add(mesh1);var geometry2 = new THREE.SphereGeometry(60, 40, 40);var material2 = new THREE.MeshLambertMaterial({ color: 0xff00ff,});var mesh2 = new THREE.Mesh(geometry2, material2);mesh2.translateX(70); //球体网格模型沿Y轴正方向平移120scene.add(mesh2);var geometry3 = new THREE.CylinderGeometry(50, 50, 100, 25);var material3 = new THREE.MeshLambertMaterial({ color: 0xffff00,});var mesh3 = new THREE.Mesh(geometry3, material3);// mesh3.translateX(120); //球体网格模型沿Y轴正方向平移120mesh3.position.set(120, 0, 0); //设置mesh3模型对象的xyz坐标为120,0,0scene.add(mesh3);成果 ...

December 14, 2021 · 2 min · jiezi

关于three.js:ThreeJS-模型点击事件添加

created () { //申明raycaster和mouse变量 var raycaster = new THREE.Raycaster(); var mouse = new THREE.Vector2();},mounted () { window.addEventListener( 'click', onMouseClick, false );},methods: { onMouseClick( event ) { //通过鼠标点击的地位计算出raycaster所须要的点的地位,以屏幕核心为原点,值的范畴为-1到1. mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1; mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1; // 通过鼠标点的地位和以后相机的矩阵计算出raycaster raycaster.setFromCamera( mouse, camera ); // 获取raycaster直线和所有模型相交的数组汇合 var intersects = raycaster.intersectObjects( scene.children ); console.log(intersects); //将所有的相交的模型的色彩设置为红色,如果只须要将第一个触发事件,那就数组的第一个模型扭转色彩即可 for ( var i = 0; i < intersects.length; i++ ) { intersects[ i ].object.material.color.set( 0xff0000 ); } }}

September 11, 2021 · 1 min · jiezi

关于three.js:Threejs-学习路程

记录一下集体学习 Three.js 的历程 以下链接的次要内容蕴含:根本文档 + 前期解决通道 + 模型换肤 + 组案例 + 多通道渲染等,内容比拟系统,就不一一阐明了。 文档学习:Three.js 零根底入门Three.js 文档后处理通道 案例演示:官网案例演示(含预览图)官网案例(含预览图)智慧城市案例模型旁增加文字正文1模型旁增加文字正文2星云图陨石火球模型换肤(可搜 start.jpg 查看实现)太阳系行星运行图水波球创立文字流光路线 纯特效展现(无代码):特效展现一特效展现二 UnrealBloomPass 通道相干案例文章:高亮空幻成果实现分区 Bloom 辉光物体高亮 模型相干文章:模型边界 扩大:泛光WebGL学习之HDR与BloomHDR和Bloom 如有侵权请告知

August 24, 2021 · 1 min · jiezi

关于three.js:Threejs实现穿越云层动效

上文说到,我对《你的性情主导色》流动中最感兴趣的局部就是通过 Three.js 实现穿梭云层动效了,据作者说每朵云呈现的地位都是随机的,成果很好,下图是我实现的版本。 在线 Demo 首先说下实现穿梭云层动效的基本思路: 沿着 Z 轴平均的放一堆 64*64 的立体图形,这些立体的 X 坐标和 Y 坐标是随机的(很像下图的桶装薯片)把下面的所有图形合并成一个大的图形把大的图形和贴片材质(云)生成网格,网格放进场景中动效就是将相机从远处沿着 Z 轴迟缓挪动,就会有了穿梭云层的成果 首先官网文档提供了一个创立一个场景的疾速开始,浏览后能够对上面的内容更好的了解。 上面介绍下Three.js中的基本概念。仅限我这老手的了解。有讲的好的文档或者分享,欢送帮忙指个路。 场景场景就是一块空间,用来装下咱们想要渲染的内容。最简略的用途就是,场景能够增加一个网格,而后渲染进去。 // 初始化场景var scene = new THREE.Scene();// 其余代码...// 把物体增加进场景scene.add(mesh);// 渲染场景renderer.render(scene, camera);这里说下场景中的坐标规定:原点是 canvas 的立体核心,Z 轴垂直于 X、Y 轴,正向是冲着咱们的,我这里把 Z 轴的线做了些旋转,不然咱们看不到,如下图: 代码: const scene = new THREE.Scene();var camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);camera.position.set(0, 0, 100);const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);// 线段1,红色的,从原点到X轴40const points = [];points.push(new THREE.Vector3(0, 0, 0));points.push(new THREE.Vector3(40, 0, 0));const geometry1 = new THREE.BufferGeometry().setFromPoints(points);var material1 = new THREE.LineBasicMaterial({ color: 'red' });var line1 = new THREE.Line(geometry1, material1);// 线段2,蓝色的,从原点到Y轴40points.length = 0;points.push(new THREE.Vector3(0, 0, 0));points.push(new THREE.Vector3(0, 40, 0));const geometry2 = new THREE.BufferGeometry().setFromPoints(points);var material2 = new THREE.LineBasicMaterial({ color: 'blue' });var line2 = new THREE.Line(geometry2, material2);// 线段3,绿色的,从原点到Z轴40points.length = 0;points.push(new THREE.Vector3(0, 0, 0));points.push(new THREE.Vector3(0, 0, 40));const geometry3 = new THREE.BufferGeometry().setFromPoints(points);var material3 = new THREE.LineBasicMaterial({ color: 'green' });var line3 = new THREE.Line(geometry3, material3);// 做了个旋转,不然看不到Z轴上的线line3.rotateX(Math.PI / 8);line3.rotateY(-Math.PI / 8);scene.add(line1, line2, line3);renderer.render(scene, camera);相机场景内的物体要想被咱们看见,也就是渲染进去,须要相机去“看”,通过下面的坐标系图,咱们晓得同一个物体,相机察看的角度不同,必定也会呈现出不一样的画面。最罕用的就是这里用的透视相机,能够穿透物体,用在这里正好穿透云层,成果拔群。 ...

August 17, 2021 · 3 min · jiezi

关于three.js:带你入门threejs从0到1实现一个3d可视化地图

前言终于到周末了,前几篇的文章始终给大家介绍2d,canvas 和svg的一些货色。7月份我打算输入3篇万字长文带大家系统地学习可视化表白的3种形式,svg、canvas、webgl。所以这是第一篇文章3d的。 读完本篇文章,你能够学到什么 对于three.js 这个框架有一个简略的了解,能够入门下。学习three中的Raycaster,次要是用鼠标来判断以后抉择的是哪一个物体。我用一个简略的实例 带大家用three实现简略的可视化地球案例 。3d框架的抉择——three.js1.为什么抉择three.js 官网对 Threejs 的介绍非常简单:“Javascript 3D library”。openGL 是一个跨平台3D/2D的绘图规范,WebGL 则是openGL 在浏览器上的一个实现。web前端开发人员能够间接用WebGL 接口进行编程,但 WebGL 只是十分根底的绘图API,须要编程人员有很多的数学知识、绘图常识能力实现3D编程工作,而且代码量微小。Threejs 对 WebGL 进行了封装,让前端开发人员在不须要把握很多数学知识和绘图常识的状况下,也可能轻松进行web 3D开发,升高了门槛,同时大大晋升了效率。总结来一句话: 就是你不懂计算机图形学,只有了解了three.js的一些基本概念你能够。 Threejs 的基本要素——场景定义如下: 场景:是一个三维空间,所有物品的容器,能够把场景设想成一个空房间,接下来咱们会往房间里放要出现的物体、相机、光源等。用代码示意就是如下: const scene = new THREE.Scene();你就把他设想成一个房间,而后你能够往里面去增加一些物体,加一个正方体哈,加矩形,什么都能够。其实three.js 整个之间的关系是一个 树形构造。 Threejs 的基本要素——相机相机:Threejs必须要往场景中增加一个相机,相机用来确定地位、方向、角度,相机看到的内容就是咱们最总在屏幕上看到的内容。在程序运行过程中,能够调整相机的地位、方向和角度。three.js 中的相机分为两种一种是正交相机 和透视相机,接下来我给大家一一介绍,然而了解照相机的状况下,你要先了解一个概念——视椎体 透视相机视锥体是摄像机可见的空间,看上去像截掉顶部的金字塔。视锥体由6个裁剪面围成,形成视锥体的4个侧面称为上左下右面,别离对应屏幕的四个边界。为了避免物体离摄像机过近,设置近切面,同时为了避免物体离摄像机太远而不可见,设置远切面。 oc 就是照相机的地位, 近立体、和远平面图中曾经标注。从图中能够看出,棱台组成的6个面之内的货色,是能够被看到的。 影响透视照相机的大小因素: 摄像机视锥体垂直视线角度 也就是图中的a摄像机视锥体近端面 也就是图中的 near plane摄像机视锥体远端面 也就是图中的far plane摄像机视锥体长宽比 示意输入图像的宽和高之比对应的three 中的照相机: const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 ); 透视相机最大的特点:就是合乎咱们人眼察看事物的特点, 近大远小。 近大远小的背地的实现原理就是相机会有一个投影矩阵: 投影矩阵的做的事件很简略,就是把视椎体转换成一个正方体。 所以远截面的点就要放大, 近距离的反而放大。 ...

July 4, 2021 · 5 min · jiezi

关于three.js:借助threejs实现全景图片360°预览

简介上篇文章前端如何实现一个图片笼罩的球体,咱们借助three.js实现了一个3D的旋转球体。这篇文章咱们略微革新下代码,实现一个全景图片预览示例。本文会应用three.js以及插件来实现3种demo demo&&代码1.three.js实现 2.借助JeremyHeleine/Photo-Sphere-Viewer插件实现 3.借助mistic100/Photo-Sphere-Viewer插件实现 · 示例代码地址(gitee) 启动我的项目须要动态服务器以下示例只展现要害代码,残缺代理去仓库查看即可1.three.js实现预览地址残缺代码 创立场景//globevar width = window.innerWidth;var height = window.innerHeight;const textureLoader = new THREE.TextureLoader();//scenevar scene = new THREE.Scene();//cameravar camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 10000);camera.position.y = 0;camera.position.z = 500;scene.add(camera);创立图片以及球体这里咱们为了实现全景,球的半径能够适当做大一些,就有那个全景的感觉了 //imgTexturevar imgTexture = textureLoader.load("./park.jpg");imgTexture.minFilter = THREE.LinearFilter;imgTexture.magFilter = THREE.LinearFilter;imgTexture.format = THREE.RGBFormat;var cubeGeometry = new THREE.SphereGeometry(500, 60, 40);var sphereMat = new THREE.MeshBasicMaterial({ map: imgTexture });sphereMat.side = THREE.BackSide;var cube = new THREE.Mesh(cubeGeometry, sphereMat);scene.add(cube);要害代码var cubeGeometry = new THREE.SphereGeometry(500, 60, 40);咱们把半径改为50看下改为500再看下, ...

July 2, 2021 · 1 min · jiezi

关于three.js:前端如何实现一个图片覆盖的球体

简介做展现类我的项目的时候,通常要实现一些圆形抉择的成果。这个借助css的rotate 就能够实现.然而!要实现一个3D的带图片的旋转球体该怎么做呢?让咱们开始吧。 残缺代码codesandbox残缺代码gitee预览地址备用预览地址工具three.jsOrbitControls 绘制创立一个场景将场景借助three.js来进行显示,咱们须要以下几个对象:场景、相机和渲染器,这样咱们就能透过摄像机渲染出场景。 let camera, scene, renderer;// scenescene = new THREE.Scene();// cameracamera = new THREE.PerspectiveCamera( 10, window.innerWidth / window.innerHeight, 0.1, 200);camera.position.set(30, 5, 20);//rendererrenderer = new THREE.WebGLRenderer();renderer.setPixelRatio(window.devicePixelRatio);renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);咱们花一点点工夫来解释一下这里产生了什么。咱们当初建设了场景、相机和渲染器。 three.js里有几种不同的相机,在这里,咱们应用的是PerspectiveCamera(透视摄像机)。 第一个参数是视线角度(FOV)。视线角度就是无论在什么时候,你所能在显示器上看到的场景的范畴,数值越大越远。 第二个参数是长宽比(aspect ratio)。 也就是你用一个物体的宽除以它的高的值。比如说,当你在一个宽屏电视上播放老电影时,能够看到图像好像是被压扁的。 接下来的两个参数是近截面(near)和远截面(far)。 当物体某些局部比摄像机的远截面远或者比近截面近的时候,该这些局部将不会被渲染到场景中。或者当初你不必放心这个值的影响,但将来为了取得更好的渲染性能,你将能够在你的应用程序里去设置它。 接下来是渲染器。这里是施展魔法的中央。除了咱们在这里用到的WebGLRenderer渲染器之外,Three.js同时提供了其余几种渲染器,当用户所应用的浏览器过于老旧,或者因为其余起因不反对WebGL时,能够应用这几种渲染器进行降级。 除了创立一个渲染器的实例之外,咱们还须要在咱们的应用程序里设置一个渲染器的尺寸。比如说,咱们能够应用所须要的渲染区域的宽高,来让渲染器渲染出的场景填充斥咱们的应用程序。因而,咱们能够将渲染器宽高设置为浏览器窗口宽高。对于性能比拟敏感的应用程序来说,你能够应用setSize传入一个较小的值,例如window.innerWidth/2和window.innerHeight/2,这将使得应用程序在渲染时,以一半的长宽尺寸渲染场景。 如果你心愿放弃你的应用程序的尺寸,然而以较低的分辨率来渲染,你能够在调用setSize时,将updateStyle(第三个参数)设为false。例如,假如你的<canvas> 标签当初曾经具备了100%的宽和高,调用setSize(window.innerWidth/2, window.innerHeight/2, false)将使得你的应用程序以一半的分辨率来进行渲染。 最初一步很重要,咱们将renderer(渲染器)的dom元素(renderer.domElement)增加到咱们的HTML文档中。这就是渲染器用来显示场景给咱们看的<canvas>元素。 “嗯,看起来很不错,那你说的那个球体在哪儿?”接下来,咱们就来增加球体。 创立球体let earth;const earthGeometry = new THREE.SphereGeometry(2, 16, 16);const earthMaterial = new THREE.MeshBasicMaterial({ specular: 0x333333, shininess: 5, map: textureLoader.load("./img/earth_atmos_2048.jpg"), specularMap: textureLoader.load("./img/earth_specular_2048.jpg"), normalMap: textureLoader.load("./img/earth_normal_2048.jpg"), normalScale: new THREE.Vector2(0.85, 0.85),});earth = new THREE.Mesh(earthGeometry, earthMaterial);scene.add(earth);// controlsconst controls = new THREE.OrbitControls(camera, renderer.domElement);controls.minDistance = 5;controls.maxDistance = 100;controls.enablePan = false;要创立一个球体,咱们须要一个SphereGeometry(球缓冲几何体)对象. 这个对象该几何体是通过扫描并计算围绕着Y轴(程度扫描)和X轴(垂直扫描)的顶点来创立的 ...

July 1, 2021 · 1 min · jiezi

关于three.js:threejs-glsl-用-shader-写出夜空中的萤火虫

参考课程是这位老哥的 three.js 教程 十分厉害 感兴趣的能够买来看看如果自身对 shader 还不够理解的话,the book of shaders 是十分好的入门读物说到做“萤火虫”,必定会想到用“粒子”(particles)。 用 buffer geometry 创立粒子首先定义一个 buffer geometry, 而后创立一个 BufferAttribute 用于存储每一个萤火虫的地位信息; const firefliesGeometry = new THREE.BufferGeometry()const firefliesCount = 30 //创立萤火虫的个数const positionArray = new Float32Array(firefliesCount * 3)for(let i = 0; i < firefliesCount; i++){ positionArray[i * 3 + 0] = Math.random() * 4 positionArray[i * 3 + 1] = Math.random() * 4 positionArray[i * 3 + 2] = Math.random() * 4}firefliesGeometry.setAttribute('position', new THREE.BufferAttribute(positionArray, 3))咱们先给它一个 pointsMaterial 看看这些点都在哪里; ...

June 27, 2021 · 2 min · jiezi

关于three.js:threejs添加警报点

1.初始化scene,创立警报点增加到scene serverGroup = new THREE.Group();scene.add(serverGroup);pointGroup = this.addAletPoint([1.5,112,0])pointGroup.mesh.name = 'zhongxin'serverGroup.add(pointGroup.mesh);serverGroup.add(pointGroup.mesh2);// 增加警报点addAletPoint(position) { let vertexShader = [ 'varying vec3 vVertexWorldPosition;', 'varying vec3 vVertexNormal;', 'varying vec4 vFragColor;', 'void main(){', ' vVertexNormal = normalize(normalMatrix * normal);', //将法线转换到视图坐标系中 ' vVertexWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;', //将顶点转换到世界坐标系中 ' // set gl_Position', ' gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);', '}' ].join('\n') let fragmentShader1 = [ 'uniform vec3 glowColor;', 'uniform float coeficient;', 'uniform float power;', 'varying vec3 vVertexNormal;', 'varying vec3 vVertexWorldPosition;', 'varying vec4 vFragColor;', 'void main(){', ' vec3 worldCameraToVertex= vVertexWorldPosition - cameraPosition;', //世界坐标系中从相机地位到顶点地位的间隔 ' vec3 viewCameraToVertex = (viewMatrix * vec4(worldCameraToVertex, 0.0)).xyz;', //视图坐标系中从相机地位到顶点地位的间隔 ' viewCameraToVertex = normalize(viewCameraToVertex);', //规一化 ' float intensity = pow(coeficient + dot(vVertexNormal, viewCameraToVertex), power);', ' gl_FragColor = vec4(glowColor, intensity);', '}' ].join('\n') //实质透明度递加 let sphere = new THREE.SphereBufferGeometry(2, 16, 16) let material = new THREE.ShaderMaterial({ uniforms: { coeficient: { type: 'f', value: 1.0 }, power: { type: 'f', value: 1 }, glowColor: { type: 'c', value: new THREE.Color('#ff0000') } }, vertexShader: vertexShader, fragmentShader: fragmentShader1, blending: THREE.NormalBlending, // depthWrite:false, // depthTest: true, transparent: true }) let mesh = new THREE.Mesh(sphere, material) let sphere2 = new THREE.SphereBufferGeometry(0.1, 16, 16) let material2 = new THREE.MeshPhongMaterial({ color: new THREE.Color('#ff0000') // depthWrite:false, // depthTest: true }) let mesh2 = new THREE.Mesh(sphere2, material2) mesh.position.set(...position) // ,高度, mesh2.position.set(...position) // ,高度, return { mesh: mesh, mesh2: mesh2 }},2.原作者地址:https://segmentfault.com/u/yo...3.管制警报点显示以及闪动(lightI在data()曾经初始化了,作为光源的直径大小,通过scale来管制) ...

June 24, 2021 · 2 min · jiezi

关于three.js:Threejs-raycaster当canvas不是满屏幕时点击捕捉模型

let a = document.getElementsByTagName("canvas") var canvas for(let i of a){ if(i.parentElement&&i.parentElement.id&&i.parentElement.id =="modelBox"){ canvas = i } } let x = ((event.clientX - canvas.getBoundingClientRect().left) / canvas.offsetWidth) *2-1;// 规范设施横坐标 // 这里的mainCanvas是个dom元素,getBoundingClientRectangle会返回以后元素的视口大小. let y = -((event.clientY - canvas.getBoundingClientRect().top) / canvas.offsetHeight) *2+1;// 规范设施纵坐标 let standardVector =new THREE.Vector3(x, y,1);// 规范设施坐标 // 规范设施坐标转世界坐标 let worldVector = standardVector.unproject(camera); // 射线投射方向单位向量(worldVector坐标减相机地位坐标) let ray = worldVector.sub(camera.position).normalize(); // 创立射线投射器对象 let raycaster =new THREE.Raycaster(camera.position, ray); // 获取raycaster直线和所有模型相交的数组汇合 var intersects = raycaster.intersectObjects(scene.children, true);这里定位的modelBox是threejs的canvas的父辈div的id ...

December 11, 2020 · 1 min · jiezi

关于three.js:精读用threejs实现全景图

之前做组内分享,选了threejs来学习一下,接下来就把用threejs实现的全景图来解读一下。 1. three.js根底回顾1.1 three.js概览1. 是什么? Three.js 是一款 webGL 框架,它封装了底层的图形接口,使得可能在无需把握繁冗的图形学常识的状况下,也能用简略的代码实现三维场景的渲染。 2. 外围 渲染器: 将场景中的物体进行渲染场景: 场景是所有物体的容器,也对应着咱们创立的三维世界相机: 一种投影形式,将三维的场景显示到二维的屏幕上1.2 相机1. 是什么 咱们应用Three.js创立的场景是三维的,而通常状况下显示屏是二维的,那么三维的场景如何显示到二维的显示屏上呢?照相机就是这样一个形象,它定义了三维空间到二维屏幕的投影形式,用“照相机”这样一个类比,能够使咱们直观地了解这一投影形式。 2. 分类 正交投影照相机: 对于在三维空间内平行的线,投影到二维空间中也肯定是平行的透视投影照相机: 有近大远小的成果1.3 模型1. 次要有以下几种模型 2. 模型包含什么 几何形态材质1.4 光与影1. 分类 环境光指场景整体的光照成果。是因为场景内若干光源的屡次反射造成的亮度统一的成果,通常用来为整个场景指定一个根底亮度 点光源点光源是不计光源大小,能够看作一个点收回的光源。点光源照到不同物体外表的亮度是线性递加的 平行光对于任意平行的立体,平行光照耀的亮度都是雷同的 聚光灯聚光灯是一种非凡的点光源,它可能朝着一个方向投射光线 暗影明暗是绝对的,暗影的造成也就是因为比四周取得的光照更少。因而,要造成暗影,光源必不可少。 2. 全景图的实现建设球体模型并令照相机位于球体中一直挪动照相机的地位使其察看到球体内的各个角度2.1 建设根本场景初始化渲染器const setupRenderer = () => { const renderer = new THREE.WebGLRenderer({antialias: true}) renderer.setSize(window.innerWidth, window.innerHeight) document.body.appendChild(renderer.domElement) return renderer}初始化相机// 建设透视投影相机制作出“近大远小”的成果const setupCamera = () => { const aspectRatio = window.innerWidth / window.innerHeight const camera = new THREE.PerspectiveCamera(90, aspectRatio, 0.0001, 10000) camera.position.set(window.obj.camerax, window.obj.cameray, window.obj.cameraz) return camera}初始化场景scene和辅助线 const scene = new THREE.Scene() const axesHelper = new THREE.AxesHelper(1000) const cameraHelper = new THREE.CameraHelper(camera) const gridHelper = new THREE.GridHelper(1000, 10) // 安排场景 scene.add(axesHelper) scene.add(cameraHelper) scene.add(gridHelper)建设球体模型// 采纳加载纹理贴图的形式将一张一般的全景图贴在球体的外表const sphereGeo = new THREE.SphereGeometry(radius)const sphereMaterial = new THREE.MeshBasicMaterial({ map: texture, side: THREE.DoubleSide,})const sphere = new THREE.Mesh(sphereGeo, sphereMaterial)这样,根本的场景就展示进去了。此时咱们只能看到球中的一个方向。 ...

November 7, 2020 · 3 min · jiezi

关于three.js:炫酷用Perlin-Noise生成随机地形

良久不发THREE.js的教程了,明天给大家带了一个小案例,介绍如何用柏林噪声在THREE.js中生成随机地形,先来看下成果。 随机数和噪声 噪声实际上就是随机数生成器,一般噪声生成的随机数毫无法则可言。 在上世纪80年代,柏林噪声的发明者Ken Perlin尝试用一般噪声模仿随机成果,用于正在制作的经典迪士尼电影电子世界争霸战(TRON),但始终无奈失去称心的成果,柏林噪声算法就这样应运而生。 2010年TRON重拍版本 柏林噪声 自然界中层叠的山峦,大理石的纹理,海面高下起伏的波浪,这些景象看似横七竖八,却都有外在法则可循,一般的噪声无奈模拟出这些天然成果,柏林噪声算法使这些成为可能。 用柏林噪声算法能够失去一组平滑插值的随机数,它们之间互相关联,能够用来生成靠近天然的随机值。下图中,右边是一般噪声生成的随机纹理,左边是柏林噪声生成的纹理,能够看到后者生成的纹理更加天然平滑,随机值之间有显著过渡成果。 我用canvas绘制了两组图像,别离是用两种算法生成一些随机点,而后将这些点连接起来,能够看到右侧柏林噪声失去的是一条绝对平滑的曲线,而右边一般噪声的图形毫无法则可言。 一般噪声和柏林噪声随机点连线 生成随机地形 在THREE.js咱们用一个立体来生成随机地形,立体是由一系列顶点形成的,咱们只须要扭转这些顶点的z轴坐标值,就能产生高下起伏的成果,咱们先用随机噪声来试试。 let pos = this.groundGeo.attributes.position.arrayfor(let i = 0; i < pos.length; i+=3) { pos[i+2] = Math.random() * 2}this.groundGeo.attributes.position.needsUpdate = true 随机噪声产生的地形 能够看到随机噪声产生的地形太过于僵硬,必定不是咱们想要的后果,再来试试柏林噪声。 let pos = this.groundGeo.attributes.position.arrayfor(let i = 0; i < pos.length; i+=3) { pos[i+2] = this.simplex.noise2D(tx, ty) * 2}this.groundGeo.attributes.position.needsUpdate = true咱们失去了更加天然平滑的地形成果,这里咱们应用的Simplex Noise是柏林噪声的更高效,简介的实现,也是柏林噪声的作者提出的,这里咱们不关怀这些算法的实现细节。 柏林噪声产生的地形成果 挪动地形 要实现地形向观察者挪动,咱们只须要扭转柏林噪声的x坐标,让它随着工夫递增。 let pos = this.groundGeo.attributes.position.arraythis.offsetX += 0.01for(let i = 0; i < pos.length; i+=3) { let tx = this.offsetX let ty = this.offsetY tx += row * 0.05 ty += col * 0.05 pos[i+2] = this.simplex.noise2D(tx, ty) * 5}this.groundGeo.attributes.position.needsUpdate = true这里应用一个offsetX变量,它的值在每帧循环中递增。留神,我只贴了要害代码,具体细节能够点击浏览原文来取得本案例的源文件。 ...

September 9, 2020 · 1 min · jiezi

新手向用Threejs写一个旋转的动态三角形注释超详细

Three.JS是基于WebGL的Javascript开源框架,一模一样的效果我也有用webGL做过,链接是这个https://segmentfault.com/a/11... html部分还是很简单 <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>demoThree</title></head><body> <canvas id="demo-three"></canvas></body><script type="text/javascript" src="./three.js"></script><script type="text/javascript" src="./demo_three.js"></script></html>下面是js部分,每行代码都有比较详细的注释解释 // 定义宽高const width = 400;const height = 400;const canvas = document.getElementById('demo-three');// WebGLRenderer如果不传参数不会把dom元素和最终渲染出来的屏幕上的html上的dom元素相关联,那种情况就需要把domElement,append到body上,才能有相关画布出现let renderer = new THREE.WebGLRenderer({ canvas});// 绘制场景,在scene中维护在场景中渲染的物体let scene = new THREE.Scene();// 使用正交相机(没有透视效果,适合我们这次的平面三角形),正交相机需要定义上下左右前后的坐标情况,从而规定正交相机所能截取到的相关的平面空间中,物体的情况(视角空间的截取)let camera = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, -1000, 1000)// 黑色背景,透明度1.0renderer.setClearColor(new THREE.Color(0x000000), 1.0);// renderer大小renderer.setSize(400, 400);// 在three.js官网可查看,有很多预定义的geometry,但是没有三角形,我们可以通过shapeGeometry来绘制let triangleShape = new THREE.Shape();// 将绘制起始点移动到(const0, 100)这个位置triangleShape.moveTo(0, 100);// 开始划线triangleShape.lineTo(-100, -100);triangleShape.lineTo(100, -100);triangleShape.lineTo(0, 100);// 定义几何体const geometry = new THREE.ShapeGeometry(triangleShape);// 定义材质,三角形只是基本图形,不需要光照const material = new THREE.MeshBasicMaterial({ color: 0xff0000, // 默认是一面的(只渲染法向量所对应那一面),定义为DoubleSide,将正反面都渲染出来 side: THREE.DoubleSide});// 第一个参数geometry,是我们之前定义的几何体的坐标情况,就很像之前的webGl中顶点着色器传入的顶点序列,不涉及颜色信息,只是定义几何体形状// 第二个参数material, 很像webGl中片段着色器中的,给顶点序列上色let mesh = new THREE.Mesh(geometry, material);// 设置mesh在scene中的位置mesh.position.x = 0;mesh.position.y = 0;mesh.position.z = 1;scene.add(mesh);// 相机沿着z轴负方向望进去,所以把相机设置在了原点// 设置位置直接通过position,要比原生webGl去修改往顶点着色器传的数据要方便很多camera.position.x = 0;camera.position.y = 0;camera.position.z = 0;// camera从(0, 0, 0)的位置望向(0,0,1)camera.lookAt(new THREE.Vector3(0, 0, 1));let currentAngle = 0;let lastTimestamp = Date.now();function animate() { // 记下时间,拿到现在的时间戳,因为每次requestAnimationFrame间隔时间是不知道的 const now = Date.now(); // 减去老的时间戳 const duration = now - lastTimestamp; lastTimestamp = now; // 想要requestAnimationFrame间隔时间内转180度 currentAngle = currentAngle + duration / 1000 * Math.PI;}function render() { animate(); // mesh绕y轴旋转currentAngle角度 mesh.rotation.set(0, currentAngle, 0); // 渲染 renderer.render(scene, camera); requestAnimationFrame(render);}render();效果: ...

July 8, 2020 · 1 min · jiezi

threejs-load-obj-文件-合并-group中所有mesh

需求load obj文件得到的是个group 有多个mesh ,现在需要合并,方便做移动操作。 解决方法创建geometry let geometry = new THREE.Geometry();遍历 merge for(let i = 0;i<arr.length;i++){ let item = new THREE.Geometry().fromBufferGeometry( arr[i].geometry ) geometry.merge( item,arr[i].matrix ); }注意尝试过 BufferGeometry 并不好用,所以这里吧 Geometry 转成了 BufferGeometry

June 8, 2020 · 1 min · jiezi

Threejs-in-autonomous-driving-5图片替代模型

在自动驾驶实际中的研发工具中,camera一般是跟随主车进行移动的。那么主车是以固定视角展示给用户的。所以并不需要使用一个很漂亮的模型。由此可见我们完全可以使用图片来代替主车模型。来加速我们的页面加载速度和提升视觉效果。geometry + texture尝试如果简单把图片作为纹理贴在多边形上有多种问题: 1.因为,图片很难达到和模型完全匹配。(uv也非常难以调整)2.而且一个6面多边形即使把高度设置为1也会看起来很奇怪。3.通过多边形做主车来贴纹理的方式,主车转弯时会因为矩阵转换导致问题意想不到的效果。 结论:显而易见使用geometry + textrue的方式并不能实现。 Sprite我们需要用到一个threejs的特殊的载体, SpriteSprite是一个总是面朝着摄像机的平面,通常含有使用一个半透明的纹理。 通过这种方式我们可以轻松的把texture应用在sprite,视觉上看起来也非常的棒。 import adcPng from 'assets/model/car.png';let texture = new THREE.TextureLoader() .load(adcPng);texture.center.set(0, 0);texture.needsUpdate = true;let material = new THREE.SpriteMaterial({ map: texture, sizeAttenuation: true, transparent: true, alphaTest: 0.5, opacity: 1});let sprite = new THREE.Sprite(material);car.scale.set(3.5, 3.5, .8); // 需要对sprite进行缩放car.add(sprite);scene.add(car);我的blog: neverland.github.io我的email enix@foxmail.com

October 17, 2019 · 1 min · jiezi

Threejs-in-autonomous-driving-3merge-geometry

由于使用场景的关系,我们的产品主要设备是ipad,使用软件为chrome或者safari。对webgl无节制的使用,很容易造成灾难性的后果崩溃。所以我们要减少cpu和memory的使用。原理据,运行效率会提高很多。 这里感谢一篇文章,详细的介绍了merge geometry。里面的API虽然经过版本迭代有所变化,但是效果依然。通过这种技术成功的使我们可以一次展示成千上万个geometry。performance-merging-geometry 实现实现非常的简单,见一下案例代码。官方API Geometry.merge let geometry = new THREE.Geometry();junction.forEach(({point}) => { point.push(point[0]); let junction = getShapeGeometryFromPoints(point, false); geometry.merge(junction);});验证直观上可以看到cpu和memory使用减少了,怎样可以去量化呢。Threejs的renderer示例提供了一个可以接口,renderer.info属性可以看到render的实时情况。使用renderer.info.calls可以对比一下前后的drawacall情况。 我的blog: neverland.github.io我的email enix@foxmail.com这里照抄一段webgl高级编程的原文。任何对WebGL API的调用都会带来开销。每个调用都会要求CPU进行额外的处理和数据复制,这回占用时间并要求CPU做一些额外工作。通常,如果GPU接收到大批可并行处理的数

October 17, 2019 · 1 min · jiezi

Threejs-in-autonomous-driving-2模型精简

在开发准备阶段建模同学都会提供一个车模, 从前段考量一般来说超过100kb都算是大文件了,这个模型一般是obj+mtl文件,这两个一般都会超过MB。推动精简的话都非常都难。精简方案删减模型的顶点和面片模型压缩第一种专业性比较强,我们是搞不定的,那么就可以从第二种思路为出发点。通过万能的搜索引擎搜索我们可以找到被誉为3d业界的json格式的gltf。 gltf简介。 这里推荐两个工具 obj2gltf https://www.npmjs.com/package...gltf-pipeline https://www.npmjs.com/package...obj2gltf和gltf-pipeline利用obj2gltf和gltf这个工具可以把obj+mtl格式转为gltf, gltf-pipeline可以进一步把gltf转为glb(binary)这样可以得到一个非常小的模型文件。 $user obj2gltf -i car_r.obj -o car.gltf 可以看到转换后得到一个非常小的文件,gltf-pipeline使用不在赘述。在threejs中可以使用gltfloader来使用gltf或者glb文件! exportlet gltfLoader = function (gltfSrc) { return new Promise((resolve, reject) => { new THREE.GLTFLoader() .load(gltfSrc, ({scene}) => resolve(scene), noop, error => reject({ type: 'gltf' }) ); });};结语:通过对glb格式的模型的使用,我们发现相对于obj格式的模型。不论是加载速度还是解析渲染速度都有了质的飞跃。完全没有了obj带来的卡顿感觉。 我的blog: neverland.github.io我的email enix@foxmail.com

October 17, 2019 · 1 min · jiezi

Threejs入门之上海外滩

THREE.js入门之上海外滩最近入了three.js的坑,想用three.js做一些demo以便巩固自己最近所掌握的知识点,而且正好赶上国庆放假,随,有了这篇~预览地址: Three.js之上海外滩 欢迎start❤️~本篇虽是关于Three.js入门的文章, 但是太过入门的就不讲了。没意义,网上很多知识,本篇主要是把自己在写demo时候遇到的坑点给记录下来, 有什么不懂的直接去查阅文档或者网上搜,这里要提一下:Three.js的官方文档和例子对于开发者也是挺好的(有中文版!) 废话不多, 先看下效果吧: 代码比较多,就不一一讲解了,本篇主要分为以下几个部分: 初始化代码,创建场景,相机,渲染器,灯光,搭建不规则的地面几何体等搭建东方明珠搭建上海中心大厦搭建环球金融中心搭建金茂大厦随机算法搭建其他建筑物给所有建筑物进行贴图优化搭建黄浦江搭建360全景空间

October 4, 2019 · 1 min · jiezi

WebGLRenderTarget离屏渲染

WebGLRenderTarget(离屏渲染)WebGL渲染目标对象WebGLRenderTarget实现了WebGL的离屏渲染功能,如果你有一定的WebGL或OpenGL基础,对帧缓冲区、离线渲染、后处理等概念应该是不陌生的。 个人WebGL/Three.js技术博客 .render()方法WebGL渲染器WebGLRenderer渲染方法.render()的参数( Scene, Camera, WebGLRenderTarget, forceClear ). Scene:要渲染的场景对象Camera:场景对象对应的相机对象WebGLRenderTarget:如果参数指定了WebGL渲染目标WebGLRenderTarget,渲染的图像结果保存到该对象,或者说保存到GPU自定义帧缓冲区中,不会显示到canvas画布上; 如果没有指定渲染目标,也就是没有该参数,渲染结果会直接显示到canvas画布上,或者说渲染结果保存到canvas画布对应的默认帧缓冲区中.无渲染目标(Canvas显示)执行下面代码会把场景scene的渲染结果保存到canvas画布对应的默认帧缓冲区中,形象点说就是可以直接显示到Cnavas画布上,显示器会自动读取CPU默认帧缓冲区上面的图像数据显示。 renderer.render(scene, camera);var renderer = new THREE.WebGLRenderer();renderer.setSize(width, height);// 渲染结果canvas元素插入到body元素中document.body.appendChild(renderer.domElement);// .domElement属性返回的一个canvas画布对象,保存了render方法的渲染结果console.log(renderer.domElement);有渲染目标(Canvas不显示)执行下面代码WebGL渲染器的渲染结果,也就是一张图像,不会直接显示在Canvas画布上,从Three.js的角度阐述,渲染结果的RGBA像素数据存储到了WebGL渲染目标对象WebGLRenderTarget中,通过目标对象的纹理属性.texture可以获得渲染结果的RGBA像素数据,也就是一个Three.js的纹理对象THREE.Texture,可以作为材质对象颜色贴图属性map的属性值;从原生WebGL的角度阐述,就是渲染结果的RGBA像素值存储到了GPU一个自定义的帧缓冲区中,屏幕默认不会直接读取该缓冲区中的像素数据,通过WebGL的特定API可以获取,更多的信息可以百度WebGL或OpenGL离屏渲染。 // 创建一个WebGL渲染目标对象WebGLRenderTarget// 设置渲染结果(一帧图像)的像素为500x500var target = new THREE.WebGLRenderTarget(500, 500);// 设置特定target的时候,render渲染结果不会显示在canvas画布上renderer.render(scene, camera,target); //执行渲染操作.texture通过WebGL渲染目标WebGLRenderTarget的纹理属性.texture可以获得WebGL渲染器的渲染结果,该属性返回的结果是一个纹理对象THREE.Texture,可以作为材质Material对象颜色贴图属性map的属性。 var material = new THREE.MeshLambertMaterial({ // WebGL渲染目标对象属性.texture返回一张纹理贴图,也就是scene在camera下的渲染结果 map: target.texture,});WebGLRenderTarget实现灰度图后处理功能这节课主要内容是把WebGL渲染目标对象WebGLRenderTarget和自定义着色器材质对象ShaderMaterial结合实现后处理功能。 灰度计算后处理场景Scene对象的渲染结果保存到渲染目标对象target中 var target = new THREE.WebGLRenderTarget(500, 500);renderer.render(scene, camera,target);target.texture从渲染目标对象target获得渲染结果,然后通过ShaderMaterial对象把渲染结果传值给片元着色器中uniform定义的变量texture,然后进行灰度计算后处理。 // 自定义顶点着色器对象var material2 = new THREE.ShaderMaterial({ uniforms: { // texture对应顶点着色器中uniform声明的texture变量 texture: { // WebGL渲染目标对象属性.texture返回一张纹理贴图 value: target.texture }, }, // 顶点着色器 vertexShader: document.getElementById('vertexShader').textContent, // 片元着色器 fragmentShader: document.getElementById('fragmentShader').textContent,});材质对象material2是场景2中一个网格模型的纹理贴图,通过render渲染方法把后处理灰度效果显示出来 ...

June 17, 2019 · 1 min · jiezi

Threejs三维模型几何体旋转缩放和平移

Three.js三维模型几何体旋转、缩放和平移创建场景中的三维模型往往需要设置显示大小、位置、角度,three.js提供了一系列网格模型对象的几何变换方法,从WebGL的角度看,旋转、缩放、平移对应的都是模型变换矩阵,关于矩阵变换内容可以观看本人博客发布的原生WebGL课程。 网格模型对象的旋转、缩放、平移等方法或属性可以查找three.js文档的Object3D对象,该对象是网格模型对象、点模型对象、线条模型对象的基类。 个人WebGL/Three.js技术博客 缩放立方体网格模型x轴方向放大2倍,如果连续执行两次该语句,相等于比原来方法4倍 mesh.scale.x = 2.0;//x轴方向放大2倍立方体网格模型整体缩小0.5倍,相当于xyz三个方向分别缩小0.5倍 mesh.scale.set(0.5,0.5,0.5);//缩小为原来0.5倍网格模型Mesh的属性scale返回值是一个Vector3对象,查看three.js官方文档你可以知道Vector3对象具有属性x、y、z对于上面的代码而言xyz表示坐标值,xyz数据类型是float,Vector3对象还具有方法set(),set方法有三个表示xyz坐标的参数。 平移立方体网格模型沿着x轴正方向平移100,可以多次执行该语句,每次执行都是相对上一次的位置进行平移变换 mesh.translateX(100);//沿着x轴正方向平移距离100网格模型沿着向量(0,1,0)表示的方向平移100 var axis = new THREE.Vector3(0,1,0);//向量axismesh.translateOnAxis(axis,100);//沿着axis轴表示方向平移100translateOnAxis(axis, distance)方法相比.translateX、.translateY、.translateZ更通用,可以实现立方体沿着任何方向旋平移,参数axis表示平移方向,使用对象Vector3表示 旋转立方体网格模型绕立方体的x轴旋转/4,可以多次执行该语句,每次执行都是相对上一次的角度进行旋转变化 mesh.rotateX(Math.PI/4);//绕x轴旋转/4网格模型绕(0,1,0)向量表示的轴旋转/8 var axis = new THREE.Vector3(0,1,0);//向量axismesh.rotateOnAxis(axis,Math.PI/8);//绕axis轴旋转/8rotateOnAxis(axis, angle)方法相比.rotateX、.rotateY、.rotateZ更通用,可以实现立方体绕任何轴旋转,参数axis表示旋转轴,使用对象Vector3表示 位置属性position立方体网格模型位置坐标(80,2,10) mesh.position.y = 80;//设置网格模型几何中心y坐标立方体网格模型几何中心y轴坐标值80 mesh.position.set(80,2,10);//设置网格模型几何中心三维坐标position属性和平移方法translateX()一样都是设置距离,方法translateX()设置的相对上次位置进行平移,两次执行该方法,距离会叠加,position属性设置的距离是相对坐标系原点位置, 两次执行position属性立方体的会只会更新重新定位,两次的距离参数不是叠加关系,而是替换关系。 角度属性rotation立方体网格模型位置坐标(80,2,10) mesh.position.y = 80;//设置网格模型几何中心y坐标立方体网格模型几何中心y轴坐标值80 mesh.position.set(80,2,10);//设置网格模型几何中心三维坐标rotation属性和旋转方法rotateX()差异类似position属性和平移方法translateX()的差异,一个是相对坐标系设置角度、位置,一个是相对当前的三维模型的状态设置角度、位置参数。 旋转与平移参考的都是坐标系,不过参考的坐标系稍有不同,平移参考的是世界坐标系或者说三维场景对象Scene的坐标系,和相机对象一样,在整个三维场景中的位置, 三维模型的旋转参考的是模型坐标系,也就是对三维模型本身建立的坐标系。 基类Object3D点模型Points、线模型Line、精灵模型sprite、组对象Group等threejs模型对象的基类都是Object3D,这些模型对象的角度、位置、缩放属性和旋转、平移、缩放方法都可以查看threejs文档基类Object3D 几何体变换几何体Geometry和网格模型Mesh一样也就有旋转缩放平移等方法,通过网格模型或几何体的方法都可以对模型进行变换,但是本质是不一样的,网格模型Mesh执行旋转平移缩放变化,并不会改变自身绑定几何体的顶点坐标,会改变模型对应的模型矩阵ModelMatrix,几何体执行旋转缩放平移变换会改变几何体本身包含的顶点位置、法向量等数据。 如果对上面阐述不太理解,建议最好看看本人博客发布的threejs课程中第二章关于几何体顶点的介绍,threejs进阶课程中关于模型矩阵等概念的介绍。 下面的程序是通过一个几何体创建了多个网格模型,网格模型可以共享几何体对象和材质对象都,几何体对象本质上是一组顶点相关数据,每创建一个网格模型, 相当于多次利用显存中的同一组定点相关数据渲染出多个三维模型的效果,几何体顶点虽然是同一组数据,但是可以在GPU着色器中对这组数据进行矩阵变换,来呈现出不同的效果。 /** * 创建网格模型1、网格模型2 */var box=new THREE.BoxGeometry(50,50,50);//创建一个立方体几何对象var material=new THREE.MeshLambertMaterial({color:0x0000ff});//材质对象var mesh1=new THREE.Mesh(box,material);//网格模型对象1var mesh2=new THREE.Mesh(box,material);//网格模型对象2mesh1.translateX(-50);//沿着x轴负方向平移距离50mesh2.translateX(50);//沿着x轴正方向平移距离50scene.add(mesh1);//网格模型1添加到场景中scene.add(mesh2);//网格模型2添加到场景中代码中的网格模型mesh1、网格模型mesh2都是通过同一个几何体对象Geometry创建,默认情况下,几何体对象的顶点位置决定了网格模型在场景中的显示位置, 两个网格模型执行方法translateX()进行平移变换错开显示。网格模型的平移变换方法translateX()会通过three.js引擎转化为WebGL中CPU顶点着色器的矩阵变换程序。 更改上面的程序,插入下面一段代码,放大其中一个网格模型,可以看到另外外一个网格模型的显示大小并不受影响。 mesh2.scale.y = 2.0;//y轴方向放大2倍网格模型对象可以进行缩放平移旋转变换,几何体对象也拥有相关的几何变换方法和属性,几何体进行几何变换,本质上更改的是显存中的顶点相关数据, 网格模型进行几何变换,不会更改显存中的顶点数据,顶点数据的变换是在GPU渲染管线的顶点着色器处理单元中借助程序逐顶点执行矩阵乘法运算。 几何体对象执行方法scale(),尺寸缩小为原来的0.5倍,刷新浏览器你会看到两个网格模型代表的立方体都缩小了,对比上面的程序可以看出更改几何体的参数,与之相关的网格模型都会变化。 这很好理解,网格模型的几何变换更改的是要与顶点数据进行乘法运算的模型矩阵,几何体对象进行变换更累刷新的是显存上的顶点相关数据,每次渲染出一个网格模型, 都会从网格模型构造函数指定的顶点对象获取顶点数据。 var box=new THREE.BoxGeometry(50,50,50);//创建一个立方体几何对象box.scale(0.5,0.5,0.5);//几何体缩小为原来0.5倍几何体对象可以进行上面程序中的缩放变换,自然也有平移、缩放变换的相关方法,具体使用方法可以参考three.js文档的Geometry对象,立方体、球体等几何体的构造函数返回的结果都是Geometry对象, 这些构造函数返回的对象都会继承Geometry对象的属性和方法。 ...

June 16, 2019 · 1 min · jiezi

Threejs克隆clone和复制copy

Three.js克隆.clone()和复制.copy()你查看Threejs的文档可以发现Threejs的很多类都具有克隆.clone()和复制.copy()方法,比如网格模型Mesh、几何体Geometry、三维向量Vector3... 个人WebGL/Three.js技术博客 .copy()方法简单的说就是复制一个对象的属性值赋值给给另一个对象对应的属性。克隆方法.clone()是相当于新建一个对象,然后复制原对象的属性值赋值给新的对象对应属性,也就是说通过克隆方法.clone()创建一个和原来对象完全一样的对象。 如果你想了解一个threejs对象的克隆或复制方法,除了查看文档以外,最好的方式就是查看threejs官方包src目录下的源码。 几何体克隆.clone()下面的代码利用已有的立方体几何体对象执行克隆方法.clone()返回一个新的几何体对象,返回的的新的几何体对象包含原来的几何体顶点数据、顶点索引数居、UV坐标数据, 然后利用两个几何体分别创建一个网格模型。 var box=new THREE.BoxGeometry(10,10,10);var box2 = box.clone();//克隆几何体box.translate(20,0,0);//平移原几何体 新克隆的几何体不受影响var material=new THREE.MeshLambertMaterial({color:0x0000ff});//材质对象—蓝色var material2=new THREE.MeshLambertMaterial({color:0xff0000});//材质对象—红色var mesh=new THREE.Mesh(box,material);var mesh2=new THREE.Mesh(box2,material2);几何体复制.copy()执行语句geometry2.copy(geometry1);,几何体geometry1的顶点相关数据会替换几何体geometry2的顶点相关数据。 var box=new THREE.BoxGeometry(10,10,10);//创建一个立方体几何对象var sphere=new THREE.SphereGeometry(10,40,40);//创建一个球体几何对象box.copy(sphere);//球体数据复制到box几何体,替换box原来的顶点数据var material=new THREE.MeshLambertMaterial({color:0x0000ff});//材质对象—蓝色var mesh=new THREE.Mesh(box,material);//网格模型对象scene.add(mesh);//网格模型添加到场景中三维向量克隆和复制// 创建一个三维向量v1,xyz分量分别为1,3,6var v1 = new THREE.Vector3(1,3,6)// 克隆v1返回一个新的三维向量v2,v2的xyz分量和v1一样var v2 = v1.clone()// 创建一个三维向量v1,xyz分量分别为1,3,6var v1 = new THREE.Vector3(1,3,6)// 创建一个三维向量v2,xyz分量分别为5,7,8var v2 = new THREE.Vector3(5,7,8)// 通过复制方法可以复制v1的xyz分量的属性值赋值给v2的xyz三个分量// 执行完复制后,v2的xyz分量值变为1,3,6v2.copy(v1);网格模型克隆.clone()网格模型执行.clone()方法克隆的时候,会新建一个网格模型对象Mesh,不过两个网格模型的几何体和材质对象是共享的。 // 网格模型Mesh的源码,查看源码你可以看到克隆网格模型的时候,几何体和材质是共享的clone: function () { return new this.constructor( this.geometry, this.material ).copy( this );}网格模型mesh执行方法.clone()返回一个网格模型mesh2,平移网格模型mesh,网格模型mesh2并不受影响,如果在代码中插入语句box.scale(1.5,1.5,1.5);, 你会发现两个网格模型的尺寸都会变化,这很好理解,克隆网格模型的时候,几何体数据并没有克隆,两个网格模型共用一个几何体对象的顶点相关数据。 var box=new THREE.BoxGeometry(10,10,10);//创建一个立方体几何对象var material=new THREE.MeshLambertMaterial({color:0x0000ff});//材质对象var mesh=new THREE.Mesh(box,material);//网格模型对象var mesh2 = mesh.clone();//克隆网格模型mesh.translateX(20);//网格模型mesh平移box.scale(1.5,1.5,1.5);//几何体缩放scene.add(mesh);//网格模型添加到场景中scene.add(mesh2);//网格模型添加到场景中box.scale(1.5,1.5,1.5);//几何体缩放

June 16, 2019 · 1 min · jiezi

Threejs加载obj模型文件

Three.js加载.obj模型文件使用三维软件导出.obj模型文件的时候,会同时导出一个材质文件.mtl, .obj和.stl文件包含的数据一样都是几何体对象的顶点位置、顶点法向量等顶点相关数据, 材质文件.mtl包含的是RGB颜色值等材质信息。 加载.obj三维模型的时候,可以只加载.obj文件,然后借助three.js引擎自定义材质对象Material,也可以同时加载obj和mtl文件。 obj文件不包含场景的相机、光照信息,不能导出骨骼动画、变形动画,如果希望导出光照信息、相机信息、骨骼动画信息、变形动画信息,可以选择fbx、gltf等格式。 个人WebGL/Three.js技术博客 只加载obj文件obj文件包含了模型的几何体信息 <!-- 引入obj模型加载库OBJLoader.js --><script src="../../three.js-master/examples/js/loaders/OBJLoader.js"></script>/** * OBJ文件加载 只加载obj文件中的几何信息,不加载材质文件.mtl */var loader = new THREE.OBJLoader();// 没有材质文件,系统自动设置Phong网格材质loader.load('./立方体/box.obj',function (obj) { // 控制台查看返回结构:包含一个网格模型Mesh的组Group console.log(obj); // 查看加载器生成的材质对象:MeshPhongMaterial console.log(obj.children[0].material); scene.add(obj); // 加载后的一些编辑操作 obj.children[0].scale.set(20,20,20);//网格模型缩放 obj.children[0].geometry.center();//网格模型的几何体居中 obj.children[0].material.color.set(0xff0000);//设置材质颜色})同时加载obj文件和mtl文件mtl文件包含了模型的材质信息,比如模型颜色、透明度等信息,还有纹理贴图的路径,比如颜色贴图、法线贴图、高光贴图等等。 <!-- 引入obj模型加载库OBJLoader.js --><script src="../../three.js-master/examples/js/loaders/OBJLoader.js"></script><!-- 引入obj模型材质加载库MTLLoader.js --><script src="../../three.js-master/examples/js/loaders/MTLLoader.js"></script>/** * OBJ和材质文件mtl加载 */var OBJLoader = new THREE.OBJLoader();//obj加载器var MTLLoader = new THREE.MTLLoader();//材质文件加载器MTLLoader.load('./立方体/box.mtl', function(materials) { // 返回一个包含材质的对象MaterialCreator console.log(materials); //obj的模型会和MaterialCreator包含的材质对应起来 OBJLoader.setMaterials(materials); OBJLoader.load('./立方体/box.obj', function(obj) { console.log(obj); obj.scale.set(10, 10, 10); //放大obj组对象 scene.add(obj);//返回的组对象插入场景中 })})obj包含多个网格模型obj文件可以包含多个网格模型对象,不一定就是一个,这些网格模型对象全部是并列关系,无法通过父子关系构建一个树结构层级模型。 ...

June 15, 2019 · 1 min · jiezi

Threejs相机对象正投影OrthographicCamera透视投影PerspectiveCamera

Three.js相机对象(正投影OrthographicCamera、透视投影PerspectiveCamera)针对不同应用的三维场景需要使用不同的投影方式,比如机械、工业设计领域常常采用正投影(平行投影), 游戏场景往往采用透视投影(中心投影)。为了完成三维场景不同的投影方式,three.js封装WebGL API和相关算法,提供了正投影OrthographicCamera、透视投影PerspectiveCamera等相机对象。 个人WebGL/Three.js技术博客 正投影和透视投影简单解释下面对正投影相机和透视投影相机的投影算法进行简单介绍,对于初学者你有一个印象就可以,如果想深入了解,可以学习图形学或者阅读threejs官方源码src目录下文件OrthographicCamera.js和PerspectiveCamera.js 生活中的物体都是三维的,但是人的眼睛只能看到正面,不能看到被遮挡的背面,三维几何体在人眼睛中的效果就像一张相机拍摄的二维照片,你看到的是一个2D的投影图。 空间几何体转化为一个二维图的过程就是投影,不同的投影方式意味着投影尺寸不同的算法。 生活中的物体都是三维的,但是人的眼睛只能看到正面,不能看到被遮挡的背面,三维几何体在人眼睛中的效果就像一张相机拍摄的二维照片,你看到的是一个2D的投影图。 空间几何体转化为一个二维图的过程就是投影,不同的投影方式意味着不同的算法。 对于正投影而言,一条直线放置的角度不同,投影在投影面上面的长短不同;对于透视投影而言,投影的结果除了与几何体的角度有关,还和距离相关, 人的眼睛观察世界就是透视投影,比如你观察一条铁路距离越远你会感到两条轨道之间的宽度越小。无论正投影还是透视投影,three.js都对相关的投影算法进行了封装, 大家只需要根据不同的应用场景自行选择不同的投影方式。使用OrthographicCamera相机对象的时候,three.js会按照正投影算法自动计算几何体的投影结果; 使用PerspectiveCamera相机对象的时候,three.js会按照透视投影算法自动计算几何体的投影结果。 正投影相机对象OrthographicCamera构造函数格式:OrthographicCamera( left, right, top, bottom, near, far ) OrthographicCamera构造函数参数列表,参数的数据类型都是number。 left——渲染空间的左边界right——渲染空间的右边界top——渲染空间的上边界bottom——渲染空间的下边界near——near属性表示的是从距离相机多远的位置开始渲染,一般情况会设置一个很小的值。 默认值0.1far——far属性表示的是距离相机多远的位置截止渲染,如果设置的值偏小小,会有部分场景看不到。 默认值1000三维场景中坐标值不在三维空间中的网格模型不会被渲染出来,会被剪裁掉,比如你把上面代码中far参数的值从1000更改为420,你会发现长方体的一部分无法显示。 注意:左右边界的距离与上下边界的距离比值与画布的渲染窗口的宽高比例要一致,否则三维模型的显示效果会被单方向不等比例拉伸 OrthographicCamera构造函数本质上是对WebGL投影矩阵的封装,宽度width、高度height越大,三维模型顶点的位置坐标就会越大,超出可视区域的网格模型就会被剪裁掉, 不会再显示在屏幕上,大家还可以看到参数left与right、参数式top与bottom互为相反数,这样做的目的是能够是lookAt指向的对象能够显示在canvas画布的中间位置。 /** * 正投影相机设置 */var width = window.innerWidth; //窗口宽度var height = window.innerHeight; //窗口高度var k = width / height; //窗口宽高比var s = 200; //三维场景显示范围控制系数,系数越大,显示的范围越大//创建相机对象var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);camera.position.set(200, 300, 200); //设置相机位置camera.lookAt(scene.position); //设置相机方向(指向的场景对象)透视投影相机对象PerspectiveCamera构造函数格式:PerspectiveCamera( fov, aspect, near, far ) ...

June 15, 2019 · 1 min · jiezi

Threejs模型标签

Three.js模型标签在很多的实际的项目中,你可能需要给一个Three.js的模型添加标签,标签可以通过一个包含文字图形信息的HTML元素或者一个three.js的精灵模型来表示。 常见问题three.js三维模型如何添加注释文字?Three.js模型对象如何设置标签?three.js如何用HTML元素设置显示热点如何把模型的世界坐标转化为屏幕坐标?每个人的基础不同,虽然知识点区别不大,可能描述的时候会有很大区别。 视频讲解和源码下面对知识点简单介绍,关于视频讲解可以关注我博客发布可的相关课程,个人WebGL/Three.js技术博客。 层级模型复杂的项目,一个three.js场景往往包含包含多个模型对象,模型对象也会有一些父对象,这样就会形成一个层级模型,从数据结构的角度来看就是树结构。一个模型相对世界坐标系原点的位置是世界坐标,相对父对象的位置是局部坐标。 获取模型位置你如果想给一个模型设置标签,首先需要获得模型在世界坐标系中所在的位置,如果你希望标注一个模型的某个局部位置,那就要获得该局部区域的一个顶点坐标。 .position属性是一个模型的局部位置,相对父对象的位置,如果一个网格模型Mesh直接属于场景Scene,没有除了Scene意外的父对象.vertices如果几何体是Geometry类型,可以通过.vertices属性访问顶点,通过下标可以访问具体的顶点位置坐标。.attributes.position如果几何体是BufferGeometry类型,可以通过.attributes.position属性访问顶点位置数据。.getWorldPosition()实际项目中一个模型对象可以有多个父对象,这个时候想获得该模型对象的世界坐标,就不能通过.position属性,应该通过.getWorldPosition()方法实现,该方法的使用参考基类Object3D。实际项目中获得一个模型,可能是通过点击获得,或者遍历对象根据属性值找到某个或某些模型,等等这里不展开说,这不是本节课的重点,关于递归遍历模型或者鼠标点击选中某个模型可以关注课程的其它部分。 精灵模型Sprite作为标签在使用精灵模型表示标签之前,你应该先了解精灵模型Sprite有什么特点。使用精灵模型表示一个模型对象的标签,那么精灵模型就要位于模型对象的附近。可以获得要标注模型的世界坐标,然后来设置精灵标签的位置,适当偏移一点就可以,当然也可以把精灵对象插入到模型对象的父对象中,和模型对象一样作为父对象的子对象,这样的话如果模型父对象的位置变化,精灵模型可以跟着一起变化。 标签的样式可以让美术设计好直接作为精灵的贴图就可以,如果标签不是特定的,比如用户输入文字,平台自动生成模型标签,可以通过程序自动化合成一个纹理作为精灵模型的贴图,关于如何自动合成纹理贴图这里不详细阐述。 /** * 创建点精灵模型 */// 创建精灵材质对象SpriteMaterialvar spriteMaterial = new THREE.SpriteMaterial({ map: new THREE.TextureLoader().load("立方体.png"), //设置精灵纹理贴图 transparent: true,//开启透明(纹理图片png有透明信息)});// 创建精灵模型对象,不需要几何体geometry参数var sprite = new THREE.Sprite(spriteMaterial);sprite.scale.set(30, 30, 1); //精灵大小// 把精灵模型插入到模型对象的父对象下面group.add( sprite);// 父对象group位置变化,网格模型及其对象的标签同样发生变化group.position.set(10, 0, -80);// 表示标签信息的精灵模型对象相对父对象设置一定的偏移sprite.translateY(30);div等html元素作为标签(世界坐标转屏幕坐标)通过html元素表示标签相比较使用精灵来说比较麻烦,需要进行坐标变换,一个模型显示在Canvas画布上,经过了相机的视图、投影变换,如果想把一个div元素标注在一个模型附近,就需要计算模型渲染到画布上的具体位置,也就是所谓的屏幕坐标。使用html元素的好处是可以直接输入汉字,利用css来设置一下标签的样式,当然也可以直接加载美术设计的标签。 世界坐标转WebGL标准设备坐标世界坐标:世界坐标简单说就是模型在three.js三维空间中的位置,但是注意不是局部位置属性.position,你可以通过.getWorldPosition()方法获得世界坐标,每个子对象都有一个相对父对象的位置,把一个对象的所有父对象相对Scene坐标原点的位置加起来就是一个模型的世界坐标屏幕坐标:你可以理解为Canvas画布上的位置,单位是像素px,这和HTML说的像素是一样的。//创建一个三维向量作为世界坐标var worldVector = new THREE.Vector3();//获取网格模型boxMesh的世界坐标,赋值给worldVectorboxMesh.getWorldPosition(worldVector);//世界坐标转标准设备坐标,standardVector是WebGL设备坐标var standardVector = worldVector.project(camera);.project()是向量Vector3的方法,一个表示位置的向量Vector3把相机作为参数执行该方法可以得到经过相机变换后的坐标。一般threejs渲染器渲染的时候,会自动从相机对象读取视图和投影矩阵对模型的顶点进行变换。 WebGL标准设备坐标转屏幕坐标Canvas全屏显示的时候复合下面的计算规则,如果不是全屏要注意修改计算规则。如果不是全屏,计算a和b的值,不能使用window.innerWidth,而应该使用canvas的具体宽高。如果canvas是局部显示相对body区域的左上角有偏移,那么div元素也要设置一定的偏移。 // 根据WebGL标准设备坐标standardVector计算div标签在浏览器页面的坐标var a = window.innerWidth / 2;var b = window.innerHeight / 2;var x = Math.round(standardVector.x * a + a); //标准设备坐标转屏幕坐标var y = Math.round(-standardVector.y * b + b); //标准设备坐标转屏幕坐标/** * 设置标签元素的位置 */div.style.left = x + 'px';//这里的130px主要是为了标签和模型有一定偏移,当然也可以不设置,两者叠加在一起div.style.top = y - 130 + 'px'; ...

June 14, 2019 · 1 min · jiezi

BufferGeometry和Geometry有什么不同

BufferGeometry和Geometry有什么不同如果你刚接触Three.js,查看文档的时候,通过BoxBufferGeometry、SphereBufferGeometry可以分别用来创建长方体、球体,同样通过BoxGeometry、SphereGeometry也可以用来分别创建长方体、球体。BoxBufferGeometry、SphereBufferGeometry等Three.js API的基类是BufferGeometry,BoxGeometry、SphereGeometry等Three.js API的基类是Geometry。 测试代码你会发现BufferGeometry和Geometry可以实现同样的功能,这时候你可能至少会思考它们会有什么不同,简单的点说BufferGeometry和Geometry对象的数据结构不同,但是都可以用来描述几何体的顶点信息。 学习建议个人WebGL/Three.js技术博客 如果你想简单理解BufferGeometry和Geometry有什么不同,就是两者的数据结构不同,缓冲类型几何体BufferGeometry相比普通几何体Geometry性能更好。 如果想深入理解,建议先有一定的原生WebGL基础,可以学习本博客的原生WebGL视频教程,另一方面可以学习Three.js视频教程中第2章对BufferGeometry和Geometry的详细介绍。 顶点概念如果你想深入理解BufferGeometry和Geometry到底有什么不同,你至少要对几何体的顶点数据要有一定概念,比如顶点位置、顶点法向量、顶点颜色、顶点纹理坐标UV等数据,如果你有一定原生WebGL基础,对这些肯定是了解的。 无论是缓冲类型几何体对象BufferGeometry还是普通几何体对象Geometry,它们本质上都是用来描述一个几何体顶点数据的对象,通过不用的属性来表示不同的顶点数据,这些构造函数也封装了一些相关方法。 数据结构有了顶点的概念,你在浏览器的控制台打印查看BufferGeometry和Geometry对象有哪些属性,这些属性的属性值分别表示顶点的什么数据。 var geometry = new THREE.BoxGeometry(10, 8, 9);console.log('几何体数据结构',geometry);console.log('顶点位置数据',geometry.vertices);console.log('顶点纹理坐标',geometry.faceVertexUvs);console.log('几何体三角形信息',geometry.faces);var geometry = new THREE.BoxBufferGeometry(7, 6, 8);console.log('几何体数据结构',geometry);console.log('顶点位置、法向量、UV、颜色顶点等数据集合',geometry.attributes);console.log('顶点位置数据',geometry.attributes.position);console.log('顶点索引数据',geometry.index);渲染过程在执行WebGL渲染器WebGLRenderer渲染方法.render()的时候,渲染器会对场景和相机进行解析渲染,解析场景Scene自然会解析场景中模型对应的几何体对象Geometry。关于渲染器是如何解析渲染场景和相机对象的,在《Three.js进阶视频教程》中进行了介绍和讲解,有兴趣可以详细了解,这里不再展开详述,这里只说和几何体相关的内容。 Three.js渲染器在解析几何体对象的时候,如果几何体对象是普通几何体对象Geometry,Three.js的WebGL渲染器会把普通几何体对象Geometry转化为缓冲类型几何体对象BufferGeometry,然后再提取BufferGeometry包含的顶点信息,这里可以看出来直接使用BufferGeometry解析的时候相对Geometry少了一步,自然性能更高一些。不过从开发者使用的角度来看,Geometry可能对程序员更友好一些。

June 14, 2019 · 1 min · jiezi

Threejs欧拉对象Euler和四元数Quaternion

Three.js欧拉对象Euler和四元数Quaternion欧拉对象和四元数主要用来表达对象的旋转信息。 关键词:欧拉Euler、四元数Quaternion、矩阵Matrix4 个人WebGL/Three.js技术博客 欧拉对象Euler构造函数:Euler(x,y,z,order)参数xyz分别表示绕xyz轴旋转的角度值,角度单位是弧度。参数order表示旋转顺序,默认值XYZ,也可以设置为YXZ、YZX等值 // 创建一个欧拉对象,表示绕着xyz轴分别旋转45度,0度,90度var Euler = new THREE.Euler( Math.PI/4,0, Math.PI/2);设置欧拉对象的 var Euler = new THREE.Euler();Euler.x = Math.PI/4;Euler.y = Math.PI/2;Euler.z = Math.PI/4;四元数Quaternion四元数对象Quaternion使用x、y、z和w四个分量表示。 var quaternion = new THREE.Quaternion();console.log('查看四元数结构',quaternion);console.log('查看四元数w分量',quaternion.w);四元数方法.setFromAxisAngle()四元数的方法.setFromAxisAngle(axis, angle)通过旋转轴axis和旋转角度angle设置四元数数据,也就是x、y、z和w四个分量。 绕单位向量Vector3(x,y,z)表示的轴旋转度 k = sin/2 q=( xk , yk , z*k, cos/2) var quaternion = new THREE.Quaternion();// 旋转轴new THREE.Vector3(0,1,0)// 旋转角度Math.PI/2quaternion.setFromAxisAngle(new THREE.Vector3(0,1,0),Math.PI/2)console.log('查看四元数结构',quaternion);四元数乘法.multiply()对象的一个旋转可以用一个四元数表示,两次连续旋转可以理解为两次旋转对应的四元数对象进行乘法运算。 // 四元数q1、q2分别表示一个旋转,两个四元数进行乘法运算,相乘结果保存在q2中// 在q1表示的旋转基础在进行q2表示的旋转操作q1.quaternion.multiply( q2 );欧拉、四元数和矩阵转化欧拉对象、四元数对象和旋转矩阵可以相关转化,都可以表示旋转变换。 Matrix4.makeRotationFromQuaternion(q)通过矩阵对象Matrix4的.makeRotationFromQuaternion(q)方法可以把四元数转化对应的矩阵对象。 quaternion.setFromEuler(Euler)通过欧拉对象设置四元数对象 Euler.setFromQuaternion(quaternion)四元数转化为欧拉对象 Object3DObject3D对象角度属性.rotation的值是欧拉对象Euler,四元数属性.quaternion的值是四元数对象Quaternion。 执行Object3D对象旋转方法,会同时改变对象的角度属性和四元数属性。四元数属性和位置.position、缩放属性.scale一样会转化为对象的本地矩阵属性.matrix,本地矩阵属性值包含了旋转矩阵、缩放矩阵、平移矩阵。 Object3D对象角度属性.rotation和四元数属性.quaternion是相互关联的一个改变会同时改变另一个。 // 一个网格模型对象,基类是Object3Dvar mesh = new THREE.Mesh()// 绕z轴旋转mesh.rotateZ(Math.PI)console.log('查看角度属性rotation',mesh.rotation);console.log('查看四元数属性quaternion',mesh.quaternion);.rotateOnAxis(axis, angle)表示绕绕着任意方向某个轴axis旋转一定角度angle,绕X、Y和Z轴旋转对应的方法分别是rotateX()、rotateY()和rotateZ(),绕着XYZ特定轴旋转的方法是基于.rotateOnAxis()方法实现的。 ...

June 13, 2019 · 1 min · jiezi

Threejs跨域问题无法显示

Three.js跨域问题通过Three.js加载obj、FBX等格式外部模型文件的时候是ajax异步加载数据的过程,需要建立本地服务器来解决,如果不这样直接使用浏览器打开加载三维模型的.html文件,会出现报错无法模型文件无法加载,浏览器控制报错跨域问题的情况。 个人WebGL/Three.js技术博客 浏览器控制台报错:three.js:30833 Access to XMLHttpRequest at 'file://....' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https. 如果你有很好的前后端基础,肯定对跨域问题很了解,如果不了解也没关系,下面会详细说明如何解决。 解决方案解决方案就是在本地自定义服务器,可以通过nodejs、python等任何一个你熟悉的语言来实现。 编辑器如果不想麻烦在本地自定义服务器,也可以使用会自动帮助你建立本地服务器的一些代码编辑器,比如常见的的atom、WebStorm等编辑器,当然有些编辑器不会自动建立本地服务器,如果你不想更换不熟悉的代码编辑器,那就需要后端语言自定义本地服务器。 Nodejs自定义服务器如果你熟悉其它的后端语言直接使用你熟悉的后端语言自定义服务器就可以,如果不熟悉,可以选择Nodejs。 如果你没有前后端基础,刚开始学习Threejs可以不展开学习前端、后端知识,能够调试three.js代码就行。如果你想开发项目,前后端的知识还是要补充一些,如果不是专门的后端工程师也没必要重新学习一门后端语言,直接使用Nodejs就行,Nodejs和前端一样使用的是JavaScript语言。 使用Nodejs自定义服务器很简单,首先是你要先百度Nodejs安装的相关文章,先在你的电脑上安装配置好Nodejs,熟悉下NPM的使用,然后使用npm执行npm install http-server安装http-server模块,如果想创建创建一个自定义的服务器,打开命令行,在你要打开的html文件所在的目录下,执行http-server就可以完成服务器创建,具体不了解的可以百度相关内容。 浏览器访问http://localhost:8080或http://127.0.0.1:8080 地址打开相应的.html文件就可以显示三维模型效果。 前端脚手架刚开始学习three.js的时候,为了方便,可能不会使用一些前端框架,如果你实际开发项目的时候,可能会会把Three.js和Vue.js、React或AngularJs任何一种前端框架结合使用,这时候如果你使用这些前端框架的脚手架,比如VueJs的vue-cli脚手架,为了调试它本身就会创建本地服务器,这时候可以直接加载三维模型文件,不过注意不要把模型文件和html文件放在两个不同的服务器地址下面。 相关文章所谓的跨域(Cross-Origin):https://blog.csdn.net/u011037...

June 13, 2019 · 1 min · jiezi

Threejs线宽lineWidth无效

Three.js线宽.lineWidth无效在编写Three.js程序的时候,你设置线模型Line对应线材质LineBasicMaterial的线宽属性.lineWidth,可能是无效果。 个人技术博客 .lineWidth属性关于线材质LineBasicMaterial和虚线材质LineDashedMaterial的线宽属性.lineWidth的介绍可以查看Three.js文档。 .lineWidth属性的主要功能是控制线条粗细,默认值是1。 一般在windows操作系统平台上使用Three.js引擎的WebGL渲染器WebGLRenderer进行渲染的时候,.lineWidth属性值设置为多少都是无效的,也就是说无论如何设置,线模型线宽都显示为1。 // 基础线材质var mat = new THREE.LineBasicMaterial({ color: 0xee1111, // 设置无效,线宽显示为1 linewidth: 6,});//线条模型对象var line = new THREE.Line(geo, mat);解决方案整体思路就是使用带状网格模型Mesh表示线条模型Line。 参考Three.js官方库案例three.js-master/examples/webgl_lines_fat.html 参考github MeshLine:https://github.com/quzheqing/... webgl_lines_fat.html案例引入相关文件,注意LineGeometry.js依赖LineSegmentsGeometry.js,Line2.js依赖LineSegments2.js。 <script src='/js/lines/LineSegmentsGeometry.js'></script><script src='/js/lines/LineGeometry.js'></script><script src='/js/lines/LineMaterial.js'></script><script src='/js/lines/LineSegments2.js'></script><script src='/js/lines/Line2.js'></script>写一个案例,引入顶点坐标,绘制一个字母M效果,如果用曲线返回足够多的顶点也可以绘制一个光滑的曲线 阅读/examples/js/lines目录下的文件,可以看出来Line2的基类是LineSegments2,LineSegments2的基类是Mesh,Line2和LineSegments2本质上都是一个网格模型,你可以把代码中THREE.Line2替换为THREE.Mesh,显示效果是一样的。 var geometry = new THREE.LineGeometry();// 顶点坐标构成的数组pointArrvar pointArr = [-100,0,0, -100,100,0, 0,0,0, 100,100,0, 100,0,0,]// 几何体传入顶点坐标geometry.setPositions( pointArr);// 自定义的材质var material = new THREE.LineMaterial( { color: 0xdd2222, // 线宽度 linewidth: 5,} );// 把渲染窗口尺寸分辨率传值给材质LineMaterial的resolution属性// resolution属性值会在着色器代码中参与计算material.resolution.set(window.innerWidth,window.innerHeight);var line = new THREE.Line2(geometry, material);每个顶点对应一个颜色,颜色会进行插值计算。 var colorArr = [1,0,0,0,1,0,0,0,1,0,1,0,1,0,0,]// 设定每个顶点对应的颜色值geometry.setColors( colorArr );var material = new THREE.LineMaterial( { // color: 0xfd1232, linewidth: 5, // 注释color设置,启用顶点颜色渲染 vertexColors: THREE.VertexColors,} );

June 12, 2019 · 1 min · jiezi

Threejs模型几何体面积体积计算

Three.js模型几何体面积、体积计算在工作中通过Three.js开发项目的时候,一些特定的情况下你可能需要计算一个三维模型的表面积或者体积,比如在3D打印的Web项目中,你需要计算一个三维模型的体积,然后通过体积计算打印一个三维模型所需要的3D打印材料费;比如开发的一个程序中,需要自动计算一个地面、墙面或某个零件的表面需要多少涂料,肯定需要先计算它的外表面面积是多少。 个人技术博客 思路——模型几何体表面积计算如果是一个立方体、球体等规则几何体,计算它们的面积,可以直接代入公式,实际应用中,不一定就是规则几何体,下面的任务是探讨一个通用的算法,可以计算任意形状的几何体,任意自由形状的曲面表面积。 一个三维模型,可能包含多个网格模型Mesh,如果一个三维模型有多个网格模型,你可以分别计算每一个网格模型的表面积,然后求和即可。这里先考虑如何计算一个网格模型Mesh的表面积,每个网格模型的几何体本质上都是多个三角形组成,计算一个网格模型的外表面积,只需要计算该网格模型几何体所有三角形面积就可以,如果想计算所有三角形的面积,肯定要先计算一个三角形的面积。 一个三角形面积已知三角形的三个顶点p1, p2, p3的坐标值,利用三个顶点的坐标计算三角形的面积,每个顶点的坐标通过一个三维向量对象Vector3表示,具体代码见下方。 下面的计算主要是向量的计算,如果你对Three.js向量相关内容不了解,可以查看官方文档Vector3接口的介绍,更多向量Vector3相关的内容可以参考Three.js进阶视频教程数学部分。 //三角形面积计算function AreaOfTriangle(p1, p2, p3){ var v1 = new THREE.Vector3(); var v2 = new THREE.Vector3(); // 通过两个顶点坐标计算其中两条边构成的向量 v1 = p1.clone().sub(p2); v2 = p1.clone().sub(p3); var v3 = new THREE.Vector3(); // 三角形面积计算 v3.crossVectors(v1,v2); var s = v3.length()/2; return s}计算几何体表面积上面代码封装了一个三角形面积计算函数AreaOfTriangle(),只要以三维向量Vector3形式输入三角形三个顶点坐标就可以返回一个三角形面积值。一般来说加载外部ply、stl、obj、fbx等格式三维模型,模型的几何体都是BufferGeometry,可以通过BufferGeometry的顶点索引.index属性和attributes.position属性获得三角形顶点的位置坐标。 这里不加载外部模型,先以Three.js自带的Geometry类型API为例创建一个几何体,然后计算它的表面,也就是计算一个球体或立方体的表面积来验证上面函数AreaOfTriangle()是否正确,你可以通过公式先计算一个立方体或球体的表面积,然后再和通过调用AreaOfTriangle()函数计算的结果相比较,来验证AreaOfTriangle()函数的算法是否可行。 // var geometry = new THREE.SphereGeometry(10, 50, 50);var geometry = new THREE.BoxGeometry(10, 10, 10);// 声明一个变量表示几何体的表面积var area = 0.0;// 遍历一个几何体的全部三角形geometry.faces,所有三角形面积累积就是几何体的表面积// 对于不规则曲面,细分程度越高,面积计算精度越高for (var i = 0; i < geometry.faces.length; i++) { //三角形的对应顶点索引 var a = geometry.faces[i].a; var b = geometry.faces[i].b; var c = geometry.faces[i].c; // 获得三角形对三个顶点的坐标 var p1 = geometry.vertices[a]; var p2 = geometry.vertices[b]; var p3 = geometry.vertices[c]; // 调用三角形面积计算函数AreaOfTriangle area += AreaOfTriangle(p1, p2, p3); //三角形Face3面积累计计算}// 查看面积计算结果document.write("面积:" + area)计算几何体体积几何体的每一个三角形可以和顶点坐标构成一个四面体,计算一个几何体的体积,可以计算所有所有三角形和坐标原点构成的四面体体积,然后求和。 ...

June 12, 2019 · 1 min · jiezi

Threejs自发光贴图emissiveMap

Three.js自发光贴图.emissiveMap在Three.js材质中,和颜色贴图属性.map对应的是颜色属性.color,和高光贴图属性.specularMap对应的是高光颜色属性.specular,与粗糙度贴图属性.roughnessMap对应是粗糙度属性roughness....本文说到的自发光贴图属性.emissiveMap对应的是自发光属性.emissive。 个人技术博客 自发光属性.emissive自发光属性.emissive的属性值和颜色贴图属性.map的属性值相同都是Three.js的颜色对象THREE.Color。自发光属性.emissive默认值是黑色0x000000。也就是模型默认是不发光的,如果一个模型是发光的,比如电源上一个电源灯,你可以把电源灯的材质设置为对应的发光颜色。大家应该都知道,支持光照的Three.js材质颜色是受光照影响的,不过材质的自发光颜色.emissive是不受光照影响的。 自发光贴图属性.emissiveMap如果一个网格模型Mesh整体上是同一种颜色,直接设置颜色属性.color就可以,如果一个充电宝使用了一个网格模型Mesh来表示,这时候整个充电宝Mesh不同区域的颜色是不一样的,那就需要使用颜色贴图属性.map。 同样道理,一个充电宝Mesh不同区域有的发光,有的不发光,这时候不能使用自发光属性.emissive整体设置同样发光效果,可以通过自发光贴图属性.emissiveMap来解决。如果充电宝发光的电源灯单独使用一个Mesh表示,这种情况下可以设置.emissive即可,不需要美术导出自发光贴图,一般美术出图的时候可能会把多个零件合并为一个网格模型Mesh。 THree.js系统渲染的时候,颜色贴图属性.map和颜色属性.color的RGB分量会分别进行乘法运算,颜色属性.color默认值是白色0xffffff,一般设置了.map,不去要去更改.color,不过你可以尝试更改.color,比如设置为红色,你会发现模型的颜色在map的基础上会有颜色变化。 自发光贴图属性.emissiveMap类似颜色贴图属性.map,Threejs计算材质的发光效果,会把自发光贴图属性.emissiveMap和.emissive的RGB分量分别进行乘法运算。 Three.js自发光贴图.emissiveMap无效如果你设置了Three.js模型材质的自发光贴图属性.emissiveMap,渲染结果中没有显示,这种情况下,你要注意下材质的自发光属性.emissive是否设置,因为Threejs渲染的时候,着色器会从自发光贴图.emissiveMap提取像素值RGB,然后和自发光.emissive的属性值相乘,而Three.js中.emissive的默认值是黑色,也就是0x000000,这种情况下,无论.emissiveMap产生的任何自发光都相当于没有。 有一点要注意,如果没有自发光贴图.emissiveMap,千万不能把.emissive设置为白色,这样的话整个模型都会发白光,.emissiveMap一般局部是发光颜色,其它区域是黑色。局部发光颜色对应模型的某个位置,比如充电宝的电源灯。 var mat = new THREE.MeshPhysicalMaterial({ emissive:0xffffff,// emissive默认黑色,设置为白色 emissiveMap: texLoader.load("./模型/emissiveMap.png"),})

June 11, 2019 · 1 min · jiezi

Threejs模型隐藏或显示

Three.js模型隐藏或显示你在使用Three.js开发项目的过程中,可能需要隐藏一个模型,或者一个模型处于隐藏状态,又希望让它显示出来,那么你可以继续阅读下去。 个人技术博客 材质属性.visible查看Three.js文档的基类Material,可以知道材质属性.visible的作用就是控制绑定该材质的模型对象是否可见,默认值是true,LineBasicMaterial、SpriteMaterial、MeshBasicMaterial等材质都会继承基类Material的可见性.visible属性,也就是说无论点模型Points、线模型Line或网格模型Mesh默认都是可见的。如果想隐藏一个模型可以设置该模型材质的.visible属性值为true。 // 隐藏网格模型mesh,visible的默认值是truemesh.material.visible =false// 使网格模型mesh处于显示状态mesh.material.visible =true隐藏一个层级模型如果一个模型对象包含了多个网格模型Mesh,嵌套了很多层,形成了一个树结构,只有根部节点是网格模型Mesh,中间节点都是组对象Group或Object3D对象。如果你想通过控制材质的.visible属性批量隐藏该模型对象下的所有网格模型Mesh,首先需要做的就是要递归遍历树结构找到所有的网格模型Mesh,然后把所有网格模型Mesh材质的.visible属性设置为false。 通过对象的.traverse()方法递归遍历一个模型,然后通过对象的类型属性.type判断该对象是不是网格模型对象Mesh,如果是的话执行obj.material.visible =false。 modelObject.traverse(function(obj) { if (obj.type === "Mesh") { obj.material.visible =false }})属性.visible本质如果你有兴趣了解Three.js底层知识,可以阅读这段话,根据提示深入研究,如果没有兴趣,可以跳过,只要会使用.visible就可以了。 Three.js的WebGL渲染器WebGLRenderer在渲染一个点Points、线Line、网格Mesh等模型对象的时候,会判断它绑定材质的.visible属性值,如果一个模型绑定材质的.visible属性是false,该模型就不会被渲染,具体可以阅读src目录下的WebGLRenderer.js源码。

June 11, 2019 · 1 min · jiezi

threejsFailed-to-execute-texImage2D

使用three.js贴图片时报错:Failed to execute 'texImage2D' on 'WebGLRenderingContext': No function was found that matched the signature代码参照官方写的如下: var loader = new THREE.TextureLoader(); // load a resource loader.load( // resource URL 'img/dabeijing.png', // onLoad callback function ( texture ) { var mesh = new THREE.Mesh(new THREE.SphereGeometry(1000, 100, 100), new THREE.MeshBasicMaterial({ map: texture})); _scene.add(mesh); }, // onProgress callback currently not supported undefined, // onError callback function ( err ) { console.error( 'An error happened.' +err); } );刚开始用three.js,实在不知道为什么会报错,搜了下没发现有相关的错,于是仔细看了下错误提示,发现上面有这么一句话一开始我以为这是three.js内部给图片修改尺寸的提示,没注意看,实在找不出别的问题就试着改了下,我照着这个比例改的,然后就发现不报错了。先记录一下,有空可以查查为什么。 ...

May 9, 2019 · 1 min · jiezi

threejs-3d中-获取鼠标经过地点的新建元素

获取鼠标经过地点的元素 // 获取鼠标经过地点的元素 intersectObjects(pointer,camera,obj) { var domElement = viewer.impl.canvas; var pointerVector = new THREE.Vector3(); var pointerDir = new THREE.Vector3(); var ray = new THREE.Raycaster(); var rect = domElement.getBoundingClientRect(); var x = ( ( pointer.clientX - rect.left ) / rect.width ) * 2 - 1; var y = - ( ( pointer.clientY - rect.top ) / rect.height ) * 2 + 1; if (camera.isPerspective) { pointerVector.set(x, y, 0.5); pointerVector.unproject(camera); ray.set(camera.position, pointerVector.sub(camera.position).normalize()); } else { pointerVector.set(x, y, -1); pointerVector.unproject(camera); pointerDir.set(0, 0, -1); ray.set(pointerVector, pointerDir.transformDirection(camera.matrixWorld)); } var intersections = ray.intersectObjects( obj, true ); return intersections[0] ? intersections[0] : false;}// 鼠标滑过小球市时触发的事件hoverIntersectObjects(event){ var flag =true; var pointer = event.pointers ? event.pointers[ 0 ] : event; var camera = viewer.getCamera(); var objects = viewer.impl.overlayScenes.lable.scene.children; var obj = viewer.vmethods.intersectObjects(pointer,camera,objects).object; if(obj){ //隐藏不可选 if(!obj.visible)return; if(this.oldObj) this.oldObj.material.color.setHex( this.oldObj.currentHex ); this.oldObj=obj; this.oldObj.currentHex = obj.material.color.getHex(); this.oldObj.material.color.setHex( 0x660000); viewer.impl.invalidate(false, false, true); var pos=viewer.worldToClient(this.oldObj.position); var posX=pos.x-$(".k-tooltip").width()/2; var posY=pos.y-$(".k-tooltip").height()-20; $(".k-tooltip").css({"left":posX,"top":posY}); $(".k-tooltip").show(); $(".k-tooltip .name").text(this.oldObj.name); }else{ if(this.oldObj) { this.oldObj.material.color.setHex( this.oldObj.currentHex ); viewer.impl.invalidate(false, false, true) $(".k-tooltip").hide(); } }}

April 29, 2019 · 1 min · jiezi

3d中获取对象包围盒-位置的常用方法

// 获取对象的包围盒getObjboundsById(objId){ var box = new THREE.Box3(); var bounds = new THREE.Box3(); var instanceTree = viewer.model.getData().instanceTree; var fragList = viewer.model.getFragmentList(); box.makeEmpty(); bounds.makeEmpty(); instanceTree.enumNodeFragments(objId, function (fragId) { fragList.getWorldBounds(fragId, box); bounds.union(box) }, true) return bounds} // 获取中心位置getObjPosition(dbId){ var bound = vmethods.getObjboundsById(dbId); var posx = (bound.max.x + bound.min.x) / 2; var posy = (bound.max.y + bound.min.y) / 2; var posz = (bound.max.z + bound.min.z) / 2; var pos = { x:posx, y:posy, z:posz } return pos;}

April 29, 2019 · 1 min · jiezi

WebGL-threejs学习笔记-法向量网格材质MeshNormalMaterial的介绍和创建360度全景天空盒的方法

WebGL学习----Three.js学习笔记(5)点击查看demo演示Demo地址:https://nsytsqdtn.github.io/d...简单网格材质 MeshNormalMaterialMeshNormalMaterial是一种不受渲染时使用的颜色影响的材质,它只与自己每一个面从内到外的法向量有关。法向量在webgl中用处十分广泛,光的反射,以及三维图形的纹理映射都与这个有关。从图中可以看到,网格的每一面渲染的颜色都是不一样的,如果我们想要在物体表面添加法向量,我们可以使用的THREE.ArrowHelper去表示每一个法向量,它的参数为 THREE.ArrowHelper(dir, origin, length, color, headLength, headWidth) **其中参数的意义为:dir:方向,默认是法向量origin:开始的坐标位置 length:辅助线的长度color:辅助线的颜色headLength:头部的长度headWidth:头部的宽度** 对于一个球体,要描述它每一个面的法向量,首先需要对它的每一个面进行遍历,取出这个面上的三个顶点(因为webgl的面都是三角形,所以是三个顶点),通过divideScalar(3)这个函数计算它的中心位置,我们就可以在这个中心位置点上,从内向外引出一个ArrowHelper,来模拟法向量。 for(let i=0;i<sphereGeometry.faces.length;i++){//在每一个面上面循环 let face = sphereGeometry.faces[i];//得到每个面的对象 let centroid = new THREE.Vector3(); //先创建一个vector3对象,要使用这个对象找到每个面的中心 centroid.add(sphereGeometry.vertices[face.a]); // 将这该面的三个顶点的索引传给sphereGeometry.vertices找到其顶点的坐标 //再添加进centroid centroid.add(sphereGeometry.vertices[face.b]); centroid.add(sphereGeometry.vertices[face.c]); centroid.divideScalar(3);//三角形的中心点坐标 let arrow = new THREE.ArrowHelper( face.normal,//face这个面的法向量 centroid, 2, 0xffcc55, 0.5, 0.5);//箭头辅助线,相当于把法向量用箭头表示出来 sphere.add(arrow); }其中,centroid.add(sphereGeometry.vertices[face.a])这段代码中的sphereGeometry.vertices存有几何体的所有顶点信息,通过[ ]索引可以取得其中的某一个顶点。face.a还有下面的face.b和c都是该面的顶点索引号,表示这个面是由顶点编号为face.a,face.b,face.c的三个顶点所构成的一个三角形(webgl的面都是三角形),然后我们再计算这三个顶点的中心点。 菜单面板的设置在菜单面板中设置一些MeshNormalmaterial的一些属性,便于去测试这种材质的一些特质其中:**this.visible = meshMaterial.visible;//是否可见 this.wireframe = meshMaterial.wireframe;//是否以线框的方式渲染物体 this.wireframeWidth = meshMaterial.wireframeLinewidth;//线框的宽度 this.transparent = meshMaterial.transparent;//是否透明 this.opacity = meshMaterial.opacity;//透明度,需要transparent为true才有效果 this.side = "front";//边的渲染方式,有三种,前面,后面,还有双面 this.selectMesh = "sphere";//当前选择的几何体 this.shading = "smooth";//着色方式,有平面着色和平滑着色,对一个面很平的几何体几乎看不出区别,如正方体** function initDatGUI() { //设置菜单中需要的参数 controls = new function () { this.rotationSpeed = 0.02; this.visible = meshMaterial.visible;//是否可见 this.wireframe = meshMaterial.wireframe;//是否以线框的方式渲染物体 this.wireframeWidth = meshMaterial.wireframeLinewidth;//线框的宽度 this.transparent = meshMaterial.transparent;//是否透明 this.opacity = meshMaterial.opacity;//透明度,需要transparent为true才有效果 this.side = "front";//边的渲染方式,有三种,前面,后面,还有双面 this.selectMesh = "sphere";//当前选择的几何体 this.shading = "smooth";//着色方式,有平面着色和平滑着色,对一个面很平的几何体几乎看不出区别,如正方体 }; let gui = new dat.GUI(); //将刚刚设置的参数添加到菜单中 let F1 = gui.addFolder("Mesh"); F1.add(controls, "rotationSpeed", 0, 0.1); F1.add(controls, "visible").onChange(function (e) { meshMaterial.visible = e; }); F1.add(controls, "wireframe").onChange(function (e) { meshMaterial.wireframe = e; }); F1.add(controls, "wireframeWidth",0,10).onChange(function (e) { meshMaterial.wireframeWidth = e; }); F1.add(controls, "transparent").onChange(function (e) { meshMaterial.transparent = e; }); F1.add(controls, "opacity",0,1).onChange(function (e) { meshMaterial.opacity = e; }); F1.add(controls, "side",["front","back","double"]).onChange(function (e) { switch (e) { case "front": meshMaterial.side = THREE.FrontSide; break; case "back": meshMaterial.side = THREE.BackSide; break; case "double": meshMaterial.side = THREE.DoubleSide; break; } meshMaterial.needsUpdate = true;//要在程序中让材质更新需要添加这一句话 }); F1.add(controls, "selectMesh",["sphere","cube","plane"]).onChange(function (e) { //先把场景的物体清除,再来添加 scene.remove(cube); scene.remove(sphere); scene.remove(plane); switch (e) { case "sphere": scene.add(sphere); break; case "cube": scene.add(cube); break; case "plane": scene.add(plane); break; } }); F1.add(controls, "shading",["flat","smooth"]).onChange(function (e) { switch (e) { case "flat": meshMaterial.shading = THREE.FlatShading; break; case "smooth": meshMaterial.shading = THREE.SmoothShading; break; } meshMaterial.needsUpdate = true;//要在程序中让材质更新需要添加这一句话 }); }**注意在程序运行过程中想要改变材质的属性,需要在改完以后,添加一句meshMaterial.needsUpdate = true,这样才能更新成功。** ...

April 26, 2019 · 4 min · jiezi

WebGL-threejs学习笔记-创建threejs代码的基本框架

WebGL学习----Three.js学习笔记(1)webgl介绍WebGL是一种3D绘图协议,它把JavaScript和OpenGL ES 2.0结合在一起,通过增加OpenGL ES 2.0的一个JavaScript绑定,WebGL可以为HTML5 Canvas提供硬件3D加速渲染。 WebGL技术标准免去了开发网页专用渲染插件的麻烦,可被用于创建具有复杂3D结构的网站页面,甚至可以用来设计3D网页游戏。 原生的WebGl比较复杂,主要通过对顶点着色器和片元着色器的操作,来实现渲染,但实现起来比较复杂,需要一定的数学基础,但更多的是需要学习基础的耐心。 Three.js介绍Three.js是一个js的开源框架,它把webgl的所有东西都封装好了,我们不再需要去了解webgl那些比较麻烦的细节,直接在此框架上进行开发,既方便,又快捷,而且很容易就能学习,相对于原生的webgl花100多行代码画几个三角形,Three.js只需要几行代码就能实现更复杂的3D效果。 下载地址: https://github.com/mrdoob/thr...。 环境搭建为了以后的学习方便,首先是搭建一个万能框架,所有的three.js开发都可以在此框架上进行。 <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Three.js</title> <script src="../../../Import/three.js"></script> <script src="../../../Import/stats.js"></script> <script src="../../../Import/Setting.js"></script> <style type="text/css"> div#canvas-frame { border: none; cursor: pointer; width: 100%; height: 850px; background-color: #333333; } </style> <script> let renderer; function initThree() { //TODO } let camera; function initCamera() { //TODO } let scene; function initScene() { //TODO } let light; function initLight() { //TODO } let cube; function initObject() { //TODO } //提前定义好的一个功能文件,方便以后的每一个程序调用 function initSetting() { loadAutoScreen(camera,renderer);//自适应屏幕 loadFullScreen();//网页全屏播放 loadStats();//性能检测插件 } function threeStart() { initSetting(); initThree(); initCamera(); initScene(); initLight(); initObject(); animation(); } function animation(){ renderer.clear(); renderer.render(scene,camera); stats.update(); requestAnimationFrame(animation); } </script></head><body onload="threeStart()"><div id="canvas-frame"></div></body></html>其中Setting.js是我写在另一个文件里面的功能文件,把一些常用的功能放在里面,方便以后写的程序可以直接去调用Setting.js的代码如下: ...

April 24, 2019 · 2 min · jiezi

WebGL-threejs学习笔记-纹理贴图模拟太阳系运转

纹理贴图的应用以及实现一个太阳系的自转公转点击查看demo演示demo地址:https://nsytsqdtn.github.io/d...three.js中的纹理纹理贴图是通过将图像应用到对象的一个或多个面,来为3D对象添加细节的一种方法。可以使用TextureLoader类的load方法来加载纹理 function loadImgTexture(){ var loader = new THREE.TextureLoader(); loader.load("metal-rust.jpg",function(texture){ var geometry = new THREE.BoxGeometry(10,10,10); var material = new THREE.MeshBasicMaterial({color:0x739783,map:texture}); mesh = new THREE.Mesh(geometry,material); scene.add(mesh); })}实现效果如下: 八大行星的贴图资源网上到处都可以找到,这里给一个还不错的地址:https://tieba.baidu.com/p/487... 完整太阳系代码: <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Three.js</title> <script src="../../Import/three.js"></script> <script src="../../Import/stats.js"></script> <script src="../../Import/Setting.js"></script> <script src="../../Import/OrbitControls.js"></script> <style type="text/css"> div#canvas-frame { border: none; cursor: pointer; width: 100%; height: 850px; background-color: #333333; } </style></head><body onload="threeStart()"><script> let renderer; function initThree() { width = document.getElementById('canvas-frame').clientWidth; height = document.getElementById('canvas-frame').clientHeight; renderer = new THREE.WebGLRenderer({ antialias : true }); renderer.setSize(width,height); document.getElementById("canvas-frame").appendChild(renderer.domElement); renderer.setClearColor(0x333333, 1.0); } let camera; function initCamera() { camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000); camera.position.x = 0; camera.position.y = 500; camera.position.z = 2000; camera.up.x = 0; camera.up.y = 1; camera.up.z = 0; camera.lookAt(0,0,0); } let scene; function initScene() { scene = new THREE.Scene(); let bgTexture = new THREE.TextureLoader().load("../../Image/universe.jpg");//背景贴图 scene.background = bgTexture;//把场景的背景设置为一张图片 } let light; function initLight() { light = new THREE.PointLight(0xffffff,1);//点光源,模拟太阳 light.position.set(0,0,0); scene.add(light); light = new THREE.AmbientLight(0xffffff,0.3);//环境光 scene.add(light); } let sun; let earth; let tuxing; let shuixing; let jinxing; let huoxing; let muxing; let tianwangxing; let haiwangxing; function initObject() { let geometry = new THREE.SphereGeometry(15, 50, 50); let texture = THREE.ImageUtils.loadTexture("../../Image/shuixing.jpg");//定义一个贴图,并从本地文件中加载 let material = new THREE.MeshBasicMaterial({map:texture});//把上面定义的texture设置为星球的纹理材质 shuixing = new THREE.Mesh(geometry,material); shuixing.position.set(0,0,150);//3 scene.add(shuixing); geometry = new THREE.SphereGeometry(25, 50, 50); texture = THREE.ImageUtils.loadTexture("../../Image/jinxing.jpg"); material = new THREE.MeshBasicMaterial({map:texture}); jinxing = new THREE.Mesh(geometry,material); jinxing.position.set(0,0,200);//4 scene.add(jinxing); geometry = new THREE.SphereGeometry(30, 50, 50); texture = THREE.ImageUtils.loadTexture("../../Image/earth.jpg"); material = new THREE.MeshBasicMaterial({map:texture}); earth = new THREE.Mesh(geometry,material); earth.position.set(0,0,300);//6 scene.add(earth); geometry = new THREE.SphereGeometry(20, 50, 50); texture = THREE.ImageUtils.loadTexture("../../Image/huoxing.jpg"); material = new THREE.MeshBasicMaterial({map:texture}); huoxing = new THREE.Mesh(geometry,material); huoxing.position.set(0,0,400);//8 scene.add(huoxing); geometry = new THREE.SphereGeometry(65, 50, 50); texture = THREE.ImageUtils.loadTexture("../../Image/muxing.jpg"); material = new THREE.MeshBasicMaterial({map:texture}); muxing = new THREE.Mesh(geometry,material); muxing.position.set(0,0,600);//500 12 scene.add(muxing); geometry = new THREE.TorusGeometry(80,6,20,20); texture = THREE.ImageUtils.loadTexture("../../Image/muxinghuan.jpg"); material = new THREE.MeshBasicMaterial({map:texture}); let muxinghuan = new THREE.Mesh(geometry,material); muxinghuan.rotation.x = 1.4;//木星环 muxing.add(muxinghuan); geometry = new THREE.SphereGeometry(55, 50, 50); texture = THREE.ImageUtils.loadTexture("../../Image/tuxing.jpg"); material = new THREE.MeshBasicMaterial({map:texture}); tuxing = new THREE.Mesh(geometry,material); tuxing.position.set(0,0,800);//16 scene.add(tuxing); geometry = new THREE.TorusGeometry(65,8,20,20); texture = THREE.ImageUtils.loadTexture("../../Image/tuxinghuan.jpg"); material = new THREE.MeshBasicMaterial({map:texture}); let tuxinghuan = new THREE.Mesh(geometry,material); tuxinghuan.rotation.x = 1.4; tuxing.add(tuxinghuan); geometry = new THREE.SphereGeometry(45, 50, 50); texture = THREE.ImageUtils.loadTexture("../../Image/tianwangxing.jpg"); material = new THREE.MeshBasicMaterial({map:texture}); tianwangxing = new THREE.Mesh(geometry,material); tianwangxing.position.set(0,0,950);//19 scene.add(tianwangxing); geometry = new THREE.SphereGeometry(40, 50, 50); texture = THREE.ImageUtils.loadTexture("../../Image/haiwangxing.jpg"); material = new THREE.MeshBasicMaterial({map:texture}); haiwangxing = new THREE.Mesh(geometry,material); haiwangxing.position.set(0,0,1100);//22 scene.add(haiwangxing); geometry = new THREE.SphereGeometry(120, 50, 50); texture = THREE.ImageUtils.loadTexture("../../Image/sun.jpg"); material = new THREE.MeshBasicMaterial({map:texture, emissive: 0xffffff,side: THREE.DoubleSide,}); sun = new THREE.Mesh(geometry,material); sun.position.set(0,0,0); scene.add(sun); //在太阳外面设置一层透明的罩,看上去更像太阳 let canvas = document.createElement( 'canvas' ); canvas.width = 256; canvas.height = 256; let context = canvas.getContext( '2d' ); let gradient = context.createRadialGradient( canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2 );//创建一个圆形渐变对象 gradient.addColorStop( 0.1, 'rgba(255,60,0,0.01)' );//内圈的颜色 gradient.addColorStop( 1, 'rgba(255,125,0,0.5)' );//最外面的颜色 context.fillStyle = gradient; context.fillRect( 0, 0, canvas.width, canvas.height );//将一个画布使用圆形渐变对象进行填充 let shadowTexture = new THREE.CanvasTexture( canvas );//把刚刚画好的画布拿来作为画布贴图 let shadowMaterial = new THREE.MeshBasicMaterial( { map: shadowTexture,transparent:true } );//用此贴图来当材质 let shadowGeo = new THREE.SphereGeometry( 130,50,50); let shadowMesh; shadowMesh = new THREE.Mesh( shadowGeo, shadowMaterial ); shadowMesh.position.y = 0; shadowMesh.position.x = 0; sun.add( shadowMesh ); //画线,把太阳系每个星球运行轨迹画出来 for(let j=3;j<=22;j++) { if (j==3||j==4||j==6||j==8||j==12||j==16||j==19||j==22){ let radius = 50 * j; let lineGeometry2 = new THREE.Geometry(); for (let i = 0; i <= 2 * Math.PI; i += Math.PI / 30) { lineGeometry2.vertices.push(new THREE.Vector3(radius * Math.cos(i), 0, radius * Math.sin(i), 0)) } let material2 = new THREE.LineBasicMaterial({color: 0x8f6cab}) let cycleMesh = new THREE.Line(lineGeometry2, material2); cycleMesh.position.set(0, 0, 0); scene.add(cycleMesh); } } } //每个星球的自转函数 function zizhuan() { sun.rotation.y = sun.rotation.y+0.008>=2*Math.PI?0:sun.rotation.y+0.008; shuixing.rotation.y = shuixing.rotation.y+0.005>=2*Math.PI?0:shuixing.rotation.y+0.005; jinxing.rotation.y = jinxing.rotation.y+0.003>=0?2*Math.PI:jinxing.rotation.y+0.003; earth.rotation.y = earth.rotation.y+0.012>=2*Math.PI?0:earth.rotation.y+0.012; huoxing.rotation.y = huoxing.rotation.y+0.01>=2*Math.PI?0:huoxing.rotation.y+0.01; tuxing.rotation.y = tuxing.rotation.y+0.04>=2*Math.PI?0:tuxing.rotation.y+0.06; muxing.rotation.y = muxing.rotation.y+0.03>=2*Math.PI?0:muxing.rotation.y+0.08; tianwangxing.rotation.z = tianwangxing.rotation.z+0.015>=2*Math.PI?0:tianwangxing.rotation.z+0.015; haiwangxing.rotation.y = haiwangxing.rotation.y+0.02>=2*Math.PI?0:haiwangxing.rotation.y+0.02; } let shuixingAngle = 0; let jinxingAngle = 0; let earthAngle = 0; let huoxingAngle = 0; let tuxingAngle = 0; let muxingAngle = 0; let tianwangxingAngle = 0; let haiwangxingAngle = 0; //每个星球的公转函数 function gongzhuan() { shuixingAngle = shuixingAngle+0.03>=2*Math.PI?0:shuixingAngle +0.03; shuixing.position.set(150*Math.sin(shuixingAngle) ,0,150*Math.cos(shuixingAngle)); jinxingAngle = jinxingAngle-0.02>=0?2*Math.PI:jinxingAngle -0.02; jinxing.position.set(200*Math.sin(jinxingAngle) ,0,200*Math.cos(jinxingAngle)); earthAngle = earthAngle+0.015>=2*Math.PI?0:earthAngle +0.015; earth.position.set(300*Math.sin(earthAngle) ,0,300*Math.cos(earthAngle)); huoxingAngle = huoxingAngle+0.01>=2*Math.PI?0:huoxingAngle +0.01; huoxing.position.set(400*Math.sin(huoxingAngle) ,0,400*Math.cos(huoxingAngle)); muxingAngle = muxingAngle+0.006>=2*Math.PI?0:muxingAngle +0.006; muxing.position.set(600*Math.sin(muxingAngle) ,0,600*Math.cos(muxingAngle)); tuxingAngle = tuxingAngle+0.004>=2*Math.PI?0:tuxingAngle +0.004; tuxing.position.set(800*Math.sin(tuxingAngle) ,0,800*Math.cos(tuxingAngle)); tianwangxingAngle = tianwangxingAngle+0.003>=2*Math.PI?0:tianwangxingAngle +0.003; tianwangxing.position.set(950*Math.sin(tianwangxingAngle) ,0,950*Math.cos(tianwangxingAngle)); haiwangxingAngle = haiwangxingAngle+0.002>=2*Math.PI?0:haiwangxingAngle +0.002; haiwangxing.position.set(1100*Math.sin(haiwangxingAngle) ,0,1100*Math.cos(haiwangxingAngle)); } function initSetting() { loadAutoScreen(camera,renderer); loadFullScreen(); loadStats(); } let controller; function threeStart() { initThree(); initCamera(); initScene(); initLight(); initObject(); initSetting(); controller = new THREE.OrbitControls(camera,renderer.domElement); controller.target = new THREE.Vector3(0,0,0); controller.autoRotate = true; animation(); } function animation() { zizhuan(); gongzhuan(); renderer.clear(); renderer.render(scene,camera); requestAnimationFrame(animation); stats.update(); }</script><div id="canvas-frame"></div></body></html>注意从本地读取图片或者其他文件时,浏览器一般是不允许的,所以会出现加载不了纹理或者背景图片的情况,只能在Chrome浏览器的属性--目标的最后,加上 --allow-file-access-from-files(前面有一个空格),就可以从这个打开的快捷方式中去读取到本地文件,如果是Edge浏览器似乎就直接可以。也可以使用npm或者tomcat搭建一个本地服务器,然后把图片放进去就可以直接读取了。 ...

April 24, 2019 · 4 min · jiezi

WebGL-threejs学习笔记-阴影与实现物体的动画

实现物体的旋转、跳动以及场景阴影的开启与优化本程序将创建一个场景,并实现物体的动画效果运行的结果如图: 完整代码如下: <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Three.js</title> <script src="../../../Import/three.js"></script> <script src="../../../Import/stats.js"></script> <script src="../../../Import/Setting.js"></script> <script src="../../../Import/OrbitControls.js"></script> <script src="../../../Import/dat.gui.min.js"></script> <style type="text/css"> div#canvas-frame { border: none; cursor: pointer; width: 100%; height: 850px; background-color: #333333; } </style></head><body onload="threeStart()"><div id="canvas-frame"></div><script> //控制面板中需要的两个数据 let control = new function () { this.rotationSpeed = 0.01; this.jumpSpeed = 0.03; }; let renderer, camera, scene; let controller; let width,height; //初始化渲染器 function initThree() { width = document.getElementById('canvas-frame').clientWidth; height = document.getElementById('canvas-frame').clientHeight; renderer = new THREE.WebGLRenderer({ antialias: true });//定义渲染器 renderer.setSize(width, height);//设置渲染的宽度和高度 document.getElementById("canvas-frame").appendChild(renderer.domElement);//将渲染器加在html中的div里面 renderer.setClearColor(0x333333, 1.0);//渲染的颜色设置 renderer.shadowMapEnabled = true;//开启阴影,默认是关闭的,太影响性能 renderer.shadowMapType = THREE.PCFSoftShadowMap;//阴影的一个类型,可以不设置对比看效果 } //初始化摄像机 function initCamera() { camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);//perspective是透视摄像机,这种摄像机看上去画面有3D效果 //摄像机的位置 camera.position.x = 50; camera.position.y = 50; camera.position.z = 50; camera.up.x = 0; camera.up.y = 1;//摄像机的上方向是Y轴 camera.up.z = 0; camera.lookAt(0, 0, 0);//摄像机对焦的位置 //这三个参数共同作用才能决定画面 } //初始化场景 function initScene() { scene = new THREE.Scene(); } //摄像机的控制,可以采用鼠标拖动来控制视野 function cameraControl() { controller = new THREE.OrbitControls(camera, renderer.domElement); controller.target = new THREE.Vector3(0, 0, 0); } //一个很方便的控制面板,方便更改程序的参数 function datGUI() { let gui = new dat.GUI(); //可以设置可以调动的范围 gui.add(control, "rotationSpeed", 0, 0.05); gui.add(control, "jumpSpeed", 0, 0.08); } //初始化灯光 function initLight() { let light = new THREE.SpotLight(0xffffff, 1.0, 0);//点光源 light.position.set(-40, 60, -10); light.castShadow = true;//开启阴影 light.shadowMapWidth = 8192;//阴影的分辨率 light.shadowMapHeight = 8192; scene.add(light); light = new THREE.AmbientLight(0xffffff, 0.2);//环境光,如果不加,点光源照不到的地方就完全是黑色的 scene.add(light); } let cube; let sphere; //初始化物体 function initObject() { //定义了一个地面 let planeGeometry = new THREE.PlaneGeometry(100, 100, 1, 1); let planeMaterial = new THREE.MeshLambertMaterial({ color: 0xcccccc, }); let plane = new THREE.Mesh(planeGeometry, planeMaterial); plane.rotation.x = -0.5 * Math.PI; plane.position.x = 15; plane.receiveShadow = true;//开启地面的接收阴影 scene.add(plane);//添加到场景中 //定义了一个方块 let cubeGeometry = new THREE.BoxGeometry(4, 4, 4); let cubeMaterial = new THREE.MeshLambertMaterial({ color: 0xff1111, }); cube = new THREE.Mesh(cubeGeometry, cubeMaterial); cube.position.x = -4; cube.position.y = 3; cube.position.z = 0; cube.castShadow = true;//开启阴影 scene.add(cube); //定义了一个球体 let sphereGeometry = new THREE.SphereGeometry(4, 100, 100); let sphereMaterial = new THREE.MeshLambertMaterial({ color: 0xba7890, }); sphere = new THREE.Mesh(sphereGeometry, sphereMaterial); sphere.position.x = 20; sphere.position.y = 4; sphere.position.z = 2; sphere.castShadow = true;//开启阴影 scene.add(sphere); } //方块的自动旋转 function cubeRotation() { cube.rotation.x += control.rotationSpeed; cube.rotation.y += control.rotationSpeed; cube.rotation.z += control.rotationSpeed; } let step = 0; //球体的抛物线运动轨迹 function boxJump() { step += control.jumpSpeed; sphere.position.x = 20 + 10 * (Math.cos(step)); sphere.position.y = 4 + 10 * (Math.abs(Math.sin(step))); } //定义的一个功能文件 function initSetting() { loadAutoScreen(camera,renderer); loadFullScreen(); loadStats(); } //主函数 function threeStart() { initThree(); initCamera(); initScene(); initLight(); initObject(); cameraControl(); datGUI(); initSetting(); animation(); } //动画 function animation() { cubeRotation();//方块旋转函数 boxJump();//球体运动函数 stats.update();//更新性能检测器 renderer.clear(); renderer.render(scene, camera);//开始渲染 requestAnimationFrame(animation);//重复执行此函数,不停的渲染,达到动画的效果 }</script></body></html>其中OrbitControls.js和dat.gui.min.js这两个文件都是Three.js自带的两个很好用的工具,第一个是可以让摄像机有轨道地进行移动,而不用再自己写函数去实现,第二个是一个轻量级的图形用户界面库(GUI 组件),使用这个库可以很容易地创建出能够改变代码变量的界面组件,方便我们测试程序。 ...

April 24, 2019 · 2 min · jiezi

基于 three.js 的 3D 粒子动效实现

作者:个推web前端开发工程师 梁神一、背景粒子特效是为模拟现实中的水、火、雾、气等效果由各种三维软件开发的制作模块,原理是将无数的单个粒子组合使其呈现出固定形态,借由控制器、脚本来控制其整体或单个的运动,模拟出现真实的效果。three.js是用JavaScript编写的WebGL的第三方库,three.js提供了丰富的API帮助我们去实现3D动效,本文主要介绍如何使用three.js实现粒子过渡效果,以及基本的鼠标交互操作。(注:本文使用的关于three.js的API都是基于版本r98的。)二、实现步骤1. 创建渲染场景scenescene实际上相当于一个三维空间,用于承载和显示我们所定义的一切,包括相机、物体、灯光等。在实际开发时为了方便观察可添加一些辅助工具,比如网格、坐标轴等。scene = new THREE.Scene(); scene.fog = new THREE.Fog(0x05050c, 10, 60); scene.add( new THREE.GridHelper( 2000, 1 ) ); // 添加网格2. 添加照相机cameraTHREE里面实现了几种相机:PerspectiveCamera(透视相机)、 OrthographicCamera(正交投影相机)、CubeCamera(立方体相机或全景相机)和 StereoCamera(3D相机)。本文介绍我们主要用到的 PerspectiveCamera(透视相机):视觉效果是近大远小。配置参数 PerspectiveCamera(fov, aspect, near, far)。fov:相机的可视角度。aspect:相机可视范围的长宽比。near:相对于深度剪切面的远的距离。far:相对于深度剪切面的远的距离。camera = new THREE.PerspectiveCamera(45, window.innerWidth /window.innerHeight, 5, 100); camera.position.set(10, -10, -40); scene.add(camera);3. 添加场景渲染需要的灯光three.js里面实现的光源:AmbientLight(环境光)、DirectionalLight(平行光)、HemisphereLight(半球光)、PointLight(点光源)、RectAreaLight(平面光源)、SpotLight(聚光灯)等。配置光源参数时需要注意颜色的叠加效果,如环境光的颜色会直接作用于物体的当前颜色。各种光源的配置参数有些区别,下面是本文案例中会用到的二种光源。let ambientLight = new THREE.AmbientLight(0x000000, 0.4); scene.add(ambientLight); let pointLight = new THREE.PointLight(0xe42107); pointLight.castShadow = true; pointLight.position.set(-10, -5, -10); pointLight.distance = 20; scene.add(pointLight);4. 创建、导出并加载模型文件loader创建模型,可以使用three.js editor进行创建或者用three.js的基础模型生成类进行生成,相对复杂的或者比较特殊的模型需要使用建模工具进行创建(c4d、3dmax等)。使用three.js editor进行创建,可添加基本几何体,调整几何体的各种参数(位置、颜色、材质等)。使用模型类生成。let geometryCube = new THREE.BoxBufferGeometry( 1, 1, 1 ); let materialCube = new THREE.MeshBasicMaterial( {color: 0x00ff00} ); let cubeMesh = new THREE.Mesh( geometryCube, materialCube ); scene.add( cubeMesh );导出需要的模型文件(此处使用的是 obj格式的模型文件)。加载并解析模型文件数据。let onProgress = function (xhr) { if (xhr.lengthComputable) { // 可进行计算得知模型加载进度 } }; let onError = function () {}; particleSystem = new THREE.Group(); var texture = new THREE.TextureLoader().load(’./point.png’); new THREE.OBJLoader().load(’./model.obj’, function (object) { // object 模型文件数据 }, onProgress, onError);5. 将导入到模型文件转换成粒子系统Points获取模型的坐标值。拷贝粒子坐标值到新建属性position1上 ,这个作为粒子过渡效果的最终坐标位置。给粒子系统添加随机三维坐标值position,目的是把每个粒子位置打乱,设定起始位置。let color = new THREE.Color(’#ffffff’); let material = new THREE.PointsMaterial({ size: 0.2, map: texture, depthTest: false, transparent: true }); particleSystem= new THREE.Group(); let allCount = 0 for (let i = 0; i < object.children.length; i++) { let name = object.children[i].name let _attributes = object.children[i].geometry.attributes let count = _attributes.position.count _attributes.positionEnd = _attributes.position.clone() _attributes.position1 = _attributes.position.clone() for (let i = 0; i < count * 3; i++) { _attributes.position1.array[i]= Math.random() * 100 - 50 } let particles = new THREE.Points(object.children[i].geometry, material) particleSystem.add(particles) allCount += count } particleSystem.applyMatrix(new THREE.Matrix4().makeTranslation(-5, -5,-10));6. 通过tween动画库实现粒子坐标从position到position1点转换利用 TWEEN 的缓动算法计算出各个粒子每一次变化的坐标位置,从初始位置到结束位置时间设置为2s(可自定义),每次执行计算之后都需要将attributes的position属性设置为true,用来提醒场景需要更新,在下次渲染时,render会使用最新计算的值进行渲染。let pos = { val: 1 }; tween = new TWEEN.Tween(pos).to({ val: 0 }, 2500).easing(TWEEN.Easing.Quadratic.InOut).onUpdate(callback); tween.onComplete(function () { console.log(‘过渡完成complete’) }) tween.start(); function callback() { let val = this.val; let particles = particleSystem.children; for (let i = 0; i < particles.length; i++) { let attributes = particles[i].geometry.attributes let name = particles[i].name if (name.indexOf(’’) === -1) { let positionEnd =_attributes.positionEnd.array let position1 =_attributes.position1.array let count =_attributes.position.count for (let j = 0; j < count *3; j++) { _attributes.position.array[j] = position1[j] *val + positionEnd[j] * (1 - val) } } _attributes.position.needsUpdate = true // 设置更新 } }7. 添加渲染场景render创建容器。定义render渲染器,设置各个参数。将渲染器添加到容器里。自定义的渲染函数 render,在渲染函数里面我们利用 TWEEN.update 去更新模型的状态。调用自定义的循环动画执行函数 animate,利用requestAnimationFrame方法进行逐帧渲染。let container = document.createElement(‘div’); document.body.appendChild(container); renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setClearColor(scene.fog.color); renderer.setClearAlpha(0.8); renderer.setSize(window.innerWidth, window.innerHeight); container.appendChild(renderer.domElement); // 添加webgl渲染器 function render() { particleSystem.rotation.y += 0.0001; TWEEN.update(); particleSystem.rotation.y += (mouseX + camera.rotation.x) * .00001; camera.lookAt(new THREE.Vector3(-10, -5, -10)) controls.update(); renderer.render(scene, camera); } function animate() { // 开始循环执行渲染动画 requestAnimationFrame(animate); render(); }8. 添加鼠标操作事件实现角度控制我们还可以添加鼠标操作事件实现角度控制,其中winX、winY分别为window的宽高的一半,当然具体的坐标位置可以根据自己的需求进行计算,具体的效果如下图所示。document.addEventListener(‘mousemove’, onDocumentMouseMove, false); function onDocumentMouseMove(event) { mouseX = (event.clientX - winX) / 2; mouseY = (event.clientY - winY) / 2; }三、优化方案1. 减少粒子数量随着粒子数量的增加,需要的计算每个粒子的位置和大小将会非常耗时,可能会造成动画卡顿或出现页面假死的情况,所以我们在建立模型时可尽量减少粒子的数量,能够有效提升性能。在以上示例中,我们改变导出模型的精细程度,可以得到不同数量的粒子系统,当粒子数量达到几十万甚至几百万的时候,在动画加载时可以感受到明显的卡顿现象,这主要是由于fps比较低,具体的对比效果如下图所示,左边粒子数量为30万,右边粒子数量为6万,可以明显看出左边跳帧明显,右边基本保持比较流畅的状态。2. 采用GPU渲染方式编写片元着色器代码,利用webgl可以为canvas提供硬件3D加速,浏览器可以更流畅地渲染页面。目前大多数设备都已经支持该方式,需要注意的是在低端的设备上由于硬件设备原因,渲染的速度可能不及基于cpu计算的方式渲染。四、总结综上所述,实现粒子动效的关键在于计算、维护每个粒子的位置状态,而three.js提供了较为便利的方法,可以用于渲染整个粒子场景。当粒子数量极为庞大时,想要实现较为流畅的动画效果需要注意优化代码、减少计算等,也可以通过提升硬件配置来达到效果。本文中的案例为大家展示了3D粒子动效如何实现,大家可以根据自己的实际需求去制作更炫酷的动态效果。 ...

April 8, 2019 · 2 min · jiezi

Three.js官方demo分析(四)--- 海洋

这是今天分析的demo, 这是源码。下面是要分析的内容:1.第一人称控制器 // 初始化第一人称控制器,类似cs等第一人称控制器 controls = new THREE.FirstPersonControls( camera ); // 相机的移动速度 controls.movementSpeed = 500; // 鼠标移动查看速度 controls.lookSpeed = 0.1;2.初始化场景 // 设置背景颜色 scene.background = new THREE.Color( 0xaaccff ); // 指定场景中的雾为指数雾,参数为雾的颜色和雾的密度会增长多快 scene.fog = new THREE.FogExp2( 0xaaccff, 0.0007 );3.设置时间相关的对象 // 初始化时间相关的对象 clock = new THREE.Clock(); // 每一帧的时间 var delta = clock.getDelta(); // 从开始到现在总共的时间 var time = clock.getElapsedTime() * 10;4.海浪的波动——海浪的波动实质是将平面翻转,通过平面宽度和高度分段数,将平面切成很多小片。 geometry = new THREE.PlaneBufferGeometry( 20000, 20000, worldWidth - 1, worldDepth - 1 ); geometry.rotateX( - Math.PI / 2 );小片的数量等于worldWidth * worldDepth,遍历小片,以时间和小片序号为参数,计算小片在y轴的位置并设置。 // 获取物体的属性 var position = geometry.attributes.position; // 控制波浪的波动, position.count = worldWidth * worldDepth for ( var i = 0; i < position.count; i ++ ) { var y = 35 * Math.sin( i / 5 + ( time + i ) / 7 ); position.setY( i, y ); } position.needsUpdate = true; ...

March 29, 2019 · 1 min · jiezi

Three.js官网demo分析(三)--- 聚光灯与阴影

这是今天要分析的demo,这是源码下面来分析几段代码1.控制器的设置 // 初始化控制器 var controls = new THREE.OrbitControls( camera, renderer.domElement ); // 如果有animate函数,直接在函数里用controls.update(), 不用下面的方式 controls.addEventListener( ‘change’, render ); // 最大最小移动距离 controls.minDistance = 20; controls.maxDistance = 500; // 是否可以平移 controls.enablePan = true; // 平移速度 controls.keyPanSpeed = 89; // 控制器聚焦的对象 controls.target.copy( mesh.position ); controls.update();2.初始化聚光灯 // 初始化聚光灯 spotLight = new THREE.SpotLight( 0xffffff, 1 ); // 聚光灯的位置 spotLight.position.set( 50, 20, 0 ); // 聚光灯光束的角度,设置的是角的一边到角度二分线的角度 spotLight.angle = Math.PI / 6; // 光束从中心到边缘衰减的百分比,值为从0到1的数字 spotLight.penumbra = 0.5; // 沿着光照距离的衰减量, 但我改变数值没什么变化 spotLight.decay = 20; // 从光源发出光的最大距离,其强度根据光源的距离线性衰减 spotLight.distance = 200;3.阴影的设置 // 是否投射阴影 spotLight.castShadow = true; // 设置阴影的分辨率 spotLight.shadow.mapSize.width = 1024; spotLight.shadow.mapSize.height = 1024; // 阴影能显示的近点和远点 spotLight.shadow.camera.near = 10; spotLight.shadow.camera.far = 20; scene.add( spotLight );4.辅助线的引入 // 模拟聚光灯的辅助线 lightHelper = new THREE.SpotLightHelper( spotLight ); scene.add( lightHelper ); // 拟灯光相机的视椎体,传入的参数相当于产生阴影的灯光相机 shadowCameraHelper = new THREE.CameraHelper( spotLight.shadow.camera ); scene.add( shadowCameraHelper ); // 坐标轴 scene.add( new THREE.AxesHelper( 10 ) );需要注意的是shadowCameraHelper = new THREE.CameraHelper( spotLight.shadow.camera )这句,生成视椎体如下它模拟的是灯光相机的视椎体,并不是我们定义的相机的视椎体,传入的参数是灯光相机,即spotLight.shadow5.阴影的产生首先要渲染器声明使用阴影贴图 // 设置使用阴影贴图 ?? renderer.shadowMap.enabled = true; // 定义阴影贴图类型 ?? 据说这个类型的阴影看起来比较清晰 renderer.shadowMap.type = THREE.PCFSoftShadowMap;然后灯光设置 // 是否投射阴影 spotLight.castShadow = true;之后设置材质是否接受阴影 // 材质是否接受阴影 mesh.receiveShadow = true;最后设置材质是否投射阴影 // 对象是否被渲染到阴影贴图中 mesh.castShadow = true; ...

March 29, 2019 · 1 min · jiezi

Three.js官网demo分析(二)---多种立体与光线

这是今天要分析的官网demo,这是源码。初始化相机,场景后,这次还初始化了两种光源。 // 初始化环境光照,表示环境的基础亮度,参数1为光源颜色,参数2为光源强度 // 环境光在没有物体的情况下没用 // 不透明物体的颜色是反射光的颜色,在初始化纹理处可设置ambient属性,表示物体反射环境光的能力 var ambientLight = new THREE.AmbientLight( 0xcccccc, 0.5 ); scene.add( ambientLight ); // 初始化点光源,参数为颜色,亮度,照射的最远距离 var pointLight = new THREE.PointLight( 0xffffff, 0.8 );具体可以看入门指南中的光与影,另外除了光,你还需要了解光对不同材质的作用才能很好的得到你想要的效果,对于这一点,入门指南中材质章节有很好的解释。大致的意思如下:BasicMaterial-基础材质直接用color属性设置材质颜色,不会受光照影响,不会有高光阴影部分。MeshLambertMaterial-漫反射材料,颜色主要为光照在物体上漫反射到我们眼睛中的颜色,color属性表示物体对散射光的反射能力,如果光源为白光0xffffff,color设置为0x00ff00,即能反射绿光,那么物体就会呈现绿色,而如果光源为红光,那么物体就不会反射光,表现为黑色。emissive表现物体自发光的能力,一般在反射光弱的地方(如背光部分)会比较明显。ambient表示材料对环境光的反射能力,环境光一般是给场景一个基础的光亮,要与其他光源区别开来。MeshPhongMaterial-phone材质,有镜面反射的效果。其他属性与漫反射材质一样,有两个独有的属性,specular和shininess,specular表示镜面反射部分对光的反射能力。shininess控制高光的光斑的大小。然后回到源码 // 将点光源作为相机的子对象,点光源的位置即相机的位置 camera.add( pointLight ); scene.add( camera );文档中说可以通过.add( object )方法来将对象进行组合,该方法将对象添加为子对象camera继承自object3D,用这个属性将点光源添加为子对象,然后点光源的位置就会随相机的位置改变,就像你拿着一个手电筒一样。 // 加载纹理 var map = new THREE.TextureLoader().load( ’textures/UV_Grid_Sm.jpg’ ); // wrapS表示纹理在水平方向如何包裹,wrapT表示在垂直方向,使用RepeatWrapping,纹理将简单地重复到无穷大 ?? map.wrapS = map.wrapT = THREE.RepeatWrapping; // 沿着轴,通过具有最高纹素密度的像素的样本数 ?? map.anisotropy = 16; // 初始化Phong网孔材料,具有镜面高光的光泽表面 var material = new THREE.MeshPhongMaterial( { map: map, side: THREE.DoubleSide } );加载纹理这部分我不是很理解,感兴趣可以看下官网。然后是构建几何体部分,推荐看入门指南中的基础几何形状和官网。其中比较有趣的是 // 构建二维对象数组 var points = []; for ( var i = 0; i < 50; i ++ ) { points.push( new THREE.Vector2( Math.sin( i * 0.2 ) * Math.sin( i * 0.1 ) * 15 + 50, ( i - 5 ) * 2 ) ); } // 车床几何体,由二维对象数组形成的曲线绕x轴旋转一周获得 object = new THREE.Mesh( new THREE.LatheBufferGeometry( points, 20 ), material ); object.position.set( - 100, 0, - 200 ); scene.add( object );这个可以用来构造花瓶等曲面轴对称物体,其中的二维对象数组就像我们学的一元函数一样,在坐标系中可以表示出各种曲线,然后绕x轴旋转一周得到立体图形。这个的关键就是怎么得到你想要形状的数学表达式,这个就需要数学功底了,不知道有没有画一条线就能得到近似表达式的软件。 // 对该对象和子对象应用的回调函数 scene.traverse( function ( object ) { // 如果对象为网格对象 if ( object.isMesh === true ) { object.rotation.x = timer * 5; object.rotation.y = timer * 2.5; } } );这个traverse方法也是继承自object3D,这里用来给场景中的所有mesh物体加上循环调用的函数,使其不断旋转。下面是最近搜集的好资料three.js学习群群主的博客,相当详细。https://blog.csdn.net/qq_3010…three.js正统的学习网站 http://learningthreejs.com/挺详细的博文 https://www.cnblogs.com/cathe…有人写的文档 https://teakki.com/p/58a3ef1b…对光源的讲解 http://www.hangge.com/blog/ca… ...

March 28, 2019 · 1 min · jiezi

Three.js官网demo分析(一)

Three.js官方文档比较精简,重点介绍api,没有一个合适的零基础的教程,但官网的demo十分丰富,遂产生从demo来逐渐学习Three.js的想法。以下是目前收集到的较好的学习资源:Three.js入门指南:http://www.ituring.com.cn/boo…Three.js官网中文文档:https://threejs.org/docs/inde…大牛博客,有很多demo: http://www.wjceo.com/blog/thr…图解WebGL&Three.js工作原理: https://www.cnblogs.com/wanbo...Three.js源码注释: https://blog.csdn.net/omni360…好,下面回归正题,这是今天要分析的: demo,官网的例子,可以直接跳到github上查看源码,我建议直接把three.js整个项目下载下来,可以直接查看demo的效果,直接打开html文件可能效果有不一样,可以看官网的说法, 也就是需要启动一个本地的服务器,如果你用的webstorm的话,直接右键html文件,就会有个 run 文件的选项,点击就可以启动一个本地服务器来启动本地的文件。下面是demo的代码:var camera, scene, renderer; var mesh; init(); animate(); function init() { // 初始化透视相机,参数(fov : Number, aspect : Number, near : Number, far : Number) // fov — 摄像机视锥体垂直视野角度 // aspect — 摄像机视锥体长宽比 // near — 摄像机视锥体近端面 // far — 摄像机视锥体远端面 camera = new THREE.PerspectiveCamera( 70, ( window.innerWidth - 600 ) / window.innerHeight, 1, 1000 ); // position是camera继承自Object3D的属性,表示相机对象在三维空间中的位置 camera.position.z = 400; // 初始化场景 scene = new THREE.Scene(); // 加载纹理,TextureLoader是一个加载器,load方法加载文件,返回一个新的纹理对象 var texture = new THREE.TextureLoader().load( ’textures/crate.gif’ ); // BoxBufferGeometry(width : Float, height : Float, depth : Float, widthSegments : Integer, heightSegments : Integer, depthSegments : Integer) var geometry = new THREE.BoxBufferGeometry( 200, 200, 200 ); // 材质,map属性表示贴图,这里将之前的texture纹理对象添加进来 var material = new THREE.MeshBasicMaterial( { map: texture } ); // Mesh方法表示以三角形组成基本网格来形成物体 mesh = new THREE.Mesh( geometry, material ); // 将物体添加到场景中 scene.add( mesh ); // 初始化渲染器,antialias表示抗锯齿, canvas属性将three.js加载到已有的canvas元素上。 renderer = new THREE.WebGLRenderer( { antialias: true, canvas: document.getElementById(‘my-canvas’) } ); // 设置设备像素比。通常用于避免HiDPI设备上绘图模糊 renderer.setPixelRatio( window.devicePixelRatio ); // .setSiZe( width : Integer, height : Integer, updateStyle : Boolean ) // 设置输出canvas的大小,将updateStyle设置为false以阻止对canvas的样式做改变 renderer.setSize( window.innerWidth - 600, window.innerHeight ); // domElement为自动创建或已有的canvas元素 document.body.appendChild( renderer.domElement ); // 监听浏览器尺寸的修改 window.addEventListener( ‘resize’, onWindowResize, false ); } // 浏览器尺寸改变时的回调,重新设置相机和渲染器参数 function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; // 在大多数属性发生改变之后,你将需要调用.updateProjectionMatrix来使得这些改变生效。 camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); } // 动画 function animate() { // 每一帧调用一次 requestAnimationFrame( animate ); mesh.rotation.x += 0.005; mesh.rotation.y += 0.01; renderer.render( scene, camera ); }对于初始化透视相机中fov — 摄像机视锥体垂直视野角度和视锥体的理解,可以看看入门指南中的透视相机一章,但视锥体的长宽比都说要和canvas的长宽比一样,我试了一下,如果不一样的话物体就会变形。有点难以理解,其实道理跟图片要在固定长宽的div填满且没有裁剪和空白的情形一样,如果图片跟div长宽比不同的话就会变形。这里的视锥体垂直视野角度在照相机距离物体角度固定的情况下,相当于固定了宽,再根据长宽比算出长,视锥体的面积就固定了,此时视锥体的就相当于一张图片,而canvas就相当于固定宽高的div,所以此时视锥体拉伸铺满。widthSegments属性可以看入门指南中的基本形状了解。设备像素比可以看链接一般情况下new THREE.WebGLRenderer()会自动创建一个canvas元素,如果想用自己定义的canvas元素的话,可以传入canvas属性,值为获取的元素。 ...

March 26, 2019 · 2 min · jiezi

记一次雪花效果

前言最近,公司UI小姐姐告诉我能不能做一个关于雪花的效果图,最好是能体现雪花的远近感(远的时候比较小 近的时候雪花比较大),我寻思良久,一开始用canvas做了一个雪花效果 感觉不是很满意,然后就该用了three.js做了一个关于雪花的效果。效果还行 给大家先看一下效果。附上地址在线预览:雪花活动页GitHub地址:传送门 如果觉的有用,记得帮我fork和star一下 谢谢1:准备工作为了能够显示任何带有three.js的东西,我们需要三件事:场景,相机和渲染器,这样我们就可以用相机渲染场景代码:function init() { container = document.createElement(‘div’); container.className = ‘snow’; document.body.appendChild(container); camera = new THREE.PerspectiveCamera( 75, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 10000 ); //透视投影相机 camera.position.z = 1000; scene = new THREE.Scene(); scene.add(camera); renderer = new THREE.CanvasRenderer(); renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT); // console.log(SCREEN_WIDTH, SCREEN_HEIGHT); var material = new THREE.ParticleBasicMaterial( { map: new THREE.Texture(particleImage) } );2:随机生成不同位置一定数量的雪花for (var i = 0; i < 200; i++) { particle = new Particle3D( material); particle.position.x = Math.random() * 2000 - 1000; particle.position.y = Math.random() * 2000 - 1000; particle.position.z = Math.random() * 2000 - 1000; particle.scale.x = particle.scale.y = 1; scene.add( particle ); particles.push(particle); }进行雪花位置优化for(var i = 0; i < particles.length; i++) { var particle = particles[i]; particle.updatePhysics(); with(particle.position) { if(y < -1000) y += 2000; if(x > 1000) x -= 2000; else if(x <- 1000) x += 2000; if(z > 1000) z -= 2000; else if(z <- 1000) z += 2000; } }3:雪花远小近大的效果雪花的远小近大的效果是通过改变相机的位置来的 camera.position.x += (mouseX - camera.position.x ) * 0.05; camera.position.y += (- mouseY - camera.position.y ) * 0.05; camera.lookAt(scene.position); renderer.render( scene, camera );4:雪花的自由下落这里是利用了gravity重力,让他下落的,我们也可以通过改变它的大小来改变速度。Particle3D = function(material){ THREE.Particle.call(this,material); this.velocity = new THREE.Vector3(0,-8,0); this.velocity.rotateX(randomRange(-45,45)); this.velocity.rotateY(randomRange(0,360)); this.gravity = new THREE.Vector3(0,0,0); this.drag=1;};Particle3D.prototype = new THREE.Particle();Particle3D.prototype.constructor = Particle3D;Particle3D.prototype.updatePhysics = function(){ this.velocity.multiplyScalar(this.drag); this.velocity.addSelf(this.gravity); this.position.addSelf(this.velocity);}5:雪花旋转效果至于雪花的旋转我也做了一定的优化THREE.Vector3.prototype.rotateY=function(angle){ cosRY = Math.cos(angle * Math.PI/180); sinRY = Math.sin(angle * Math.PI/180); var tempz = this.z;; var tempx = this.x; this.x = (tempx * cosRY) + (tempz * sinRY); this.z = (tempx * -sinRY) + (tempz * cosRY);}活动页活动页效果就不细细说了,我就把雪花效果添加进去活动页。使用pointer-events:none,表示它将捕获不到任何点击,而只是让事件穿透到它的下面。 .snow { position: fixed; top: 0; left: 0; z-index: 10000; transform: translate3d(0, 0, 0); width: 100%; height: 100%; pointer-events: none; }其他这个活动页还有一个问题,就是按住屏幕(往下轻滑),雪就卡住了一小会希望有大佬来帮我解决这个问题。鄙人不胜感激。总结作为一个即将毕业的大四学生,这是我来公司实习做的第一个活动页,希望可以帮助大家,互相学习,一起进步。当然也有一些不足之处,请大家多多指教。如果大家有什么好的想法的话可以联系我的qq:137032979.码字不容易,希望大家点个赞。前端路漫漫,与君共勉之。 ...

January 22, 2019 · 2 min · jiezi

Three.js 选择拾取对象学习总结

Three.js 选择对象的本质是从点击位置发射光线,但屏幕坐标系与webgl坐标系是不同的,而把屏幕的二维坐标转化为三维坐标就是关键,做一步换算后交由Raycaster的setFromCamera方法即可。所以思路如下:1.获取屏幕坐标(x, y)2.换算至webgl坐标中的(x2,y2),此时长度仍为像素单位下的长度3.由于webgl坐标轴的范围为(-1,1),做个比值,称为标准化4.Raycaster.setFromCamera(mouse, camera)发射射线5.Raycaster.intersectObjects获取射得的所有对象,再按需要操作先对比下各坐标轴,再按代码分析步骤代码中使用了我封装的方法,threeConf对象包含three.js的基本组件,但不影响拾取对象的逻辑,拾取的代码于此一、坐标轴1.屏幕坐标轴即我们的屏幕的二维坐标,矩形为我们的屏幕,左上角就是(0, 0),长度即是以px为单位2.世界坐标系即webgl中的坐标系,屏幕中心为(0, 0, z),第三个坐标为从屏幕指向我们的z轴。三个坐标的大小范围为(-1,1),如图这里其实还有个相机坐标系,因为还没有学习webgl或者opengl怕胡说八道,我觉得一篇很棒的blog贴于文末但是我们实现选择拾取功能时,但单纯从three.js角度来看可以不需要相关知识二、代码思路全部代码请看上方地址1.坐标标准化//1.获取屏幕坐标(x, y)//2.换算至webgl坐标中的(x2,y2),此时长度单位仍为像素//3.由于webgl坐标轴的范围为(-1,1),做个比值,称为标准化//这三步后化简的式子如下,这里我们three.js的窗口为整个浏览器let rayRaster = new THREE.Raycaster();let mouse = new THREE.Vector2();function onMouseMove(event) { mouse.x = (event.clientX/window.innerWidth) * 2 - 1; mouse.y = 1 -(event.clientY/window.innerHeight) * 2 ;}window.addEventListener(‘mousemove’, onMouseMove, false);x1 = event.clientX,y1 = event.clientY,即点击位置,换算过程如下(这里默认画布宽高为浏览器宽高)根据式子带入x1与y1即可得到代码中相同的式子2.发射射线,获取对象这里截取射线代码function render() { //射线射出 rayRaster.setFromCamera(mouse, threeConf.camera); //射线上的物体 let intersects = rayRaster.intersectObjects(threeConf.scene.children); //选中另一物体的情况,仍为同一物体则不更新,这里我们只拿第一个物体 if(selectedObj !== intersects[0]){ selectedObj && selectedObj.object.material.color.set(0x87CEEB); //复原 intersects.length && intersects[0].object.material.color.set(0xff0000); selectedObj = intersects[0]; document.body.style.cursor = “pointer”; } //未选中物体情况 if(intersects[0] === undefined){ document.body.style.cursor = “auto”; } threeConf.stats.update(); threeConf.renderer.render(threeConf.scene, threeConf.camera); requestAnimationFrame(render);}即可三、参考资料OpenGL.坐标系统的介绍与坐标变换的实现(此篇blog包含相机坐标轴)https://blog.csdn.net/stringN…ThreeJS中的点击与交互——Raycaster的用法https://segmentfault.com/a/11…Three.js Raycaster官方文档https://threejs.org/docs/#api… ...

December 19, 2018 · 1 min · jiezi