Fix #109679: Normal map strength is applied incorrectly #109763

Merged
Brecht Van Lommel merged 7 commits from kaspian.jakobssongmail.com/blender:fix-normals into main 2023-07-17 20:06:50 +02:00
Contributor

Previously the normal strength calculations were linearly extrapolating
the supplied normal map respect to different scaling in the xy resp.
z-axis. This new method takes this into account and applies it in the
correct way.

The calculations are identical to the ones presented in #109679 but
translated into glsl.

Please see #109679 for more information and background.


Previously the error was presented in the following way:
(strength = 20 for clearer example)
Previous.png
Where the left side of the normal map is the node group and the right side is the normal map node

And now they are applied in the same way as the node-group suggested in #109679:
Current.png

I've attached a test file with a packed normal map which can be used to reference previous version of blender and with this fix applied.
normal_map_test_3.3.blend
normal_map_test.blend

I believe this is the correct way of scaling normals based on the previous discussion but I'm open for suggestions.

Previously the normal strength calculations were linearly extrapolating the supplied normal map respect to different scaling in the xy resp. z-axis. This new method takes this into account and applies it in the correct way. The calculations are identical to the ones presented in #109679 but translated into glsl. Please see #109679 for more information and background. --- Previously the error was presented in the following way: (strength = 20 for clearer example) ![Previous.png](/attachments/78228efe-ee1e-467c-a674-28946f636c5b) Where the left side of the normal map is the node group and the right side is the normal map node And now they are applied in the same way as the node-group suggested in #109679: ![Current.png](/attachments/3e34dbaf-f866-4c33-ba3f-fb67f2b4ed2d) I've attached a test file with a packed normal map which can be used to reference previous version of blender and with this fix applied. [normal_map_test_3.3.blend](/attachments/13fad1db-68ec-4306-b0b6-f6cbff6686a1) [normal_map_test.blend](/attachments/fbbc492e-0eb0-47d1-b914-c1cce1e5763d) I believe this is the correct way of scaling normals based on the previous discussion but I'm open for suggestions.
Kaspian Jakobsson added 1 commit 2023-07-05 23:55:37 +02:00
8887b519e8 Fix #109679: Normal strength is applied incorrectly
Previously the normal strength calculations were linearly extrapolating
the supplied normal map respect to different scaling in the xy resp.
z-axis. This new method takes this into account and applies it in the
correct way.

The calculations are identical to the ones presented in #109679 but
translated into glsl.

Please see #109679 for more information and background.
Iliya Katushenock added this to the Render & Cycles project 2023-07-05 23:57:49 +02:00
Iliya Katushenock added the
Module
EEVEE & Viewport
label 2023-07-05 23:57:53 +02:00
Thanks for the PR! Are you able to attempt a fix for Cycles[1] and OSL[2] as well? The .glsl is only used for Eevee. [1] https://projects.blender.org/blender/blender/src/branch/main/intern/cycles/kernel/svm/tex_coord.h#L266 [2] https://projects.blender.org/blender/blender/src/branch/main/intern/cycles/kernel/osl/shaders/node_normal_map.osl#L6

This looks good to me. Although I am not sure what we are our reference in this case. What standard are we trying to match?

I would also see that fixed for Cycles in the same PR to avoid introducing discrepancies between renderers.
@brecht maybe you have an opinion on this subject since it break compatibility of a core functionality.

Also note that formatting is incorrect. Please run make format in the blender-git directory to fix this.

This looks good to me. Although I am not sure what we are our reference in this case. What standard are we trying to match? I would also see that fixed for Cycles in the same PR to avoid introducing discrepancies between renderers. @brecht maybe you have an opinion on this subject since it break compatibility of a core functionality. Also note that formatting is incorrect. Please run `make format` in the `blender-git` directory to fix this.
Author
Contributor

Thank you for the comments and reviews.
I'll update the PR with Cycles and OSL support, and fix the formatting issues when I get home from work.

Also - I'll update this comment with a bit more background regarding the material I referenced, as to further make sure the change is actually sound :-)!

BR,
Kaspian

Thank you for the comments and reviews. I'll update the PR with Cycles and OSL support, and fix the formatting issues when I get home from work. Also - I'll update this comment with a bit more background regarding the material I referenced, as to further make sure the change is actually sound :-)! BR, Kaspian

This seems to assume the normal being mixed is in tangent space, but I think it's in world space here? So it would work for a plane pointing along the Z axis, but not other shapes.

This seems to assume the normal being mixed is in tangent space, but I think it's in world space here? So it would work for a plane pointing along the Z axis, but not other shapes.

Also - I'll update this comment with a bit more background regarding the material I referenced, as to further make sure the change is actually sound :-)!

If the logic is deemed to be sound, then be sure to include some explanation comments in the code as well! ;)

> Also - I'll update this comment with a bit more background regarding the material I referenced, as to further make sure the change is actually sound :-)! If the logic is deemed to be sound, then be sure to include some explanation comments in the code as well! ;)
First-time contributor

This seems to assume the normal being mixed is in tangent space, but I think it's in world space here? So it would work for a plane pointing along the Z axis, but not other shapes.

I noticed the same thing. My original setup is in tangent space, this code will need to be too.

> This seems to assume the normal being mixed is in tangent space, but I think it's in world space here? So it would work for a plane pointing along the Z axis, but not other shapes. I noticed the same thing. My original setup is in tangent space, this code will need to be too.
Kaspian Jakobsson added 4 commits 2023-07-07 22:18:38 +02:00
65ce55fd29 Fix normals strength from worldspace to tangent space
Previously the normal strength was modified in world space instead of
tangent space. The node returns directly after, but keeps the old
method in order to not break ex. object space normals strength
5311419496 Add Cycles and OSL support for tangent normal map fix
This applies the same fix as for Eevee, i.e. the scaled normals
are applied before transforming from tangent space to world space.
Previous behavior is retained for all normals which aren't in tangent
space.
buildbot/vexp-code-patch-coordinator Build done. Details
61bbfd883f
Formatting
Author
Contributor

@brecht @Alex-R-Heskett you're both entirely right, it looked correct when doing it in world space as well but I think that was a happy coincidence rather than it being correct! :-)
My latest commit corrects this by simply scaling the x and y components before doing the transform from tangent-space to world-space. I've also added support for Cycles and OSL, formatted and added comments.

Here's a first preview comparing the Eevee edition, it's a bit hard to see on the scaled sphere but it works anyway.
prev_current_eevee.png

Here's some links for reference:
https://computergraphics.stackexchange.com/questions/5411/correct-way-to-set-normal-strength
https://github.com/TwoTailsGames/Unity-Built-in-Shaders/blob/master/CGIncludes/UnityCG.cginc

With the relevant snippet here

fixed3 UnpackNormalWithScale(fixed4 packednormal, float scale)
{
#ifndef UNITY_NO_DXT5nm
    // Unpack normal as DXT5nm (1, y, 1, x) or BC5 (x, y, 0, 1)
    // Note neutral texture like "bump" is (0, 0, 1, 1) to work with both plain RGB normal and DXT5nm/BC5
    packednormal.x *= packednormal.w;
#endif
    fixed3 normal;
    normal.xy = (packednormal.xy * 2 - 1) * scale;
    normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
    return normal;
}

I'll add some more comparisons with the Cycles/OSL support soon!

@brecht @Alex-R-Heskett you're both entirely right, it looked correct when doing it in world space as well but I think that was a happy coincidence rather than it being correct! :-) My latest commit corrects this by simply scaling the x and y components before doing the transform from tangent-space to world-space. I've also added support for Cycles and OSL, formatted and added comments. Here's a first preview comparing the Eevee edition, it's a bit hard to see on the scaled sphere but it works anyway. ![prev_current_eevee.png](/attachments/0f833634-b4b9-4495-8f69-4767c3dcc2c4) Here's some links for reference: [https://computergraphics.stackexchange.com/questions/5411/correct-way-to-set-normal-strength](https://computergraphics.stackexchange.com/questions/5411/correct-way-to-set-normal-strength) [https://github.com/TwoTailsGames/Unity-Built-in-Shaders/blob/master/CGIncludes/UnityCG.cginc](https://github.com/TwoTailsGames/Unity-Built-in-Shaders/blob/master/CGIncludes/UnityCG.cginc) With the relevant snippet here ```glsl fixed3 UnpackNormalWithScale(fixed4 packednormal, float scale) { #ifndef UNITY_NO_DXT5nm // Unpack normal as DXT5nm (1, y, 1, x) or BC5 (x, y, 0, 1) // Note neutral texture like "bump" is (0, 0, 1, 1) to work with both plain RGB normal and DXT5nm/BC5 packednormal.x *= packednormal.w; #endif fixed3 normal; normal.xy = (packednormal.xy * 2 - 1) * scale; normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy))); return normal; } ``` I'll add some more comparisons with the Cycles/OSL support soon!
Author
Contributor

And here is the setup in cycles and OSL!

Screenshot 2023-07-08 114341.png
Screenshot 2023-07-08 114259.png

The previous linear extrapolating behaviour is also kept for object-, world, blender object-and blender world space, in order not to break compability and/or continue a discussion what the desired behavior should be. But I think that might be a discussion for another issue/PR :-)

And here is the setup in cycles and OSL! ![Screenshot 2023-07-08 114341.png](/attachments/a6dc1a4f-c986-4e9a-b2cf-e4fc966fa94c) ![Screenshot 2023-07-08 114259.png](/attachments/f2be4057-b263-4a78-8522-61e25ebd7147) The previous linear extrapolating behaviour is also kept for object-, world, blender object-and blender world space, in order not to break compability and/or continue a discussion what the desired behavior should be. But I think that might be a discussion for another issue/PR :-)
Brecht Van Lommel requested review from Brecht Van Lommel 2023-07-12 16:43:44 +02:00
Brecht Van Lommel requested review from Clément Foucault 2023-07-12 16:43:44 +02:00
Brecht Van Lommel approved these changes 2023-07-12 16:44:48 +02:00
Brecht Van Lommel left a comment
Owner

This looks fine to me now.

It's breaking compatibility, but I think it's acceptable for 4.0, and adding an option or inserting nodes for the old behavior does not seem worth it.

This looks fine to me now. It's breaking compatibility, but I think it's acceptable for 4.0, and adding an option or inserting nodes for the old behavior does not seem worth it.
Brecht Van Lommel added 1 commit 2023-07-12 17:08:00 +02:00
buildbot/vexp-code-patch-coordinator Build done. Details
58cbcf1273
Merge branch 'main' into HEAD

@blender-bot build

@blender-bot build

The author name in the git log has some strange special character in it:

Author: <U+0096>kaspian.jakobsson..

When we squash commit this it won't be a problem, but just FYI, you might want to change the config for future commits.

The author name in the `git log` has some strange special character in it: ``` Author: <U+0096>kaspian.jakobsson.. ``` When we squash commit this it won't be a problem, but just FYI, you might want to change the config for future commits.
Brecht Van Lommel added 1 commit 2023-07-12 18:22:32 +02:00
buildbot/vexp-code-patch-coordinator Build done. Details
1d41f45e61
Merge branch 'main' into fix-normals

Hmm, I think that tangent space is probably what most people are using in their files.
So unless there is a good reason not to, I would think just fixing all of them in one go would be the best as we are already going to break the mode most people are using.

But of course this is up to Clement and Brecht to decide.

Hmm, I think that tangent space is probably what most people are using in their files. So unless there is a good reason not to, I would think just fixing all of them in one go would be the best as we are already going to break the mode most people are using. But of course this is up to Clement and Brecht to decide.

So unless there is a good reason not to, I would think just fixing all of them in one go would be the best as we are already going to break the mode most people are using.

One reason would be that it is expensive to go into tangent space just for that strength parameter. It is possible, but I don't know if it is worth it.

> So unless there is a good reason not to, I would think just fixing all of them in one go would be the best as we are already going to break the mode most people are using. One reason would be that it is expensive to go into tangent space just for that strength parameter. It is possible, but I don't know if it is worth it.
Clément Foucault approved these changes 2023-07-13 10:44:09 +02:00
Kaspian Jakobsson requested review from Brecht Van Lommel 2023-07-13 13:18:20 +02:00
Author
Contributor

Misclick with the re-requested review @brecht, sorry!

Thank you for all the reviews and comments! I'll look more into whether a implementation that's only dependent on the last mix of normals can be done - as it should be to only scale the normals after the transformation from tangent-space to world-space is applied, right?

I'll submit another PR if that is the case, otherwise for this PR tangent space is probably the best way forward to correct it (for now).

BR,
Kaspian

Misclick with the re-requested review @brecht, sorry! Thank you for all the reviews and comments! I'll look more into whether a implementation that's only dependent on the last mix of normals can be done - as it should be to only scale the normals after the transformation from tangent-space to world-space is applied, right? I'll submit another PR if that is the case, otherwise for this PR tangent space is probably the best way forward to correct it (for now). BR, Kaspian
Brecht Van Lommel approved these changes 2023-07-17 20:05:19 +02:00
Brecht Van Lommel merged commit b767a62f32 into main 2023-07-17 20:06:50 +02:00
Brecht Van Lommel deleted branch fix-normals 2023-07-17 20:06:52 +02:00
Member

This patch introduces quite some noise in the benchmark bmw27 scene, because that scene uses a texture with possible negative color.z, causing some normals to be pointing in the wrong hemisphere after this change. Not sure if we should check the sign, but still I would expect the normal to stay unchanged if strength == 0.

This patch introduces quite some noise in the benchmark bmw27 scene, because that scene uses a texture with possible negative `color.z`, causing some normals to be pointing in the wrong hemisphere after this change. Not sure if we should check the sign, but still I would expect the normal to stay unchanged if `strength == 0`.
First-time contributor

This expects the Blue channel of the normal map to be in the 0.5-1 range.

This expects the Blue channel of the normal map to be in the 0.5-1 range.

@kaspian.jakobssongmail.com are you looking into a solution for this? Or shall we solve it?

It wasn't immediately obvious to me how best to do it.

@kaspian.jakobssongmail.com are you looking into a solution for this? Or shall we solve it? It wasn't immediately obvious to me how best to do it.
Author
Contributor

@brecht

@kaspian.jakobssongmail.com are you looking into a solution for this? Or shall we solve it?

It wasn't immediately obvious to me how best to do it.

I'll take a look at it and let you know if I can't fix it in the upcoming days

@brecht > @kaspian.jakobssongmail.com are you looking into a solution for this? Or shall we solve it? > > It wasn't immediately obvious to me how best to do it. I'll take a look at it and let you know if I can't fix it in the upcoming days
Author
Contributor

Hi!
Due to an oversight from my part the z-part wasn't actually scaled but that is now remedied following [1]. The noise introduced in the BMW scene was due to not actually scaling the z-normals whatsoever, but this is corrected with the latest commit.

[1] https://docs.unity3d.com/Packages/com.unity.shadergraph@7.1/manual/Normal-Strength-Node.html

OSL with the patch:
bmw_osl_patch.png

Blender 3.3:
bmw_blender_3.3.png

Edit:
Doesn't seem as this PR is following my branch still, unsure how to push to this now since it's merged? Maybe start a new PR?
Here's the relevant commit anyway:
https://projects.blender.org/kaspian.jakobssongmail.com/blender/commit/15615bad476352f840a80a71d2825f0b7952907b

Hi! Due to an oversight from my part the z-part wasn't actually scaled but that is now remedied following [1]. The noise introduced in the BMW scene was due to not actually scaling the z-normals whatsoever, but this is corrected with the latest commit. [1] [https://docs.unity3d.com/Packages/com.unity.shadergraph@7.1/manual/Normal-Strength-Node.html](https://docs.unity3d.com/Packages/com.unity.shadergraph@7.1/manual/Normal-Strength-Node.html) OSL with the patch: ![bmw_osl_patch.png](/attachments/113d2693-1f74-4497-b9f0-fd28f42f200f) Blender 3.3: ![bmw_blender_3.3.png](/attachments/a901fa42-c129-4da9-9f56-8d6fa24ea904) **Edit:** Doesn't seem as this PR is following my branch still, unsure how to push to this now since it's merged? Maybe start a new PR? Here's the relevant commit anyway: [https://projects.blender.org/kaspian.jakobssongmail.com/blender/commit/15615bad476352f840a80a71d2825f0b7952907b](https://projects.blender.org/kaspian.jakobssongmail.com/blender/commit/15615bad476352f840a80a71d2825f0b7952907b)

@kaspian.jakobssongmail.com you need to create a new PR.
This is because you can't really merge a pr multiple times, or at least you are not supposed to.

@kaspian.jakobssongmail.com you need to create a new PR. This is because you can't really merge a pr multiple times, or at least you are not supposed to.
First-time contributor

The noise in the BMW scene is due to the CarShellNew material using Noise Texture as a normal map which produces RGB values with range 0 to 1 which should be 0.5 to 1 for the blue channel.

230801_133739_blender_Blender_[DDesktopbmw27_cpu.blend].png
230801_133753_blender_Blender_[DDesktopbmw27_cpu.blend].png

230801_133808_blender_Blender_[DDesktopbmw27_cpu.blend].png
230801_133814_blender_Blender_[DDesktopbmw27_cpu.blend].png

230801_133829_blender_Blender_[DDesktopbmw27_cpu.blend].png
230801_133838_blender_Blender_[DDesktopbmw27_cpu.blend].png

If one will search "normal map online" or "generate normal map" the first URL is https://cpetry.github.io/NormalMap-Online/ which generates normal maps with the blue channel with range 0-1 by default.

230801_140306_msedge_NormalMap-Online_and_14_more_pages_-Profile_1-_M.png
230801_135509_blender_Blender_[DDesktopbmw27_cpu.blend].png
230801_140145_blender_Blender_[DDesktopbmw27_cpu.blend].png
230801_140159_blender_Blender_[DDesktopbmw27_cpu.blend].png

The noise in the BMW scene is due to the CarShellNew material using Noise Texture as a normal map which produces RGB values with range 0 to 1 which should be 0.5 to 1 for the blue channel. ![230801_133739_blender_Blender_[DDesktopbmw27_cpu.blend].png](/attachments/a10ab63e-e9dd-4d92-87b5-7548111e7b44) ![230801_133753_blender_Blender_[DDesktopbmw27_cpu.blend].png](/attachments/9e43464b-dfad-40d1-bc3f-1090d7a39322) ![230801_133808_blender_Blender_[DDesktopbmw27_cpu.blend].png](/attachments/fc679a8d-c9d4-4e2c-89f2-9b9d57a64fbc) ![230801_133814_blender_Blender_[DDesktopbmw27_cpu.blend].png](/attachments/ebbe8567-4e76-4857-95c6-1ed6af1619b6) ![230801_133829_blender_Blender_[DDesktopbmw27_cpu.blend].png](/attachments/9eb74b8a-c05f-4282-8fe1-e80af041101b) ![230801_133838_blender_Blender_[DDesktopbmw27_cpu.blend].png](/attachments/b441c860-f891-48d3-ac5d-b133b73dd49f) If one will search "normal map online" or "generate normal map" the first URL is https://cpetry.github.io/NormalMap-Online/ which generates normal maps with the blue channel with range 0-1 by default. ![230801_140306_msedge_NormalMap-Online_and_14_more_pages_-_Profile_1_-_M.png](/attachments/052e08f8-03f0-414b-bc69-6085c0c88d9f) ![230801_135509_blender_Blender_[DDesktopbmw27_cpu.blend].png](/attachments/55833300-b029-4e85-bd48-8621d4144d2d) ![230801_140145_blender_Blender_[DDesktopbmw27_cpu.blend].png](/attachments/5edbae81-bfb1-4245-89fd-a8f0e9749cdc) ![230801_140159_blender_Blender_[DDesktopbmw27_cpu.blend].png](/attachments/1b985fc1-99ee-441f-b7de-c7b3c04a81cb)

Thanks, @kaspian.jakobssongmail.com, I pushed the fix to main. It seems to work well in my tests.

@unwave you can check tomorrow's daily builds to see if solved your examples. It's possible to create normal maps like that where the normals goes below the surface, I'm not sure we'll every be able to render those well but hopefully no worse than before now.

Thanks, @kaspian.jakobssongmail.com, I pushed the fix to main. It seems to work well in my tests. @unwave you can check tomorrow's daily builds to see if solved your examples. It's possible to create normal maps like that where the normals goes below the surface, I'm not sure we'll every be able to render those well but hopefully no worse than before now.
Sign in to join this conversation.
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No Assignees
8 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#109763
No description provided.