Spec-Zone.ru › Web APIs

Матричная математика для веб

Матрицы могут использоваться для представления преобразований объектов в пространстве и применяются для выполнения многих ключевых типов вычислений при построении изображений и визуализации данных в веб-приложениях. В этой статье рассматривается, как создавать матрицы и как использовать их с преобразованиями CSS и типом преобразования matrix3d.

Хотя в этой статье для упрощения объяснений используется CSS, матрицы являются основополагающим понятием, используемым многими технологиями, включая WebGL, API WebXR (VR и AR) и GLSL шейдеры. Эта статья также доступна в виде набора контента MDN. В примерах используются набор утилитарных функций, доступных в глобальном объекте с именем MDN.

Матрицы преобразования

Существует множество типов матриц, но нас интересуют матрицы 3D преобразования. Эти матрицы состоят из набора из 16 значений, расположенных в сетке 4×4. В JavaScript матрицу легко представить в виде массива.

Начнем с рассмотрения единичной матрицы. Это специальная матрица преобразования, которая действует аналогично числу 1 в скалярном умножении; точно так же, как n * 1 = n, умножение любой матрицы на единичную матрицу приводит к матрице результата, значения которой совпадают со значениями исходной матрицы.

Единичная матрица выглядит так в JavaScript:

let identityMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];

Что означает умножение на единичную матрицу? Самый простой пример — умножение одной точки на единичную матрицу. Так как 3D точка требует только трех значений (x, y, и z ), а матрица преобразования — это матрица из 4×4 значений, нам нужно добавить четвертое измерение к точке. По соглашению, это измерение называется перспективой и обозначается буквой w. Для типичной позиции установка w в 1 позволит сделать математику корректной.

После добавления компонента w к точке, обратите внимание на аккуратное выравнивание матрицы и точки:

[1, 0, 0, 0,
 0, 1, 0, 0,
 0, 0, 1, 0,
 0, 0, 0, 1]

[4, 3, 2, 1] // Point at [x, y, z, w]

Компонент w имеет некоторые дополнительные применения, которые выходят за рамки этой статьи. Подробнее о его полезности см. статью WebGL model view projection.

Умножение матрицы и точки

В нашем примере кода определена функция для умножения матрицы и точки — multiplyMatrixAndPoint():

// point • matrix
function multiplyMatrixAndPoint(matrix, point) {
  // Give a simple variable name to each part of the matrix, a column and row number
  let c0r0 = matrix[0],
    c1r0 = matrix[1],
    c2r0 = matrix[2],
    c3r0 = matrix[3];
  let c0r1 = matrix[4],
    c1r1 = matrix[5],
    c2r1 = matrix[6],
    c3r1 = matrix[7];
  let c0r2 = matrix[8],
    c1r2 = matrix[9],
    c2r2 = matrix[10],
    c3r2 = matrix[11];
  let c0r3 = matrix[12],
    c1r3 = matrix[13],
    c2r3 = matrix[14],
    c3r3 = matrix[15];

  // Now set some simple names for the point
  let x = point[0];
  let y = point[1];
  let z = point[2];
  let w = point[3];

  // Multiply the point against each part of the 1st column, then add together
  let resultX = x * c0r0 + y * c0r1 + z * c0r2 + w * c0r3;

  // Multiply the point against each part of the 2nd column, then add together
  let resultY = x * c1r0 + y * c1r1 + z * c1r2 + w * c1r3;

  // Multiply the point against each part of the 3rd column, then add together
  let resultZ = x * c2r0 + y * c2r1 + z * c2r2 + w * c2r3;

  // Multiply the point against each part of the 4th column, then add together
  let resultW = x * c3r0 + y * c3r1 + z * c3r2 + w * c3r3;

  return [resultX, resultY, resultZ, resultW];
}

Примечание: В наших примерах на этой странице используются строчные векторы для представления точек и правое умножение для применения матриц преобразования. То есть, выше выполняется point * matrix где point — строчный вектор 4x1. Если вы хотите использовать столбцовые векторы и левое умножение, вам нужно соответствующим образом скорректировать функцию умножения и транспонировать каждую представленную ниже матрицу.

Например, матрица translationMatrix, представленная ниже, изначально выглядит так:

[1, 0, 0, 0,
 0, 1, 0, 0,
 0, 0, 1, 0,
 x, y, z, 1]

После транспонирования она будет выглядеть так:

[1, 0, 0, x,
 0, 1, 0, y,
 0, 0, 1, z,
 0, 0, 0, 1]

Теперь, используя функцию выше, мы можем умножить точку на матрицу. Используя единичную матрицу, она должна вернуть точку, идентичную исходной, поскольку точка (или любая другая матрица), умноженная на единичную матрицу, всегда равна сама себе:

// sets identityResult to [4,3,2,1]
let identityResult = multiplyMatrixAndPoint(identityMatrix, [4, 3, 2, 1]);

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

Умножение двух матриц

Помимо умножения матрицы на точку, вы также можете умножать две матрицы друг на друга. Функция из вышеизложенного может быть повторно использована для помощи в этом процессе:

//matrixB • matrixA
function multiplyMatrices(matrixA, matrixB) {
  // Slice the second matrix up into rows
  let row0 = [matrixB[0], matrixB[1], matrixB[2], matrixB[3]];
  let row1 = [matrixB[4], matrixB[5], matrixB[6], matrixB[7]];
  let row2 = [matrixB[8], matrixB[9], matrixB[10], matrixB[11]];
  let row3 = [matrixB[12], matrixB[13], matrixB[14], matrixB[15]];

  // Multiply each row by matrixA
  let result0 = multiplyMatrixAndPoint(matrixA, row0);
  let result1 = multiplyMatrixAndPoint(matrixA, row1);
  let result2 = multiplyMatrixAndPoint(matrixA, row2);
  let result3 = multiplyMatrixAndPoint(matrixA, row3);

  // Turn the result rows back into a single matrix
  return [
    result0[0],
    result0[1],
    result0[2],
    result0[3],
    result1[0],
    result1[1],
    result1[2],
    result1[3],
    result2[0],
    result2[1],
    result2[2],
    result2[3],
    result3[0],
    result3[1],
    result3[2],
    result3[3],
  ];
}

Давайте посмотрим на эту функцию в действии:

let someMatrix = [4, 0, 0, 0, 0, 3, 0, 0, 0, 0, 5, 0, 4, 8, 4, 1];

let identityMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];

// Returns a new array equivalent to someMatrix
let someMatrixResult = multiplyMatrices(identityMatrix, someMatrix);

Предупреждение: Эти функции матриц написаны для ясности объяснения, а не для скорости или управления памятью. Эти функции создают множество новых массивов, что может быть особенно дорогостоящим при реальных операциях в реальном времени из-за сбора мусора. В реальном коде производства лучше использовать оптимизированные функции. glMatrix — пример библиотеки, которая сосредоточена на скорости и производительности. В библиотеке glMatrix делается упор на целевые массивы, которые выделяются перед циклом обновления.

Матрица переноса

Матрица переноса основана на единичной матрице и используется в 3D графике для перемещения точки или объекта в одном или нескольких из трех направлений (x, y, и/или z). Самый простой способ понять перенос — представить себе, как вы поднимаете чашку кофе. Чашку кофе нужно держать вертикально и в той же ориентации, чтобы не пролить кофе. Она может двигаться вверх над столом и вокруг в пространстве.

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

let x = 50;
let y = 100;
let z = 0;

let translationMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1];

Расположите расстояния вдоль трех осей в соответствующих позициях в матрице переноса, а затем умножьте ее на точку или матрицу, которую вам нужно переместить в 3D пространстве.

Работа с DOM с помощью матрицы

Очень простой способ начать использовать матрицу — использовать функцию CSS matrix3d() transform. Сначала мы создадим простой <div> с некоторым содержимым. Стиль не показан, но он задан фиксированной шириной и высотой и центрирован на странице. Элемент <div> имеет заданный переход для преобразования, что позволяет легко увидеть выполняемые действия при анимации матрицы.

<div id="move-me" class="transformable">
  <h2>Move me with a matrix</h2>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit…</p>
</div>

Наконец, для каждого примера мы будем генерировать матрицу 4×4, а затем обновлять стиль <div> так, чтобы в нем применялось преобразование, заданное как matrix3d. Имейте в виду, что хотя матрица состоит из 4 строк и 4 столбцов, она сводится к одной строке из 16 значений. В JavaScript матрицы всегда хранятся в одномерных списках.

// Create the matrix3d style property from a matrix array
function matrixArrayToCssMatrix(array) {
  return `matrix3d(${array.join(",")})`;
}

// Grab the DOM element
let moveMe = document.getElementById("move-me");

// Returns a result like: "matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 50, 100, 0, 1);"
let matrix3dRule = matrixArrayToCssMatrix(translationMatrix);

// Set the transform
moveMe.style.transform = matrix3dRule;

Посмотреть на JSFiddle

An example of matrix translation

Матрица масштабирования

Матрица масштабирования увеличивает или уменьшает размер объекта в одном или нескольких из трех измерений: ширине, высоте и глубине. В типичных (декартовых) координатах это приводит к растяжению или сжатию объекта в соответствующих направлениях.

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

let w = 1.5; // width  (x)
let h = 0.7; // height (y)
let d = 1; // depth  (z)

let scaleMatrix = [w, 0, 0, 0, 0, h, 0, 0, 0, 0, d, 0, 0, 0, 0, 1];

Посмотреть на JSFiddle

An example of matrix scaling

Матрица вращения

Матрица вращения используется для поворота точки или объекта. Матрицы вращения выглядят немного сложнее, чем матрицы масштабирования и преобразования. Они используют тригонометрические функции для выполнения поворота. Хотя в этом разделе не будут подробно разбираться все шаги (см. эту статью на Wolfram MathWorld для получения подробностей), рассмотрим пример для наглядности.

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

// Manually rotating a point about the origin without matrices
let point = [10, 2];

// Calculate the distance from the origin
let distance = Math.sqrt(point[0] * point[0] + point[1] * point[1]);

// The equivalent of 60 degrees, in radians
let rotationInRadians = Math.PI / 3;

let transformedPoint = [
  Math.cos(rotationInRadians) * distance,
  Math.sin(rotationInRadians) * distance,
];

Можно закодировать такие шаги в матрицу и сделать это для каждого из x, y, и z измерений. Ниже представлено представление против часовой стрелки о вращении вокруг оси Z в левосторонней системе координат:

let sin = Math.sin;
let cos = Math.cos;

// NOTE: There is no perspective in these transformations, so a rotation
//       at this point will only appear to only shrink the div

let a = Math.PI * 0.3; //Rotation amount in radians

// Rotate around Z axis
let rotateZMatrix = [
  cos(a),
  -sin(a),
  0,
  0,
  sin(a),
  cos(a),
  0,
  0,
  0,
  0,
  1,
  0,
  0,
  0,
  0,
  1,
];

Посмотреть на JSFiddle

An example of matrix rotation.

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

function rotateAroundXAxis(a) {
  return [1, 0, 0, 0, 0, cos(a), -sin(a), 0, 0, sin(a), cos(a), 0, 0, 0, 0, 1];
}

function rotateAroundYAxis(a) {
  return [cos(a), 0, sin(a), 0, 0, 1, 0, 0, -sin(a), 0, cos(a), 0, 0, 0, 0, 1];
}

function rotateAroundZAxis(a) {
  return [cos(a), -sin(a), 0, 0, sin(a), cos(a), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
}

Посмотреть на JSFiddle

Композиция матриц

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

Порядок умножения матриц имеет значение. При умножении чисел a * b = c и b * a = c оба верны. Например, 3 * 4 = 12, и 4 * 3 = 12. В математике эти числа описываются как коммутативные. Матрицы не гарантируют сохранение равенства при смене порядка, поэтому матрицы некоммутативны.

Другая головоломка состоит в том, что умножение матриц в WebGL и CSS должно происходить в обратном порядке, чем интуитивно представляются операции. Например, чтобы уменьшить объект на 80%, переместить его вниз на 200 пикселей, а затем повернуть на 90 градусов вокруг начала координат, это будет выглядеть примерно так в псевдокоде.

transformation = rotate * translate * scale

Композиция нескольких преобразований

Функция, которую мы будем использовать для составления наших матриц, это multiplyArrayOfMatrices(), которая является частью набора функций для вспомогательных задач, представленных в начале этой статьи. Она принимает массив матриц и перемножает их, возвращая результат. В коде шейдеров WebGL это встроенно в язык, и можно использовать оператор *. Кроме того, в этом примере используются функции scale() и translate(), которые возвращают матрицы, как определено выше.

let transformMatrix = MDN.multiplyArrayOfMatrices([
  rotateAroundZAxis(Math.PI * 0.5), // Step 3: rotate around 90 degrees
  translate(0, 200, 0), // Step 2: move down 200 pixels
  scale(0.8, 0.8, 0.8), // Step 1: scale down
]);

Просмотреть на JSFiddle

An example of matrix composition

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

let transformMatrix = MDN.multiplyArrayOfMatrices([
  scale(1.25, 1.25, 1.25), // Step 6: scale back up
  translate(0, -200, 0), // Step 5: move back up
  rotateAroundZAxis(-Math.PI * 0.5), // Step 4: rotate back
  rotateAroundZAxis(Math.PI * 0.5), // Step 3: rotate around 90 degrees
  translate(0, 200, 0), // Step 2: move down 200 pixels
  scale(0.8, 0.8, 0.8), // Step 1: scale down
]);

Почему матрицы важны

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

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

© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Matrix_math_for_the_web

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API