Как я могу использовать матрицу из CSS array3d в шейдере WebGL2?

В настоящее время у меня есть преобразование CSS matrix3d, которое мне нравится, скажем, например:

transform: matrix3d(1.3453,0.1357,0.0,0.0003,0.2096,1.3453,0.0,0.0003,0.0,0.0,1.0,0.0,-100.0,-100.0,0.0,1.0);
transform-origin: 0 0;

Я использую холст WebGL2 и хотел бы, чтобы изображение, которое я на нем нарисовал, имело такое же преобразование.

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

#version 300 es

in vec2 a_position;
in vec2 a_texCoord;
uniform vec2 u_resolution;
out vec2 v_texCoord;

uniform mat4 u_matrix;

void main() {
  vec2 zeroToOne = a_position / u_resolution;
  vec2 zeroToTwo = zeroToOne * 2.0;
  vec2 clipSpace = zeroToTwo - 1.0;

  gl_Position = u_matrix*vec4(clipSpace * vec2(1, -1), 0, 1);
  v_texCoord = a_texCoord;
}

Затем я могу передать матрицу шейдеру в моем коде JavaScript:

const matrixLocation = gl.getUniformLocation(program, "u_matrix");
gl.uniformMatrix4fv(matrixLocation, false, new Float32Array(matrix));

Сейчас я застрял в выяснении того, каким должно быть значение matrix в моем коде. Я пытался прочитать о том, как устроено преобразование matrix3d, и на основе этого перестроить матрицу, но безуспешно.

Как я могу использовать здесь преобразование matrix3d в шейдере WebGL2?

Обновлено: полный рабочий пример добавлен по запросу в комментариях.

// Set up matrices

// Based on: matrix3d(1.3453,0.1357,0.0,0.0003,0.2096,1.3453,0.0,0.0003,0.0,0.0,1.0,0.0,-100.0,-100.0,0.0,1.0)
const cssMatrix = new Float32Array([1.3453, 0.1357, 0.0, 0.0003, 0.2096, 1.3453, 0.0, 0.0003, 0.0, 0.0, 1.0, 0.0, -100.0, -100.0, 0.0, 1.0]);
const identityMatrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);

// identityMatrix works correctly while the cssMatrix does not
const matrixArray = cssMatrix;
// const matrixArray = identityMatrix;

// Set up shaders

const vertexShaderSource = `#version 300 es

in vec2 a_position;
in vec2 a_texCoord;

uniform vec2 u_resolution;
uniform mat4 u_matrix;
out vec2 v_texCoord;

void main() {
  vec2 zeroToOne = a_position / u_resolution;
  vec2 zeroToTwo = zeroToOne * 2.0;
  vec2 clipSpace = zeroToTwo - 1.0;

  gl_Position = u_matrix*vec4(clipSpace * vec2(1, -1), 0,1);

  v_texCoord = a_texCoord;
}`;

const fragmentShaderSource = `#version 300 es

precision highp float;
uniform sampler2D u_image;
in vec2 v_texCoord;
out vec4 outColor;

void main() {
  outColor = texture(u_image, v_texCoord);
}`;

// Load the test image

const img = document.getElementById("image");
img.src = "https://i.imgur.com/NIt86ft.png";
img.onload = () => renderImg(img);

// Rest of this code is boilerplate based off of
// https://webgl2fundamentals.org/webgl/lessons/webgl-image-processing.html

function createProgram(
  gl, shaders, opt_attribs, opt_locations, opt_errorCallback) {
  const errFn = opt_errorCallback || console.error;
  const program = gl.createProgram();
  shaders.forEach(function(shader) {
    gl.attachShader(program, shader);
  });
  if (opt_attribs) {
    opt_attribs.forEach(function(attrib, ndx) {
      gl.bindAttribLocation(
        program,
        opt_locations ? opt_locations[ndx] : ndx,
        attrib);
    });
  }
  gl.linkProgram(program);

  // Check the link status
  const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (!linked) {
    // something went wrong with the link
    const lastError = gl.getProgramInfoLog(program);
    errFn('Error in program linking:' + lastError);

    gl.deleteProgram(program);
    return null;
  }
  return program;
}

function loadShader(gl, shaderSource, shaderType, opt_errorCallback) {
  const errFn = opt_errorCallback || console.error;
  // Create the shader object
  const shader = gl.createShader(shaderType);
  // Load the shader source
  gl.shaderSource(shader, shaderSource);
  // Compile the shader
  gl.compileShader(shader);
  // Check the compile status
  const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (!compiled) {
    // Something went wrong during compilation; get the error
    const lastError = gl.getShaderInfoLog(shader);
    console.error('*** Error compiling shader \'' + shader + '\':' + lastError + `\n` + shaderSource.split('\n').map((l, i) => `${i + 1}: ${l}`).join('\n'));
    gl.deleteShader(shader);
    return null;
  }
  return shader;
}

function createProgramFromSources(
  gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) {
  const shaders = [];
  const defaultShaderType = [
    'VERTEX_SHADER',
    'FRAGMENT_SHADER',
  ];
  for (let ii = 0; ii < shaderSources.length; ++ii) {
    shaders.push(loadShader(
      gl, shaderSources[ii], gl[defaultShaderType[ii]], opt_errorCallback));
  }
  return createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback);
}

function resizeCanvasToDisplaySize(canvas, multiplier) {
  multiplier = multiplier || 1;
  const width = canvas.clientWidth * multiplier | 0;
  const height = canvas.clientHeight * multiplier | 0;
  if (canvas.width !== width || canvas.height !== height) {
    canvas.width = width;
    canvas.height = height;
    return true;
  }
  return false;
}

function renderImg(image) {
  const canvas = document.getElementById("canvas");
  const gl = canvas.getContext("webgl2");
  if (!gl)
    return;

  var program = createProgramFromSources(gl, [vertexShaderSource, fragmentShaderSource]);

  var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
  var texCoordAttributeLocation = gl.getAttribLocation(program, "a_texCoord");
  var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
  var imageLocation = gl.getUniformLocation(program, "u_image");
  var matrixLocation = gl.getUniformLocation(program, "u_matrix");

  // Create a vertex array object (attribute state)
  var vao = gl.createVertexArray();

  // and make it the one we're currently working with
  gl.bindVertexArray(vao);

  // Create a buffer and put a single pixel space rectangle in
  // it (2 triangles)
  var positionBuffer = gl.createBuffer();

  // Turn on the attribute
  gl.enableVertexAttribArray(positionAttributeLocation);

  // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
  var size = 2; // 2 components per iteration
  var type = gl.FLOAT; // the data is 32bit floats
  var normalize = false; // don't normalize the data
  var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
  var offset = 0; // start at the beginning of the buffer
  gl.vertexAttribPointer(
    positionAttributeLocation, size, type, normalize, stride, offset);

  // provide texture coordinates for the rectangle.
  var texCoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    0.0, 0.0,
    1.0, 0.0,
    0.0, 1.0,
    0.0, 1.0,
    1.0, 0.0,
    1.0, 1.0,
  ]), gl.STATIC_DRAW);

  // Turn on the attribute
  gl.enableVertexAttribArray(texCoordAttributeLocation);

  // Tell the attribute how to get data out of texCoordBuffer (ARRAY_BUFFER)
  var size = 2; // 2 components per iteration
  var type = gl.FLOAT; // the data is 32bit floats
  var normalize = false; // don't normalize the data
  var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
  var offset = 0; // start at the beginning of the buffer
  gl.vertexAttribPointer(
    texCoordAttributeLocation, size, type, normalize, stride, offset);

  // Create a texture.
  var texture = gl.createTexture();

  // make unit 0 the active texture uint
  // (ie, the unit all other texture commands will affect
  gl.activeTexture(gl.TEXTURE0 + 0);

  // Bind it to texture unit 0's 2D bind point
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // Set the parameters so we don't need mips and so we're not filtering
  // and we don't repeat at the edges.
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

  // Upload the image into the texture.
  var mipLevel = 0; // the largest mip
  var internalFormat = gl.RGBA; // format we want in the texture
  var srcFormat = gl.RGBA; // format of data we are supplying
  var srcType = gl.UNSIGNED_BYTE; // type of data we are supplying
  gl.texImage2D(gl.TEXTURE_2D,
    mipLevel,
    internalFormat,
    srcFormat,
    srcType,
    image);

  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  setRectangle(gl, 0, 0, image.width, image.height);

  resizeCanvasToDisplaySize(gl.canvas);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  gl.clearColor(0, 0, 0, 0);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  gl.useProgram(program);
  gl.bindVertexArray(vao);
  gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
  gl.uniform1i(imageLocation, 0);

  gl.uniformMatrix4fv(matrixLocation, false, matrixArray);

  gl.drawArrays(gl.TRIANGLES, 0, 6);
}

function setRectangle(gl, x, y, width, height) {
  var x1 = x;
  var x2 = x + width;
  var y1 = y;
  var y2 = y + height;
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    x1, y1,
    x2, y1,
    x1, y2,
    x1, y2,
    x2, y1,
    x2, y2,
  ]), gl.STATIC_DRAW);
}
#image {
  transform: matrix3d(1.3453, 0.1357, 0.0, 0.0003, 0.2096, 1.3453, 0.0, 0.0003, 0.0, 0.0, 1.0, 0.0, -100.0, -100.0, 0.0, 1.0);
  transform-origin: 0 0;
}

img,
canvas {
  border: 1px solid #000;
}
<img src = "" crossOrigin = "" id = "image"><br>
<canvas id = "canvas" width=320 height=240></canvas>
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
0
59
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

CSS-матрица3d — это обычная однородная матрица размером 4x4, элементы которой упорядочены по строкам, поэтому в вашем случае строка 1 — это 1.3453, 0.1357, 0.0, 0.0003, строка 2 — 0.2096, 1.3453, 0.0, 0.0003, строка 3 — 0.0, 0.0, 1.0, 0.0 и строка 4 — -100.0, -100.0, 0.0, 1.0.

Преобразовать это в массив float32 довольно просто: вы можете либо работать со строкой CSS, измельчить ее, чтобы преобразовать числа в действительные числа, и «привести» их к массиву float32, либо воспользоваться преимуществом DOMMatrix объект и заставьте его выполнить преобразование за вас.

Например.

const matrixFromCSS = `matrix3d(.....)`;
const matrixAsFloat32Array = new Float32Array(
  matrixFromCSS 
    .replace(`matrix3d(`,``)
    .split(`,`)
    .map(v => parseFloat(v)
  )
);

или

const matrixFromCSS = `matrix3d(.....)`;
const matrixAsDOMMatrix = new DOMMatrix(matrixFromCSS);
const matrixAsFloat32Array = matrixAsDOMMatrix.toFloat32Array();

Простое преобразование, похоже, не работает, по крайней мере, не так, как я пытаюсь это сделать в посте. Результаты сильно отличаются от преобразования CSS Matrix3d, и именно эту часть я сейчас пытаюсь выяснить в своем вопросе.

rebane2001 27.06.2024 22:55

Тогда я настоятельно рекомендую вам написать небольшое доказательство концепции, которое вы можете включить в свой пост в виде работоспособного фрагмента — на самом деле для этого нужен только div с CSS-трансформацией, элемент холста и достаточно JS для настройки Canvas для WebGL, который копирует полученное вами CSS-преобразование getComputedStyle

Mike 'Pomax' Kamermans 27.06.2024 23:36

Добавил в пост доказательство концепции. Он корректно работает с единичной матрицей, но не с CSS. Пример должен быть довольно длинным, поскольку для webgl2 требуется много шаблонов — я не думаю, что его можно сделать намного короче.

rebane2001 28.06.2024 12:57
Ответ принят как подходящий

Я просто экспериментировал, а не искал в документации правильные математические вычисления, так что на самом деле это может не сработать, но для моего теста это работает. 😅

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

Другая проблема, с которой вы столкнетесь, заключается в том, что 3d css выталкивает элемент из обычного прямоугольника, который элемент будет заполнять, но WebGL не может рисовать за пределами холста.

Я также удалил transform-origin:, потому что это добавило бы математики. То же самое можно сказать и о perspective: или любых других преобразованиях в CSS.

Одно отличие от кода в вашем вопросе. Этот код умножал проецируемые положения вершин на матрицу (после деления на разрешение, умножения на 2 и вычитания 1).

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

// Set up matrices


// Based on: matrix3d(1.3453,0.1357,0.0,0.0003,0.2096,1.3453,0.0,0.0003,0.0,0.0,1.0,0.0,-100.0,-100.0,0.0,1.0)
const cssMatrix = new Float32Array([
  1.3453, 0.1357, 0.0, 0.0003, 
  0.2096, 1.3453, 0.0, 0.0003,
  0.0, 0.0, 1.0, 0.0,
  -100.0, -100.0, 0.0, 1.0,
]);
const identityMatrix = new Float32Array([
  1, 0, 0, 0,
  0, 1, 0, 0,
  0, 0, 1, 0,
  0, 0, 0, 1,
]);

// identityMatrix works correctly while the cssMatrix does not
const matrixArray = cssMatrix;
//const matrixArray = identityMatrix;

// Set up shaders

const vertexShaderSource = `#version 300 es

in vec4 a_position;
in vec2 a_texCoord;

uniform mat4 u_matrix;
out vec2 v_texCoord;

void main() {
  gl_Position = u_matrix * a_position;

  v_texCoord = a_texCoord;
}`;

const fragmentShaderSource = `#version 300 es

precision highp float;
uniform sampler2D u_image;
in vec2 v_texCoord;
out vec4 outColor;

void main() {
  outColor = texture(u_image, v_texCoord);
}`;

// Load the test image

const img = document.getElementById("image");
img.src = "https://i.imgur.com/NIt86ft.png";
img.onload = () => renderImg(img);

// Rest of this code is boilerplate based off of
// https://webgl2fundamentals.org/webgl/lessons/webgl-image-processing.html

function createProgram(
  gl, shaders, opt_attribs, opt_locations, opt_errorCallback) {
  const errFn = opt_errorCallback || console.error;
  const program = gl.createProgram();
  shaders.forEach(function(shader) {
    gl.attachShader(program, shader);
  });
  if (opt_attribs) {
    opt_attribs.forEach(function(attrib, ndx) {
      gl.bindAttribLocation(
        program,
        opt_locations ? opt_locations[ndx] : ndx,
        attrib);
    });
  }
  gl.linkProgram(program);

  // Check the link status
  const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (!linked) {
    // something went wrong with the link
    const lastError = gl.getProgramInfoLog(program);
    errFn('Error in program linking:' + lastError);

    gl.deleteProgram(program);
    return null;
  }
  return program;
}

function loadShader(gl, shaderSource, shaderType, opt_errorCallback) {
  const errFn = opt_errorCallback || console.error;
  // Create the shader object
  const shader = gl.createShader(shaderType);
  // Load the shader source
  gl.shaderSource(shader, shaderSource);
  // Compile the shader
  gl.compileShader(shader);
  // Check the compile status
  const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (!compiled) {
    // Something went wrong during compilation; get the error
    const lastError = gl.getShaderInfoLog(shader);
    console.error('*** Error compiling shader \'' + shader + '\':' + lastError + `\n` + shaderSource.split('\n').map((l, i) => `${i + 1}: ${l}`).join('\n'));
    gl.deleteShader(shader);
    return null;
  }
  return shader;
}

function createProgramFromSources(
  gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) {
  const shaders = [];
  const defaultShaderType = [
    'VERTEX_SHADER',
    'FRAGMENT_SHADER',
  ];
  for (let ii = 0; ii < shaderSources.length; ++ii) {
    shaders.push(loadShader(
      gl, shaderSources[ii], gl[defaultShaderType[ii]], opt_errorCallback));
  }
  return createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback);
}

function resizeCanvasToDisplaySize(canvas, multiplier) {
  multiplier = multiplier || 1;
  const width = canvas.clientWidth * multiplier | 0;
  const height = canvas.clientHeight * multiplier | 0;
  if (canvas.width !== width || canvas.height !== height) {
    canvas.width = width;
    canvas.height = height;
    return true;
  }
  return false;
}

function renderImg(image) {
  const canvas = document.getElementById("canvas");
  const gl = canvas.getContext("webgl2");
  if (!gl)
    return;

  var program = createProgramFromSources(gl, [vertexShaderSource, fragmentShaderSource]);

  var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
  var texCoordAttributeLocation = gl.getAttribLocation(program, "a_texCoord");
  var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
  var imageLocation = gl.getUniformLocation(program, "u_image");
  var matrixLocation = gl.getUniformLocation(program, "u_matrix");

  // Create a vertex array object (attribute state)
  var vao = gl.createVertexArray();

  // and make it the one we're currently working with
  gl.bindVertexArray(vao);

  // Create a buffer and put a single pixel space rectangle in
  // it (2 triangles)
  var positionBuffer = gl.createBuffer();

  // Turn on the attribute
  gl.enableVertexAttribArray(positionAttributeLocation);

  // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
  var size = 2; // 2 components per iteration
  var type = gl.FLOAT; // the data is 32bit floats
  var normalize = false; // don't normalize the data
  var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
  var offset = 0; // start at the beginning of the buffer
  gl.vertexAttribPointer(
    positionAttributeLocation, size, type, normalize, stride, offset);

  // provide texture coordinates for the rectangle.
  var texCoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    0.0, 0.0,
    1.0, 0.0,
    0.0, 1.0,
    0.0, 1.0,
    1.0, 0.0,
    1.0, 1.0,
  ]), gl.STATIC_DRAW);

  // Turn on the attribute
  gl.enableVertexAttribArray(texCoordAttributeLocation);

  // Tell the attribute how to get data out of texCoordBuffer (ARRAY_BUFFER)
  var size = 2; // 2 components per iteration
  var type = gl.FLOAT; // the data is 32bit floats
  var normalize = false; // don't normalize the data
  var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
  var offset = 0; // start at the beginning of the buffer
  gl.vertexAttribPointer(
    texCoordAttributeLocation, size, type, normalize, stride, offset);

  // Create a texture.
  var texture = gl.createTexture();

  // make unit 0 the active texture uint
  // (ie, the unit all other texture commands will affect
  gl.activeTexture(gl.TEXTURE0 + 0);

  // Bind it to texture unit 0's 2D bind point
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // Set the parameters so we don't need mips and so we're not filtering
  // and we don't repeat at the edges.
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

  // Upload the image into the texture.
  var mipLevel = 0; // the largest mip
  var internalFormat = gl.RGBA; // format we want in the texture
  var srcFormat = gl.RGBA; // format of data we are supplying
  var srcType = gl.UNSIGNED_BYTE; // type of data we are supplying
  gl.texImage2D(gl.TEXTURE_2D,
    mipLevel,
    internalFormat,
    srcFormat,
    srcType,
    image);

  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  setRectangle(gl, 0, 0, image.width, image.height);

  function render(time) {
    time *= 0.001; // convert to seconds
    resizeCanvasToDisplaySize(gl.canvas);
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    gl.clearColor(0, 0, 0, 0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.useProgram(program);
    gl.bindVertexArray(vao);
    gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
    gl.uniform1i(imageLocation, 0);

    // compute some CSSMatrix
    const m = m4.perspective(
      10 * Math.PI / 180,
      canvas.clientWidth / canvas.clientHeight,
      10, 500);
    const cm = m4.lookAt(
      [0, 0, 50],  // camera
      [0, 0, 0],   // target
      [0, 1, 0],   // up
    );
    const view = m4.inverse(cm);
    m4.multiply(m, view, m);
    m4.zRotate(m, time, m);
    m4.yRotate(m, Math.sin(time) * 0.25, m);
    
    // Convert the CSSMatrix to WebGL?
    // Note: There's an assumption here that the vertex data
    // is in pixels which is a poor assumption IMO. It would be better,
    // at least for the simple case, to use a unit square around the origin
    const mat = m4.identity();
    m4.orthographic(0, canvas.width, canvas.height, 0, -100, 100, mat);
    m4.translate(mat, img.width / 2, img.height / 2, 0, mat); 
    m4.multiply(mat, m, mat);
    m4.translate(mat, -img.width / 2, -img.height / 2, 0, mat); 
    gl.uniformMatrix4fv(matrixLocation, false, mat);

    img.style.transform = `matrix3d(${[...m].join(',')})`;

    gl.drawArrays(gl.TRIANGLES, 0, 6);
    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}

function setRectangle(gl, x, y, width, height) {
  var x1 = x;
  var x2 = x + width;
  var y1 = y;
  var y2 = y + height;
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    x1, y1,
    x2, y1,
    x1, y2,
    x1, y2,
    x2, y1,
    x2, y2,
  ]), gl.STATIC_DRAW);
}
#image {
}

img,
canvas {
  border: 1px solid #000;
}
<img src = "" crossOrigin = "" id = "image"><br>
<canvas id = "canvas" width=320 height=240></canvas>
<script src = "https://webgl2fundamentals.org/webgl/resources/m4.js"></script>

Приведенный выше код не использует вашу матрицу, но если вы закомментируете весь код, который управляет m, и просто измените его на const m = matrixArray, вы увидите, что они совпадают.

Другие вопросы по теме