Преобразование нормалей в webgl

У меня есть 2 облака точек с нормалями. Перед визуализацией точек на холсте webgl мне нужно преобразовать PointCloud1 в то же координатное пространство, что и PointCloud2, и у меня есть матрица преобразования T (rot, scale, trans), которая идеально подходит для точек. Я хочу сделать то же самое для нормалей. Прочитав несколько руководств, я попробовал это:

n '= транспонировать (обратный (T)) * n

Я понимаю математику, стоящую за этим, но в некоторых учебниках упоминается, что я должен брать только верхнюю матрицу 3 * 3 в T. Я не уверен, следует ли мне это делать.

  1. умножить на верхний 3*3 T с n = [a b c] или
  2. умножьте на 4*4 T с n = [a b c 0].

Если я воспользуюсь вторым вариантом, я получу n' = [a' b' c' d']. Должен ли я разделить этот вектор на d', чтобы сделать его однородным?


person userJS    schedule 03.06.2015    source источник


Ответы (3)


Поскольку вы говорите, что используете только повороты, переводы и масштабирование, вам не нужно беспокоиться обо всем этом обратном транспонировании. По крайней мере, пока под «масштабированием» вы подразумеваете равномерное масштабирование. Если вы используете неравномерное масштабирование, обычное преобразование требует особого внимания (см. мой ответ здесь для получения информации о неравномерном масштабировании: нормальная матрица для неравномерного масштабирования).

Обычно верно, что обратное транспонирование верхней левой подматрицы 3x3 используется для преобразования нормалей. Но в этом случае верхняя левая подматрица 3x3 содержит только вращение и масштабирование. Матрицы обратного транспонирования вращения и масштабирования такие же, как исходная матрица. Таким образом, весь расчет обратного транспонирования не работает.

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

Создаете ли вы явно отдельную матрицу 3x3 для нормалей или используете обычную матрицу 4x4 для вектора нормалей, расширенного нулевым компонентом, во многом зависит от предпочтений. Математически результат точно такой же. Преимущество использования матрицы 4x4 в том, что проще не передавать в шейдер лишнюю матрицу 3x3. Недостатком является то, что это потенциально менее эффективно, поскольку вы выполняете умножение с большей матрицей, чем необходимо.

На самом деле, лучший подход, ИМХО, состоит в том, чтобы передать в шейдер отдельную матрицу нормалей 3x3 и сделать так, чтобы она содержала только часть поворота вашего преобразования, но не масштабирование. Таким образом, вы можете пропустить шаг, на котором вам обычно приходится повторно нормализовать нормали после применения преобразования, поскольку чистое вращение оставляет длину вектора неизменной.

Что касается последней части вашего вопроса: если вы используете матрицу 4x4, не делите на 4-й компонент результирующего вектора. 4-й компонент будет равен нулю, и вы не хотите делить на ноль...

person Reto Koradi    schedule 04.06.2015
comment
Мое масштабирование действительно равномерное. Прямо сейчас я получил свою матрицу преобразования, выполнив масштабирование * вращение * перевод. Это матрица 4 * 4, как упоминалось ранее. Последним компонентом при умножении будет t41*nx + t42*ny + t43*nz + t44*0. Этот компонент не равен 0. Это причина моего замешательства. Но я понимаю, почему обратное транспонирование не нужно - person userJS; 04.06.2015

Вы можете сделать любой из них, так как они дадут тот же результат. Если четвертый компонент вектора равен нулю, эта строка матрицы не влияет на результат. Однако более эффективно использовать 3x3 в верхнем левом углу, так как это избавляет от ненужной математики.

Итак, процедура такова:

  1. Возьмите верхние 3x3 матрицы
  2. Примените обратное транспонирование к матрице 3x3
  3. Примените матрицу 3x3 к своему обычному
person Vincent    schedule 03.06.2015

Ответы выше хороши, но я покажу вам несколько фрагментов кода, которые я использую в WebGL, чтобы реализовать это.

Обычно мой вершинный шейдер GLSL выглядит примерно так. NormalMatrix — это матрица 3x3, вычисляемая на стороне клиента как обратная транспонирование верхней части 3x3 ModelViewMatrix и передаваемая как юниформ-переменная:

attribute vec4 vertexPosition;
attribute vec3 vertexNormal;
...

uniform mat4 ModelViewProjection;
uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
...

void main() {
   gl_Position = ModelViewProjection*vertexPosition;

   vec3 P = vec3(ModelViewMatrix * vertexPosition);
   vec3 N = normalize(NormalMatrix * vertexNormal);
   vec3 L = normalize(light0Position - P);

   ...
}

На стороне клиента мой класс Matrix4x4 имеет метод normal(), который извлекает соответствующую матрицу 3x3:

  var ModelView = View.mult(Model);
  var NormalMatrix = ModelView.normal();
  var MVP = Projection.mult(ModelView);
  gl.uniformMatrix4fv(program.ModelViewProjection, false, MVP.array);
  gl.uniformMatrix4fv(program.ModelViewMatrix, false, ModelView.array);
  gl.uniformMatrix3fv(program.NormalMatrix, false, NormalMatrix);

Я храню матрицы в порядке столбцов (GL):

function Matrix4x4() {
    this.array = [1, 0, 0, 0,   // stored in column-major order
                  0, 1, 0, 0,
                  0, 0, 1, 0,
                  0, 0, 0, 1];
}

Метод normal() фактически возвращает массив из 9 элементов, представленный матрицей 3x3 в порядке столбцов:

Matrix4x4.prototype.normal = function() {
    var M = this;
    var determinant =    
        +M.elem(0,0)*(M.elem(1,1)*M.elem(2,2) - M.elem(2,1)*M.elem(1,2))
        -M.elem(0,1)*(M.elem(1,0)*M.elem(2,2) - M.elem(1,2)*M.elem(2,0))
        +M.elem(0,2)*(M.elem(1,0)*M.elem(2,1) - M.elem(1,1)*M.elem(2,0));
    var invDet = 1.0/determinant;
    var normalMatrix = [];
    var N = function(row,col,val) { normalMatrix[col*3 + row] = val; }
    N(0,0, (M.elem(1,1)*M.elem(2,2) - M.elem(2,1)*M.elem(1,2))*invDet);
    N(1,0,-(M.elem(0,1)*M.elem(2,2) - M.elem(0,2)*M.elem(2,1))*invDet);
    N(2,0, (M.elem(0,1)*M.elem(1,2) - M.elem(0,2)*M.elem(1,1))*invDet);
    N(0,1,-(M.elem(1,0)*M.elem(2,2) - M.elem(1,2)*M.elem(2,0))*invDet);
    N(1,1, (M.elem(0,0)*M.elem(2,2) - M.elem(0,2)*M.elem(2,0))*invDet);
    N(2,1,-(M.elem(0,0)*M.elem(1,2) - M.elem(1,0)*M.elem(0,2))*invDet);
    N(0,2, (M.elem(1,0)*M.elem(2,1) - M.elem(2,0)*M.elem(1,1))*invDet);
    N(1,2,-(M.elem(0,0)*M.elem(2,1) - M.elem(2,0)*M.elem(0,1))*invDet);
    N(2,2, (M.elem(0,0)*M.elem(1,1) - M.elem(1,0)*M.elem(0,1))*invDet);
    return normalMatrix;
}

Вы можете найти мой клиентский модуль matrix.js здесь.

person wcochran    schedule 04.06.2015