关于 pixijs-3d
pixijs-3d 是面向 PixiJS 3D 扩展开发者的编码技能,介绍场景、相机、材质与模型的工作方式,并提供按任务组织的参考配方。
主要功能
- 01说明 3D 场景图与相机设置
- 02涵盖几何体、灯光和材质
- 03介绍模型加载与动画
- 04支持 2D 与 3D 场景组合
- 05列出常见渲染与交互陷阱
适用场景
- 01搭建带灯光和阴影的 3D 场景
- 02加载模型并播放动画
- 03将 2D 界面叠加到 3D 视图
- 04排查相机、材质或输入问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ pixijs/ pixijs-skills --skill pixijs-3d安装后在 Claude Code 中输入/pixijs-3d确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/2d-and-3d-together.mdcameras.mdcustom-shaders.mdenvironment-and-sky.mdfrom-threejs.mdgeometry.mdinstancing.mdmaterials.mdmath.mdmodels-and-animation.mdparticles.mdpicking-and-input.mdpost-processing.mdrender-to-texture.mdscene-and-lighting.mdscene-graph.mdreferences/from-threejs/materials-and-lights.mdobjects-and-animation.mdrenderer-and-scene.md详细介绍
PixiJS 3D(@pixi/3d)是 PixiJS 的 3D 扩展。View3D 是 Pixi 舞台上的普通 2D 显示对象,它会在自己的矩形区域内渲染 3D 场景,因此 3D 和 2D 内容可以自由混合。场景以 view.root 为根节点,视图拥有自己的 camera,灯光是场景中的节点,而 Pixi 应用的 ticker 就是帧循环。
本文介绍整体思路、完整的入门应用,以及一些容易被忽略的约定。references/ 文件夹中按任务提供了完整、可运行的示例;按需打开即可。
如果本文未涵盖某个签名或选项,请获取该类的 API 页面:https://pixijs.download/3d/release/docs/<module>.<Class>.html.md(scene.View3D、materials.Material3D、extras_camera.OrbitCamera、maths.Vector3)。要查找类所属的模块,或某个主题的指南,请在 https://pixijs.download/3d/release/docs/llms.txt 中查找。
快速开始
一个带阴影、包含两个基本图形和一个已加载模型,并支持鼠标轨道控制的光照场景。为安装了 pixi.js 和 @pixi/3d 的 Vite + TypeScript 应用补全 src/main.ts。
import {
CubeGeometry,
DirectionalLight,
Material3D,
Mesh3D,
Model3D,
PlaneGeometry,
SphereGeometry,
View3D,
} from '@pixi/3d';
import type { Scene3DSource } from '@pixi/3d';
import { OrbitCamera } from '@pixi/3d/extras';
import { Application, Assets } from 'pixi.js';
const app = new Application();
await app.init({ resizeTo: window, backgroundColor: 0x87b7e6, antialias: true });
document.body.appendChild(app.canvas);
// A View3D is a 2D display object that renders a 3D scene into its rectangle.
const view = new View3D({
autoResize: true,
shadowQuality: 'medium',
environment: { skyColor: 0xbcd4f0, groundColor: 0x6b6250, intensity: 0.7 }, // hemisphere fill light
});
app.stage.addChild(view);
// Lights are nodes under view.root. Lights and cameras face their local -Z axis.
const sun = new DirectionalLight({ color: 0xfff2dc, intensity: 3, castShadow: true });
sun.rotation.set(-Math.PI / 4, Math.PI / 6, 0);
view.root.addChild(sun);
// PlaneGeometry lies in XY with a +Z normal: rotate it -90° around X to make a floor.
const ground = new Mesh3D({
geometry: new PlaneGeometry({ width: 30, height: 30 }),
material: new Material3D({ baseColor: 0x7f8f6a, roughness: 0.95, metallic: 0 }),
rotation: { x: -Math.PI / 2, y: 0, z: 0 },
castShadow: false,
receiveShadow: true,
});
view.root.addChild(ground);
// Meshes take a geometry and a material. Options objects set position, rotation, scale, shadows.
view.root.addChild(
new Mesh3D({
geometry: new CubeGeometry({ width: 2, height: 2, depth: 2 }),
material: new Material3D({ baseColor: 0xd9534f, roughness: 0.6, metallic: 0 }),
position: { x: -3, y: 1, z: 0 },
}),
new Mesh3D({
geometry: new SphereGeometry({ radius: 1.2 }),
material: new Material3D({ baseColor: 0x4a90d9, roughness: 0.3, metallic: 0.2 }),
position: { x: 3, y: 1.2, z: 0 },
}),
);
// Models: load a glTF/GLB with Assets, then wrap the source in a Model3D.
const source = await Assets.load<Scene3DSource>('/assets/models/Duck.glb');
// The file's own node transforms set its size (this duck is about 1.6 units); read duck.getLocalBounds() before scaling.
const duck = new Model3D({ source, position: { x: 0, y: 0, z: 3 } });
view.root.addChild(duck);
// The view owns its camera. OrbitCamera (extras) drives it from the mouse.
view.camera.position.set(0, 5, 12);
view.camera.lookAt({ x: 0, y: 1, z: 0 });
new OrbitCamera({ view, enableDamping: true });
// Per-frame work goes on the application ticker.
app.ticker.add((ticker) => {
duck.rotation.y += 0.01 * ticker.deltaTime;
});
约定
- 场景图。 所有 3D 内容都放在
view.root(一个Container3D)下;将网格添加到app.stage不会产生有用效果。节点具有position、rotation(欧拉弧度)、quaternion、scale、visible、lookAt(point)、addChild、对整个子树执行遍历的traverse(callback),以及通过getLocalBounds()获取边界。 - 选项对象。 构造函数接收一个选项对象:
new Mesh3D({ geometry, material, position: { x, y, z }, castShadow, parent: view.root })。之后,相同的值会作为普通属性提供(mesh.position.set(x, y, z))。 - 摄像机。
view.camera是一个Camera3D:fov是垂直视野角,单位为度(默认值为 60);near为 0.1,far为 1000;mode为'perspective'或'orthographic',并配有orthographicSize(视口在世界单位中的宽度的一半,默认值为 5)。使用position.set(...)和lookAt(point)调整朝向。 - 朝向。 摄像机和灯光沿其局部 -Z 方向朝向;网格和 glTF 模型朝向 +Z。
DirectionalLight通过rotation(或lookAt)调整朝向,而非通过目标点。 - 平面。
PlaneGeometry位于 XY 平面,法线朝向 +Z。要将其用作地面,请绕 X 轴旋转 -π/2。 - 基本图形选项。
CubeGeometry({ width, height, depth })、SphereGeometry({ radius })、CylinderGeometry({ radiusTop, radiusBottom, height })(没有radius)、ConeGeometry({ radius, height })、CapsuleGeometry({ radius, height })(高度包含两端的半球)、TorusGeometry({ radius, tube })(位于 XZ 平面)、PlaneGeometry({ width, height })、IcosahedronGeometry({ radius, detail })。每种图形也都接受分段数。 - 光照。
DirectionalLight、PointLight、SpotLight、AmbientLight都是节点。柔和的补光来自视图的environment:半球光使用{ skyColor, groundColor, intensity },基于图像的光照则使用CubeTexture(通过Assets.load加载.cubemap或.hdr)。阴影:在灯光上设置castShadow,在网格上设置castShadow/receiveShadow,并在视图上设置shadowQuality('low' | 'medium' | 'high' | 'ultra')。 - 材质。
Material3D基于物理渲染(baseColor、metallic、roughness、baseColorTexture、alphaMode、alpha);发光效果属于可选项,通过emissive: { color, strength }设置,不能使用普通颜色值。FlatMaterial不受光照影响。自定义外观通过extraBits中的着色器代码实现,而非通过ShaderMaterial类。material.clone(options)会复制材质,并修改选项或添加代码片段。 - 模型。 使用
Assets.load<Scene3DSource>(url),然后调用new Model3D({ source })。动画片段通过model.player播放。来自同一资源的模型会共享其材质;model.materials会为单个模型提供独立材质。 - 扩展功能。
OrbitCamera、FPSCamera、ParticleSystem3D、BloomEffect、SsaoEffect、TiltShiftEffect、MotionBlurEffect、TintEffect、FxaaEffect、ToonMaterial、PortalPass、PortalMaterial、dissolveBit位于@pixi/3d/extras中。 - 大量副本。
BatchedMesh3D可以通过一次调用绘制数千个实例(setMatrixAt、setInstanceDataAt)。独立的Mesh3D节点若共享几何体、相同类型的材质和相同的纹理_源_,也会自动批处理为实例化绘制:同一精灵表的不同帧算作同一个源,因此,显示同一张精灵表的不同帧的Sprite3D或带纹理网格仍可一起绘制。将同一种效果的图像打包到同一张精灵表中。 - 3D 上的 2D 内容。 HUD 文本、按钮和精灵都是普通 Pixi 对象,在视图之后添加到舞台。
view.worldToScreen(point)可将 2D 标签放置在 3D 位置上;它始终绘制在 3D 内容之上。若要让场景中的图像被墙壁遮挡,请使用new Sprite3D({ texture, height, billboardMode, parent: view.root })。 - 周边应用使用 pixi.js。
Application和await app.init、Assets、Text、指针事件以及 ticker 均属于 pixi.js v8;如果已安装pixijsskill,请使用它来处理这些内容。 - 渲染器。 除非传入
preference: 'webgpu',否则app.init使用 WebGL;代码中的其他内容无需更改。MSAA 通过app.init({ antialias: true })设置;View3D没有antialias选项。 - 没有枚举。
shadowQuality、alphaMode和mode等选项使用字符串字面量。不存在LightType或ShadowQuality枚举。
会静默出错的陷阱
每行都是修复方法;链接指向对应规则。这些问题不会报错,但会导致绘制或行为不正确。查看你所构建内容对应的分组。
场景图与数学
lookAt、reparentChild和加载的 glTF 节点会设置quaternion,节点会按最后写入的rotation或quaternion转动:对这些节点使用rotation.y += x会替换实际转向,因此应使用quaternion.multiply(q)让它们转动。scene-graphaddChild会保留节点的局部数值,因此节点在新父级下会跳到相同偏移处;reparentChild则会保留节点在世界空间中的位置。scene-graphview.root保存着摄像机:清除或隐藏view.root会导致渲染抛出错误。将关卡内容放在独立的Container3D下,并清除该容器。scene-graphgetLocalBounds()使用节点自身的空间,且不包括隐藏的子节点:要获取世界空间中的包围盒,请使用getLocalBounds().applyMatrix4(node.getGlobalTransform())。mathVector3数学运算会原地修改(a.add(b)会改变a;传入out可保留原值),并使用subtract、multiplyScalar、magnitude和copyFrom。节点的position是实时值,因此请复制需要保留的值。math- 高度为
h的地面平面应写作new Plane(new Vector3(0, 1, 0), -h)(注意负号),或使用setFromNormalAndCoplanarPoint。math
光照与颜色
- 视图默认使用
toneMapping: 'none',此时受光颜色会在 1 以上截断:对于强光、HDR 环境或泛光效果,请设置toneMapping: 'aces'。environment-and-sky - 从 Three.js 迁移:
HemisphereLight(sky, ground, i)对应environment: { skyColor, groundColor, intensity: i / Math.PI };方向光、点光、聚光和环境光的强度则按 1:1 对应迁移。from-threejs - 直接光源和
AmbientLight的强度会除以 π,而半球光不会:强度接近 3 的太阳光和强度接近 0.7 的半球光可以充分照亮baseColor,而强度为 0.2 的AmbientLight几乎看不见。scene-and-lighting - 顶点颜色使用线性光:请使用
srgbToLinear转换 sRGB 取色值,否则中间色调会显得发白。geometry - 一个视图最多为 32 个光源着色,并使用 16 张阴影贴图;投射阴影的对象超过 256 个时,太阳光的阴影范围只会测量一次,因此场景变化后请调用
sun.fitShadowBounds()。scene-and-lighting
表面
alphaMode默认为'opaque',此模式会忽略alpha和纹理的 alpha 通道(透明 PNG 像素会显示为黑色):请设置为'blend'或'mask'。materials'mask'镂空,或会移动或丢弃顶点的着色器模块(dissolveBit),仍会投射未位移的完整阴影并绘制完整轮廓:请为其设置castShadow: false;如果仍显示轮廓,则设置material.disablePass('outline')。materials、custom-shaders- 除非设置
flipV: true,否则图像在PlaneGeometry上会上下颠倒;在CubeGeometry的面上也会如此,除非材质设置了uvTransform: new Matrix(1, 0, 0, -1, 0, 1)(Matrix来自 pixi.js)。from-threejs - 在不透明材质上使用
ContainerTexture时,2D 内容的透明部分会显示为黑色:请添加背景、设置clearColor,或使用alphaMode: 'blend'。render-to-texture
模型与动画
- glTF 文件自身的节点变换已经设置好尺寸,并使模型朝向 +Z、以 Y 轴为上:缩放前读取
getLocalBounds(),不要添加修正旋转。models-and-animation - 加载的模型会绘制其源文件中的材质,因此修改一个材质会影响该文件中的所有模型:通过
model.materials = (m) => m.clone()为单个模型创建副本;要修改已加载的材质,请使用clone(options),而不是会丢弃纹理的new Material3D。models-and-animation - 大多数骨骼都不是场景节点,因此
getChildByLabel('Hand', true)会返回 null:请使用model.skeletons[0]。models-and-animation - 请写入
mesh.weights;赋予新数组会使其与驱动动画的片段断开连接。models-and-animation - 没有几何体或材质的
Mesh3D不会绘制任何内容,也不会报错。geometry
输入与摄像机
-
OrbitCamera、FPSCamera、ParticleSystem3D和模型的动画player会在Ticker.shared上自行更新,而不是在app.ticker上更新,因此它们的更新可能发生在你的帧代码之后,或晚一帧:需要控制顺序时,请传入autoUpdate: false,并在app.ticker回调中调用update(ticker)(摄像机移动时,orbit.update()会返回true)。cameras -
3D 节点的默认
eventMode: 'passive':请设为'static'(也可以作为Mesh3D选项),否则不会拾取任何内容。pointertap会对每种鼠标按键触发,因此请检查e.button。picking-and-input -
全屏视图会对整个矩形区域进行命中测试,因此会阻挡对其后方 2D 对象的点击:请将可交互的 2D 内容放在视图上方,或设置
view.hitArea。2d-and-3d-together -
OrbitCamera.enabled = false只会停止输入;update()仍会持续重新调整摄像机朝向,因此在自行移动摄像机前,还要设置autoUpdate = false(或调用destroy())。cameras -
FPSCamera的行走方向会跟随俯仰角,因此向下看时摄像机也会下沉:对于贴地行走的摄像机,请在update()后重新固定position.y。cameras
效果与渲染到纹理
BloomEffect的阈值在光线线性空间中为 1:请将发光物体设为emissive,并将strength设为大于 1;阈值接近 0.5 时,所有受光表面都会产生泛光。MSAA 会在效果处理中保留,因此只有未启用antialias时才添加FxaaEffect。post-processing- 在使用电影风格曲线(
'aces')的网格上显示摄像机画面时,画面会经过两次曲线处理,导致颜色发白;对于泛光类效果,文档建议的修复方法是使用format: 'rgba16float',但它目前在FlatMaterial监视器上显示过暗,因此请目视比较屏幕与场景,或在 2DSprite中显示画面。请使用antialias: true创建画面,否则边缘会有锯齿。render-to-texture - 使用
enabled切换效果;添加第一个曲线前效果或移除最后一个曲线前效果时,所有材质都会重新编译。post-processing
粒子
ParticleContainer3D在调用setParticleAt后,必须调用upload()才会显示内容。particles
当前库的限制(修复前的变通方法)
- 没有法线的 glTF 网格(例如 Fox.glb)会像每个面都朝向 +Z 一样受光,转向背面时则会变黑:请使用
getAttributeData重建平面法线。from-threejs SphereGeometry会将图像左右镜像:添加uvTransform: new Matrix(-1, 0, 0, 1, 1, 0)和rotation.y = Math.PI即可修复。from-threejs- 创建时未传入
position、scale或anchor的Sprite3D会以 1×1 单位绘制,而不是采用纹理尺寸,直到其中一个属性发生变化:请始终传入position。2d-and-3d-together
应打开哪个参考文档
| 任务 | 文件 |
|---|---|
| 场景图:父级与子级、变换、旋转与四元数、lookAt、局部空间与世界空间、按标签查找、重新设置父级、边界与尺寸、可见性、场景事件、销毁 | references/scene-graph.md |
| 向量、四元数、矩阵、平滑转向、盒体、球体、射线、平面、视锥体测试、球面轨道 | references/math.md |
| 场景、地面、太阳、点光源和聚光灯、阴影 | references/scene-and-lighting.md |
| 加载 glTF、调整取景、播放和交叉淡化动画片段、模型自身的材质、溶解、骨骼、变形目标 | references/models-and-animation.md |
| PBR、非光照、卡通、发光、透明度、纹理 | references/materials.md |
| 内置几何体、自定义顶点数据、地形、线条和点 | references/geometry.md |
| 数千个实例、逐实例数据、批处理、LOD | references/instancing.md |
| 点击、悬停、拖动、射线投射、屏幕到世界坐标转换、键盘 | references/picking-and-input.md |
| 摄像机控制、轨道、第一人称、正交、裁剪、跟随 | references/cameras.md |
| 天空盒、立方体贴图、HDR(.hdr)、基于图像的照明、雾、色调映射 | references/environment-and-sky.md |
| 泛光、SSAO、移轴、运动模糊、FXAA、效果顺序、场景分辨率 | references/post-processing.md |
| 发射器、爆发、手动驱动的粒子 | references/particles.md |
| 将第二个摄像机渲染到纹理、表面上的实时 2D 内容、传送门 | references/render-to-texture.md |
| HUD、标签、Sprite3D 广告牌、2D 滤镜和蒙版、多个视图、分辨率 | references/2d-and-3d-together.md |
| 着色器片段、自定义材质效果 | references/custom-shaders.md |
| 移植 Three.js 应用:成对程序、会改变画面的差异、完整名称对照表 | references/from-threejs.md |
如果你熟悉 Three.js 或名称相近但无关的 pixi3d 包
| 你可能会选择 | 在这里使用 |
|---|---|
Scene、renderer.render | app.stage 上的 View3D;由 Pixi 渲染 |
| renderer.setAnimationLoop | app.ticker.add |
| PerspectiveCamera、OrthographicCamera | view.camera;camera.mode = 'orthographic' + orthographicSize |
| OrbitControls、CameraOrbitControl | OrbitCamera(扩展功能) |
Mesh(geometry, material)、Mesh3D.createCube() | new Mesh3D({ geometry: new CubeGeometry(), material }) |
| MeshStandardMaterial、StandardMaterial | Material3D |
| MeshBasicMaterial | FlatMaterial |
| ShaderMaterial | extraBits 中的 ShaderBit |
| HemisphereLight、LightingEnvironment、ImageBasedLighting | 视图的 environment |
| ShadowCastingLight | 光源上的 castShadow: true |
GLTFLoader、Model.from | Assets.load<Scene3DSource> + new Model3D({ source }) |
| AnimationMixer | model.player |
| InstancedMesh | BatchedMesh3D |
| Raycaster | 网格上的指针事件,或 view.raycast |
| Box3 | node.getLocalBounds() |
| EffectComposer | view.passes,以及来自扩展功能的效果 |
| CSS2DRenderer | 舞台上的 Pixi Text + worldToScreen |
其他所有 Three.js 名称,以及重命名后行为有所不同的情况,请参阅 references/from-threejs.md。