mcpField
P
pixijs-3d

pixijs-3d

github.com/pixijs开发372151 次安装3 天前更新
代码仓库

关于 pixijs-3d

pixijs-3d 是面向 PixiJS 3D 扩展开发者的编码技能,介绍场景、相机、材质与模型的工作方式,并提供按任务组织的参考配方。

主要功能

  1. 01说明 3D 场景图与相机设置
  2. 02涵盖几何体、灯光和材质
  3. 03介绍模型加载与动画
  4. 04支持 2D 与 3D 场景组合
  5. 05列出常见渲染与交互陷阱

适用场景

  1. 01搭建带灯光和阴影的 3D 场景
  2. 02加载模型并播放动画
  3. 03将 2D 界面叠加到 3D 视图
  4. 04排查相机、材质或输入问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/pixijs/pixijs-skills --skill pixijs-3d

安装后在 Claude Code 中输入/pixijs-3d确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
2d-and-3d-together.md
cameras.md
custom-shaders.md
environment-and-sky.md
from-threejs.md
geometry.md
instancing.md
materials.md
math.md
models-and-animation.md
particles.md
picking-and-input.md
post-processing.md
render-to-texture.md
scene-and-lighting.md
scene-graph.md
references/from-threejs/
materials-and-lights.md
objects-and-animation.md
renderer-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;如果已安装 pixijs skill,请使用它来处理这些内容。
  • 渲染器。 除非传入 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-graph
  • addChild 会保留节点的局部数值,因此节点在新父级下会跳到相同偏移处;reparentChild 则会保留节点在世界空间中的位置。scene-graph
  • view.root 保存着摄像机:清除或隐藏 view.root 会导致渲染抛出错误。将关卡内容放在独立的 Container3D 下,并清除该容器。scene-graph
  • getLocalBounds() 使用节点自身的空间,且不包括隐藏的子节点:要获取世界空间中的包围盒,请使用 getLocalBounds().applyMatrix4(node.getGlobalTransform())。math
  • Vector3 数学运算会原地修改(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 监视器上显示过暗,因此请目视比较屏幕与场景,或在 2D Sprite 中显示画面。请使用 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
数千个实例、逐实例数据、批处理、LODreferences/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.renderapp.stage 上的 View3D;由 Pixi 渲染
renderer.setAnimationLoopapp.ticker.add
PerspectiveCamera、OrthographicCameraview.camera;camera.mode = 'orthographic' + orthographicSize
OrbitControls、CameraOrbitControlOrbitCamera(扩展功能)
Mesh(geometry, material)、Mesh3D.createCube()new Mesh3D({ geometry: new CubeGeometry(), material })
MeshStandardMaterial、StandardMaterialMaterial3D
MeshBasicMaterialFlatMaterial
ShaderMaterialextraBits 中的 ShaderBit
HemisphereLight、LightingEnvironment、ImageBasedLighting视图的 environment
ShadowCastingLight光源上的 castShadow: true
GLTFLoader、Model.fromAssets.load<Scene3DSource> + new Model3D({ source })
AnimationMixermodel.player
InstancedMeshBatchedMesh3D
Raycaster网格上的指针事件,或 view.raycast
Box3node.getLocalBounds()
EffectComposerview.passes,以及来自扩展功能的效果
CSS2DRenderer舞台上的 Pixi Text + worldToScreen

其他所有 Three.js 名称,以及重命名后行为有所不同的情况,请参阅 references/from-threejs.md。

评论

支持 Markdown