@pixi/3d
    Preparing search index...

    Type Alias ShaderBit<VERTEX_TAGS, FRAGMENT_TAGS, BIND_GROUPS>Advanced

    One piece of a shader: the attributes, varyings, bindings and code it adds to a template

    A ShaderDescriptor lists the bits, and the compiler merges them into one program per backend, filling each template slot with the bits' code in list order.

    The type parameters narrow the slot and group names a bit may use:

    • VERTEX_TAGS: the vertex slots, such as ['input', 'beforeTransform', 'afterTransform', 'end']
    • FRAGMENT_TAGS: the fragment slots, such as ['material', 'lighting', 'end']
    • BIND_GROUPS: the bind group names, such as ['global', 'camera', 'material']
    const colorBit: ShaderBit = {
    name: "color",
    attributes: {
    aPosition: "float32x3",
    aColor: "float32x4",
    },
    interStage: {
    vColor: "vec4<f32>",
    },
    bindings: {
    global: { uTime: "f32" },
    material: { uColor: "vec4<f32>" },
    },
    gpu: {
    vertex: { input: "output.vColor = input.aColor;" },
    fragment: { material: "albedo *= input.vColor.rgb * material.uColor.rgb;" },
    },
    gl: {
    vertex: { input: "vColor = aColor;" },
    fragment: { material: "albedo *= vColor.rgb * material.uColor.rgb;" },
    },
    };
    type ShaderBit<
        VERTEX_TAGS extends readonly string[] = readonly string[],
        FRAGMENT_TAGS extends readonly string[] = readonly string[],
        BIND_GROUPS extends readonly string[] = readonly string[],
    > = {
        attributes?: Record<string, VertexFormat>;
        bindings?: Partial<Record<BIND_GROUPS[number], BindingGroup>>;
        gl?: {
            fragment?: Partial<Record<FRAGMENT_TAGS[number], string>>;
            header?: string;
            vertex?: Partial<Record<VERTEX_TAGS[number], string>>;
        };
        gpu?: {
            fragment?: Partial<Record<FRAGMENT_TAGS[number], string>>;
            header?: string;
            vertex?: Partial<Record<VERTEX_TAGS[number], string>>;
        };
        interStage?: InterStage;
        name: string;
        outputs?: Record<string, FragmentOutputType>;
        overrideDefs?: Record<string, ShaderOverrideDef>;
        requires?: ShaderBit[];
        structs?: Record<string, StructDefinition>;
        textures?: string[];
    }

    Type Parameters

    • VERTEX_TAGS extends readonly string[] = readonly string[]
    • FRAGMENT_TAGS extends readonly string[] = readonly string[]
    • BIND_GROUPS extends readonly string[] = readonly string[]
    Index
    attributes?: Record<string, VertexFormat>

    The vertex attributes this bit reads; a name redeclared with another format warns, and the first wins

    bindings?: Partial<Record<BIND_GROUPS[number], BindingGroup>>

    The bindings this bit adds, keyed by bind group name

    Fields in the material group are per-material values, read as material.<name> in every stage slot but the vertex input slot, which WebGL runs before declaring material. A header helper can't see material, so pass it the values as arguments. A field is f32, i32, u32, mat2x2<f32> or a vec2, vec3 or vec4 of f32, and other value types throw. Texture and sampler types there are dropped; declare textures in textures. A material can have any number of fields, but a write to one uploads them all, and a draw that merges materials reads all of them in each stage, one texel per 16 bytes.

    Fields in the instance group are the per-slot values of a BatchedMesh3D, read as instance.<name> in the same slots. Each must match a field of the batch's instanceData option by name and type. A custom template adds {{vertexStoreAccess}} and {{fragmentStoreAccess}} to its WGSL, the vertex one after it declares inst as the engine's template does, and {{storeAccess}} to each GLSL stage, after the vertex {{input}}. A pass's descriptor also lists instanceFetchBitGl in its bitsGl.

    Don't add fields of your own to global or camera, which the engine fills from fixed buffers, or the draw can fail. A material's shader always has the engine's fields there, so a bit reads them without declaring them, as camera.uCameraPosition in WGSL or uCameraPosition in GLSL.

    gl?: {
        fragment?: Partial<Record<FRAGMENT_TAGS[number], string>>;
        header?: string;
        vertex?: Partial<Record<VERTEX_TAGS[number], string>>;
    }

    The GLSL code for WebGL; ShaderBitTemplate says which slot each key fills

    Type Declaration

    • Optionalfragment?: Partial<Record<FRAGMENT_TAGS[number], string>>

      Code for the fragment stage's slots, keyed by slot name

    • Optionalheader?: string

      Code for the {{header}} slot of both stages' templates, such as functions and constants

    • Optionalvertex?: Partial<Record<VERTEX_TAGS[number], string>>

      Code for the vertex stage's slots, keyed by slot name

    gpu?: {
        fragment?: Partial<Record<FRAGMENT_TAGS[number], string>>;
        header?: string;
        vertex?: Partial<Record<VERTEX_TAGS[number], string>>;
    }

    The WGSL code for WebGPU; ShaderBitTemplate says which slot each key fills

    The compiler doesn't check that a textureSample, dpdx or dpdy sits in uniform control flow, so keep it there yourself. A branch on a material value is uniform. Under a branch on a per-fragment value, the derivative, and so the mip level, is undefined.

    Type Declaration

    • Optionalfragment?: Partial<Record<FRAGMENT_TAGS[number], string>>

      Code for the fragment stage's slots, keyed by slot name

    • Optionalheader?: string

      Code for the template's {{header}} slot, such as functions and constants

    • Optionalvertex?: Partial<Record<VERTEX_TAGS[number], string>>

      Code for the vertex stage's slots, keyed by slot name

    interStage?: InterStage

    The varyings this bit declares; a name redeclared with another type warns, and the first wins

    name: string

    A unique name; shaders are cached by their bits' names, so two different bits with one name throw

    outputs?: Record<string, FragmentOutputType>

    The fragment outputs this bit declares, keyed by name, such as { normal: 'vec4<f32>' }

    The fragment stage writes one as outputs.normal, and every field starts at zero. ShaderDescriptorOptions#outputLocations says which output each location writes, and an output with no location is dropped, so a bit can write one without knowing which passes use it. In a view with scene outputs, a material on the engine's template can write any SceneOutput by its name.

    The engine's template declares color as vec4<f32> and fills it from finalColor and alpha after the end slot, so set those rather than outputs.color. Two bits that declare one name with different types throw. A custom template needs the output slots: in WGSL {{fragmentDeclareOutputs}} and {{fragmentWriteOutputs}}, returning FragmentOutput, and in the GLSL fragment template {{declareOutputs}} and {{writeOutputs}}.

    const depthOutputBit: ShaderBit = {
    name: 'myDepthOutput',
    outputs: { depth: 'f32' },
    gpu: { fragment: { end: 'outputs.depth = input.position.z;' } },
    gl: { fragment: { end: 'outputs.depth = gl_FragCoord.z;' } },
    };
    overrideDefs?: Record<string, ShaderOverrideDef>

    Specialization constants, keyed by name; see ShaderOverrideType

    requires?: ShaderBit[]

    Other bits this bit needs in the shader, such as one that declares a helper function it calls

    The compiler adds each required bit once, just ahead of the first bit that requires it, so two bits can share a helper without declaring it twice. A material need not list them.

    A required bit should hold only shader code, such as helpers and constants: one the material doesn't list can't add texture slots, material fields or instance fields. Declare those on a listed bit, and list it ahead of any bit that requires it.

    const luminanceBit: ShaderBit = {
    name: 'luminance',
    gpu: { header: 'fn luminance(c: vec3<f32>) -> f32 { return dot(c, vec3<f32>(0.2126, 0.7152, 0.0722)); }' },
    gl: { fragment: { header: 'float luminance(vec3 c) { return dot(c, vec3(0.2126, 0.7152, 0.0722)); }' } },
    };

    const desaturateBit: ShaderBit = {
    name: 'desaturate',
    requires: [luminanceBit],
    gpu: { fragment: { material: 'albedo = vec3<f32>(luminance(albedo));' } },
    gl: { fragment: { material: 'albedo = vec3(luminance(albedo));' } },
    };
    structs?: Record<string, StructDefinition>

    WGSL structs this bit declares, keyed by struct name

    The first bit to declare a name wins and later ones are dropped without a warning, so bits that share a struct should import one definition.

    textures?: string[]

    The textures this bit samples, as camelCase names such as 'transmission'

    In a material's shader, each name gets two helpers in both languages, which BaseMaterial3DOptions#textures fills by name. sampleX(uv), such as sampleTransmission(uv), picks its mip level from the fragment's derivatives, so it works in the fragment stage only, and sampleXDirect(uv) reads mip 0 in either stage. Pixi uploads images with their alpha premultiplied by default, so divide a sample's rgb by its a, as in c.rgb / max(c.a, 0.001), to read the colour the image stores.

    const myBit: ShaderBit = {
    name: 'myFeature',
    textures: ['myCustomMap'],
    gpu: { fragment: { material: 'let color = sampleMyCustomMap(input.vUV);' } }
    }