马黑PHP整站系统

使用three.js画一条水平线

位置: 首页 > 前端三套件[ 发布时间: 2025.5.4  作者: 马黑  阅读: 26 ]

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 发布: 画这个线感觉是在空地上找好位置,加了个瞄准仪,用激光扫射出来的。。不过可以设定颜色和材质。。跟看动画片似的,好玩

Copyright © 2023 All Right Reserved 马黑PHP文章管理整站系统v1.8
联系我们: gxblk@163.com