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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

馃搹 Add renormalization based on pixel format to Lanczos kernel #48

Merged
merged 11 commits into from
Apr 29, 2024

Conversation

KYovchevski
Copy link
Collaborator

When downsampling normal maps we need to renormalize the values to get a correct normal map.
Unfortunately, we can't turn it into a separate pass unless we split the entire ISPCprogram up, since the program does two passes, one vertical and one horizontal, and we should be normalizing after both of them for complete correctness.
As a result, the renormalization was made a step of the writing process for the pixels, which is only ran for Snorm formats. To accommodate this, Rgba8Snorm and Rgb8Snorm formats were added, while previous formats were renamed to *Unorm. The formats were also reflected to the ISPC code so that we can make decisions in the program and not rely on having many more different functions that the Rust side needs to call correctly.

if (is_snorm(pixel_format)) {
// Assumption: Snorm formats are only used for normal maps, so we normalize them to preserve vector length.
color = normalize(color);
color = color * 0.5f + 0.5f;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the idea here is to ensure the range stays within 0..1? Since the source is already always in that range, why do we still do this?

Comment on lines 117 to 119
// The final color is a sum of numbers that are multiplied by the weights of their respective pixels.
// Because of their numbers, floating point precision leads to the final color being potentially outside of the 0-255 range by a slight margin.
// This would cause an underflow/overflow, which we avoid with the clamps.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment only applies to the unorm branch, should probably be moved

pixel_ptr[2] = clamp(color[2], 0.0f, 255.0f);
}
if (is_snorm(pixel_format)) {
// Assumption: Snorm formats are only used for normal maps, so we normalize them to preserve vector length.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumption is probably incorrect. I would make it explicit from the name that were inputting normalmaps

if (is_snorm(pixel_format)) {
// Assumption: Snorm formats are only used for normal maps, so we normalize them to preserve vector length.
color = normalize(color);
color = color * 0.5f + 0.5f;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find the code that does the reverse when unpacking, also we probably need to know the space of the normal map here (typically tangent space, and we only have 2 components). I don't think this is correct what's happening here.

Copy link
Member

@Jasper-Bekkers Jasper-Bekkers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on this.

  1. Unorm vs snorm is not the right nomenclature for this. They just indicate value ranges, not intended use
  2. We shouldn't lanczos filter our normal maps as discussed offline. Lanczos filters can result in negative values even if all it's inputs are positive, resulting in incorrect normals (for regular images would result perceived sharpness).
  3. This doesn't appear to take into account the type of normal map, and so most likely it's incorrect reconstructing the normals. Notice that we use tangent space normals for which we reconstruct one of the components.
  4. It appears this removes srgb support as well? We're we ignoring that during downsampling? If so, we probably need a follow up also to adresss this issue, for more info see http://www.ericbrasseur.org/gamma.html?i=1

@KYovchevski
Copy link
Collaborator Author

  1. Unorm vs snorm is not the right nomenclature for this. They just indicate value ranges, not intended use
  2. We shouldn't lanczos filter our normal maps as discussed offline. Lanczos filters can result in negative values even if all it's inputs are positive, resulting in incorrect normals (for regular images would result perceived sharpness).

I've implemented a different path that can be taken by the user which applies a box filter, as discussed offline. This means that the format is not connected to the way the downsampling is done, and instead the user must explicitly say that it is a normal map.

  1. This doesn't appear to take into account the type of normal map, and so most likely it's incorrect reconstructing the normals. Notice that we use tangent space normals for which we reconstruct one of the components.

I am not sure why non-tangent space normals would require a special treatment. At lower mips there will be noticeable mistakes in the normal map, but I believe that would be due to the fact that we only have a few pixels to represent any direction for the normals.

  1. It appears this removes srgb support as well? We're we ignoring that during downsampling? If so, we probably need a follow up also to adresss this issue, for more info see http://www.ericbrasseur.org/gamma.html?i=1

I don't think there was anything for SRGB textures before this, unless something was lost with #15 getting merged. I can investigate this and if that is the case create a separate PR about this afterwards. I don't think it belongs in this PR.

@Jasper-Bekkers
Copy link
Member

Jasper-Bekkers commented Mar 15, 2024

I am not sure why non-tangent space normals would require a special treatment. At lower mips there will be noticeable mistakes in the normal map, but I believe that would be due to the fact that we only have a few pixels to represent any direction for the normals.

You'd need to decode the values, normalize them, and encode them again in the proper format.

For example for our tangent space normals, we store them in R & G and then use the following routine to decode them;

        float3 normalTs = float3(material.unpackNormalTsXy(sampleData) * 2.0 - 1.0, 0.0);
        normalTs.z = sqrt(max(0.01, 1.0 - dot(normalTs.xy, normalTs.xy)));

If you'd just take the texture data's rgb value and normalize that you wouldn't end up with the correct normal in the first place.

@KYovchevski
Copy link
Collaborator Author

I am not sure why non-tangent space normals would require a special treatment. At lower mips there will be noticeable mistakes in the normal map, but I believe that would be due to the fact that we only have a few pixels to represent any direction for the normals.

You'd need to decode the values, normalize them, and encode them again in the proper format.

For example for our tangent space normals, we store them in R & G and then use the following routine to decode them;

        float3 normalTs = float3(material.unpackNormalTsXy(sampleData) * 2.0 - 1.0, 0.0);
        normalTs.z = sqrt(max(0.01, 1.0 - dot(normalTs.xy, normalTs.xy)));

If you'd just take the texture data's rgb value and normalize that you wouldn't end up with the correct normal in the first place.

Ok, this makes sense now. I will add something so that it can be indicated whether the normal needs to be decoded, however I would like to point out that our framework does not downsample R8g8 formatted normal maps. Looking at the code, it always does the R8g8b8 -> R8g8 conversion after the downsampling, and it does that for each mip level.

Copy link
Contributor

@Athosvk Athosvk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some minor comments but looks good!

src/ispc/downsample_ispcx86_64-pc-windows-msvc.lib Outdated Show resolved Hide resolved
src/ispc/kernels/downsampling.ispc Outdated Show resolved Hide resolved
src/ispc/kernels/downsampling.ispc Outdated Show resolved Hide resolved
src/ispc/kernels/downsampling.ispc Show resolved Hide resolved
src/ispc/kernels/downsampling.ispc Outdated Show resolved Hide resolved
src/ispc/kernels/downsampling.ispc Outdated Show resolved Hide resolved
src/ispc/mod.rs Outdated Show resolved Hide resolved
MarijnS95 and others added 6 commits April 23, 2024 14:03
According to [GitHub documentation] it is now possible to get a Mac M1
runner using the aarch64 architecture.  This means `x86_64` is no longer
the only host architecture for which we expect the `x86_64-apple-darwin`
target to always be natively installed.

Install it unconditionally next to the `aarch64` Mac/iOS triples and let
`rustup` figure out and skip what's already installed.

[GitHub documentation]: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners
@KYovchevski KYovchevski merged commit fcce237 into main Apr 29, 2024
10 checks passed
@MarijnS95 MarijnS95 deleted the renormalization branch May 1, 2024 10:14
) -> Vec<u8> {
assert!(
matches!(src.format, Format::Rgba8),
matches!(format, Format::Rgba8Unorm | Format::Rgba8Snorm),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about Srgba8?

KYovchevski added a commit that referenced this pull request May 30, 2024
Introduce ImagePixelFormat trait
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.

None yet

4 participants