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

Octahedron normals #12

Open
CptPotato opened this issue Jun 3, 2023 · 3 comments
Open

Octahedron normals #12

CptPotato opened this issue Jun 3, 2023 · 3 comments

Comments

@CptPotato
Copy link

CptPotato commented Jun 3, 2023

Hey there!

Have you considered encoding normals using octahedron mapping? It's a neat way to map a direction vector into two components and it has a more uniform distribution than storing (angle, z).

I haven't profiled it against the current implementation, but here's some sample code:

code
use glam::{Vec2, Vec3};

/// Encode a 3d direction vector to a 2d vector using octahedron mapping.
/// The output vector is in the range [-1..1]. The input vector doesn't have to be normalized.
pub fn encode_oct(dir: Vec3) -> Vec2 {
    let norm = dir.x.abs() + dir.y.abs() + dir.z.abs();
    let nx = dir.x / norm;
    let ny = dir.y / norm;
    if dir.z.is_sign_positive() {
        Vec2::new(nx, ny)
    } else {
        // fold over negative z
        Vec2::new(
            (1.0 - ny.abs()) * nx.signum(),
            (1.0 - nx.abs()) * ny.signum(),
        )
    }
}

/// Decode an octahedron mapped direction vector back to the original one.
/// The output is normalized.
pub fn decode_oct(mut oct: Vec2) -> Vec3 {
    let z = 1.0 - oct.x.abs() - oct.y.abs();
    oct += oct.signum() * z.min(0.0);
    Vec3::new(oct.x, oct.y, z).normalize()
}

If you want to, I could open a PR to compare it to the current impl.

@FractalFir
Copy link
Owner

Great suggestion. From what it looks like, it should be faster (since it uses no trigonometric functions). If you would like to work on it, then I will gladly help. What you would need to implement is mainly 3 functions: one to encode, another to decode, and one which will convert an angle(in radians or degrees) which would return a minimal number of bits required to save the normal with a given precision. Their signature could look something like that:

fn encode(normal:Vec3,prec_bits:u8)->(f64,f64)
fn decode(a:f64,b:f64)->Vector3
fn bits_from_angle(angle:f64)->u8

I can then take those functions and add tmf-specific stuff around them.
However, this may take me some time(I am going on a short vacation soon and the project has some work that is has more impact). This is the ranking of size of different segments in the example mesh:

  1. NormalSegment: 16.92 kb
  2. UvSegment: 28.559 kb,
  3. VertexSegment: 41.839 kb,
  4. NormalTriangleSegment: 74.119 kb,
  5. VertexTriangleSegment: 76.101 kb,
  6. UvTriangleSegment: 76.593 kb,
    Total size: 314.131kb
    As you can see, normals themselves already do not take all that much space, so the size reduction would be small, but the improvement in speed could be a nice bonus.

@novacrazy
Copy link

The hemioct method on page 27 of https://jcgt.org/published/0003/02/01/paper.pdf is also worth looking into. Greater accuracy than oct32p.

@CptPotato
Copy link
Author

The hemioct method on page 27 of https://jcgt.org/published/0003/02/01/paper.pdf is also worth looking into. Greater accuracy than oct32p.

This seems like a good idea for normal maps, but I'm not sure about arbitrary vectors since you have to worry about the other hemisphere as well (adding a z sign bit, and at that point the accuracy/size improvement is lost). It could be faster, though.

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 a pull request may close this issue.

3 participants