- 图形学
- 3D渲染
【免费下载链接】spritejs
A cross platform high-performance graphics system.
Plane 是 SpriteJS Next 3D 扩展库(spritejs.ext3d)中用于创建三维平面网格元素的组件,继承自 Mesh3d。本文以 Plane API 文档 为骨架,结合仓库内 3D 示例、太阳系示例、文字标签示例 等真实源码,系统讲解 Plane 的构造函数、属性体系、只读属性、方法及典型应用场景,帮助你在 SpriteJS 的 WebGL 场景中熟练使用 Plane 搭建地面、背景、标签与动画元素。
一、Plane 是什么
Plane 是 SpriteJS 3D 扩展中的平面元素(Plane element),用于在 3D 图层中绘制一块由三角形构成的平面网格。它的继承关系为:
Plane extends Mesh3d extends Group3d extends Node3d extends Node从源码结构看,3D 扩展文档 说明通过spritejs.ext3d访问全部 3D API,Plane 与 Cube、Sphere、Cylinder 等一起作为预置几何体元素提供。与 Mesh3d 直接接收自定义model(顶点数据)不同,Plane 会自动生成自身的平面几何体,你只需要通过width、height、widthSegments、heightSegments四个专属属性控制平面的大小与细分程度,即可直接用于渲染,无需手工书写顶点坐标。
在仓库的实际项目中,Plane 最常见的用法包括:
- 作为地面 / 阴影接收面:见 阴影示例 与 骨骼动画示例;
- 作为贴图平面 / 天体表面:见 太阳系示例;
- 作为纹理文字标签:见 文字示例。
二、快速上手:创建第一个 Plane
2.1 引入 3D 扩展
Plane 属于 3D 扩展库。按 3D 扩展文档 说明,通过 CDN 加载时需保证扩展脚本位于 spritejs 脚本之后:
<script src="https://unpkg.com/spritejs@3/dist/spritejs.es.min.js"></script> <script src="https://unpkg.com/sprite-extend-3d/dist/sprite-extend-3d.js"></script>若使用模块方式:
import {Scene} from 'spritejs'; import {Plane, shaders} from 'sprite-extend-3d';2.2 最小可运行示例
参照 examples/test-3d.html 中的用法,创建一个带旋转动画的 Plane:
const {Scene} = spritejs; const container = document.getElementById('container'); const scene = new Scene({container, displayRatio: 2}); const layer = await scene.layer3d('fglayer', { camera: {fov: 35}, // 视野 35° }); const {Plane, Sphere, Cube} = spritejs.ext3d; layer.camera.attributes.pos = [5, 3, 6]; layer.camera.lookAt([0, 0, 0]); // 自定义着色器:仅输出法线光照效果 const program = layer.createProgram({ vertex, // 顶点着色器 fragment, // 片元着色器 cullFace: null, // 关闭背面剔除,保证平面双面可见 }); const plane = new Plane(program); plane.attributes.pos = [0, 1.3, 0]; layer.append(plane); plane.animate([ {rotateY: 0}, {rotateY: -360}, ], { duration: 10000, iterations: Infinity, });要点说明:
layer.createProgram()基于当前 WebGL 上下文创建 GLProgram,Plane 渲染依赖它;new Plane(program)即可得到一个宽高为 1×1 的默认平面;- 使用
layer.append(plane)将元素加入 3D 图层,元素才会被渲染。
三、构造函数与专属属性
3.1 构造函数
new Plane(program, attrs)program:由layer.createProgram()创建的 GLProgram 对象;attrs:可选,属性配置对象,可直接传入下文表格中的任意属性。
3.2 专属属性:width / height / segments
Plane 定义了四个专属属性(不继承,默认值均为 1):
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| width | number | 1 | 元素的宽度 |
| height | number | 1 | 元素的高度 |
| widthSegments | number | 1 | 宽度方向的细分段数 |
| heightSegments | number | 1 | 高度方向的细分段数 |
用法说明:
width、height决定平面的几何尺寸。例如 太阳系示例 中创建“太阳”时,将宽高放大到 100:
const sun = new Plane(sunProgram); sun.attr({width: 100, height: 100}); layer.append(sun); layer.bindTime(sunProgram); // 让 uTime 随时间自动更新widthSegments、heightSegments控制平面的顶点细分数量。文档未对该二参数给出详细说明;从同属 Mesh3d 子类的 Sphere(widthSegments/heightSegments控制经纬线细分,见 Sphere 文档)以及 Cylinder 文档 的同类参数可以推断,它们决定平面在宽、高方向上被分割为多少段,段数越高顶点越密,可承载更强的顶点级形变与法线细节,同时消耗更多显存与计算资源。需要平滑形变(如水面波动)时可将段数调高,普通静态地面保持默认值 1 即可。
3.3 一个注意点:动态更新几何
修改 Plane 的width/height/widthSegments/heightSegments会触发几何数据重建。这与 Plane 重写的两个方法密切相关:
onPropertyChange(key, newValue, oldValue):当元素属性值改变时执行的动作,是属性变化进入内部逻辑的入口;remesh():重新创建元素的 Mesh 对象,即基于新属性重建平面网格。
这也是 Plane 从 Mesh3d 继承的setGeometry(model)、updateMesh()等方法的底层调用基础——属性变化最终会反映到 GPU 侧的顶点缓冲上。
四、完整属性表:继承属性体系
除上述四个专属属性外,Plane 还继承了来自 Mesh3d、Node3d、Node 的全部属性,完整如下:
| 属性名 | 继承自 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
| mode | Mesh3d | string | TRIANGLES | 设置绘制模式 |
| colors | Mesh3d | Color | #80808080 | 每个顶点的颜色 |
| colorDivisor | Mesh3d | number | 3 | 每个 color 值赋给几个顶点 |
| x | Node3d | number | 0 | 元素 X 轴坐标 |
| y | Node3d | number | 0 | 元素 Y 轴坐标 |
| z | Node3d | number | 0 | 元素 Z 轴坐标 |
| pos | Node3d | Array | [0, 0, 0] | [x, y, z] 简写 |
| rotateX | Node3d | number | 0 | 绕 X 轴旋转 |
| rotateY | Node3d | number | 0 | 绕 Y 轴旋转 |
| rotateZ | Node3d | number | 0 | 绕 Z 轴旋转 |
| rotate | Node3d | Array | [0, 0, 0] | [rotateX, rotateY, rotateZ] 简写 |
| scaleX | Node3d | number | 1 | X 轴拉伸 |
| scaleY | Node3d | number | 1 | Y 轴拉伸 |
| scaleZ | Node3d | number | 1 | Z 轴拉伸 |
| scale | Node3d | Array | [1, 1, 1] | [scaleX, scaleY, scaleZ] 简写 |
| raycast | Node3d | string | undefined | 碰撞检测模式:默认检测 bounds,设为sphere则进行球形检测 |
| visibility | Node3d | enum | enum{visible, hidden} | 元素是否可见 |
| id | Node | string | '' | 设定元素的 ID |
| name | Node | string | '' | 设定元素的 name |
| className | Node | string | '' | 设定元素的 className |
| display | Node | string | '' | 设定元素的可见性 |
| pointerEvents | Node | string | visible | 同 CSS 的 pointerEvents |
4.1 渲染相关:mode / colors / colorDivisor
mode:GL 绘制模式,默认TRIANGLES。渲染层面,Plane 的几何体以三角形为单位组织顶点,可直接改用TRIANGLE_STRIP、LINES等其他模式输出特殊效果;colors:每个顶点的颜色,默认#80808080(半透明灰)。支持单个颜色字符串,也支持按顶点数量提供颜色数组(如'red blue green orange',参照 3D 扩展文档 中 Mesh3d 的用法);colorDivisor:每条颜色值覆盖的顶点数,默认 3(即一个三角形 3 个顶点共享一个颜色值)。
4.2 变换相关:位置 / 旋转 / 缩放
来自 Node3d 的变换属性是最常用的操作入口,推荐使用简写形式批量设置。例如把平面平铺成“地面”并放大:
// 阴影示例:作为地面接收阴影 const ground = new Plane(program2, { rotateX: 90, // 从竖直面旋转为水平地面 scale: 6, // 放大 6 倍 y: -3, // 下沉到 y = -3 }); layer.append(ground);参见 demos/3d/shadow/index.js。其中rotateX: 90是最经典的手法——Plane 默认位于 XOY 平面(法线朝 Z 轴),绕 X 轴旋转 90° 后变为水平面,从而作为地面承接阴影。
4.3 拾取与可见性
raycast:碰撞检测模式。文档说明默认检测包围盒(bounds),设为sphere则进行球形检测。需配合图层的layer.setRaycast()启用拾取,才能让 Plane 响应鼠标/触屏事件(详见本文第七节);visibility:visible/hidden,控制元素是否参与渲染;pointerEvents:同 CSS 语义,默认visible,控制元素是否接收指针事件。
五、只读属性(Properties)
Plane 的只读属性全部来自继承,按继承层级组织如下。
5.1 来自 Mesh3d
| 只读属性 | 说明 |
|---|---|
| geometry | 获取元素的几何体对象 |
| meshes | 当前元素及其子孙元素的 Mesh 对象列表 |
| model | 获取几何数据 |
| program | 获取 GLProgram |
5.2 来自 Group3d
| 只读属性 | 说明 |
|---|---|
| childNodes | children 的别名 |
| children | 子元素 |
| meshes | 当前元素及其子孙元素的 Mesh 对象列表 |
Plane 继承自 Group3d,因此它本身也是一个三维分组容器,可以append其他 3D 元素作为子节点(参见 Group3d 文档)。
5.3 来自 Node3d
| 只读属性 | 说明 |
|---|---|
| body | 当前元素的 Mesh 或 Transform 对象 |
| isVisible | 元素是否可见 |
| localMatrix | 当前元素的变换矩阵 |
| matrix | localMatrix 的别名 |
| mesh | 当前元素的 Mesh 对象,如果没有则返回 null |
| modelViewMatrix | 经过相机换算后的变换矩阵 |
| normalMatrix | 法向变换矩阵(3×3),用于计算光照 |
| renderMatrix | 相对于 layer 的变换矩阵 |
| worldMatrix | renderMatrix 的别名 |
| zDepth | 元素相对 z 轴的深度 |
这些矩阵在自定义着色器中常被作为 uniform 使用:例如 PBR 示例 的顶点着色器通过uniform mat4 modelMatrix / modelViewMatrix / projectionMatrix与uniform mat3 normalMatrix完成顶点变换与法线换算,与这里的矩阵体系一一对应。
5.4 来自 Node
| 只读属性 | 说明 |
|---|---|
| ancestors | 返回当前元素的祖先元素列表 |
| animations | 返回当前元素执行中的所有动画 |
| layer | 返回当前绘制上下文中的 Layer 对象 |
| parent | 返回当前对象的父对象 |
| renderer | 返回当前绘制上下文中的渲染对象 |
| zOrder | 返回当前对象被添加到对象树上的次序 |
| attributes | 返回当前元素的属性对象 |
| className | 相当于 element.attributes.className |
| id | 相当于 element.attributes.id |
| name | 相当于 element.attributes.name |
| zIndex | 相当于 element.attributes.zIndex |
六、方法体系
6.1 Plane 自身重写的方法
| 方法 | 说明 |
|---|---|
| onPropertyChange(key, newValue, oldValue) | 当元素属性值被改变时,执行的动作 |
| remesh() | 重新创建 Mesh 对象 |
这两个方法分别负责属性变化的响应与网格重建,是 Plane 在尺寸/细分属性变化时保持几何正确性的核心机制。
6.2 继承自 Mesh3d
| 方法 | 说明 |
|---|---|
| addEventListener(type, listener, options = {}) | 注册事件监听器 |
| cloneNode(deep = false) | 复制一个元素,deep 为 true 时同时复制子元素 |
| removeAllListeners(type) | 移除某类型的所有事件监听器 |
| removeEventListener(type, listener, options) | 移除事件监听器 |
| setGeometry(model = this[_model]) | 设置或更新几何信息 |
| setProgram(program) | 设置或更新 GLProgram |
| updateMesh() | 设置或更新 Mesh 信息 |
6.3 继承自 Group3d(分组与查询)
| 方法 | 说明 |
|---|---|
| append(...els) | 批量添加元素到 group 中 |
| appendChild(el) | 将一个元素添加到 group 中 |
| getElementById(id) | 返回指定 id 的子元素 |
| getElementsByClassName(className) | 返回指定 className 的子元素列表 |
| getElementsByName(name) | 返回指定 name 的子元素列表 |
| getElementsByTagName(name) | 返回指定类型的子元素列表 |
| insertBefore(el, ref) | 将指定元素插入到 ref 元素之前;ref 为 null 时追加到末尾;ref 非 null 且不是 group 子元素时抛出异常 |
| querySelctor(selector) | 根据选择器返回指定的子元素 |
| querySelectorAll(selector) | 根据选择器返回所有匹配的子元素列表 |
| replaceChild(el, ref) | 用 el 替换 ref;ref 不在当前 group 中时抛出异常 |
| removeAllChildren() | 移除 group 的所有子元素 |
| removeChild(el) | 将指定元素移出 group |
| setResolution({width, height}) | 设置元素的上下文分辨率 |
6.4 继承自 Node3d
| 方法 | 说明 |
|---|---|
| connect(parent, zOrder) | 元素被添加到对象树上时调用,parent 与 zOrder 被赋给元素 |
| disconnect() | 元素从对象树上移除时调用,移除 parent 与 zOrder 属性 |
| lookAt([x, y, z]) | 让元素转向到对应坐标所在的方向 |
| setBody(body, update = true) | 设置元素的 Mesh 对象 |
| traverse(callback) | 遍历当前元素和所有的子孙元素 |
| updateMatrix() | 更新 matrix |
| updateMatrixWorld(force = false) | 更新 worldMatrix |
6.5 继承自 Node
| 方法 | 说明 |
|---|---|
| activateAnimations() | 激活元素上正在执行的所有动画 |
| animate(frames, timing) | 执行动画 |
| attr(...args) | 读取或批量设置属性 |
| cloneNode() | 复制整个元素 |
| deactivateAnimations() | 停止元素上正在执行的所有动画 |
| dispatchEvent(event) | 转发一个自定义事件 |
| dispatchPointerEvent(event) | 转发一个鼠标或触屏事件 |
| forceUpdate() | 强制重绘画布 |
| getAttribute(key) | 读取元素属性值 |
| getListeners(type, {capture = false} = {}) | 获取事件监听器 |
| getOffsetPosition(x, y) | 将相对于 Layer 的指定 [x, y] 坐标变换为相对于当前元素的坐标,以锚点为原点 |
| getResolution() | 获取元素的上下文分辨率 |
| isPointCollision(x, y) | 判断事件坐标是否与元素相交 |
| setAttribute(key, value) | 设置元素属性值 |
| setMouseCapture() | 捕获鼠标 |
| setResolution({width, height}) | 设置元素的上下文分辨率 |
| releaseMouseCapture() | 释放鼠标 |
| remove() | 将元素从 parent 上移除 |
| removeAttribute(key) | 移除元素属性值,恢复为默认值 |
| transition(sec, easing = 'linear') | 创建过渡动画 |
常用组合技巧:attr({...})一次设置多个属性;animate()与transition()驱动动画;cloneNode()复制平面做实例化复用;remove()从场景中摘除元素。
七、实战场景详解
7.1 地面与阴影接收面
Plane 最常见的用途是作为地面。在 阴影示例 中,地面 Plane 配合阴影系统工作:
const ground = new Plane(program2, { rotateX: 90, // 平铺为地面 scale: 6, y: -3, }); layer.append(ground); shadow.add(plane); // 将动态模型加入阴影 shadow.add(ground); // 地面参与阴影 layer.setShadow(shadow);骨骼动画示例 中同样的手法用于承接角色阴影:
const plan = new Plane(shadowProgram, {width: 7, height: 7}); plan.attr({rotateX: -90, y: -1});这里rotateX取-90与90效果一致(平面翻转 180° 无差异),关键都是把竖直的默认平面旋转为水平面。
7.2 贴图平面:太阳与天体表面
太阳系示例 中,Plane 配合纹理与自定义噪声着色器绘制一个 100×100 的“太阳”:
const texture = layer.createTexture({ image: '...', wrapS: layer.gl.REPEAT, }); const sunProgram = layer.createProgram({ transparent: true, vertex: shaders.TEXTURE.vertex, fragment, // 程序化噪声片元着色器 cullFace: null, texture, uniforms: { specularIntensity: {value: 0.5}, uTime: {value: 0}, }, }); const sun = new Plane(sunProgram); sun.attr({width: 100, height: 100}); layer.append(sun); layer.bindTime(sunProgram); // 每帧自动更新 uTime,驱动噪声动画由此可知,Plane 是纯几何容器,外观完全由 program 决定:换一张纹理、换一组着色器,同一个 Plane 就能渲染成太阳、水面或任意贴图平面。
7.3 纹理文字标签(Billboard)
文字示例 中,Plane 按纹理图片的宽高比自适应尺寸,并隐藏网格颜色,作为文字标签贴片:
const label = new Plane(program, { width: 1, height: texture.image.height / texture.image.width, // 保持宽高比 colors: 'transparent', // 顶点颜色透明,仅显示纹理 });这是 3D 场景中常见的“公告板(Billboard)”做法:用一张带透明通道的文字纹理贴在平面上,再把colors设为transparent,即可在场景中显示不依赖字体渲染的文字或图标。
7.4 动画与旋转
Plane 继承的动画 API 可直接驱动平面旋转。除 examples/test-3d.html 中的animate()外,阴影示例 还在layer.tick回调中逐帧修改属性:
layer.tick((t) => { if(plane) { plane.attributes.z = Math.sin(t * 0.001); plane.attributes.rotateX = Math.sin(t * 0.001 + 2) * 18; plane.attributes.rotateY = Math.sin(t * 0.001 - 4) * -18; } });两种方式分别适合“关键帧动画”与“程序化动画”两类需求。
八、与 Plane 相关的体系位置
- 基类 Mesh3d:Plane 的渲染基座。
mode、colors、colorDivisor及几何/程序管理方法均定义于此,详见 Mesh3d 文档; - 父容器 Group3d:Plane 可作为分组容器嵌套其他 3D 元素,详见 Group3d 文档;
- 变换基座 Node3d:坐标、旋转、缩放及矩阵体系,详见 Node3d 文档;
- 创建环境 Layer3d:
scene.layer3d()、layer.createProgram()、layer.createTexture()等均在 Layer3d 文档 中有完整参数说明; - 同族几何体:Sphere(文档)、Cylinder(文档)与 Plane 使用相同属性体系,可相互对照学习;
- 整体入门:坐标系统、相机、光照、交互等前置知识见 3D 扩展文档。
九、小结
Plane 是 SpriteJS 3D 扩展中最常用的基础几何元素:通过width/height控制尺寸、widthSegments/heightSegments控制细分、rotateX: 90平铺成地面,再结合 Mesh3d 的颜色体系、Node3d 的变换体系与 Node 的动画体系,即可快速搭建地面、贴图平面、文字标签和程序化动画等各类 3D 效果。实际使用中可对照本仓库的 demos/3d 目录 下的各示例代码,理解 Plane 与着色器、纹理、阴影和动画的组合方式。
- 图形学
- 3D渲染
【免费下载链接】spritejs
A cross platform high-performance graphics system.
相关推荐
SpriteJS 3D 平面元素(Plane)完全指南:属性、继承体系与实战用法
SpriteJS 3D 平面元素(Plane)完全指南:属性、继承体系与实战用法 导读 Plane 是 SpriteJS 跨平台高性能图形系统(sprite e
图形学3D渲染SpriteJS 3D 扩展 Mesh3d 完全指南:定义 3D 模型元素的核心 API 与实战
SpriteJS 3D 扩展 Mesh3d 完全指南:定义 3D 模型元素的核心 API 与实战 Mesh3d 是 SpriteJS Next 3D 扩展(ex
图形学3D渲染SpriteJS 3D 扩展 Cylinder 圆柱与锥体元素完全指南:属性、继承体系与实战用法
SpriteJS 3D 扩展 Cylinder 圆柱与锥体元素完全指南:属性、继承体系与实战用法 本文以 SpriteJS 3D 扩展中的 Cylinder 元
图形学3D渲染
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考