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

Added transparency to StandardNodeMaterial #16996

Closed
wants to merge 10 commits into from

Conversation

DanielSturk
Copy link
Contributor

In an effort to align more with the Enterprise PBR model (#16977) I've implemented transparency (#15941).
3989eec928d4a7b990b50638e99f9fe6

Transparency differs from opacity in that transparency only hides the diffuse lighting (more physically accurate), whereas opacity completely fades the material (non-physical).
transparency vs opacity (taken from https://dassaultsystemes-technology.github.io/EnterprisePBRShadingModel/spec.md.html)

Admittedly, this is somewhat hacked in, and mostly serves as a proof of concept for accelerating the changes towards Enterprise PBR. I'll spend the remainder of this post explaining what should be changed.

The blending is straight forward: at the end of the fragment shader, opacity still affects the final alpha, but an additional 'transparency' term is added which only affects the opacity of the diffuse lighting. For example, when transparency=1, the only visible light is coming from the specular (see the above Enterprise PBR chart). Unfortunately, there is no alpha value pertaining to the specular stored at this point, so I must infer one by re-calculating the fresnel term, and dividing it out of the specular light.

The obvious change that must be made is that the F term of the DFG should never be included in the directSpecular or indirectSpecular values, and it should only be calculated once at the end of the shader, where it is treated as the alpha term of the specular light.

Unfortunately I am struggling to seperate the fresnel term, mainly due to the opaque environment BRDF (#7489). F can easily be factored out of BRDF_Specular_GGX(), but not BRDF_Specular_GGX_Environment() or BRDF_Specular_Multiscattering_Environment().

# Conflicts:
#	examples/webgl_materials_transparency.html
#	src/materials/MeshPhysicalMaterial.js
#	src/renderers/WebGLRenderer.js
#	src/renderers/shaders/ShaderLib.js
@donmccurdy
Copy link
Collaborator

Thank you for contributing this! ❤️

I’m traveling this week, but would be happy to review it next week.

@mrdoob mrdoob added this to the r107 milestone Jul 11, 2019
@WestLangley
Copy link
Collaborator

Admittedly, this is somewhat hacked in, and mostly serves as a proof of concept

Agreed. The rendering is pretty, but I am unable to comprehend the model on which your changes are based. Do you have a reference for your modeling approach?

@WestLangley
Copy link
Collaborator

Babylon has another solution. Live demo: https://www.babylonjs-playground.com/#19JGPR#13

Instead of having both material.opacity and material.transparency, as in this PR, Babylon has some flags that can be set: useRadianceOverAlpha and/or useSpecularOverAlpha.

@WestLangley
Copy link
Collaborator

MeshPhysicalMaterial was designed to model plastics and metals.

Perhaps a better approach would be to create THREE.PBRGlassMaterial and use a custom shader appropriate for modeling glass.

Related reading ADOBE_materials_thin_transparency.

@mrdoob
Copy link
Owner

mrdoob commented Jul 14, 2019

MeshPhysicalThinMaterial?

@DanielSturk
Copy link
Contributor Author

DanielSturk commented Jul 15, 2019

Agreed. The rendering is pretty, but I am unable to comprehend the model on which your changes are based. Do you have a reference for your modeling approach?

I appreciate the glTF paper you linked. This part happens to describes what I'm actually trying to do, which is mix the diffuse/specular based on the fresnel. This may have not been obvious, so I went back and cleaned up my code
a6553b3abae64a4ba5d4c9d596627973
There's 2 major differences between with how we're doing it, however.

  1. We should be factoring fresnel out of the BRDF, only incorporating it at the end for mixing. Right now I'm essentially re-calculating it at the end and factoring it out of the specular term (hacky)
  2. Their fresnel term F is a vec3 (as is ours), and is used for mixing with the transmitted light. Since we're using WebGL blending, we can't do a component-wise blending with the background, so I must estimate with fresnelApprox. Real materials can have non-grayscale transparency, of course.

@DanielSturk
Copy link
Contributor Author

Instead of having both material.opacity and material.transparency, as in this PR, Babylon has some flags that can be set: useRadianceOverAlpha and/or useSpecularOverAlpha.

Although I appreciate the elegance of Babylon's method, I support the Enterprise PBR model (see #16977), which allows for separate transparency/opacity values.
Opacity and transparency are occasionally used in conjunction. Usually for fading out an object with transparent glass/whatever, or like this
refractive_sphere_cutout (taken from here)

@WestLangley
Copy link
Collaborator

@DanielSturk Thank you for the link to the Arnold doc. I think it explains quite well the justification for both transparency and opacity. The fact that such a detailed explanation is required leads me to believe the API was confusing to users, but I think the API is OK.

I also support adding the feature you are trying to implement.

I do not understand your physical modeling, however. Am I correct in guessing that this is something you developed on you own?

If that is the case, I suggest you use linearToRelativeLuminance() to compute an average fresnel value. (Doing so does not resolve my concerns, however. )

This is my attempt to reverse-engineer your code. Is it accurate?

vec3 fresnel = BRDF_Specular_GGX_Environment( ... );

float F = luminance( fresnel );

vec4 fragColor = ( 1 - F ) * vec4( diffuseRGB * ( 1 - T ), 1 - T ) + F * vec4( specularRGB, F );

fragColor.rgb /= fragColor.a;

return fragColor;

@DanielSturk
Copy link
Contributor Author

@WestLangley Thanks for the help. I knew there was multiple ways of calculating luminescence but I didn't know we had a standard one in Three.JS, I'll add that in. As far as I can tell your code is correct (multiply diffuse by transparency, mix diffuse/specular using fresnel).

Also your are correct that I "developed" it myself, in relation to the luminescence thing. After some sleuthing I found that Babylon does something similar, but it seems less physical/accurate than our method, because it looks like they are calculating specular alpha from the specular light (wouldn't this mean brighter highlights are more opaque? seems wrong)

@WestLangley
Copy link
Collaborator

@DanielSturk

As far as I can tell your code is correct

Just to clarify, it is not my code, it is a representation of your code. Unfortunately, I cannot understand any mathematical or physical justification for your equation. You are also dividing by fragColor.a, which can be zero.

Like I said, I support your efforts, but as of now, not the implementation. I do not have an alternate solution, however.

wouldn't this mean brighter highlights are more opaque? seems wrong

Yes, that is what they claim. I think their claim is an accurate one. Their solution is to increase the opacity of the specular highlights using some heuristic.

@donmccurdy
Copy link
Collaborator

donmccurdy commented Jul 20, 2019

The API surface related to alpha and transparency is becoming complex:

  • .opacity
  • .transparency
  • .transparent
  • .alphaTest
  • .alphaMap
  • .refractionRatio
  • .envMap + THREE.CubeRefractionMapping

In particular, .transparent and .transparency have no relation to one another, which does seem confusing. .opacity and .alphaMap are functionally equivalent, despite unrelated names. The distinction between .transparency and .opacity is likely to cause confusion as well, although I agree with the reasons for that separation. For another reference, here is Blender's Principled BSDF node:

Screen Shot 2019-07-20 at 2 04 14 PM

Note the distinct "alpha" and "transmission" sockets.

My first, and simplest, suggestion would be that perhaps we should implement this only on NodeMaterial first, to buy some time to experiment with techniques and API. I'm nervous about the suggestion of a separate material type for physically-based transparency — Blender and glTF are both using a single ubermaterial.

We've also discussed having an equivalent of dither temporal AA or alpha hashing, which would throw another complication into the existing material API but is easier with NodeMaterial's .clip property.

…erial

# Conflicts:
#	src/renderers/shaders/ShaderChunk/transparency_pars_fragment.glsl.js
# Conflicts:
#	examples/jsm/nodes/materials/nodes/StandardNode.js
#	examples/webgl_materials_transparency.html
@DanielSturk
Copy link
Contributor Author

@donmccurdy

I'm totally satisfied with implementing this in the StandardNodeMaterial first (in fact I have no interest in non-node materials), so I undid the MeshPhysicalMaterial changes and applied the StandardNodeMaterial changes.

I don't have a solution for the names. It's already convoluted enough. That's one nice thing about node materials is having significantly less attributes (eg. alpha and transparency, but no .alphaMap, no .transparent (at the user level))

Also I don't see the point of adding yet another material, specifically for transparency. I think fewer materials is better

@DanielSturk DanielSturk changed the title Added transparency Added transparency to StandardNodeMaterial Jul 24, 2019
@sunag
Copy link
Collaborator

sunag commented Jul 25, 2019

@DanielSturk I found these changes great, this is exactly what I suggest here #16971 (comment), you can do this in sheen PR too?.

To isolate this change completely of the core for now, you can transform transparency_pars_fragment in a node like this:

https://github.com/mrdoob/three.js/blob/dev/examples/jsm/nodes/bsdfs/RoughnessToBlinnExponentNode.js

Removing this of ShaderChuck.

@WestLangley
Copy link
Collaborator

The model makes no mathematical sense and produces bizarre results. Here is what happens when the envMapIntensity is reduced: the material turns black.

Screen Shot 2019-07-25 at 12 39 02 PM

It should be clear.

Please do not try to develop your own PBR shading models. Implement models you can reference elsewhere -- models that, at a minimum, are physically plausible.

@DanielSturk
Copy link
Contributor Author

@WestLangley I didn't think that was the desired behavior, since currently (including with opacity), envMapIntensity darkens the appearance of the material.
dd14a62b80717519ef857ccf12a3c395
Anyways, I see you've created your own PR. Ping me if you'd like me to close this one

@WestLangley
Copy link
Collaborator

@DanielSturk Sorry, I do support your efforts, but I cannot support your model.

Perhaps you can have a look at #17114 and try to break it. That is the only way I think we will be able to arrive at a model that is reasonably acceptable.

@Mugen87
Copy link
Collaborator

Mugen87 commented Oct 8, 2019

Enhancing the node material should based on #17114 now. It's probably better to create a new PR for this. Closing.

@Mugen87 Mugen87 closed this Oct 8, 2019
@Mugen87 Mugen87 removed this from the r110 milestone Oct 8, 2019
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.

6 participants