steps3D - Octahedral mapping

Octahedral mapping

Натолкнулся в ряде публикаций на использование интересного метода компактного хранения единичных векторов без их сильного искажения.

Пусть у нас есть единичные вектора, но без сильной корреляции между соседними. Например, это могут быть значения нормали в G-буфере, которые мы хотим хранить максимально компактно и без заметной потери точности. Наиболее очевидное решение - это сферические координаты, но у них есть две особых точки - полюса. Кроме того точность распределена сильно неравномерно - у экватора она явно оставляет желать лучшего.

Простой и красивый метод довольно часто встречается, причем в самых разных задачах, вплоть до расчета глобального освещения. Давайте вместо единичной сферы рассмотрим единичный октаэдр. Тогда каждой грани октаэдра будет соответствовать сегмент сферы. Спроектируем октаэдр на плоскость \(z=0\) - результат полностью ляжет в квадрат \([-1,1]^2\). Но при этом половине точек квадрата будет соответствовать две точки октаэдра, а половине - ни одной.

Возьмем четыре нижние грани октаэдра и отобразим их на свободные участки квадрата как показано на рисунке выше. В результате мы получаем взаимно-однозначное отображение октаэдра на квадрат, т.е. каждой точке квадрата соответствует ровно одна точка на октаэдре и, значит, ровно один единичный вектор.

При этом искажения равномерно распределены по всем направлениям и довольно невелики. Ниже приводится код на GLSL, реализующий данное отображение.


    // Returns ±1
vec2 signNotZero ( in vec2 v )
{
    return vec2 ( (v.x >= 0.0) ? +1.0 : -1.0, (v.y >= 0.0) ? +1.0 : -1.0 );
}
    // Assume normalized input.
    // Output is on [-1, 1] for each component.
vec2 toOct ( in vec3 v )
{
        // Project the sphere onto the octahedron,
        // and then onto the xy plane
    vec2 p = v.xy * (1.0 / (abs(v.x) + abs(v.y) + abs(v.z)));
         // Reflect the folds of the lower hemisphere 
         // over the diagonals
    return (v.z <= 0.0) ? ((1.0 - abs(p.yx)) * signNotZero(p)) : p;

}

vec3 fromOct ( in vec2 e )
{
    vec3 v = vec3 ( e.xy, 1.0 - abs ( e.x ) - abs ( e.y ) );

    if (v.z < 0 ) 
        v.xy = ( 1.0 - abs ( v.yx ) ) * signNotZero ( v.xy );
    
    return normalize ( v );

}

Исходная статья с обзором ряда подобных методов - A Survey of Efficient Representations for Independent Unit Vectors.