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

Compress Vertex Normal/Tangent Vectors Using Octahedral Mapping #2395

Closed
The-O-King opened this issue Mar 3, 2021 · 10 comments
Closed

Compress Vertex Normal/Tangent Vectors Using Octahedral Mapping #2395

The-O-King opened this issue Mar 3, 2021 · 10 comments

Comments

@The-O-King
Copy link

The-O-King commented Mar 3, 2021

Describe the project you are working on

Godot Renderer

Describe the problem or limitation you are having in your project

Applications with large amounts of vertex data can be bottlenecked by memory bandwidth, especially on mobile devices; and applications with lower amounts of vertex data still throw away a lot of power/time/cache performance

Describe the feature / enhancement and how it helps to overcome the problem or limitation

A potential area of improvement is reducing vertex size, specifically compressing vertex normals and tangents, saving 4 bytes/vertex

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Currently normal and tangent compression is done by using 8-bit UNORM vec4 for the normal and tangent vectors each
Using spherical coordinates this can be reduced further down to 8-bit UNORM vec2 for the normal and tangent vectors each; since the radius of these normal vectors is always assumed to be 1 only phi and theta are needed

The decompression done in the shaders is a little more expensive (requiring sin/cos to get back cartesian coordinates) but from my tests it has not negatively affected performance on mobile and desktop devices - the memory bandwidth savings outweigh these costs; and visually there isn't any regression that I could see

Using Sphere Mapping, we can map a unit sphere down to a 2D plane, meaning we can represent any normal or tangent vector using two digits. This means we can use 8-bit SNORM vec2 for both the normal and tangent vector. The decompression in the vertex shader only needs some add/mul and a single sqrt, so it's cheaper to do then the spherical coordinates mentioned above and has better visual quality too! See this link for some details

I currently have an implementation for Godot 3.2 under GLES 3 ready - GLES2 is mostly complete but is stuck as currently I am using macros to define whether the normal or tangent vector is compressed (I define two new macros, one for normal and the other for tangents) and it seems I have hit the 32-bit limit of the bitmask used to enable/disable conditionals for the shader generator function, any tips on how to solve this (or if there is a better approach without using more conditionals) are welcome :)

Using Octahedral Mapping, we can map a unit sphere to an octahedron, which then can be mapped down to a 2D plane and be represented as "texture coordinates" into this plane. This is an improvement over sphere mapping as it has higher quality, avoids some of the singularities that sphere mapping can suffer from, and is at least as cheap to decode in the vertex shader. For more details, see this paper

If this enhancement will not be used often, can it be worked around with a few lines of script?

N/A

Is there a reason why this should be core and not an add-on in the asset library?

This is a low level change to the way that vertex data management is done in the renderer

Android Games DevTech Team @ Google

@Calinou
Copy link
Member

Calinou commented Mar 3, 2021

Related to #2350.

@clayjohn
Copy link
Member

clayjohn commented Mar 3, 2021

Related https://aras-p.info/texts/CompactNormalStorage.html#method03spherical

Similar optimization is possible for normal maps as well.

@The-O-King
Copy link
Author

Yes definitely! That article was a great resource for me :) could also be used in deferred rendering with gbuffers as described in that article haha

The reason I used spherical coordinates and not something like quaternions was because in Godot there is the option of having only the normals stored (so no tangents/normal maps) and with spherical coordinates you can store just the normal vector using 2 bytes which is convenient

Also quaternions do not support skew tangent spaces for normal mapping if artists use that (I don't have a lot of experience in this area but using the spherical coordinates basically keeps support for everything Godot already supports normal wise haha)

@BastiaanOlij
Copy link

Cool technique.

@clayjohn don't we already reduce normalmaps by leaving out one axis? Only there we do a simple z = sqrt(1.0 - x*x - y*y), wonder which technique is more efficient..

@clayjohn
Copy link
Member

@BastiaanOlij take a look at the article i linked above. Our current method is the fastest (but only by a small margin) and has much worse quality than the proposed method.

@BastiaanOlij
Copy link

@clayjohn sorry missed you article, cool read :)
Wonder whats causing the quality issue, I guess because we're using very small xy values (assuming z is pointing out of the surface)?

@The-O-King The-O-King changed the title Compress Vertex Normal/Tangent Vectors Using Spherical Coordinates Compress Vertex Normal/Tangent Vectors Using Sphere Mapping Mar 22, 2021
@The-O-King The-O-King changed the title Compress Vertex Normal/Tangent Vectors Using Sphere Mapping Compress Vertex Normal/Tangent Vectors Using Octahedral Mapping Apr 22, 2021
@The-O-King
Copy link
Author

After doing a bit of experimentation, I thought that it would be good to add an option for developers between oct16 and oct32 compression (oct = octahedral map). In most scenarios the oct16 looks perfect, but in the extreme scenarios (a high poly sphere with highly specular, mirror-like material) the compression artifacts do start to show up

Oct32 however is indistinguishable from the ground truth (float32x3) and takes up the same space as the existing normal compression (snorm8x4)

I have attached some examples I created showing off the differences

(float32x3) Ground Truth in stable 3.2
float32x3

(snorm8x4) Current Compression in stable 3.2
snorm8x3

(oct16) Currently Implemented in the proposal's PR, and would be the "super compress normal" option
oct16

(oct32) Proposed addition, not currently implemented, and would be the new "compress normal" option
oct32

I haven't updated the implementing CL (godotengine/godot#46800) yet as the change to add compression depends on the change in godotengine/godot#46574 which makes it easier to pass around the mesh compression flags, but wanted to bring this up for discussion!

@clayjohn
Copy link
Member

clayjohn commented May 6, 2021

@The-O-King Given your results, is it even worthwhile to expose ground truth? Maybe compressed should use oct16 while "uncompressed" uses oct32.

@The-O-King
Copy link
Author

That's a valid question for sure, I personally think that getting rid of ground truth makes sense, and might have additional positives because it will also reduce the number of shader permutations that we have to generate, since the vertex attributes will be the same format across compressed (oct16) vs uncompressed (oct32)

So yea I'm not sure what value there would be keeping around ground truth float32x3

@akien-mga
Copy link
Member

Implemented by godotengine/godot#46800.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants