马黑黑 发表于 2025-5-4 12:32

使用three.js画一条水平线

<style>
        #artbox p { margin: 10px 0; font-size: 20px; }
</style>

<div id="artbox">
        <p>three.js 基于3d,用它画线条可能大材小用,不过值得一试:首先这是一项颇具挑战性的练习,其次可以通过此项任务的完成略窥 three.js 的工作机制、原理。</p>
        <p>首先我们需要引入 three.js。在 JS 代码中使用ES模块比较省事,像这样,从开放的、境内连接速度较好的 esm.sh 站点获取封装好的 three 核心 ES 模块:</p>
        <div><pre>
&lt;script type="module"&gt;
// 导入three.js
import * as THREE from "https://esm.sh/three";
// 其它实现绘制水平线的代码
&lt;/script&gt;
</pre></div>
<p>接下来正式开干。我们分几个步骤一一弄下去。</p>
<p>第一步,创建场景。在 three.js 的世界里,一切都得依托于场景 Scene,场景好比舞台,将来的一切都在其上摆放和展示。创建场景就是实例化一个 three.js 封装好的 Scene:</p>
<div><pre>
&lt;script type="module"&gt;
// 导入three.js
import * as THREE from "https://esm.sh/three";
// 1. 创建场景
const scene = new THREE.Scene();
// 其它实现绘制水平线的代码
&lt;/script&gt;
        </pre></div>
        <p>略微解释一下:three.js 所封装的东东我们在引入 three.js 模块时用 * 代表全部,并作为 THREE 导入,场景 Scene 属于 * 里的内容之一,我们这里将 THREE.Scene 实例化为 scene,scene 是我们自定义的实例化 THREE.Scene 场景标识。此类事情稍后我们还会做很多很多,自定义实例化变量的命名头一个字母不大写。</p>
        <p>第二步,架设一部 three 相机 camera,可拍照可录像的摄像机。相机好比屏幕前观看场景者的眼睛,处在Z轴方向,Z轴与屏幕垂直。three.js 封装了很多种类的相机,这里用它的透视相机,让我们来实例化这个相机对象:</p>
        <div><pre>
&lt;script type="module"&gt;
// 导入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个单位长度
//其它实现绘制水平线的代码
&lt;/script&gt;
        </pre></div>
        <p>相机也是场景的一部分,它可以放在场景中,也可以拿到场景外头来。作为透视相机,总要和场景拉开距离,Z轴位置若设置为 0,那将什么都看不到。</p>
        <p>第三步,创建渲染器。渲染器是 three.js 渲染效果的核心,可以把渲染器理解为相机进入拍摄状态的机制。渲染器的工作原理大致是这样子:在web页中创建 canvas 画布,将画布作为 Scene 场景,然后在其上按照设计好的各种数据绘制图形。下面我们添加 three.js 封装的渲染器 WebGLRenderer 的实例化并设置渲染尺寸、追加到 Web 页的 body 标签中:</p>
        <div><pre>
&lt;script type="module"&gt;
// 导入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元素追加到页面种
//其它实现绘制水平线的代码
&lt;/script&gt;
        </pre></div>
        <p>如上提到,appendChild 实际上是将 three.js 封装的 canvas 画布追加到了 web 页里,domElement 是渲染器的dom元素,指的就是 canvas 画布。</p>
        <p>至此,场景 scene、相机 camera、渲染器 renderer 三要素准备就绪,下一步就是完成中心任务:</p>
        <p>第四部:画一条水平线。</p>
        <p>一切要绘制的对象,哪怕只是一根线条,都需要有材质,线条的材质有实线的和虚线的,这里用前者,LineBasicMaterial,最基本的材质:</p>
        <div><pre>
&lt;script type="module"&gt;
// 导入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});
//其它实现绘制水平线的代码
&lt;/script&gt;
        </pre></div>
        <p>线条得有点坐标,我们用一个数组变量 points 装载点坐标数据,数据要通过 THREE.Vector3 创建,就是实例化它:</p>
        <div><pre>
&lt;script type="module"&gt;
// 导入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));
//其它实现绘制水平线的代码
&lt;/script&gt;
        </pre></div>
        <p>线条是材质类属,是构成几何体的材料,它要通过几何体的包装才能真正呈现出来,所以得实例化一个几何体。几何体有很多种,这里使用缓冲形几何体 BufferGeometry,然后将几何体和材质构造真正的线条:</p>
        <div><pre>
&lt;script type="module"&gt;
// 导入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);
//其它实现绘制水平线的代码
&lt;/script&gt;
        </pre></div>
        <p>最后一击:线条加入到场景中,并令渲染器工作:</p>
        <div><pre>
&lt;script type="module"&gt;
// 导入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);
&lt;/script&gt;
        </pre></div>
        <p>【附】纯 JS WebGL 画水平线代码:</p>
        <div><pre>
&lt;canvas id="glCanvas" width="400" height="400"&gt;&lt;/canvas&gt;

&lt;script&gt;
// 获取 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);
       
&lt;/script&gt;
        </pre></div>
</div>

<script type="module">
        import hlight from 'https://638183.freep.cn/638183/web/helight/helight1.js';
        const divs = artbox.querySelectorAll('div');
        const pres = artbox.querySelectorAll('pre');
        for (let x = 0; x < divs.length; x ++) {
                hlight.hl(divs, pres);
        }
</script>

红影 发表于 2025-5-4 13:27

等了一会,这个不需要留楼了吧。
这个three.js 的功能很强大,所以操作步骤也多,用来画线条好麻烦啊。{:4_173:}

红影 发表于 2025-5-4 13:29

但能由这样一个案例去感受一下 three.js 倒是好事呢,可以知道使用它所需要的操作了。
黑黑的讲解十分详细,很赞{:4_199:}

马黑黑 发表于 2025-5-4 19:15

红影 发表于 2025-5-4 13:29
但能由这样一个案例去感受一下 three.js 倒是好事呢,可以知道使用它所需要的操作了。
黑黑的讲解十分详细 ...

如果绘制一个默认的正方体,代码量要比这个少一些

马黑黑 发表于 2025-5-4 19:17

红影 发表于 2025-5-4 13:27
等了一会,这个不需要留楼了吧。
这个three.js 的功能很强大,所以操作步骤也多,用来画线条好麻烦啊。{:4 ...

你看最后附上的原生 WebGL 代码,又是着色有事编译,更麻烦呢,three.js 就是封装了很多底层的东东,让使用者只关注自己的设计和算法

红影 发表于 2025-5-4 22:22

马黑黑 发表于 2025-5-4 19:15
如果绘制一个默认的正方体,代码量要比这个少一些

那也要对它熟悉,知道怎样调用它封装的正方形才行啊{:4_173:}

红影 发表于 2025-5-4 22:23

马黑黑 发表于 2025-5-4 19:17
你看最后附上的原生 WebGL 代码,又是着色有事编译,更麻烦呢,three.js 就是封装了很多底层的东东,让使 ...

不过这样的封装相当于又增加一门语言,熟悉的人肯定方便,不熟悉的感觉无路可走呢{:4_173:}

马黑黑 发表于 2025-5-5 09:26

红影 发表于 2025-5-4 22:23
不过这样的封装相当于又增加一门语言,熟悉的人肯定方便,不熟悉的感觉无路可走呢

如果不想弄原生 WebGL/GPU,那么,花点时间学习 three.js 划算的。即使想弄 GL/GPU,three.js 带来的辩解也是很可观

马黑黑 发表于 2025-5-5 09:26

红影 发表于 2025-5-4 22:22
那也要对它熟悉,知道怎样调用它封装的正方形才行啊

这个可以去看文档,基本上画矩形是此类文档的第一个案例

花飞飞 发表于 2025-5-5 17:03

厉害,这么多步骤画成一条直直的线
定位,瞄准方向,确定颜色和材质,绘画。。
原来表面上看着简单的一条线,背后要做这么多工作。。
为白老师点赞。。。

花飞飞 发表于 2025-5-5 17:05

我是觉得吧自己画出来就很厉害了,
写出这个文档更是难得,里面有这么多演示步骤。。。
逻辑清晰,思路明确,每一步变化都明明白白。。
大爱大赞!~~

马黑黑 发表于 2025-5-5 18:04

花飞飞 发表于 2025-5-5 17:05
我是觉得吧自己画出来就很厉害了,
写出这个文档更是难得,里面有这么多演示步骤。。。
逻辑清晰,思路明 ...

{:4_191:}

花飞飞 发表于 2025-5-5 19:21

马黑黑 发表于 2025-5-5 18:04


{:4_173:}一杯敬代码

红影 发表于 2025-5-5 19:46

马黑黑 发表于 2025-5-5 09:26
如果不想弄原生 WebGL/GPU,那么,花点时间学习 three.js 划算的。即使想弄 GL/GPU,three.js 带来的辩解 ...

是的,看到了它里面好多漂亮的效果,这个太赞了{:4_187:}

红影 发表于 2025-5-5 19:47

马黑黑 发表于 2025-5-5 09:26
这个可以去看文档,基本上画矩形是此类文档的第一个案例

嗯,这个掌握了真的十分便捷呢{:4_187:}

马黑黑 发表于 2025-5-5 21:03

红影 发表于 2025-5-5 19:47
嗯,这个掌握了真的十分便捷呢

确切地说是画立方体

马黑黑 发表于 2025-5-5 21:03

红影 发表于 2025-5-5 19:46
是的,看到了它里面好多漂亮的效果,这个太赞了

就是学起来还是有点门槛的

马黑黑 发表于 2025-5-5 21:11

花飞飞 发表于 2025-5-5 19:21
一杯敬代码

老代来了么

红影 发表于 2025-5-5 22:04

马黑黑 发表于 2025-5-5 21:03
确切地说是画立方体

哦,是带立体感的,还不是平面的呢。

红影 发表于 2025-5-5 22:05

马黑黑 发表于 2025-5-5 21:03
就是学起来还是有点门槛的

这门槛有点高啊{:4_173:}
页: [1] 2 3 4
查看完整版本: 使用three.js画一条水平线