Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NME: Fix and improve the HeightToNormal block #13171

Merged
merged 4 commits into from
Oct 26, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import type { NodeMaterialBuildState } from "../../nodeMaterialBuildState";
import type { NodeMaterialConnectionPoint } from "../../nodeMaterialBlockConnectionPoint";
import { NodeMaterialBlockTargets } from "../../Enums/nodeMaterialBlockTargets";
import { RegisterClass } from "../../../../Misc/typeStore";
import { editableInPropertyPage, PropertyTypeForEdition } from "../../nodeMaterialDecorator";
import type { Scene } from "../../../../scene";

/**
* Block used to convert a height vector to a normal
*/
Expand All @@ -16,14 +19,34 @@ export class HeightToNormalBlock extends NodeMaterialBlock {
super(name, NodeMaterialBlockTargets.Fragment);

this.registerInput("input", NodeMaterialBlockConnectionPointTypes.Float);
this.registerInput("position", NodeMaterialBlockConnectionPointTypes.Vector3);
this.registerInput("normal", NodeMaterialBlockConnectionPointTypes.Vector3);
this.registerInput("tangent", NodeMaterialBlockConnectionPointTypes.Vector3);
this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Vector3);
this.registerInput("worldPosition", NodeMaterialBlockConnectionPointTypes.Vector3);
this.registerInput("worldNormal", NodeMaterialBlockConnectionPointTypes.Vector3);
this.registerInput("worldTangent", NodeMaterialBlockConnectionPointTypes.Vector3, true);
this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Vector4);
this.registerOutput("xyz", NodeMaterialBlockConnectionPointTypes.Vector3);

this._inputs[3].acceptedConnectionPointTypes.push(NodeMaterialBlockConnectionPointTypes.Vector4);
}

/**
* Defines if the output should be generated in world or tangent space.
* Note that in tangent space the result is also scaled by 0.5 and offsetted by 0.5 so that it can directly be used as a PerturbNormal.normalMapColor input
*/
@editableInPropertyPage("Generate in world space instead of tangent space", PropertyTypeForEdition.Boolean, "PROPERTIES", { notifiers: { update: true } })
public generateInWorldSpace: boolean = false;

/**
* Defines that the worldNormal input will be normalized by the HeightToNormal block before being used
*/
@editableInPropertyPage("Force normalization for the worldNormal input", PropertyTypeForEdition.Boolean, "PROPERTIES", { notifiers: { update: true } })
public automaticNormalizationNormal: boolean = true;

/**
* Defines that the worldTangent input will be normalized by the HeightToNormal block before being used
*/
@editableInPropertyPage("Force normalization for the worldTangent input", PropertyTypeForEdition.Boolean, "PROPERTIES", { notifiers: { update: true } })
public automaticNormalizationTangent: boolean = true;

/**
* Gets the current class name
* @returns the class name
Expand All @@ -42,21 +65,21 @@ export class HeightToNormalBlock extends NodeMaterialBlock {
/**
* Gets the position component
*/
public get position(): NodeMaterialConnectionPoint {
public get worldPosition(): NodeMaterialConnectionPoint {
return this._inputs[1];
}

/**
* Gets the normal component
*/
public get normal(): NodeMaterialConnectionPoint {
public get worldNormal(): NodeMaterialConnectionPoint {
return this._inputs[2];
}

/**
* Gets the tangent component
*/
public get tangent(): NodeMaterialConnectionPoint {
public get worldTangent(): NodeMaterialConnectionPoint {
return this._inputs[3];
}

Expand All @@ -67,34 +90,94 @@ export class HeightToNormalBlock extends NodeMaterialBlock {
return this._outputs[0];
}

/**
* Gets the xyz component
*/
public get xyz(): NodeMaterialConnectionPoint {
return this._outputs[1];
}

protected _buildBlock(state: NodeMaterialBuildState) {
super._buildBlock(state);

const output = this._outputs[0];

const heightToNormal = `
vec3 heightToNormal(in float height, in vec3 position, in vec3 tangent, in vec3 normal) {
vec3 biTangent = cross(tangent, normal);
if (!this.generateInWorldSpace && !this.worldTangent.isConnected) {
console.error(`You must connect the 'worldTangent' input of the ${this.name} block!`);
}

const startCode = this.generateInWorldSpace
? ""
: `
vec3 biTangent = cross(normal, tangent);
mat3 TBN = mat3(tangent, biTangent, normal);
vec3 worlddX = dFdx(position * 100.0);
vec3 worlddY = dFdy(position * 100.0);
vec3 crossX = cross(normal, worlddX);
vec3 crossY = cross(normal, worlddY);
float d = abs(dot(crossY, worlddX));
vec3 inToNormal = vec3(((((height + dFdx(height)) - height) * crossY) + (((height + dFdy(height)) - height) * crossX)) * sign(d));
inToNormal.y *= -1.0;
vec3 result = normalize((d * normal) - inToNormal);
return TBN * result;
}`;
`;

const endCode = this.generateInWorldSpace
? ""
: `
result = TBN * result;
result = result * vec3(0.5) + vec3(0.5);
`;

const heightToNormal = `
vec4 heightToNormal(in float height, in vec3 position, in vec3 tangent, in vec3 normal) {
${startCode}
${this.automaticNormalizationTangent ? "tangent = normalize(tangent);" : ""}
${this.automaticNormalizationNormal ? "normal = normalize(normal);" : ""}
vec3 worlddX = dFdx(position);
vec3 worlddY = dFdy(position);
vec3 crossX = cross(normal, worlddX);
vec3 crossY = cross(normal, worlddY);
float d = abs(dot(crossY, worlddX));
vec3 inToNormal = vec3(((((height + dFdx(height)) - height) * crossY) + (((height + dFdy(height)) - height) * crossX)) * sign(d));
inToNormal.y *= -1.0;
vec3 result = normalize((d * normal) - inToNormal);
${endCode}
return vec4(result, 0.);
}`;

state._emitExtension("derivatives", "#extension GL_OES_standard_derivatives : enable");
state._emitFunction("heightToNormal", heightToNormal, "// heightToNormal");
state.compilationString +=
this._declareOutput(output, state) +
` = heightToNormal(${this.input.associatedVariableName}, ${this.position.associatedVariableName}, ${this.tangent.associatedVariableName}.xyz, ${this.normal.associatedVariableName});\r\n`;
` = heightToNormal(${this.input.associatedVariableName}, ${this.worldPosition.associatedVariableName}, ${
this.worldTangent.isConnected ? this.worldTangent.associatedVariableName : "vec3(0.)"
}.xyz, ${this.worldNormal.associatedVariableName});\r\n`;

if (this.xyz.hasEndpoints) {
state.compilationString += this._declareOutput(this.xyz, state) + ` = ${this.output.associatedVariableName}.xyz;\r\n`;
}

return this;
}

protected _dumpPropertiesCode() {
let codeString = super._dumpPropertiesCode();
codeString += `${this._codeVariableName}.generateInWorldSpace = ${this.generateInWorldSpace};\r\n`;
codeString += `${this._codeVariableName}.automaticNormalizationNormal = ${this.automaticNormalizationNormal};\r\n`;
codeString += `${this._codeVariableName}.automaticNormalizationTangent = ${this.automaticNormalizationTangent};\r\n`;

Popov72 marked this conversation as resolved.
Show resolved Hide resolved
return codeString;
}

public serialize(): any {
const serializationObject = super.serialize();

serializationObject.generateInWorldSpace = this.generateInWorldSpace;
serializationObject.automaticNormalizationNormal = this.automaticNormalizationNormal;
serializationObject.automaticNormalizationTangent = this.automaticNormalizationTangent;

return serializationObject;
}

public _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
super._deserialize(serializationObject, scene, rootUrl);

this.generateInWorldSpace = serializationObject.generateInWorldSpace;
this.automaticNormalizationNormal = serializationObject.automaticNormalizationNormal;
this.automaticNormalizationTangent = serializationObject.automaticNormalizationTangent;
}
}

RegisterClass("BABYLON.HeightToNormalBlock", HeightToNormalBlock);