Pārlūkot izejas kodu

NormalMapEditor and some improvements (#23447)

* add Size Attenuation support

* InvertEditor: cleanup

* NormalMapNode: add scaleNode support

* add NormalMapEditor

* WebGPU: add Material.emissiveNode support

* StandardMaterialEditor: add .emissive support

* webgpu_sandbox: add emissiveNode and alphaTestNode for tests

* fix input
sunag 3 gadi atpakaļ
vecāks
revīzija
e6e322df76

+ 7 - 0
examples/jsm/node-editor/NodeEditor.js

@@ -18,6 +18,7 @@ import { SliderEditor } from './inputs/SliderEditor.js';
 import { ColorEditor } from './inputs/ColorEditor.js';
 import { ColorEditor } from './inputs/ColorEditor.js';
 import { TextureEditor } from './inputs/TextureEditor.js';
 import { TextureEditor } from './inputs/TextureEditor.js';
 import { BlendEditor } from './display/BlendEditor.js';
 import { BlendEditor } from './display/BlendEditor.js';
+import { NormalMapEditor } from './display/NormalMapEditor.js';
 import { UVEditor } from './accessors/UVEditor.js';
 import { UVEditor } from './accessors/UVEditor.js';
 import { PositionEditor } from './accessors/PositionEditor.js';
 import { PositionEditor } from './accessors/PositionEditor.js';
 import { NormalEditor } from './accessors/NormalEditor.js';
 import { NormalEditor } from './accessors/NormalEditor.js';
@@ -104,6 +105,11 @@ export const NodeList = [
 				name: 'Blend',
 				name: 'Blend',
 				icon: 'layers-subtract',
 				icon: 'layers-subtract',
 				nodeClass: BlendEditor
 				nodeClass: BlendEditor
+			},
+			{
+				name: 'Normal Map',
+				icon: 'chart-line',
+				nodeClass: NormalMapEditor
 			}
 			}
 		]
 		]
 	},
 	},
@@ -250,6 +256,7 @@ export const ClassLib = {
 	ColorEditor,
 	ColorEditor,
 	TextureEditor,
 	TextureEditor,
 	BlendEditor,
 	BlendEditor,
+	NormalMapEditor,
 	UVEditor,
 	UVEditor,
 	PositionEditor,
 	PositionEditor,
 	NormalEditor,
 	NormalEditor,

+ 49 - 0
examples/jsm/node-editor/display/NormalMapEditor.js

@@ -0,0 +1,49 @@
+import { SelectInput, Element, LabelElement } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
+import { NormalMapNode, FloatNode } from '../../renderers/nodes/Nodes.js';
+import { TangentSpaceNormalMap, ObjectSpaceNormalMap } from 'three';
+
+const nullValue = new FloatNode( 0 ).setConst( true );
+
+export class NormalMapEditor extends BaseNode {
+
+	constructor() {
+
+		const node = new NormalMapNode( nullValue );
+
+		super( 'Normal Map', 3, node, 175 );
+
+		const source = new LabelElement( 'Source' ).setInput( 3 ).onConnect( () => {
+
+			node.node = source.getLinkedObject() || nullValue;
+
+			this.invalidate();
+
+		} );
+
+		const scale = new LabelElement( 'Scale' ).setInput( 3 ).onConnect( () => {
+
+			node.scaleNode = scale.getLinkedObject();
+
+			this.invalidate();
+
+		} );
+
+		const optionsField = new SelectInput( [
+			{ name: 'Tangent Space', value: TangentSpaceNormalMap },
+			{ name: 'Object Space', value: ObjectSpaceNormalMap }
+		], TangentSpaceNormalMap ).onChange( () => {
+
+			node.normalMapType = Number( optionsField.getValue() );
+
+			this.invalidate();
+
+		} );
+
+		this.add( new Element().add( optionsField ) )
+			.add( source )
+			.add( scale );
+
+	}
+
+}

+ 15 - 10
examples/jsm/node-editor/materials/PointsMaterialEditor.js

@@ -1,4 +1,4 @@
-import { ColorInput, SliderInput, LabelElement } from '../../libs/flow.module.js';
+import { ColorInput, ToggleInput, SliderInput, LabelElement } from '../../libs/flow.module.js';
 import { BaseNode } from '../core/BaseNode.js';
 import { BaseNode } from '../core/BaseNode.js';
 import { PointsNodeMaterial } from '../../renderers/nodes/Nodes.js';
 import { PointsNodeMaterial } from '../../renderers/nodes/Nodes.js';
 import * as THREE from 'three';
 import * as THREE from 'three';
@@ -7,12 +7,7 @@ export class PointsMaterialEditor extends BaseNode {
 
 
 	constructor() {
 	constructor() {
 
 
-		const material = new PointsNodeMaterial( {
-			depthWrite: false,
-			transparent: true,
-			sizeAttenuation: true,
-			blending: THREE.AdditiveBlending
-		} );
+		const material = new PointsNodeMaterial();
 
 
 		super( 'Points Material', 1, material );
 		super( 'Points Material', 1, material );
 
 
@@ -22,6 +17,7 @@ export class PointsMaterialEditor extends BaseNode {
 		const opacity = new LabelElement( 'opacity' ).setInput( 1 );
 		const opacity = new LabelElement( 'opacity' ).setInput( 1 );
 		const size = new LabelElement( 'size' ).setInput( 1 );
 		const size = new LabelElement( 'size' ).setInput( 1 );
 		const position = new LabelElement( 'position' ).setInput( 3 );
 		const position = new LabelElement( 'position' ).setInput( 3 );
+		const sizeAttenuation = new LabelElement( 'Size Attenuation' );
 
 
 		color.add( new ColorInput( material.color.getHex() ).onChange( ( input ) => {
 		color.add( new ColorInput( material.color.getHex() ).onChange( ( input ) => {
 
 
@@ -37,20 +33,29 @@ export class PointsMaterialEditor extends BaseNode {
 
 
 		} ) );
 		} ) );
 
 
+		sizeAttenuation.add( new ToggleInput( material.sizeAttenuation ).onClick( ( input ) => {
+
+			material.sizeAttenuation = input.getValue();
+			material.dispose();
+
+		} ) );
+
 		color.onConnect( () => this.update(), true );
 		color.onConnect( () => this.update(), true );
 		opacity.onConnect( () => this.update(), true );
 		opacity.onConnect( () => this.update(), true );
-		size.onConnect(() => this.update(), true );
-		position.onConnect(() => this.update(), true );
+		size.onConnect( () => this.update(), true );
+		position.onConnect( () => this.update(), true );
 
 
 		this.add( color )
 		this.add( color )
 			.add( opacity )
 			.add( opacity )
 			.add( size )
 			.add( size )
-			.add( position );
+			.add( position )
+			.add( sizeAttenuation );
 
 
 		this.color = color;
 		this.color = color;
 		this.opacity = opacity;
 		this.opacity = opacity;
 		this.size = size;
 		this.size = size;
 		this.position = position;
 		this.position = position;
+		this.sizeAttenuation = sizeAttenuation;
 
 
 		this.material = material;
 		this.material = material;
 
 

+ 14 - 4
examples/jsm/node-editor/materials/StandardMaterialEditor.js

@@ -17,6 +17,8 @@ export class StandardMaterialEditor extends BaseNode {
 		const opacity = new LabelElement( 'opacity' ).setInput( 1 );
 		const opacity = new LabelElement( 'opacity' ).setInput( 1 );
 		const metalness = new LabelElement( 'metalness' ).setInput( 1 );
 		const metalness = new LabelElement( 'metalness' ).setInput( 1 );
 		const roughness = new LabelElement( 'roughness' ).setInput( 1 );
 		const roughness = new LabelElement( 'roughness' ).setInput( 1 );
+		const emissive = new LabelElement( 'emissive' ).setInput( 3 );
+		const normal = new LabelElement( 'normal' ).setInput( 3 );
 		const position = new LabelElement( 'position' ).setInput( 3 );
 		const position = new LabelElement( 'position' ).setInput( 3 );
 
 
 		color.add( new ColorInput( material.color.getHex() ).onChange( ( input ) => {
 		color.add( new ColorInput( material.color.getHex() ).onChange( ( input ) => {
@@ -49,18 +51,24 @@ export class StandardMaterialEditor extends BaseNode {
 		opacity.onConnect( () => this.update(), true );
 		opacity.onConnect( () => this.update(), true );
 		metalness.onConnect( () => this.update(), true );
 		metalness.onConnect( () => this.update(), true );
 		roughness.onConnect( () => this.update(), true );
 		roughness.onConnect( () => this.update(), true );
-		position.onConnect(() => this.update(), true );
+		emissive.onConnect( () => this.update(), true );
+		normal.onConnect( () => this.update(), true );
+		position.onConnect( () => this.update(), true );
 
 
 		this.add( color )
 		this.add( color )
 			.add( opacity )
 			.add( opacity )
 			.add( metalness )
 			.add( metalness )
 			.add( roughness )
 			.add( roughness )
+			.add( emissive )
+			.add( normal )
 			.add( position );
 			.add( position );
 
 
 		this.color = color;
 		this.color = color;
 		this.opacity = opacity;
 		this.opacity = opacity;
 		this.metalness = metalness;
 		this.metalness = metalness;
 		this.roughness = roughness;
 		this.roughness = roughness;
+		this.emissive = emissive;
+		this.normal = normal;
 		this.position = position;
 		this.position = position;
 
 
 		this.material = material;
 		this.material = material;
@@ -71,7 +79,7 @@ export class StandardMaterialEditor extends BaseNode {
 
 
 	update() {
 	update() {
 
 
-		const { material, color, opacity, roughness, metalness, position } = this;
+		const { material, color, opacity, emissive, roughness, metalness, normal, position } = this;
 
 
 		color.setEnabledInputs( ! color.getLinkedObject() );
 		color.setEnabledInputs( ! color.getLinkedObject() );
 		opacity.setEnabledInputs( ! opacity.getLinkedObject() );
 		opacity.setEnabledInputs( ! opacity.getLinkedObject() );
@@ -79,11 +87,13 @@ export class StandardMaterialEditor extends BaseNode {
 		metalness.setEnabledInputs( ! metalness.getLinkedObject() );
 		metalness.setEnabledInputs( ! metalness.getLinkedObject() );
 
 
 		material.colorNode = color.getLinkedObject();
 		material.colorNode = color.getLinkedObject();
-		material.opacityNode = opacity.getLinkedObject() || null;
+		material.opacityNode = opacity.getLinkedObject();
 		material.metalnessNode = metalness.getLinkedObject();
 		material.metalnessNode = metalness.getLinkedObject();
 		material.roughnessNode = roughness.getLinkedObject();
 		material.roughnessNode = roughness.getLinkedObject();
+		material.emissiveNode = emissive.getLinkedObject();
+		material.normalNode = normal.getLinkedObject();
 
 
-		material.positionNode = position.getLinkedObject() || null;
+		material.positionNode = position.getLinkedObject();
 
 
 		material.dispose();
 		material.dispose();
 
 

+ 2 - 2
examples/jsm/node-editor/math/InvertEditor.js

@@ -10,12 +10,12 @@ export class InvertEditor extends BaseNode {
 
 
 		const node = new MathNode( MathNode.INVERT, DEFAULT_VALUE );
 		const node = new MathNode( MathNode.INVERT, DEFAULT_VALUE );
 
 
-		super( 'Invert / Negate', 1, node );
+		super( 'Invert / Negate', 1, node, 175 );
 
 
 		const optionsField = new SelectInput( [
 		const optionsField = new SelectInput( [
 			{ name: 'Invert ( 1 - Source )', value: MathNode.INVERT },
 			{ name: 'Invert ( 1 - Source )', value: MathNode.INVERT },
 			{ name: 'Negate ( - Source )', value: MathNode.NEGATE }
 			{ name: 'Negate ( - Source )', value: MathNode.NEGATE }
-		] ).onChange( () => {
+		], MathNode.INVERT ).onChange( () => {
 
 
 			node.method = optionsField.getValue();
 			node.method = optionsField.getValue();
 
 

+ 13 - 3
examples/jsm/renderers/nodes/display/NormalMapNode.js

@@ -6,6 +6,8 @@ import OperatorNode from '../math/OperatorNode.js';
 import FloatNode from '../inputs/FloatNode.js';
 import FloatNode from '../inputs/FloatNode.js';
 import TempNode from '../core/TempNode.js';
 import TempNode from '../core/TempNode.js';
 import ModelNode from '../accessors/ModelNode.js';
 import ModelNode from '../accessors/ModelNode.js';
+import SplitNode from '../utils/SplitNode.js';
+import JoinNode from '../utils/JoinNode.js';
 import { ShaderNode, cond, add, mul, dFdx, dFdy, cross, max, dot, normalize, inversesqrt, equal } from '../ShaderNode.js';
 import { ShaderNode, cond, add, mul, dFdx, dFdy, cross, max, dot, normalize, inversesqrt, equal } from '../ShaderNode.js';
 
 
 import { TangentSpaceNormalMap, ObjectSpaceNormalMap } from 'three';
 import { TangentSpaceNormalMap, ObjectSpaceNormalMap } from 'three';
@@ -39,11 +41,12 @@ const perturbNormal2ArbNode = new ShaderNode( ( inputs ) => {
 
 
 class NormalMapNode extends TempNode {
 class NormalMapNode extends TempNode {
 
 
-	constructor( node ) {
+	constructor( node, scaleNode = null ) {
 
 
 		super( 'vec3' );
 		super( 'vec3' );
 
 
 		this.node = node;
 		this.node = node;
+		this.scaleNode = scaleNode;
 
 
 		this.normalMapType = TangentSpaceNormalMap;
 		this.normalMapType = TangentSpaceNormalMap;
 
 
@@ -53,10 +56,17 @@ class NormalMapNode extends TempNode {
 
 
 		const type = this.getNodeType( builder );
 		const type = this.getNodeType( builder );
 
 
-		const normalMapType = this.normalMapType;
+		const { normalMapType, scaleNode } = this;
 
 
 		const normalOP = new OperatorNode( '*', this.node, new FloatNode( 2.0 ).setConst( true ) );
 		const normalOP = new OperatorNode( '*', this.node, new FloatNode( 2.0 ).setConst( true ) );
-		const normalMap = new OperatorNode( '-', normalOP, new FloatNode( 1.0 ).setConst( true ) );
+		let normalMap = new OperatorNode( '-', normalOP, new FloatNode( 1.0 ).setConst( true ) );
+
+		if ( scaleNode !== null ) {
+
+			const normalMapScale = new OperatorNode( '*', new SplitNode( normalMap, 'xy'), scaleNode );
+			normalMap = new JoinNode( [ normalMapScale, new SplitNode( normalMap, 'z' ) ] );
+
+		}
 
 
 		if ( normalMapType === ObjectSpaceNormalMap ) {
 		if ( normalMapType === ObjectSpaceNormalMap ) {
 
 

+ 15 - 5
examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js

@@ -25,7 +25,7 @@ import ColorSpaceNode from '../../nodes/display/ColorSpaceNode.js';
 import LightContextNode from '../../nodes/lights/LightContextNode.js';
 import LightContextNode from '../../nodes/lights/LightContextNode.js';
 import OperatorNode from '../../nodes/math/OperatorNode.js';
 import OperatorNode from '../../nodes/math/OperatorNode.js';
 import WGSLNodeParser from '../../nodes/parsers/WGSLNodeParser.js';
 import WGSLNodeParser from '../../nodes/parsers/WGSLNodeParser.js';
-import { join, nodeObject } from '../../nodes/ShaderNode.js';
+import { add, join, nodeObject } from '../../nodes/ShaderNode.js';
 import { getRoughness } from '../../nodes/functions/PhysicalMaterialFunctions.js';
 import { getRoughness } from '../../nodes/functions/PhysicalMaterialFunctions.js';
 
 
 const wgslTypeLib = {
 const wgslTypeLib = {
@@ -264,13 +264,23 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 
 			}
 			}
 
 
-			// RESULT
+			// OUTGOING LIGHT
 
 
-			const outputNodeObj = nodeObject( outputNode );
+			let outgoingLightNode = nodeObject( outputNode ).xyz;
 
 
-			outputNode = join( outputNodeObj.x, outputNodeObj.y, outputNodeObj.z, nodeObject( diffuseColorNode ).w );
+			// EMISSIVE
 
 
-			//
+			const emissiveNode = material.emissiveNode;
+
+			if ( emissiveNode && emissiveNode.isNode ) {
+
+				outgoingLightNode = add( emissiveNode, outgoingLightNode );
+
+			}
+
+			outputNode = join( outgoingLightNode.xyz, nodeObject( diffuseColorNode ).w );
+
+			// OUTPUT
 
 
 			const outputEncoding = this.renderer.outputEncoding;
 			const outputEncoding = this.renderer.outputEncoding;
 
 

+ 2 - 0
examples/webgpu_sandbox.html

@@ -128,6 +128,8 @@
 
 
 				const materialCompressed = new Nodes.MeshBasicNodeMaterial();
 				const materialCompressed = new Nodes.MeshBasicNodeMaterial();
 				materialCompressed.colorNode = new Nodes.TextureNode( dxt5Texture );
 				materialCompressed.colorNode = new Nodes.TextureNode( dxt5Texture );
+				materialCompressed.emissiveNode = new Nodes.ColorNode( new THREE.Color( 0x663300 ) );
+				materialCompressed.alphaTestNode = new Nodes.OscNode();
 				materialCompressed.transparent = true;
 				materialCompressed.transparent = true;
 
 
 				const boxCompressed = new THREE.Mesh( geometryBox, materialCompressed );
 				const boxCompressed = new THREE.Mesh( geometryBox, materialCompressed );