@luma.gl/engine
Version:
3D Engine Components for luma.gl
111 lines • 4.31 kB
JavaScript
// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { ShaderInputs } from "../shader-inputs.js";
import { Material as MaterialClass } from "./material.js";
/** Logical bind-group slot reserved for material-owned bindings. */
export const MATERIAL_BIND_GROUP = 3;
/**
* Creates typed {@link Material} instances for a stable material binding schema.
*
* @example
* ```ts
* const pbrFactory = new MaterialFactory<
* {pbrMaterial: PBRMaterialUniforms},
* PBRMaterialBindings
* >(device, {modules: [pbrMaterial]});
* const pbr = pbrFactory.createMaterial();
* pbr.setProps({pbrMaterial: {baseColorFactor: [1, 0, 0, 1]}});
* const pbrVariant = pbr.clone({bindings: {pbr_baseColorSampler: texture}});
* ```
*
* @example
* ```ts
* const phongFactory = new MaterialFactory<
* {phongMaterial: PhongMaterialProps},
* {}
* >(device, {modules: [phongMaterial]});
* const phong = phongFactory.createMaterial();
* phong.setProps({phongMaterial: {ambient: 0.4, diffuse: 0.7}});
* const phongVariant = phong.clone({moduleProps: {phongMaterial: {shininess: 64}}});
* ```
*
* @example
* ```ts
* const gouraudFactory = new MaterialFactory<
* {gouraudMaterial: GouraudMaterialProps},
* {}
* >(device, {modules: [gouraudMaterial]});
* const gouraud = gouraudFactory.createMaterial();
* gouraud.setProps({gouraudMaterial: {ambient: 0.25}});
* const gouraudVariant = gouraud.clone({moduleProps: {gouraudMaterial: {diffuse: 0.8}}});
* ```
*/
export class MaterialFactory {
/** Device that creates materials for this schema. */
device;
/** Shader modules that define the material schema. */
modules;
_materialBindingNames;
_materialModuleNames;
constructor(device, props = {}) {
this.device = device;
this.modules = props.modules || [];
const shaderInputs = new ShaderInputs(Object.fromEntries(this.modules.map(module => [module.name, module])));
this._materialBindingNames = getMaterialBindingNames(shaderInputs);
this._materialModuleNames = getMaterialModuleNames(shaderInputs);
}
/** Creates one typed material instance for this factory's schema. */
createMaterial(props = {}) {
return new MaterialClass(this.device, {
...props,
factory: this
});
}
/** Returns the logical material-owned resource binding names. */
getBindingNames() {
return Array.from(this._materialBindingNames);
}
/** Returns `true` when the supplied binding belongs to this material schema. */
ownsBinding(bindingName) {
if (this._materialBindingNames.has(bindingName)) {
return true;
}
const aliasedModuleName = getModuleNameFromUniformBinding(bindingName);
return aliasedModuleName ? this._materialModuleNames.has(aliasedModuleName) : false;
}
/** Returns `true` when the supplied shader module is owned by this material schema. */
ownsModule(moduleName) {
return this._materialModuleNames.has(moduleName);
}
/** Packages resolved material bindings into bind group `3`. */
getBindingsByGroup(bindings) {
return Object.keys(bindings).length > 0 ? { [MATERIAL_BIND_GROUP]: bindings } : {};
}
}
/** Returns the module name corresponding to an auto-generated `*Uniforms` binding. */
export function getModuleNameFromUniformBinding(bindingName) {
return bindingName.endsWith('Uniforms') ? bindingName.slice(0, -'Uniforms'.length) : null;
}
function getMaterialBindingNames(shaderInputs) {
const bindingNames = new Set();
for (const module of Object.values(shaderInputs.modules)) {
for (const binding of module.bindingLayout || []) {
if (binding.group === MATERIAL_BIND_GROUP) {
bindingNames.add(binding.name);
}
}
}
return bindingNames;
}
function getMaterialModuleNames(shaderInputs) {
const moduleNames = new Set();
for (const module of Object.values(shaderInputs.modules)) {
if (module.name &&
module.bindingLayout?.some(binding => binding.group === MATERIAL_BIND_GROUP && binding.name === module.name)) {
moduleNames.add(module.name);
}
}
return moduleNames;
}
//# sourceMappingURL=material-factory.js.map