-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
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
Displacement Map: Add morph normal support #11271
Conversation
@@ -1,7 +1,11 @@ | |||
#ifdef FLIP_SIDED | |||
|
|||
objectNormal = -objectNormal; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leave objectNormal
un-negated for future transforms (like displacement) that don't care about surface orientation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. I prefer this.
#include <morphtarget_vertex> | ||
#include <skinning_vertex> | ||
#include <displacementmap_vertex> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since displacement is done along the post-transformed normal, displacement mapping needs to take place after position vertices have been transformed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be as precise as possible, you are displacing along the "morph-skinning-adjusted" normal in object space.
Ignoring skinning for the moment, morph normals do not have to be specified -- they are optional. One can specify morph targets only. In that case, the "adjusted" normal is the original normal.
It seems to me that in that case, displacement should be first. That would be problematic, unless you can demonstrate that order does not matter...
@@ -7,6 +7,7 @@ | |||
skinned += boneMatY * skinVertex * skinWeight.y; | |||
skinned += boneMatZ * skinVertex * skinWeight.z; | |||
skinned += boneMatW * skinVertex * skinWeight.w; | |||
skinned = bindMatrixInverse * skinned; | |||
|
|||
transformed = ( bindMatrixInverse * skinned ).xyz; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Continue mutating transformed
rather than creating a new variable. This removes the need to deal with a special case when skinning is enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are not mutating transformed
; you are overwriting it. So there are some implicit assumptions you are making. Can you please specify what they are?
Are you able to demonstrate that the w-component you are dropping is 1?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am making the assumption that the transformations applied to skinned
are affine. If that's the case, then you can drop the w-component since it will always be 1. The matrices in question should be affine, since they originate from the Object3D's matrixWorld
property, and are all intended to rotate/scale/skew/translate vertices around, rather than to project vertices.
The matrices in question:
https://github.com/mrdoob/three.js/blob/r85/src/objects/Skeleton.js#L136
https://github.com/mrdoob/three.js/blob/r85/src/objects/SkinnedMesh.js#L98
Is there a reason to suspect these matrices may sometimes not be affine?
Edit:
In fact, the only named non-affine transformation I can think of is the perspective projection. Obviously setting a bone's matrixWorld to a perspective projection is entirely nonsensical.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If that's the case, then you can drop the w-component since it will always be 1.
Not if the weights are all zero, for example. Of course, they shouldn't be.
You are assuming the weights are normalized. They are normalized in the constructor, so I think you are OK here. I still think doing an experiment to verify the value of .w
would be a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, but requiring the weights to be normalized actually follows from the original assumption that the matrices are affine. If the weights don't add up to one, then skinned
would be incompatible with all affine transformations. In fact, such transformations need the w-component to be one if you intend to transform the vector correctly.
In any case, I explicitly checked the weights by logging them for a bunch of Three.js examples, and they all add up to one. I checked the matrices as well and the last row for each of them is [0, 0, 0, 1], making them affine.
I believe in the end this is an entirely reasonable assumption to make.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for doing the experiment and confirming this. We should be OK, then.
This has been discussed previously. Displacement mapping and morph targets? Is there a modeling tool that generates such models? |
@WestLangley Displacement mapping is typically used to add detail to an object, and it accomplishes the same thing as normal mapping or bump mapping, just by a different means. In regards to the previous pull request you linked, I agree with @bhouston that displacement maps usually share the same UV set with normal/bump maps (thus sharing the offset/repeat), since as he says, they're all varying degrees of sub-face surface detail. As for a use case for displacement mapping and morph normals, here's a dumb example: Suppose you're modeling a highly detailed sheet of bubble wrap. You could represent the sheet as a smoothly flowing surface and add the details of the individual bubbles via a displacement map. Then you could animate the sheet to different positions by morphing. |
The main issues with displacement mapping in WebGL is that WebGL doesn't support tessellation shaders, thus you can only deform the original vertex positions, thus any details in the displacement map that are between vertices are not represented. It greatly limits the usefulness of displacement maps in Three.JS. |
@jaxry wrote
You appear to be making the assumption that morphs would precede displacement. Maybe so. That is the order that makes the most sense to me, too. I have not seen a use case in which morph targets and displacement maps (or skeletal animation and displacement maps) were used concurrently. This is why I asked my question above. Can you identify a modeling tool that does this? If you cannot, my assumption would remain that they are not used together, so the order of include files is irrelevant. Regardless, the order we apply operations must be consistent with that of the modeling software. |
Displacement maps are part of high-end game characters in CryEngine and I believe UE4. Here is an old NVIDIA demo combining tessellation, skinning and displacement maps: https://www.youtube.com/watch?v=1c_PVtMIz-A But again it doens't work well in WebGL because we are restricted to displacing non-tessellated geometry. |
@bhouston wrote
I agree, however that doesn't make displacement mapping totally worthless. If you're working with a very fine mesh, then displacement mapping is a viable way to add extra detail to an object. That happens to be my use case for example. @WestLangley
Blender. You can apply a displacement modifier to a surface and morph it via shape keys concurrently. As for when displacement mapping should be applied, here's what I know:
I don't have much experience with any modeling programs in particular. Would anyone be willing to check how a modeling tool handles the scale of displacement maps when the bones are scaled differently relative to one another? |
I redesigned the code to make as few assumptions as possible. Displacement once again takes place before morphing and skinning. If morph normals are provided, displace in the direction of the morph normals. Otherwise displace in the direction of the pre-morphed normal. Does the code look okay? From my tests, I can verify that displacement looks correct with both skinning and morphing, and the displacement direction always appears to be perpendicular to the surface. Should I also revert the changes made to skinning? |
I would recommend the following for now. See how you feel about it. I know this is somewhat subjective. defaultnormal_vertex.glsl vec3 transformedNormal = normalMatrix * objectNormal;
#ifdef FLIP_SIDED
transformedNormal = - transformedNormal;
#endif displacementmap_vertex.glsl transformed += objectNormal * ( texture2D( displacementMap, uv ).x * displacementScale + displacementBias ); morphnormal_vertex.glsl - do not change |
Unfortunately, |
So are we concluding that Very nice demo, BTW. :) |
Yes.
No. Displacement applied last will still work as intended if morph normals are not specified. But it's important to note that using |
Well then, let's see how it looks with displacement last. Sorry for the back-and-forth. |
I already implemented displacement last with the original commit. Are you asking me to revert the most recent changes back to what we had before? Either order works so it's up to you at this point. I'm testing the code with a mesh that combines displacement, skinning, and morphing. Aside from the scaling caveat I mentioned, the results are identical. By the way, thanks a lot for being so patient and working through this with me. Who knew this small change would have been such a big can of worms. |
I know. Sorry...
That means that displacement is best if performed last. Do you agree? (I am hoping so, because it is the only order that made sense to me from the beginning. I just was not sure if that order was correct if all three methods are used simultaneously -- or if they ever would be used simultaneously.) Also, if displacement is last, then proper normals must be used for displacement. So if a user uses both morphs and displacements, then a failure to specify correct morph normals is a user error.
ditto. :) |
I am confused, I always thought that displacement is sort of the very last thing in the vertex deformation pipeline -- after skinning, after morphing. |
The order should be based on the degree of transformation - largest first to smallest. Skinning is the largest scale transformation, then morphing (which if used with skinning should be minor, like facial expressions) and then displacement. |
(1) You are correct. Displacements last is best. The confusion here was we were trying to get the order right for all possible combinations. The combination that is problematic is if displacements and morph targets are specified, but morph normals are not. I think the consensus is to put displacements last and consider the problematic case a user error. Sorry for all the confusion here. I am glad this PR was filed, however. (2) Right now, we have morphing before skinning. We could switch those in a separate PR. |
572bd54
to
68d46fc
Compare
How does this look? Currently depth_vert.glsl does not compile. No |
Just edit #include <common>
#include <uv_pars_vertex>
#include <displacementmap_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
void main() {
#include <uv_vertex>
#include <skinbase_vertex>
#include <beginnormal_vertex>
#include <morphnormal_vertex>
#include <skinbase_vertex>
#include <skinnormal_vertex>
#include <defaultnormal_vertex>
#include <begin_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
#include <displacementmap_vertex>
#include <project_vertex>
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
} |
@@ -12,10 +12,18 @@ void main() { | |||
|
|||
#include <skinbase_vertex> | |||
|
|||
#ifdef USE_DISPLACEMENTMAP |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normals are only used for displacement. Prevent unnecessary computation of normals if displacement is not used. Is this okay or is this decision needlessly complex?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to, you can squash the commits into one clean one. It is not required, though.
Nice job. Thanks.
I thought so too, but after studying our implementation more carefully, I see that our morphing calculation must precede our skinning calculation. Here are some course slides that develop this idea further. Our current implementation in that regard seems correct to me. @mrdoob To summarize, this PR makes a few minor code improvements, and one significant change -- applying displacements after morphing and skinning, rather than before. This change allows the displacement to be in the direction of the morph/skinned normal, which is the correct thing to do. Shadows for morph/skinned models utilizing displacement maps should also be correct. In my opinion, this change is a good one and can be merged. |
Oops, seems like I forgot about this PR. Glad that it's still mergeable 😅 |
Thanks guys! |
I suspect /cc @takahirox @sunag |
OK, let me check later. |
I agree. I should have merged this one after releasing. But I was still working on WebVR efficiency (which I think I've solved) so there was still more time. Sorry about that. |
I think var vertexShaderChunk2 = [
"gl_Position = calculateOutline( gl_Position, transformedNormal, vec4( transformed, 1.0 ) );",
"#include <fog_vertex>"
].join( "\n" ); |
Ignore my previous comment. I think this is the right fix: var vertexShaderChunk2 = [
"#if ! defined( LAMBERT ) && ! defined( PHONG ) && ! defined( TOON ) && ! defined( PHYSICAL )",
" #ifndef USE_ENVMAP",
" vec3 transformedNormal = normalize( normal );",
" #ifdef FLIP_SIDED",
" transformedNormal = -transformedNormal;",
" #endif",
" #endif",
"#endif",
"#ifdef DECLARE_TRANSFORMED",
" vec3 transformed = vec3( position );",
"#endif",
"gl_Position = calculateOutline( gl_Position, transformedNormal, vec4( transformed, 1.0 ) );",
"#include <fog_vertex>"
].join( "\n" ); I think |
…rtex.glsl changes. See #11271.
I'll check the detail later (maybe tomorrow) but |
How so? All the examples seem to look correct? |
Oh! I'll take a look. |
Perhaps, this'll fix...
I'll make a PR later.
Yup, agreed. I'll try after fixing the issue. |
Fix OutlineEffect broken by #11271
Currently displacement mapping uses the pre-transformed
normal
attribute to calculate the displacement vector. If morph normals are enabled, then the actual surface normal points in a different direction than the normal attribute, meaning the surface is displaced in the wrong direction.This pull request changes the displacement mapping to use the post-transformed object normal.
I took the liberty to slightly refactor the skinning shader chunk to avoid having to introduce yet another
#ifdef USE_SKINNING
case. Is this change preferable?