跳到主要内容

调试绘制

有些问题很难仅凭数值调试,例如包围盒是否在预期位置、样条朝向哪里,或第二台相机实际能看到什么。调试绘制在屏幕上显示形状和纹理预览,便于检查。

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。

示例​

纹理渲染器
  • 线框形状:所有形状及动画。
  • 视锥体剔除:相机可见空间,以及按是否包含在内着色的包围盒。
  • 线:同时展示全部线函数。