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

[HELP] Converting a DDS normal map to jpeg #3525

Open
yetigit opened this issue Aug 15, 2022 · 11 comments
Open

[HELP] Converting a DDS normal map to jpeg #3525

yetigit opened this issue Aug 15, 2022 · 11 comments

Comments

@yetigit
Copy link

yetigit commented Aug 15, 2022

How do I convert this kind of normal map here to jpeg using iconvert or oiiotool ?
normaltext.zip

expected output:
Wood_Normal

@lgritz
Copy link
Collaborator

lgritz commented Aug 15, 2022

What have you tried so far? Did something not work as expected?

@yetigit
Copy link
Author

yetigit commented Aug 15, 2022

well tried to convert it with no options and got a grey output
most of my attempts gave me grey's
one time I tried with oiiotool --ch "R,G,B" and had a red and green output because the tool basically made a 0' blue channel since apparently the file didn't have one.
problem with that last one is that 1. I don't get all the colors and 2. it loses the information of the blue channel ?

@lgritz
Copy link
Collaborator

lgritz commented Aug 15, 2022

There definitely appears to be some problems with OIIO's interpretation and conversion of that file. I'm not sure what's up with that yet. The input seems to be (according to OIIO) a 2-channel file, but maybe that's not correct.

@yetigit
Copy link
Author

yetigit commented Aug 16, 2022

could manage to get my normal converted to png with the standalone nvidia tool https://developer.nvidia.com/nvidia-texture-tools-exporter it has some command line stuff in its install dir :
explorer_b2uWvNlFjT

they also have nvddsinfo.exe
so I don't know if it's of any help but it sorta describes the dds texture

C:\Program Files\NVIDIA Corporation\NVIDIA Texture Tools>nvddsinfo.exe "C:\Users\baidh\Downloads\Bistro_v5_2\Textures\Wood_Normal.dds"
Flags: 0x000A1007
        DDSD_CAPS
        DDSD_PIXELFORMAT
        DDSD_WIDTH
        DDSD_HEIGHT
        DDSD_LINEARSIZE
        DDSD_MIPMAPCOUNT
Height: 2048
Width: 2048
Depth: 0
Linear size: 4194304
Mipmap count: 12
Pixel Format:
        Flags: 0x80000004
                DDPF_FOURCC
                DDPF_NORMAL
        FourCC: 'ATI2' (0x32495441)
        Swizzle: 'A2XY' (0x59583241)
        Red mask:   0x00000000
        Green mask: 0x00000000
        Blue mask:  0x00000000
        Alpha mask: 0x00000000
Caps:
        Caps 1: 0x00401008
                DDSCAPS_COMPLEX
                DDSCAPS_TEXTURE
                DDSCAPS_MIPMAP
        Caps 2: 0x00000000
        Caps 3: 0x00000000
        Caps 4: 0x00000000
Version:
        NVIDIA Texture Tools 2.0.8

@aras-p
Copy link
Contributor

aras-p commented Aug 19, 2022

So your DDS file is a two-channel format ("ATI2" aka BC5), i.e. the file only contains X & Y components of the normal map, and whatever uses the texture is expected to compute the Z component from these using math.

It's a good question what should the conversion done by OIIO even do. JPEGs can't be two-channel as far as I know, so "something" should get written into the blue channel. But should it be the full "reconstructed z" value? How would OIIO even know that the texture is actually a normal map, and not just "some other" type of two-channel texture?

@lgritz
Copy link
Collaborator

lgritz commented Aug 19, 2022

JPEG (ok, to be pedantic, the JFIF file format, since "JPEG" itself refers to the compression method and the group that defined it) only supports 1 and 3 channel images.

Our JPEG writer, rather than be really brittle, tries to handle this JPEG restriction on number of channels as gracefully as possible. It seems obvious that if you try to write an RGBA or RGB+others image, the JPEG writer should just write the RGB portion and drop the channels it doesn't support. But what should it do with a 2-channel image? Currently, it drops the second and outputs the first as a grayscale JPEG. Perhaps what I was thinking at the time is that a 2-channel image is likely to be Gray+Alpha, and since JFIF files don't have any provision for alpha, drop it and only write the gray, analogous to what we do with the automatic RGBA -> RGB case. But in this case the channels mean something else entirely, and perhaps a better solution would be to write a 3-channel RGB? But I'm not 100% sure how to know that, unless we can assume that ATI2 DDS files are always meant to be 2-channel normal maps?

As a further aside, this is a texture map of some kind, and JPEG is a horrible format for texture maps -- first of all, because the JPEG compression will cause data loss and possible artifacts in the images, and second because JPEG/JFIF files don't support MIP mapping, so as far as OIIO is concerned, a JPEG is just an "image", and not a proper "texture."

@aras-p
Copy link
Contributor

aras-p commented Aug 19, 2022

But what should it do with a 2-channel image? Currently, it drops the second and outputs the first as a grayscale JPEG. Perhaps what I was thinking at the time is that a 2-channel image is likely to be Gray+Alpha, and since JFIF files don't have any provision for alpha, drop it and only write the gray, analogous to what we do with the automatic RGBA -> RGB case. But in this case the channels mean something else entirely, and perhaps a better solution would be to write a 3-channel RGB? But I'm not 100% sure how to know that, unless we can assume that ATI2 DDS files are always meant to be 2-channel normal maps?

In general case you can't assume that ATI2/BC5 DDS files will contain normal maps -- they often contain anything that needs "two uncorrelated channels". That said, my experience is that they more often contain something else than Luminance+Alpha; in fact the dds loader plugin in OIIO indicates the channel list as "R, G" for them.

Perhaps when faced with a 2-channel image, the OIIO JPG writer should decide to "downgrade" it to 1 channel only if the channel list has "Y, Alpha". If it's something like "R, G" then it should pad to an all-zeroes blue image.

@lgritz
Copy link
Collaborator

lgritz commented Aug 21, 2022

I'm not sure it's a "fix" for this issue -- or what a "fix" even means considering there is a lot of ambiguity in what it means to convert this kind of DDS to JPEG -- but I have submitted the following two PRs inspired by this situation, that I suppose help in some small way:

  • DDS improvements #3530 Takes a more nuanced approach to labelling the channels of a 2-channel DDS, calling them as Y,A if the header flags indicate luminance and/or alpha, otherwise label them as R,G.
  • Improve JPEG output of 2-channel images #3531 Changes JPEG output of 2-channel images to output 1 channel (dropping the second) if the input channels appeared to be Y,A, and 3 channels (adding a black channel) in all other circumstances. (In comparison, previously, attempted output of 2-channel image to JPEG would always drop the second channel and output a 1-channel grayscale JPEG.)

I think that put together, these would make oiiotool in.dds -o out.jpg for this type of DDS file have more intuitive behavior. (There are still some reasons why this is a bad idea -- JPEGs don't support MIPmaps and thus aren't proper "textures" by our reckoning, and also you should expect the lossy compression, 8 bits only, and compulsory sRGB encoding of JPEG to be poor choices for a normal map.)

Let me know if you think these effectively should close this issue, or if you think there is something additional that OIIO could reasonably do for these files.

@aras-p
Copy link
Contributor

aras-p commented Aug 22, 2022

I think both of these changes make a lot of sense.

And yes, taking a ATI2/BC5 normal map (which is already lossy compression) and converting that into a JPG (which introduces more, and different compression artifacts) is not something that makes a lot of sense. At least converting the input to some lossless format (PNG, TIF, TGA, whatever) would be better quality wise.

That said, I think the improvements above are useful in more cases.

@yetigit
Copy link
Author

yetigit commented Aug 22, 2022

I would suggest have a flag like the nvidia texture tool indicating this is to be treated as a normal map
more precisely an nvidia-normal-map , so maybe something like : --nvnormalmap

@lgritz
Copy link
Collaborator

lgritz commented Aug 23, 2022

@yetigit What does that mean? If the flag is supplied, what exactly should we do differently when converting the image from DDS to, say, jpeg?

@aras-p aras-p mentioned this issue Sep 28, 2022
5 tasks
lgritz pushed a commit that referenced this issue Oct 2, 2022
- If a DDS pixel format header contains "this is a normal map" flag
  set, then handle that properly (#3525):
  - For a DXT5, this means it's a "DXT5nm" format. Normal X & Y
    components are in green and alpha channel respectively. Compute Z
    component from those, and the result is a 3-channel tangent space
    normal map.
  - For ATI2/BC5 format that also has a "normal map" flag set, compute
    the Z component automatically too, and result is a 3-channel image.
    However a bunch of ATI2/BC5 normal maps out there in the wild do
    not have that bit set, so also have a global "dds:bc5normal"
    attribute to optionally turn this on.
- Fix support of R10G10B10A2 format - was producing garbage data due to
  code assumption that truncation down to 8 bits never needs to happen.
- Fix bit expansion ("too dark") of very low-bit formats, e.g. R3G3B2.
  Code was simply shifting the bits, instead of properly repeating
  the high bits into new low bit positions (i.e. a 3-bit "111" needs
  to become an 8-bit "11111111" and not a "11100000").
- Support "RXGB" (DXT5, with red & alpha swapped) as well as "BC4U"
  and "BC5U" formats (the latter two are just BC4 and BC5, produced by
  some older tools).
- Support alpha-only (e.g. A8) formats.

All this mostly prompted by doing DDS improvements in Blender, and
wanting to get Cycles renderer (which uses OIIO) to the same feature
parity. See https://developer.blender.org/T101405 and
https://developer.blender.org/D16087.
lgritz pushed a commit to lgritz/OpenImageIO that referenced this issue Oct 2, 2022
- If a DDS pixel format header contains "this is a normal map" flag
  set, then handle that properly (AcademySoftwareFoundation#3525):
  - For a DXT5, this means it's a "DXT5nm" format. Normal X & Y
    components are in green and alpha channel respectively. Compute Z
    component from those, and the result is a 3-channel tangent space
    normal map.
  - For ATI2/BC5 format that also has a "normal map" flag set, compute
    the Z component automatically too, and result is a 3-channel image.
    However a bunch of ATI2/BC5 normal maps out there in the wild do
    not have that bit set, so also have a global "dds:bc5normal"
    attribute to optionally turn this on.
- Fix support of R10G10B10A2 format - was producing garbage data due to
  code assumption that truncation down to 8 bits never needs to happen.
- Fix bit expansion ("too dark") of very low-bit formats, e.g. R3G3B2.
  Code was simply shifting the bits, instead of properly repeating
  the high bits into new low bit positions (i.e. a 3-bit "111" needs
  to become an 8-bit "11111111" and not a "11100000").
- Support "RXGB" (DXT5, with red & alpha swapped) as well as "BC4U"
  and "BC5U" formats (the latter two are just BC4 and BC5, produced by
  some older tools).
- Support alpha-only (e.g. A8) formats.

All this mostly prompted by doing DDS improvements in Blender, and
wanting to get Cycles renderer (which uses OIIO) to the same feature
parity. See https://developer.blender.org/T101405 and
https://developer.blender.org/D16087.
lgritz pushed a commit to lgritz/OpenImageIO that referenced this issue Oct 2, 2022
- If a DDS pixel format header contains "this is a normal map" flag
  set, then handle that properly (AcademySoftwareFoundation#3525):
  - For a DXT5, this means it's a "DXT5nm" format. Normal X & Y
    components are in green and alpha channel respectively. Compute Z
    component from those, and the result is a 3-channel tangent space
    normal map.
  - For ATI2/BC5 format that also has a "normal map" flag set, compute
    the Z component automatically too, and result is a 3-channel image.
    However a bunch of ATI2/BC5 normal maps out there in the wild do
    not have that bit set, so also have a global "dds:bc5normal"
    attribute to optionally turn this on.
- Fix support of R10G10B10A2 format - was producing garbage data due to
  code assumption that truncation down to 8 bits never needs to happen.
- Fix bit expansion ("too dark") of very low-bit formats, e.g. R3G3B2.
  Code was simply shifting the bits, instead of properly repeating
  the high bits into new low bit positions (i.e. a 3-bit "111" needs
  to become an 8-bit "11111111" and not a "11100000").
- Support "RXGB" (DXT5, with red & alpha swapped) as well as "BC4U"
  and "BC5U" formats (the latter two are just BC4 and BC5, produced by
  some older tools).
- Support alpha-only (e.g. A8) formats.

All this mostly prompted by doing DDS improvements in Blender, and
wanting to get Cycles renderer (which uses OIIO) to the same feature
parity. See https://developer.blender.org/T101405 and
https://developer.blender.org/D16087.
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

No branches or pull requests

3 participants