Using and Binding UIScript

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.

Binding 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.

UIScript Interface

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);
  }
}

Writing UIScript

  1. Create a UIScript in the editor.
Creating UIScript
  1. Specify property change callbacks by extending the 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.

Was this page helpful?