@pixi/3d
    Preparing search index...

    Class PortalMaterial

    Unlit surface that shows a render-texture feed at the viewer's own screen position

    Each fragment samples the feed where it sits on screen, so the far side holds still as the viewer moves and the quad reads as an opening rather than a picture. Pair it with a PortalPass, which draws the feed from behind the exit portal. The material needs nothing from that pass but the texture, so a planar mirror's feed works too.

    The feed shows as the view drew it: unlit, and not tone mapped a second time as long as its format suits the view (see PortalMaterialOptions#texture). The view's exposure still scales it, on top of the exposure in the feed. A portal is an opening only from the side it faces, so leave doubleSided off.

    On WebGL, a View3D drawn into a render texture, inside a pixi filter or by renderer.extract, shows the far side upside down.

    const feed = RenderTexture.create({
    width: view3d.width,
    height: view3d.height,
    format: 'rgba16float',
    });
    const entry = new Mesh3D({
    geometry: new PlaneGeometry({ width: 3, height: 4 }),
    material: new PortalMaterial({ texture: feed }),
    });

    view3d.passes = [new PortalPass({ entry, exit, texture: feed })];

    Hierarchy (View Summary)

    Index
    colorSpace: ColorSpace = 'display'

    'display': the portal only multiplies its feed by its tint, so it can skip the decode and encode

    The values the material's shader reads, one property per field its bits declare; see MaterialData for how they read and write

    const toon = new ToonMaterial({ bands: 3 });
    toon.data.bands = 5;
    destroyed: boolean = false

    Whether destroy() has run

    doubleSided: boolean

    Whether back faces are drawn too, lit with their normal flipped; set by the doubleSided option

    extraBits: readonly ShaderBit[]

    The shader bits this material was built with on top of its own, from the extraBits option

    Fixed for the material's lifetime. BaseMaterial3D#clone makes a copy with more.

    const dissolving = materials.filter((material) => material.extraBits.includes(dissolveBit));
    
    flatShading: boolean

    Whether triangles are shaded with their face normal; set by the flatShading option

    const rock = new Material3D({ baseColor: 0x8a7f72, flatShading: true });
    
    fog: boolean

    Whether the view's fog may reach this material; set by the fog option

    const sky = new Material3D({ baseColorTexture: skyTexture, fog: false });
    
    label?: string

    Name for debugging and for finding the material, such as the glTF material name the loader gives it; a Model3DOptions#materials record matches it

    const paint = source.materials.filter((material) => material.label === 'Paint');
    
    lit: boolean = false

    false: the feed is a frame the view has already lit

    state: State

    Render state (blending, depth, culling) for every draw with this material

    alphaMode and doubleSided set parts of it. To change a field after the material has rendered, see BaseMaterial3D. polygonOffset works on WebGL only; WebGPU ignores it.

    // 'blend' draws it after the opaque meshes, so with no depth test it shows through them
    const overlay = new FlatMaterial({ baseColor: 0xffcc00, alphaMode: 'blend' });
    overlay.state.depthTest = false;

    The material's textures by slot name, the same names the textures option takes

    Assigning swaps the slot, and a name the material doesn't have throws. A swap reuses the compiled shader, except that a real map in the normal slot, or the default put back, switches normal mapping on or off.

    Wrap mode, filtering and anisotropy come from each texture's source.style, which clamps by default. To tile a texture, set texture.source.style.addressMode = 'repeat', then call style.update().

    material.textures.baseColor = crateTexture;
    
    topology?: Topology

    Primitive topology for every pass that doesn't set its own; set by the topology option, and PassDescriptor lists what overrides it

    uid: number = ...

    Unique id for this material instance

    vertexColors: boolean = false

    Whether a geometry's vertex colours (its colors) tint this material

    Off by default, since exporters often fill the colour set with masks or weights; put data that isn't colour in colors1 when you can. The glTF loader turns it on, as glTF's COLOR_0 is a base colour multiplier in linear light; convert colours picked in sRGB with srgbToLinear. To change it after the material has rendered, see BaseMaterial3D.

    false
    
    const painted = new Material3D();
    painted.vertexColors = true;
    • get alpha(): number

      Base opacity, from 0 to 1; alphaMode: 'opaque' ignores it

      Returns number

      1
      
      material.alphaMode = 'blend';
      material.alpha = 0.5;
    • set alpha(value: number): void

      Sets the base opacity

      Parameters

      • value: number

        From 0 (transparent) to 1 (opaque)

      Returns void

    • get alphaCutoff(): number

      Alpha below which fragments are discarded while BaseMaterial3D#alphaMode is 'mask'

      Returns number

      0.5
      
      material.alphaMode = 'mask';
      material.alphaCutoff = 0.3;
    • set alphaCutoff(value: number): void

      Sets the alpha cutoff

      Parameters

      • value: number

        Alpha below which fragments are discarded

      Returns void

    • get alphaMode(): AlphaMode

      How the material treats alpha; see AlphaMode

      Setting it also sets the blending and depth writes of state. 'blend' turns depth writes off, and its meshes cast no shadow. On a material built from a raw descriptor, 'mask' discards only if the descriptor's bits include alphaMaskBit.

      Returns AlphaMode

      material.alphaMode = 'blend';
      material.alpha = 0.5;
    • set alphaMode(value: AlphaMode): void

      Sets the alpha mode

      Parameters

      Returns void

    • get baseColor(): number

      Base colour tint, read back as a hex number

      Takes any ColorSource in display (sRGB) space, like Container.tint. The source's alpha is ignored; BaseMaterial3D#alpha is the opacity.

      Returns number

      material.baseColor = 'tomato';
      material.baseColor; // 0xff6347
    • set baseColor(value: ColorSource): void

      Sets the base colour tint

      Parameters

      • value: ColorSource

        Display colour; its alpha is ignored

      Returns void

    • get uvTransform(): Matrix | undefined

      The UV transform you set, or undefined while the base colour texture's frame sets it

      One transform, as glTF's KHR_texture_transform defines it, applies to every texture of the material. Setting a matrix takes over from a framed (spritesheet) base colour texture, and a framed texture assigned while it's set warns that its frame is ignored; setting undefined hands it back. Either is a data write, never a shader rebuild. On a material whose bits lack uvTransformBit, setting a matrix warns and does nothing.

      A UV (u, v) becomes (a * u + b * v + tx, c * u + d * v + ty), transposed from pixi's Matrix.apply, so new Matrix().rotate(rotation).scale(sx, sy).translate(x, y) gives the KHR_texture_transform result for those values. The getter returns the material's own matrix; changing it in place does nothing until you assign it again.

      Returns Matrix | undefined

      material.uvTransform = new Matrix().scale(4, 4);
      material.uvTransform = undefined;
    • set uvTransform(m: Matrix | undefined): void

      Sets the UV transform

      Parameters

      • m: Matrix | undefined

        The transform, or undefined to follow the base colour texture's frame

      Returns void

    • get EMPTY(): BaseMaterial3D
      Advanced

      A shared material with no shader, which a Mesh3D holds until it's given a material

      A mesh on it draws nothing and compiles no shader, though its skeleton and morph targets still upload. Assign it to a mesh's material to stop the mesh drawing; to hide a mesh for a while, visible = false is cheaper. Don't give it to a BatchedMesh3D slot that's in a pass, even a hidden one: such a slot needs a real material.

      Every waiting mesh shares it, so don't edit it. Its destroy does nothing, as with pixi's Texture.EMPTY.

      Returns BaseMaterial3D

      if (mesh.material === BaseMaterial3D.EMPTY) mesh.material = new Material3D();
      
    • Makes a material of the same class that draws as this one does now, with the options you pass changed

      options are the class's constructor options. clone reads this material's current values back as those options, merges yours over them, and passes the result to the class's constructor. An option left out, or set to undefined, keeps this material's value. One that takes an object, such as a feature group or passes, is merged one level deep: emissive: { strength: 2 } on a Material3D keeps its emissive colour. descriptor is replaced whole. A group the material doesn't have, such as clearcoat, adds the feature.

      extraBits adds to this material's BaseMaterial3D#extraBits, skipping a bit it already has. Fields a new bit declares start at 0 and its texture slots hold Texture.WHITE, so set them on the copy. The fields of the extra bits it already had keep their values.

      A raw-descriptor material, or any class that inherits BaseMaterial3D's _toOptions unchanged, keeps every field value and texture it has, except those an option you pass sets.

      What isn't an option comes across too: colorSpace, lit, the passes joined with BaseMaterial3D#enablePass, and state with any change made through it, except the flags an option you pass sets, such as alphaMode's blending. Colour options read back as 8-bit hex, as their getters return them.

      Textures are shared, not copied. Everything else belongs to the copy: assign it to meshes and destroy() it when you're done, which leaves the shared textures alive. Materials with the same bits share compiled shaders, so a copy compiles nothing new unless its bits are new.

      Parameters

      • Optionaloptions: PortalMaterialOptions

        Constructor options to change on the copy; left out, it's a plain copy.

      Returns this

      The copy, of the same class.

      const dissolving = mesh.material.clone({ extraBits: [dissolveBit] });
      dissolving.data.dissolve = 0.5;
      mesh.material = dissolving;

      const ghost = mesh.material.clone({ alphaMode: 'blend', alpha: 0.4 });
    • Frees the material for good; a second call does nothing

      Its textures stay alive unless options asks for them. The engine's default textures are never destroyed, and compiled shaders stay cached in the renderer.

      Parameters

      • Optionaloptions: TypeOrBool<TextureDestroyOptions>

        { texture: true } destroys the material's textures; add textureSource: true, or pass true, to destroy their sources too

      Returns void

      mesh.material = new Material3D({ baseColor: 0x3366ff });
      oldMaterial.destroy({ texture: true });
    • Removes this material from a custom render pass

      Meshes using the material leave the pass on the next render, except one that joined it through Mesh3D#enablePass. An override given for the pass in the passes option is kept, so BaseMaterial3D#enablePass brings it back.

      Parameters

      • name: string

        The pass's name, such as 'outline'.

      Returns void

      material.disablePass('outline');
      
    • Adds this material to a custom render pass, such as an OutlinePass, as naming it true in the passes option does

      Meshes using the material draw in the pass from the next render, except one that left it through Mesh3D#disablePass. BatchedMesh3D slots take their passes from BatchedMesh3D#setPassesAt instead. Overrides for the pass can only be given at construction, and the passes option has the limit on custom pass names.

      Parameters

      • name: string

        The pass's name, such as 'outline'.

      Returns void

      view.passes = [new OutlinePass()];
      material.enablePass('outline');
    • Advanced

      Returns the shader descriptor for the material's main pass

      The base returns the descriptor the material was built with. Override it when the bits depend on the material's settings or on the view, and call _invalidateDescriptor when a setting changes the answer. Bits that declare material fields or textures must also go through _deriveLayoutFromBits in the constructor.

      Parameters

      • _view: View3D

        The view about to render this material.

      Returns ShaderDescriptor

      class MyWaterMaterial extends BaseMaterial3D {
      private readonly _plain = new ShaderDescriptor({ bitsGpu: [waterBit], bitsGl: [waterBit] });
      private readonly _reflective = new ShaderDescriptor({
      bitsGpu: [waterBit, skyReflectionBit],
      bitsGl: [waterBit, skyReflectionBit],
      });

      constructor() {
      super();
      this._deriveLayoutFromBits([waterBit]);
      }

      public override getPrimaryDescriptor(view: View3D): ShaderDescriptor {
      return view.environment ? this._reflective : this._plain;
      }
      }
    • Frees the GPU memory of this material's textures until a frame draws them again

      The next frame that draws the material re-uploads them from their CPU copies, so it keeps drawing the same. Texture sources are shared, so every other material using one re-uploads it too, as with texture.source.unload(). The engine's default textures are skipped, and so are render textures and ContainerTextures, whose pixels exist only on the GPU.

      Call BatchedMesh3D#invalidate for each batch slot that draws the material. Does nothing once the material is destroyed.

      Returns void

      view.root.removeChild(model);
      material.unload(); // hand the texture memory back until the model is shown again

      Model3D.unload For giving back a whole model's geometry and textures at once.

    • Queues this material's data for upload

      Property setters and data field assignments call it for you. Call it yourself after writing into a data vector in place. Calls in one frame upload once.

      Returns void

      const scroll = material.data.scrollOffset;
      scroll[0] += 0.01;
      material.update();