While developers can adjust custom material properties in the editor, they can also specify callback behaviors for data changes using the UIScript
directive. By leveraging hook functions exposed by UIScript, developers can implement property interdependencies, reducing the number of declared properties and simplifying the Inspector panel.
UIScript
in ShaderLab:Editor {
...
UIScript "/path/to/script";
...
}
The bound UIScript
script path supports both relative and absolute paths. Using the project root directory as an example, the absolute path is /PBRScript1.ts
and the relative path is ./PBRScript1.ts
.
The editor exposes relevant APIs through the built-in ShaderUIScript
class. The type definition of ShaderUIScript
is embedded in the Galacean Web editor, with the complete definition as follows:
import { Color, Material, Texture, Vector2, Vector3, Vector4 } from "@galacean/engine";
type ShaderPropertyValue = number | Vector2 | Vector3 | Vector4 | Color | Texture;
type ShaderMacroValue = number | Vector2 | Vector3 | Vector4 | Color;
/**
* Script for extending `Shader` UI logic.
*/
export abstract class ShaderUIScript {
/** @internal */
_propertyCallBacks: Map<string, (material: Material, value: ShaderPropertyValue) => void> = new Map();
/** @internal */
_macroCallBacks: Map<string, (material: Material, defined: boolean, value: ShaderMacroValue) => void> = new Map();
/**
* Called when the shader is switched.
* @param material - The material the shader is bound to
*/
onMaterialShaderSwitched(material: Material): void {}
/**
* Registers a property change callback.
* @param propertyName - Property name
* @param onChanged - Callback triggered when the property changes
*/
protected onPropertyChanged(
propertyName: string,
onChanged: (material: Material, value: ShaderPropertyValue) => void
): void {
this._propertyCallBacks.set(propertyName, onChanged);
}
/**
* Registers a macro change callback.
* @param macroName - Macro name
* @param onChanged - Callback triggered when the macro changes
*/
protected onMacroChanged(
macroName: string,
onChanged: (material: Material, defined: boolean, value: ShaderMacroValue) => void
): void {
this._macroCallBacks.set(macroName, onChanged);
}
}
ShaderUIScript
class.import { Material, RenderQueueType, Vector3, BlendFactor, RenderFace, CullMode, BlendMode } from "@galacean/engine";
export default class extends ShaderUIScript {
constructor() {
super();
// Register property change listeners in the constructor
this.onPropertyChanged("material_NormalTexture", (material: Material, value) => {
const shaderData = material.shaderData;
if (value) {
shaderData.enableMacro("MATERIAL_HAS_NORMALTEXTURE");
} else {
shaderData.disableMacro("MATERIAL_HAS_NORMALTEXTURE");
}
});
}
// Override shader binding callback
override onMaterialShaderSwitched(material: Material) {
const shaderData = material.shaderData;
shaderData.disableMacro("MATERIAL_OMIT_NORMAL");
shaderData.enableMacro("MATERIAL_NEED_WORLD_POS");
shaderData.enableMacro("MATERIAL_NEED_TILING_OFFSET");
// Set default values
const anisotropyInfo = shaderData.getVector3("material_AnisotropyInfo");
if (!anisotropyInfo) {
shaderData.setVector3("material_AnisotropyInfo", new Vector3(1, 0, 0));
} else {
shaderData.setFloat("anisotropy", anisotropyInfo.z);
const PI2 = Math.PI * 2;
const rotationRad = (Math.atan2(anisotropyInfo.y, anisotropyInfo.x) + PI2 ) % PI2;
shaderData.setFloat("anisotropyRotation", rotationRad * (180 / Math.PI))
}
}
}
Note: The current version of ShaderLab's material property module only defines the Inspector UI panel for materials bound to this shader in the editor. It does not automatically declare corresponding global variables in ShaderPass
. If these variables are referenced in ShaderPass
code, you must explicitly declare them in the global variables module.