You don’t need to have {} around the parameter names. The variables you need to pass to the function call are all the input.associatedVariableName
and all the output.associatedVariableName
: those are generated by the system and you don’t need to tamper with.
I think it’s easier to see what’s going on with real code. I modified the CustomBlock
file to make it work with this .json:
{
"name": "Multiply",
"comments": "Multiplies the left and right inputs of the same type together",
"target": "Neutral",
"inParameters": [
{
"name": "left",
"type": "AutoDetect"
},
{
"name": "right",
"type": "AutoDetect"
}
],
"outParameters": [
{
"name": "output",
"type": "BasedOnInput",
"typeFromInput": "left"
}
],
"inLinkedConnectionTypes" : [
{
"input1": "left",
"input2": "right",
"looseCoupling": false
}
],
"functionName": "multiply_{TYPE_left}",
"code": [
"{TYPE_output} myHelper_{TYPE_left}({TYPE_left} l, {TYPE_right} r) { return l * r; }",
"void multiply_{TYPE_left}({TYPE_left} l, {TYPE_right} r, out {TYPE_output} result) {",
" result = myHelper_{TYPE_left}(l, r);",
"}"
]
}
CustomBlock
class:
import { NodeMaterialBlock } from '../nodeMaterialBlock';
import { NodeMaterialBlockConnectionPointTypes } from '../Enums/nodeMaterialBlockConnectionPointTypes';
import { NodeMaterialBuildState } from '../nodeMaterialBuildState';
import { NodeMaterialBlockTargets } from '../Enums/nodeMaterialBlockTargets';
import { _TypeStore } from '../../../Misc/typeStore';
import { Scene } from '../../../scene';
import { NodeMaterialConnectionPoint } from "..";
import { Nullable } from "../../../types";
/**
* Custom block created from user-defined json
*/
export class CustomBlock extends NodeMaterialBlock {
private _compilationString: string;
private _options: any;
/**
* Creates a new CustomBlock
* @param options defines the options used to create the block
*/
public constructor(options: any) {
super("emptyCustomBlock");
if (options) {
this._deserializeCustomBlock(options);
}
}
/**
* Gets the current class name
* @returns the class name
*/
public getClassName() {
return "CustomBlock";
}
/**
* Builds the block's compilaton string
* @returns the current block
*/
protected _buildBlock(state: NodeMaterialBuildState) {
super._buildBlock(state);
let code = this._compilationString;
let functionName = this._options.functionName;
// Replace the TYPE_XXX placeholders (if any)
this._inputs.forEach((input) => {
const rexp = new RegExp("\\{TYPE_" + input.name + "\\}", "gm");
const type = state._getGLType(input.type);
code = code.replace(rexp, type);
functionName = functionName.replace(rexp, type);
});
this._outputs.forEach((output) => {
const rexp = new RegExp("\\{TYPE_" + output.name + "\\}", "gm");
const type = state._getGLType(output.type);
code = code.replace(rexp, type);
functionName = functionName.replace(rexp, type);
});
state._emitFunction(functionName, code, "");
// Declare the output variables
this._outputs.forEach((output) => {
state.compilationString += this._declareOutput(output, state) + ";\r\n";
});
// Generate the function call
state.compilationString += functionName + "(";
let hasComma = false;
this._inputs.forEach((input, index) => {
if (index > 0) {
state.compilationString += ", ";
hasComma = true;
}
state.compilationString += input.associatedVariableName;
});
this._outputs.forEach((output, index) => {
if (index > 0 || hasComma) {
state.compilationString += ", ";
}
state.compilationString += output.associatedVariableName;
});
state.compilationString += ");\r\n";
return this;
}
/**
* Serializes this block in a JSON representation
* @returns the serialized block object
*/
public serialize(): any {
let serializationObject = super.serialize();
serializationObject.options = this._options;
return serializationObject;
}
/**
* Deserializes this block from a JSON representation
* @hidden
*/
public _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
this._deserializeCustomBlock(serializationObject.options);
super._deserialize(serializationObject, scene, rootUrl);
}
/**
* Deserializes this block from a user-defined JSON representation
* @param options defines the options used to create the block
*/
private _deserializeCustomBlock(options: any) {
this._options = options;
this._compilationString = options.code.join("\r\n") + "\r\n";
this.name = options.name;
this.target = (<any> NodeMaterialBlockTargets)[options.target];
options.inParameters?.forEach((input: any) => {
const type = (<any> NodeMaterialBlockConnectionPointTypes)[input.type];
this.registerInput(input.name, type);
});
options.outParameters?.forEach((output: any, index: number) => {
this.registerOutput(output.name, (<any> NodeMaterialBlockConnectionPointTypes)[output.type]);
if (output.type === "BasedOnInput") {
this._outputs[index]._typeConnectionSource = this.findInputByName(output.typeFromInput)![0];
}
});
options.inLinkedConnectionTypes?.forEach((connection: any, index: number) => {
this._linkConnectionTypes(this.findInputByName(connection.input1)![1], this.findInputByName(connection.input2)![1]);
});
}
/**
* Finds the desired input by name
* @param name defines the name to search for
* @returns the input if found, otherwise returns null
*/
public findInputByName(name: string): Nullable<[NodeMaterialConnectionPoint, number]> {
if (!name) {
return null;
}
for (var i = 0; i < this._inputs.length; i++) {
if (this._inputs[i].name === name) {
return [this._inputs[i], i];
}
}
return null;
}
}
_TypeStore.RegisteredTypes["BABYLON.CustomBlock"] = CustomBlock;
With those modifications, this NM:
compiles to:
uniform vec4 u_color;
vec4 myHelper_vec4(vec4 l, vec4 r) { return l * r; }
void multiply_vec4(vec4 l, vec4 r, out vec4 result) {
result = myHelper_vec4(l, r);
}
void main(void) {
//Multiply
vec4 output2;
multiply_vec4(u_color, u_color, output2);
}
(I have only kept the relevant bits)
If adding another multiply block that is using a different type from vec4:
which compiles to (relevant bits only):
uniform vec4 u_color;
uniform float u_Float;
vec4 myHelper_vec4(vec4 l, vec4 r) { return l * r; }
void multiply_vec4(vec4 l, vec4 r, out vec4 result) {
result = myHelper_vec4(l, r);
}
float myHelper_float(float l, float r) { return l * r; }
void multiply_float(float l, float r, out float result) {
result = myHelper_float(l, r);
}
void main(void) {
//Multiply
vec4 output2;
multiply_vec4(u_color, u_color, output2);
//Multiply
float output3;
multiply_float(u_Float, u_Float, output3);
}
This example is a bit complicated by the fact we allow any type for the inputs, so we need to parameterize the glsl code with this type.
Regarding the worley shader, you should have no problems to make it work with this CustomBlock
.