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

Pass alphaTest as a uniform, rather than a macro. #15654

Closed
donmccurdy opened this issue Jan 28, 2019 · 23 comments
Closed

Pass alphaTest as a uniform, rather than a macro. #15654

donmccurdy opened this issue Jan 28, 2019 · 23 comments

Comments

@donmccurdy
Copy link
Collaborator

donmccurdy commented Jan 28, 2019

A material's .alphaTest property is currently translated into:

#ifdef ALPHATEST

if ( alpha <= ALPHATEST ) discard;

#endif

As a result of using a macro, the value cannot be changed or animated without recompiling. I'd like to propose that we allow animating the value, which may require an API addition to skip the conditional when it isn't needed. For example:

// Mask
material.alphaMode = THREE.AlphaMask; // or AlphaClip
material.alphaTest = 0.5;

// Blend
material.alphaMode = THREE.AlphaBlend; // or AlphaTransparent

// Both
material.alphaMode = THREE.AlphaBlendMask; // or AlphaTransparentMask

In the example above, alphaMode replaces .transparent. Alternatively, we could use separate boolean flags like .transparent=true and .mask=true.

Animating alphaTest is of limited use with fixed material inputs, but it becomes much more interesting when paired with NodeMaterial, where procedural offset, opacity, alphaTest, and emissive values can be used for effects like these:

dissolve smoke
51575747-353af880-1e68-11e9-8852-eebcaa14fa1a ezgif-4-e5b3bd8afcf8

For further reading, see http://glowfishinteractive.com/dissolving-the-world-part-2/. Previous discussion in #5623.

Do we want a PR for this, and if so: (1) any preference on the API, and (2) should it affect core materials or just NodeMaterial?

/cc @sunag

@sunag
Copy link
Collaborator

sunag commented Jan 28, 2019

Interesting... In case of NodeMaterial perhaps we could consider:

.alpha = new THREE.ThresholdNode( alphaMapNode, thresholdNode );

and deprecated (maintain only for backward compatibility)
the #ifdef ALPHATEST

@sunag
Copy link
Collaborator

sunag commented Jan 28, 2019

// Mask
.alpha = new THREE.ThresholdNode( mapNode, floatNode );

// Blend
.alpha = mapNode;

// Both
.alpha = new THREE.ThresholdNode( mapNode, floatNode, mapNode );
// thrid map is blend

@sunag
Copy link
Collaborator

sunag commented Jan 28, 2019

do you have the source of smoke example?

@sunag
Copy link
Collaborator

sunag commented Jan 28, 2019

do you have the source of smoke example?

I do not think I need to, I was kind of confused, it seems to have a displace map too.

@WestLangley
Copy link
Collaborator

Do we want a PR for this, and if so: (1) any preference on the API, and (2) should it affect core materials or just NodeMaterial?

I see no problem implementing Material.alphaTest as a uniform. We just need to be careful how we implement it to avoid unnecessary overhead in the shader.

Perhaps we can change the default value of Material.alphaTest from 0 to null, and then define USE_ALPHATEST if Material.alphaTest !== null.

@donmccurdy
Copy link
Collaborator Author

do you have the source of smoke example?

The screenshot is from the Shade for iOS app (https://twitter.com/_LucasRizzotto/status/1078140430036828160) which generates a JSON representation of the node graph and a Unity shader. I have both available if that's helpful. I was able to get a dissolve effect working in NodeMaterial, aside from this alphaTest question and adding a custom Noise3DNode, but haven't tried reproducing the smoke effect yet.

I like the idea of defaulting to alphaTest=null and creating a uniform otherwise. That will work in existing materials fine, and in NodeMaterial would enable this usage:

material.alpha = new NoiseNode( ... );
material.alphaTest = new Math1Node( new TimerNode(), Math1Node.SIN );

@sunag
Copy link
Collaborator

sunag commented Jan 28, 2019

Hmm. Shade is quite impressive.

About create new slots is not the way that I would follow only if there is no output more for it.
This limits the node system because it loses the tree process.

For example ThresholdNode could be used in vertex/position in place of a noise in alpha, similar to this example in void-function:

mtl.position = new THREE.BypassNode( position );
// FRAGMENT
var clipFromPos = new THREE.FunctionNode( [
"void clipFromPos( vec3 pos ) {",
" if ( pos.y < .0 ) discard;",
"}"
].join( "\n" ) );
var clipFromPosCall = new THREE.FunctionCallNode( clipFromPos, {
pos: varying
} );
mtl.color = new THREE.BypassNode( clipFromPosCall, varying );

another example would be for the developer extend the class of ThresholdNode:

// ConditionalThresholdNode is extended of ThresholdNode
// its add conditional feature
var mask = new THREE.ConditionalThresholdNode( maskNode, floatNode );
mask.conditional = ">=";

material.alpha = mask;

otherwise it would closely resemble the parameterized system, for example:

material.alpha = maskNode;
material.alphaTest = floatNode;
material.alphaTestConditional = ">=";

@donmccurdy
Copy link
Collaborator Author

@sunag I don't think I understand... the suggested ThresholdNode would still have a float output connected to .alpha, but also have a side effect of calling discard when its own output is less than a threshold? A name like ClipNode would be clearer I think, but even then it seems a little confusing. For the tree process to work well in the node system, I don't think nodes should have effects other than their output values.

Because threejs already has an alphaTest parameter, users are going to expect it to take a node as input. I did, until I got this output: #define ALPHATEST [Object object]. 😅

@sunag
Copy link
Collaborator

sunag commented Jan 28, 2019

Really ClipNode for me too is a better name. But for the purpose you want it would be necessary to add alphaMode. I think this is unnecessary for NodeMaterial since we can do everything with a node and containing more features like the conditional example.

var mask = new THREE.ClipNode( maskNode, floatNode );
mask.conditional = ">=";

material.alpha = mask;

instead of:

material.alpha = maskNode;
material.alphaTest = floatNode;
material.alphaTestConditional = ">=";
material.alphaMode = THREE.AlphaMask;

For the tree process to work well in the node system, I don't think nodes should have effects other than their output values.

I see a lot of usefulness in BypassNode for create advanced shaders like the examples varying and void-function. I understand the question of returning values, but I think this is related to architecture. Nothing here prevents BypassNode working well in your application. NodeMaterial can be used for both traditional as much the more specifics cases.

@sunag
Copy link
Collaborator

sunag commented Jan 28, 2019

@sunag
Copy link
Collaborator

sunag commented Jan 28, 2019

I update with glow
image

@sunag
Copy link
Collaborator

sunag commented Jan 28, 2019

I don't think I understand... the suggested

I think I generated this confusion because of the third argument. Sorry.

material.color = new THREE.ClipNode( maskNode, floatNode, colorNode );

@donmccurdy
Copy link
Collaborator Author

But for the purpose you want it would be necessary to add alphaMode.

It don't see a reason for that – if we consider the default value of .alphaTest to be null, then any other value implies that clipping is enabled. By adding a ClipNode are you suggesting we remove .alphaTest from NodeMaterial, or keep them both? In this example...

material.alpha = /* procedural texture, etc. */;
material.alphaTest = new Math1Node( new TimerNode(), Math1Node.SIN );

... I would argue that we haven't added any new sockets. We've just made alphaTest behave like the other inputs.

A weird part of ClipNode is that it requires that the value used for clipping also be used some other way in rendering, and it doesn't actually matter what. For example:

material.metalness = new OperatorNode(
  new ClipNode( ... ),
  new FloatNode( 0.0 ),
  OperatorNode.MUL
);

In that case, the main effect of ClipNode has nothing to do with its output... I'm not sure I see an advantage in that, over the current .alphaTest? I suppose there could be uses for making clipping totally independent of alpha, but I can't think of one right now. I'm also not sure why it needs a custom conditional other than <=, since the same effect can be done by remapping its output after the conditional.

In your code...

var clipCode = "if ( " + testValue + " " + this.conditional + " " + thresholdValue + " ) discard;"

// clip
builder.addNodeCode( clipCode );

return this.output.build( builder, output );

... is this order-independent enough to be safe? What guarantees that the appended clipCode line will appear after any possible changes to the test and threshold values?

I see a lot of usefulness in BypassNode for create advanced shaders

What do you mean by BypassNode?


Also, I like the new example! 🎊 We should create a Noise3DNode so it doesn't need to follow UVs maybe.

@sunag
Copy link
Collaborator

sunag commented Jan 29, 2019

By adding a ClipNode are you suggesting we remove .alphaTest from NodeMaterial, or keep them both?

for now keep them both. remove .alphaTest in the future, in root node materials like PhongNodeMaterial for example but keep .alphaTest in MeshStandardNodeMaterial built in ClipNode.

I'm not sure I see an advantage in that, over the current .alphaTest?

ClipNode can be used in another process, such material.position and RawNode for example.

is this order-independent enough to be safe? What guarantees that the appended clipCode line will appear after any possible changes to the test and threshold values?

this follows a hierarchy, and share nodes between slots for optimization, in case of material.color = new ClipNode(...) the clipCode is ever added here:

if added only in metalness here:

in case of added the same ClipNode in both color and metalness, clipCode is added only in color because color slot is processed first and it is unnecessary to use this code two time, this process of optimization is automatic and done in parser.

parse: function ( builder, settings ) {

What do you mean by BypassNode?

It came from of this issue:
#7522 (comment)

It has been assigned to define varys and too can be used in void functions.

Also, I like the new example! confetti_ball We should create a Noise3DNode so it doesn't need to follow UVs maybe.

Yes, this would be wonderful. Thanks!

@donmccurdy
Copy link
Collaborator Author

I see, thanks! I'm ok with whatever API for clipping you find best, then. :)

@sunag
Copy link
Collaborator

sunag commented Jan 29, 2019

@donmccurdy Thank you for your effort and support in NodeMaterial you always address very relevant needs.

I'm still not completely satisfied with ClipNode, the problem of alphaTest for me is the link with alpha slot.

I thought of a third solution, a boolean slot called clip, seems to me be a mix of the two ideas:

material.clip = new CondNode( ... )

@sunag
Copy link
Collaborator

sunag commented Jan 29, 2019

it would also be .mask

material.mask = new CondNode( ... );

@sunag
Copy link
Collaborator

sunag commented Jan 29, 2019

https://rawgit.com/sunag/three.js/dev-mask/examples/webgl_materials_nodes.html?e=dissolve

// CondNode returns a boolean value
// show fragment only if CondNode returns true
material.mask = new THREE.CondNode(
	maskNode, // mask node
	new THREE.FloatNode( .1 ), // threshold
	THREE.CondNode.GREATER // condition
);

image

@sunag
Copy link
Collaborator

sunag commented Jan 29, 2019

I post a PR to help the workflow, but if we see something better we will changes in sequence. 👍

@WestLangley
Copy link
Collaborator

@donmccurdy Do you support #15654 (comment)?

I have hacked a proof-of-concept and it seems to work great. I have not touched node materials or GLTF import/export though.

@donmccurdy
Copy link
Collaborator Author

@WestLangley I'm fine with that change too, yes. The use cases for procedural clipping that I can think of are specific to NodeMaterial; glTF does not animate alphaTest, and I'm not aware that users have requested it for fixed-input materials. But it's a simple backward-compatible change that may save someone a recompile, so why not. :)

@WestLangley
Copy link
Collaborator

@donmccurdy Well, unless there is renewed demand for it, I don't see the point, to be honest. I think I will leave the built-in materials as-is.

@donmccurdy
Copy link
Collaborator Author

Ok, let's close this since #15663 has merged and reconsider if we get more requests for it.

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

No branches or pull requests

4 participants