调试绘制
有些问题很难仅凭数值调试,例如包围盒是否在预期位置、样条朝向哪里,或第二台相机实际能看到什么。调试绘制在屏幕上显示形状和纹理预览,便于检查。
WireRenderer 和 TextureRenderer 只绘制一帧。需要持续显示时,应在更新处理函数中每帧重新提交。
WireRenderer
WireRenderer 绘制线框形状,创建后保留使用:
import { Color, Vec3, WireRenderer } from 'playcanvas';
const wire = new WireRenderer(app);
app.on('update', (dt) => {
wire.color = Color.RED;
wire.sphere(new Vec3(0, 1, 0), 2);
});
状态保存在渲染器上
无需每次调用传选项,渲染器本身保存形状使用的状态:
| 属性 | 含义 |
|---|---|
color | 绘制颜色,alpha 生效。 |
layer | 绘制图层,null 表示即时图层。 |
depthTest | 形状是否会被前方几何体遮挡。 |
segments | 近似完整圆的线段数。 |
transform | 应用到每个点的矩阵,或 null。 |
设置一次即可绘制多个形状,同一状态下绘制不会产生分配:
wire.color = Color.GREEN;
for (const item of items) {
wire.sphere(item.getPosition(), item.radius);
}
需要第二组状态时,创建第二个渲染器即可。实例不拥有 GPU 资源,共享图层与深度测试模式的实例会一起绘制,因此多个实例无额外绘制开销:
const solid = new WireRenderer(app);
const xray = new WireRenderer(app);
xray.depthTest = false; // drawn on top of everything
transform 适合在其他空间绘制一组形状。设置后,绘制的每个点都会转换:
wire.transform = entity.getWorldTransform();
wire.boxMinMax(localMin, localMax); // drawn in the entity's space
wire.transform = null;
形状
wire.line(start, end);
wire.lines(positions, colors); // Vec3[] pairs, optional Color[] per point
wire.linesPacked(positions, colors); // packed xyz / rgba numbers, the fastest form
wire.polyline(positions, colors); // an open strip
wire.loop(positions, colors); // a closed strip
wire.box(box); // BoundingBox or OrientedBox
wire.boxMinMax(min, max);
wire.sphere(center, radius);
wire.circle(center, normal, radius);
wire.cylinder(start, end, radius);
wire.capsule(start, end, radius);
wire.cone(apex, direction, angle, length);
wire.plane(center, normal, size);
wire.point(position, size);
wire.arrow(from, to);
wire.axes(matrix, size); // red, green and blue for x, y and z
wire.frustum(source); // a camera, or a view-projection matrix
wire.light(lightComponent); // the light's shape and extent, in its own color
线函数的 colors 可选。省略时全部使用渲染器 color;逐点提供时,各线段在两端颜色间渐变。
linesPacked 接收普通数字数组或 Float32Array,而非 Vec3 和 Color 实例,是提交大量几何体开销最低的方式。省略 colors 的节省比选择数组类型更明显,因为统一颜色完全避免逐顶点写颜色。
可视化相机
wire.frustum() 绘制相机可见空间,也可用于当前未渲染的相机。这尤其有用:可以通过一台相机观察另一台移动相机的视锥体:
wire.color = Color.YELLOW;
wire.frustum(observerEntity.camera);
结合 Frustum#containsAabb 可直观看到剔除效果:
const viewProjection = new Mat4().mul2(observer.camera.projectionMatrix, viewMatrix);
frustum.setFromMat4(viewProjection);
for (const meshInstance of meshInstances) {
const inside = frustum.containsAabb(meshInstance.aabb);
(inside ? greenWire : redWire).box(meshInstance.aabb);
}
关于 plane 的说明
方形在平面内的旋转根据传入法线推导,但不存在对全部方向都连续的推导。因此法线动画经过推导切换方向时,方形会看起来跳动。若要平滑旋转,应传固定法线,改为驱动 transform。
这不影响圆、圆柱、胶囊和圆锥,因为它们的环绕轴对称。
TextureRenderer
TextureRenderer 在屏幕上显示纹理供调试。创建后,每帧提交所需预览:
import { TextureRenderer } from 'playcanvas';
const textures = new TextureRenderer(app);
app.on('update', () => {
textures.draw(texture, 0.7, 0.7, 0.25, 0.25);
});
draw 参数为纹理、左边缘、上边缘、宽度和高度。坐标相对于渲染相机视口,(0, 0) 为左上,(1, 1) 为右下,宽高 1 表示占满视口。负尺寸相对于起始边翻转预览。
预览设置
layer 和 channels 作用于后续绘制调用;camera 在实际渲染时为全部预览选择相机:
| 属性 | 含义 |
|---|---|
layer | 目标图层,null 为应用默认调试层,通常是 Immediate。 |
channels | 从 r、g、b、a 选取三个字符。默认 'rgb' 显示解码颜色,单通道格式显示灰度。 |
camera | 绘制预览的 CameraComponent,默认 null 允许所有渲染目标图层的相机。 |
预览不透明,不进行深度测试或写入。应使用在场景和天空盒之后渲染的图层,避免被后续内容覆盖,相机也必须包含该图层。设置 textures.camera = camera.camera 可限制为主相机等单台相机,避免进入反射;设回 null 则允许所有渲染目标图层的相机。
预览渲染目标时,如果纹理是当前目标的颜色、解析或深度附件,渲染器自动跳过,避免同时读写同一纹理,包括 CameraFrame 和自定义通道场景。
渲染目标纹理应设置 RENDERTARGET_ORIGIN_TOP,确保 WebGL2 和 WebGPU 方向一致。
检查通道
使用 rgb 时自动解码颜色编码。单通道 R8、R16F、R32F 显示为灰度。其他通道选择显示未解码的原始值,0 到 1 直接映射为黑到白。例如 rrr 将红色显示为灰度,aaa 显示 alpha,bgr 交换红蓝。即使检查 alpha,预览本身仍不透明。
同一次更新中可用多种方式显示同一纹理:
textures.channels = 'rgb';
textures.draw(texture, 0.05, 0.05, 0.25, 0.25);
textures.channels = 'aaa';
textures.draw(texture, 0.35, 0.05, 0.25, 0.25);
通道选择由每个绘制调用捕获,保持到再次修改。需要解码颜色时改回 rgb。深度纹理和 sceneDepth 忽略通道选择,始终显示灰度。
场景深度
sceneDepth 显示当前渲染相机的深度,经线性化后按远裁剪距离归一化。需在相机启用场景深度捕获,并在捕获后渲染预览图层:
import { TextureRenderer } from 'playcanvas';
// camera is an entity with a camera component.
camera.camera.requestSceneDepthMap(true);
const textures = new TextureRenderer(app);
textures.camera = camera.camera;
app.on('update', () => {
textures.sceneDepth(0.7, 0.7, 0.25, 0.25);
});
直接向 draw 传入深度纹理,则显示未经相机相关线性化的原始深度。
支持的纹理
- 二维颜色:归一化、浮点和设备支持的压缩格式。默认 rgb 自动处理线性、sRGB、RGBM、RGBE 和 RGBP。
- 二维深度:
PIXELFORMAT_DEPTH、PIXELFORMAT_DEPTH16、PIXELFORMAT_DEPTHSTENCIL。
纹理必须与应用属于同一图形设备。不支持立方体、体纹理、数组、整数和多重采样纹理。
WebGL2 上,原始深度纹理必须禁用比较采样;原始深度和不可滤波浮点纹理还要求最近邻缩小和放大滤波。WebGPU 可忽略这些采样器设置显示它们。
清理
停止提交预览后,它会在下一帧隐藏。不再需要渲染器时调用 textures.destroy();应用销毁时也会自动释放。传入纹理始终由你管理,渲染器不会销毁。
粗线
WireRenderer 绘制适合调试叠加的一像素线。场景本身的高亮路径、路网或标注应使用 WideLineRenderer,支持屏幕或世界单位宽度、端点、接头、虚线和渐变。它是保留式的,添加一次后持续存在,直到移除。
性能计数器
调试绘制显示对象在哪里;要看耗时,使用 MiniStats。