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

NodeMaterial: ShaderNode #22603

Merged
merged 5 commits into from
Sep 30, 2021
Merged

NodeMaterial: ShaderNode #22603

merged 5 commits into from
Sep 30, 2021

Conversation

sunag
Copy link
Collaborator

@sunag sunag commented Sep 29, 2021

Introduction

If you need to create big shader code using NodeMaterial or only an extended Node that can translate to different outputs, this could be useful.

I could say it's a Three.JS Shading Language, here you can create shaders using the new NodeMaterial system with a language similar to the popular GLSL, and it can be converted to both GLSL and WGSL.

Syntax

TJSL / Checker example

const checker = new ShaderNode( ( uv ) => {

	uv = mul( uv, 2.0 );

	const cx = floor( uv.x );
	const cy = floor( uv.y );
	const result = mod( add( cx, cy ), 2.0 );

	return sign( result );

} );

WGSL ( for comparison purposes )

fn checker( inputUV:vec2<f32> ) -> f32 {

	var uv = inputUV * 2.0;

	var cx = floor( uv.x );
	var cy = floor( uv.y );
  
	var result = f32( i32( cx + cy ) % i32( 2.0 ) );

	return sign( result );

}

GLSL ( for comparison purposes )

float checker( vec2 uv ) {

	uv *= 2.0;

	float cx = floor( uv.x );
	float cy = floor( uv.y );
	float result = mod( cx + cy, 2.0 );

	return sign( result );

}

Inputs

const num = float( 1.0 );
const clr = color( 0x0099FF );
const v2 = vec2( 0, 0 );
const v3 = vec3( 0, 0, 0 );
const v4 = vec4( 0, 0, 0, 1 );

// declare an input as variable (uniform)
const myVar = uniform( v4 );
myVar.value; // accessing

Operators

const addition = add(  1, 1 );
const subtraction = sub( 1, 1 );
const multiply = mul( 1, 1 );
const division = div( 1, 1 );

Swizzle

// you can use GLSL syntax e.g: .xxx, .yyy, etc ( .rgba not yet )
const clr = color( 0x0099FF ).zyx; // inverse vector order

// create a custom vector
const customVector = join( clr.x, 0, clr.z );

Recommended hierarchy

image

Usage

import { ShaderNode, float, add, mul, floor, mod, sign } from 'nodes/ShaderNode.js';

const CheckerNode = new ShaderNode( ( uv ) => {

	uv = mul( uv, 2.0 );

	const cx = floor( uv.x );
	const cy = floor( uv.y );
	const result = mod( add( cx, cy ), 2.0 );

	return sign( result );

} );

const material = new Nodes.MeshStandardNodeMaterial();
material.colorNode = CheckerNode( new Nodes.UVNode() );

Related issues

#21322 (comment), #17105, #17321

This contribution is funded by Google via Igalia.

@mrdoob
Copy link
Owner

mrdoob commented Sep 29, 2021

Interesting! Could you add how would the WGSL output look like too?

@sunag
Copy link
Collaborator Author

sunag commented Sep 29, 2021

@mrdoob Yeah! The idea is use this syntax to generate both WGSL and GLSL. I need still finish some feature to generate WGSL but not needed change this code only the NodeBuilder and WebGPUNodeBuilder. I think that this syntax is more simple and hybrid in sense that it can generate different output and focus more in threejs features to make shaders if compared with WGSL.

@sunag
Copy link
Collaborator Author

sunag commented Sep 29, 2021

@mrdoob I edit the post and add WGSL for comparison purpose.

@mrdoob
Copy link
Owner

mrdoob commented Sep 29, 2021

Excellent!

Reading #17105 also helped a lot. I've already forgotten about that conversation...

My only feedback is that instead of tjsl() I would name it shader().

Edit: Or ShaderNode() maybe?

@sunag
Copy link
Collaborator Author

sunag commented Sep 30, 2021

I liked of ShaderNode. Updated :)

@mrdoob mrdoob added this to the r133 milestone Sep 30, 2021
@mrdoob mrdoob merged commit 90e2d69 into mrdoob:dev Sep 30, 2021
@mrdoob
Copy link
Owner

mrdoob commented Sep 30, 2021

Minor thing:

Instead of const CheckerNode = ShaderNode( ( uv ) => {} )...
Can it be const CheckerNode = new ShaderNode( ( uv ) => {} ) ?

@sunag sunag mentioned this pull request Sep 30, 2021
@sunag sunag deleted the dev-shaderlanguage branch September 30, 2021 14:50
@0b5vr 0b5vr mentioned this pull request Oct 1, 2021
23 tasks
@sunag sunag changed the title NodeMaterial: Three.JS Shader Language NodeMaterial: NodeShader Oct 1, 2021
@sunag sunag changed the title NodeMaterial: NodeShader NodeMaterial: ShaderNode Oct 1, 2021
@sunag
Copy link
Collaborator Author

sunag commented Oct 1, 2021

@mrdoob About the WIP: It is WGSL generate by NodeMaterial

image

NODE MATERIAL

const material = new Nodes.MeshBasicNodeMaterial();
material.colorNode = new Nodes.PositionNode();

VERTEX

//
// Three.JS r133 * NodeMaterial System
//

// uniforms
[[block]]
struct NodeUniformsStruct {

	nodeUniform0 : mat4x4<f32>;
	nodeUniform1 : mat4x4<f32>;

};
[[ binding( 0 ), group( 0 ) ]]
var<uniform> NodeUniforms : NodeUniformsStruct;

// varys
[[block]]
struct NodeVarysStruct {

	[[ builtin( position ) ]] Vertex: vec4<f32>;
	[[ location( 0 ) ]] nodeVary0 : vec3<f32>;

};

// codes


[[stage(vertex)]]
fn main( [[ location( 0 ) ]] position: vec3<f32> ) -> NodeVarysStruct {

	// system
	var NodeVarys: NodeVarysStruct;

	// vars
	var MaterialDiffuseColor : vec4<f32>; var nodeVar1 : vec4<f32>; var nodeVar2 : mat4x4<f32>; 

	// <flow>

	// code
	NodeVarys.nodeVary0 = position; 

	// SLOT: MVP
	nodeVar2 = ( NodeUniforms.nodeUniform0 * NodeUniforms.nodeUniform1 ); nodeVar1 = ( nodeVar2 * vec4<f32>( position, 1.0 ) ); 
	NodeVarys.Vertex = nodeVar1;

	// </flow>

	return NodeVarys;

}

FRAGMENT

//
// Three.JS r133 * NodeMaterial System
//

// uniforms


// codes


[[stage(fragment)]]
fn main( [[ location( 0 ) ]] nodeVary0 : vec3<f32> ) -> [[ location( 0 ) ]] vec4<f32> {

	// vars
	var MaterialDiffuseColor : vec4<f32>; 

	// <flow>

	// code
	

	// SLOT: COLOR
	
	return vec4<f32>( nodeVary0, 1.0 );

	// </flow>

}

RESULT

image

@sunag
Copy link
Collaborator Author

sunag commented Oct 1, 2021

Using the example of this PR: CheckerNode build on top of ShaderNode generating WGSL or GLSL:

NODE MATERIAL

const material = new Nodes.MeshBasicNodeMaterial();
material.colorNode = new Nodes.CheckerNode();

VERTEX

//
// Three.JS r133 * NodeMaterial System
//

// uniforms
[[block]]
struct NodeUniformsStruct {

	nodeUniform0 : mat4x4<f32>;
	nodeUniform1 : mat4x4<f32>;

};
[[ binding( 0 ), group( 0 ) ]]
var<uniform> NodeUniforms : NodeUniformsStruct;

// varys
[[block]]
struct NodeVarysStruct {

	[[ builtin( position ) ]] Vertex: vec4<f32>;
	[[ location( 0 ) ]] nodeVary0 : vec2<f32>;

};

// codes


[[stage(vertex)]]
fn main( [[ location( 0 ) ]] position: vec3<f32>, [[ location( 1 ) ]] uv: vec2<f32> ) -> NodeVarysStruct {

	// system
	var NodeVarys: NodeVarysStruct;

	// vars
	var MaterialDiffuseColor : vec4<f32>; var nodeVar1 : vec4<f32>; var nodeVar2 : mat4x4<f32>; 

	// <flow>

	// code
	NodeVarys.nodeVary0 = uv; 

	// SLOT: MVP
	nodeVar2 = ( NodeUniforms.nodeUniform0 * NodeUniforms.nodeUniform1 ); nodeVar1 = ( nodeVar2 * vec4<f32>( position, 1.0 ) ); 
	NodeVarys.Vertex = nodeVar1;

	// </flow>

	return NodeVarys;

}

FRAGMENT

//
// Three.JS r133 * NodeMaterial System
//

// uniforms


// codes


// --
fn mod( a : f32, b : f32 ) -> f32 {
	
	return f32( i32( a ) % i32( b ) );
	
}


[[stage(fragment)]]
fn main( [[ location( 0 ) ]] nodeVary0 : vec2<f32> ) -> [[ location( 0 ) ]] vec4<f32> {

	// vars
	var MaterialDiffuseColor : vec4<f32>; var nodeVar1 : f32; var nodeVar2 : vec2<f32>; 

	// <flow>

	// code
	

	// SLOT: COLOR
	nodeVar2 = ( nodeVary0 * vec2<f32>( 2.0 ) ); nodeVar1 = ( floor( nodeVar2.x ) + floor( nodeVar2.y ) ); 
	return vec4<f32>( vec3<f32>( sign( mod( nodeVar1, 2.0 ) ) ), 1.0 );

	// </flow>

}

RESULT

image

@mrdoob
Copy link
Owner

mrdoob commented Oct 4, 2021

@sunag This is looking great! 👏👏👏

@Oletus
Copy link
Contributor

Oletus commented Oct 12, 2021

@sunag @mrdoob Hey, this is awesome but I have one concern: can multiple ShaderNodes in the same node graph share the same uniform value?

@sunag
Copy link
Collaborator Author

sunag commented Oct 12, 2021

@Oletus Yeah! Uniforms and any other node can be share, for example. If you create two new PositionNode( PositionNode.VIEW ) . NodeMaterial will use only one( the first) and share the result, that is the computed calculations.

@sunag
Copy link
Collaborator Author

sunag commented Oct 12, 2021

WIP: A single ShaderNode of NormalMapNode generated two output: WGSL and GLSL.

Probably this weekend we can say hello to WGSL and goodby to glsllang.js in WebGPU.

ShaderNode

const perturbNormal2ArbNode = new ShaderNode( ( inputs ) => {
const { eye_pos, surf_norm, mapN, faceDirection, uv } = inputs;
// Workaround for Adreno 3XX dFd*( vec3 ) bug. See #9988
const q0 = join( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );
const q1 = join( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );
const st0 = dFdx( uv.st );
const st1 = dFdy( uv.st );
const N = surf_norm; // normalized
const q1perp = cross( q1, N );
const q0perp = cross( N, q0 );
const T = add( mul( q1perp, st0.x ), mul( q0perp, st1.x ) );
const B = add( mul( q1perp, st0.y ), mul( q0perp, st1.y ) );
const det = max( dot( T, T ), dot( B, B ) );
const scale = cond( equal( det, 0 ), 0, mul( faceDirection, inversesqrt( det ) ) );
return normalize( add( mul( T, mul( mapN.x, scale ) ), mul( B, mul( mapN.y, scale ) ), mul( N, mapN.z ) ) );
} );

WGSL

image

GLSL

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants