tmf icon indicating copy to clipboard operation
tmf copied to clipboard

Octahedron normals

Open CptPotato opened this issue 1 year ago • 3 comments

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.

CptPotato avatar Jun 03 '23 12:06 CptPotato

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.

FractalFir avatar Jun 04 '23 16:06 FractalFir

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.

novacrazy avatar Jun 10 '23 22:06 novacrazy

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.

CptPotato avatar Jun 11 '23 08:06 CptPotato