When PixiJS renders the stage and reaches a View3D, the view draws its whole 3D scene at that point. PixiJS then carries on with the 2D content after it.
The view draws each frame in passes, and each pass draws one part of the picture. This list of passes is the view's render graph. The passes run in this order:
'beforeOpaque' items in view3d.passes. They run before the view draws its scene, so a texture they fill, such as a second camera's render texture, shows in the scene on the same frame.'blend' material so it draws after them. When you create that material, set its state.depthTest to false.'afterOpaque' items. Glass refracts what these items draw, and transparent meshes draw over it.'blend' meshes from back to front by their distance from the camera, not by child order, as Materials explains.'afterTransparent' items. They run after every mesh in the scene and before any post effect.'beforeToneMapping' effects run first. Then comes the view's tone mapping, which turns the scene's light into the colours the screen shows, and then the 'afterToneMapping' effects.The view skips the shadow pass while no light casts shadows. It also skips a pass that draws meshes while no mesh in the scene belongs to it, so a scene without shadows or glass pays nothing for them. Hiding a mesh or a light, or moving a mesh off-screen, doesn't skip its passes. A pass whose meshes are all hidden still runs until you remove them from the scene.
Each item in view3d.passes runs at the step its stage names, wherever it sits in the array. Post-processing covers that list, the built-in effects and how to write your own effect.
With a 'beforeToneMapping' effect on, the view draws its scene into a floating-point texture, so those effects see light brighter than white. The view then applies the tone mapping once, after the last of those effects, rather than in each material. Transparent meshes and glass then blend with what's behind them before the tone mapping rather than after, so they can look different once you add such an effect.
On WebGL2, rendering throws on a device without the EXT_color_buffer_float extension, so check for it before you add a 'beforeToneMapping' effect such as bloom:
import { BloomEffect } from '@pixi/3d/extras';
import { RendererType } from 'pixi.js';
import type { WebGLRenderer } from 'pixi.js';
// WebGPU can always draw into a floating-point texture
const canDrawFloat =
app.renderer.type !== RendererType.WEBGL ||
Boolean((app.renderer as WebGLRenderer).context.extensions.colorBufferFloat);
if (canDrawFloat) view3d.passes = [new BloomEffect()];
A mesh's material and its own settings decide which passes draw it:
'opaque' or 'mask' puts its meshes in the opaque pass, and one whose alphaMode is 'blend' puts them in the transparent pass.When a mesh doesn't draw where you expect, its renderPassMask reports the passes that draw it as RenderPassBit flags. A BatchedMesh3D doesn't take its passes from its materials. Instead, give each object in it those flags with setPassesAt, as Batched meshes shows.
A mesh's shader is the program the GPU runs to draw it. It's built from what the mesh needs, such as its material's features, the data its geometry carries, the bones or morph targets that bend it, and a few of the view's settings. Each combination compiles once per renderer, and every mesh that needs it shares it. So the number of shaders grows with the combinations a scene uses, not with its mesh count.
The first frame that needs a new combination compiles it, and that frame can stall, most of all on WebGL. The view settings below are built into the shaders of the materials they affect. Changing one makes new combinations, which compile on the next frame:
'beforeToneMapping' effect on, the materials keep their shaders, and only the shader that applies it to the whole picture compiles.type.CubeTexture). Swapping one cube map for another recompiles no material. The first frame that uses each cube map still spends time preparing it for lighting, and later frames don't repeat that work.'beforeToneMapping' effect or turning off the last, and adding an effect whose requiredOutputs makes the view draw extra data along with the scene, such as MotionBlurEffect, which reads each pixel's motion.A mesh also gets a new shader in these cases:
Materials covers what a change to a material needs.
Values the shaders read as data compile nothing when they change: exposure, the fog's colour and distances, a hemisphere's colours, light colours and intensities, and the number of lights. The renderer keeps every combination it has compiled, so switching a setting back and forth compiles only on the first use of each value. Choose the view's settings before the first render where you can, and change them on a loading screen or in a settings menu rather than during play.
import { View3D } from '@pixi/3d';
// Given here, they're in place when the first render compiles the scene's shaders
const view3d = new View3D({
autoResize: true,
environment: { skyColor: 0xc9d2ff, groundColor: 0x2a2140, intensity: 0.9 },
fog: { type: 'linear', color: 0x14131c, near: 20, far: 34 },
});