·飞飞 - 2025-5-5 16:25
·飞飞 - 2025-5-5 16:20
·悄然 - 2025-5-3 17:17
·飞飞 - 2025-5-3 17:16
·八爷 - 2025-5-1 21:06
·飞飞 - 2025-5-1 17:34
·知名不具 - 2025-4-30 22:01
·马黑 - 2025-4-30 20:58
·飞飞 - 2025-4-30 19:51
使用three.js画一条水平线
three.js 基于3d,用它画线条可能大材小用,不过值得一试:首先这是一项颇具挑战性的练习,其次可以通过此项任务的完成略窥 three.js 的工作机制、原理。
首先我们需要引入 three.js。在 JS 代码中使用ES模块比较省事,像这样,从开放的、境内连接速度较好的 esm.sh 站点获取封装好的 three 核心 ES 模块:
<script type="module"> // 导入three.js import * as THREE from "https://esm.sh/three"; // 其它实现绘制水平线的代码 </script>
接下来正式开干。我们分几个步骤一一弄下去。
第一步,创建场景。在 three.js 的世界里,一切都得依托于场景 Scene,场景好比舞台,将来的一切都在其上摆放和展示。创建场景就是实例化一个 three.js 封装好的 Scene:
<script type="module"> // 导入three.js import * as THREE from "https://esm.sh/three"; // 1. 创建场景 const scene = new THREE.Scene(); // 其它实现绘制水平线的代码 </script>
略微解释一下:three.js 所封装的东东我们在引入 three.js 模块时用 * 代表全部,并作为 THREE 导入,场景 Scene 属于 * 里的内容之一,我们这里将 THREE.Scene 实例化为 scene,scene 是我们自定义的实例化 THREE.Scene 场景标识。此类事情稍后我们还会做很多很多,自定义实例化变量的命名头一个字母不大写。
第二步,架设一部 three 相机 camera,可拍照可录像的摄像机。相机好比屏幕前观看场景者的眼睛,处在Z轴方向,Z轴与屏幕垂直。three.js 封装了很多种类的相机,这里用它的透视相机,让我们来实例化这个相机对象:
<script type="module"> // 导入three.js import * as THREE from "https://esm.sh/three"; // 1. 创建场景 const scene = new THREE.Scene(); // 2. 创建相机 const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500); camera.position.set(0, 0, 50); // 相机位置 :基于场景,Z轴方向距离 scene 50个单位长度 //其它实现绘制水平线的代码 </script>
相机也是场景的一部分,它可以放在场景中,也可以拿到场景外头来。作为透视相机,总要和场景拉开距离,Z轴位置若设置为 0,那将什么都看不到。
第三步,创建渲染器。渲染器是 three.js 渲染效果的核心,可以把渲染器理解为相机进入拍摄状态的机制。渲染器的工作原理大致是这样子:在web页中创建 canvas 画布,将画布作为 Scene 场景,然后在其上按照设计好的各种数据绘制图形。下面我们添加 three.js 封装的渲染器 WebGLRenderer 的实例化并设置渲染尺寸、追加到 Web 页的 body 标签中:
<script type="module"> // 导入three.js import * as THREE from "https://esm.sh/three"; // 1. 创建场景 const scene = new THREE.Scene(); // 2. 创建相机 const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500); camera.position.set(0, 0, 50); // 相机位置 :基于场景,Z轴方向距离 scene 50个单位长度 // 3. 创建渲染器 const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); // 渲染器覆盖范围 document.body.appendChild(renderer.domElement); // 渲染器dom元素追加到页面种 //其它实现绘制水平线的代码 </script>
如上提到,appendChild 实际上是将 three.js 封装的 canvas 画布追加到了 web 页里,domElement 是渲染器的dom元素,指的就是 canvas 画布。
至此,场景 scene、相机 camera、渲染器 renderer 三要素准备就绪,下一步就是完成中心任务:
第四部:画一条水平线。
一切要绘制的对象,哪怕只是一根线条,都需要有材质,线条的材质有实线的和虚线的,这里用前者,LineBasicMaterial,最基本的材质:
<script type="module"> // 导入three.js import * as THREE from "https://esm.sh/three"; // 1. 创建场景 const scene = new THREE.Scene(); // 2. 创建相机 const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500); camera.position.set(0, 0, 50); // 相机位置 :基于场景,Z轴方向距离 scene 50个单位长度 // 3. 创建渲染器 const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); // 渲染器覆盖范围 document.body.appendChild(renderer.domElement); // 渲染器dom元素追加到页面种 // 4. 创建材质 const material = new THREE.LineBasicMaterial({color: 0xffee00}); //其它实现绘制水平线的代码 </script>
线条得有点坐标,我们用一个数组变量 points 装载点坐标数据,数据要通过 THREE.Vector3 创建,就是实例化它:
<script type="module"> // 导入three.js import * as THREE from "https://esm.sh/three"; // 1. 创建场景 const scene = new THREE.Scene(); // 2. 创建相机 const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500); camera.position.set(0, 0, 50); // 相机位置 :基于场景,Z轴方向距离 scene 50个单位长度 // 3. 创建渲染器 const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); // 渲染器覆盖范围 document.body.appendChild(renderer.domElement); // 渲染器dom元素追加到页面种 // 4. 创建材质 const material = new THREE.LineBasicMaterial({color: 0xffee00}); // 5. 创建线条的点坐标,并用数组记录 let points = []; points.push(new THREE.Vector3(-10, 0, 0)); points.push(new THREE.Vector3(10, 0, 0)); //其它实现绘制水平线的代码 </script>
线条是材质类属,是构成几何体的材料,它要通过几何体的包装才能真正呈现出来,所以得实例化一个几何体。几何体有很多种,这里使用缓冲形几何体 BufferGeometry,然后将几何体和材质构造真正的线条:
<script type="module"> // 导入three.js import * as THREE from "https://esm.sh/three"; // 1. 创建场景 const scene = new THREE.Scene(); // 2. 创建相机 const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500); camera.position.set(0, 0, 50); // 相机位置 :基于场景,Z轴方向距离 scene 50个单位长度 // 3. 创建渲染器 const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); // 渲染器覆盖范围 document.body.appendChild(renderer.domElement); // 渲染器dom元素追加到页面种 // 4. 创建材质 const material = new THREE.LineBasicMaterial({color: 0xffee00}); // 5. 创建线条的点坐标,并用数组记录 let points = []; points.push(new THREE.Vector3(-10, 0, 0)); points.push(new THREE.Vector3(10, 0, 0)); // 6. 创建几何体,令其构造自点坐标数组 const geometry = new THREE.BufferGeometry().setFromPoints(points); // 7. 创建线条 const line = new THREE.Line(geometry, material); //其它实现绘制水平线的代码 </script>
最后一击:线条加入到场景中,并令渲染器工作:
<script type="module"> // 导入three.js import * as THREE from "https://esm.sh/three"; // 1. 创建场景 const scene = new THREE.Scene(); // 2. 创建相机 const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500); camera.position.set(0, 0, 50); // 相机位置 :基于场景,Z轴方向距离 scene 50个单位长度 // 3. 创建渲染器 const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); // 渲染器覆盖范围 document.body.appendChild(renderer.domElement); // 渲染器dom元素追加到页面种 // 4. 创建材质 const material = new THREE.LineBasicMaterial({color: 0xffee00}); // 5. 创建线条的点坐标,并用数组记录 let points = []; points.push(new THREE.Vector3(-10, 0, 0)); points.push(new THREE.Vector3(10, 0, 0)); // 6. 创建几何体,令其构造自点坐标数组 const geometry = new THREE.BufferGeometry().setFromPoints(points); // 7. 创建线条 const line = new THREE.Line(geometry, material); // 8. 线条加入到场景中 scene.add(line); // 9. 渲染效果 renderer.render(scene, camera); </script>
【附】纯 JS WebGL 画水平线代码:
<canvas id="glCanvas" width="400" height="400"></canvas> <script> // 获取 canvas 元素 const canvas = document.getElementById('glCanvas'); const gl = canvas.getContext('webgl'); if (!gl) { console.error('WebGL not supported, falling back on experimental-webgl'); gl = canvas.getContext('experimental-webgl'); } if (!gl) { alert('Your browser does not support WebGL'); } // 顶点着色器 const vertexShaderSource = ` attribute vec4 aVertexPosition; void main() { gl_Position = aVertexPosition; } `; // 片段着色器 const fragmentShaderSource = ` void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色 } `; // 创建并编译顶点着色器 const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexShaderSource); gl.compileShader(vertexShader); if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { console.error('Error compiling vertex shader!', gl.getShaderInfoLog(vertexShader)); gl.deleteShader(vertexShader); } // 创建并编译片段着色器 const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentShaderSource); gl.compileShader(fragmentShader); if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { console.error('Error compiling fragment shader!', gl.getShaderInfoLog(fragmentShader)); gl.deleteShader(fragmentShader); } // 创建程序并附加着色器 const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('Error linking program!', gl.getProgramInfoLog(program)); } gl.useProgram(program); // 定义水平线的顶点 const vertices = [ -0.5, 0.0, 0.0, 1.0, // 左端点 0.5, 0.0, 0.0, 1.0 // 右端点 ]; // 创建顶点缓冲区 const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); // 绑定顶点属性 const positionAttributeLocation = gl.getAttribLocation(program, 'aVertexPosition'); gl.enableVertexAttribArray(positionAttributeLocation); gl.vertexAttribPointer(positionAttributeLocation, 4, gl.FLOAT, false, 0, 0); // 清除屏幕 gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // 绘制水平线 gl.drawArrays(gl.LINES, 0, 2); </script>
前一篇: 利用filter属性实现黑暗模式
下一篇: 没有了
评论列表 [1条]
#1 | 悄然 于 2025-5-5 16:27 发布: 画这个线感觉是在空地上找好位置,加了个瞄准仪,用激光扫射出来的。。不过可以设定颜色和材质。。跟看动画片似的,好玩