How to sync css transform 3d to webgl?

Viewed 202

In css, I can use perspective + matrix3d to achieve the perspective effect of 3d transformation.

For example:

transform: perspective(500px) rotateY(45deg);

it equals to

matrix3d(0.707107, 0, -0.707107, 0.00141421, 0, 1, 0, 0, 0.707107, 0, 0.707107, -0.00141421, 0, 0, 0, 1)

<div style="display:inline-block;background: linear-gradient(#F00, #00F);width:100px;height:100px;transform:perspective(300px)rotateY(45deg)"></div>

<div style="display:inline-block;background: linear-gradient(#F00, #00F);width:100px;height:100px;transform:matrix3d(0.707107, 0, -0.707107, 0.00141421, 0, 1, 0, 0, 0.707107, 0, 0.707107, -0.00141421, 0, 0, 0, 1)"></div>

Also using this matrix in webgl, a rectangle (2 triangles) is rendered, but without the effect of perspective.

function initShaders(gl, vshader, fshader) {
    let program = createProgram(gl, vshader, fshader);
    if (!program) {
      return false;
    }

    gl.useProgram(program);
    gl.program = program;

    return true;
  }


  function createProgram(gl, vshader, fshader) {
    let vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
    let fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
    if (!vertexShader || !fragmentShader) {
      return null;
    }

    let program = gl.createProgram();
    if (!program) {
      return null;
    }

    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);

    let linked = gl.getProgramParameter(program, gl.LINK_STATUS);
    if (!linked) {
      let error = gl.getProgramInfoLog(program);
      gl.deleteProgram(program);
      gl.deleteShader(fragmentShader);
      gl.deleteShader(vertexShader);
      return null;
    }
    return program;
  }

  function loadShader(gl, type, source) {
    let shader = gl.createShader(type);
    if (shader == null) {
      return null;
    }

    gl.shaderSource(shader, source);
    gl.compileShader(shader);

    let compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
    if (!compiled) {
      let error = gl.getShaderInfoLog(shader);
      console.error('Failed to compile shader: ' + error);
      gl.deleteShader(shader);
      return null;
    }

    return shader;
  }

  let canvas = document.querySelector('#canvas');
  let gl = canvas.getContext('webgl');
  gl.clearColor(0.0, 0.0, 0.0, 0.0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  let vSource = `attribute vec4 a_position;
        attribute vec4 a_color;
        varying vec4 v_color;
        uniform mat4 u_matrix;

        void main() {
         gl_Position = u_matrix * a_position;
         v_color = a_color;
        }`;

  let fSource = `precision mediump float;

    varying vec4 v_color;

    void main() {
        gl_FragColor = v_color;
    }`;
  if (!initShaders(gl, vSource, fSource)) {
    console.error('init error');
  }

  let pointBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, pointBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    -0.5, -0.5,
    0.5, -0.5,
    -0.5, 0.5,
    0.5, -0.5,
    -0.5, 0.5,
    0.5, 0.5,
  ]), gl.STATIC_DRAW);
  let a_position = gl.getAttribLocation(gl.program, 'a_position');
  gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_position);

  let colorBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    0, 0, 1, 1,
    0, 0, 1, 1,
    1, 0, 0, 1,
    0, 0, 1, 1,
    1, 0, 0, 1,
    1, 0, 0, 1,
  ]), gl.STATIC_DRAW);
  let a_color = gl.getAttribLocation(gl.program, 'a_color');
  gl.vertexAttribPointer(a_color, 4, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_color);
  let u_matrix = gl.getUniformLocation(gl.program, 'u_matrix');

  // same matrix as css above
  gl.uniformMatrix4fv(u_matrix, false, new Float32Array([
    0.707107, 0, -0.707107, 0.00141421, 0, 1, 0, 0, 0.707107, 0, 0.707107, -0.00141421, 0, 0, 0, 1
  ]));
  gl.drawArrays(gl.TRIANGLES, 0, 6);
<canvas id="canvas" style="height: 200px;width: 200px"></canvas>

How should I get a matrix in webgl that has the equivalent effect as the css transform matrix?

Lastly, I should note that I have already read WebGL 3D Perspective, but after all of that, I still confused about this question.

1 Answers

I've tried to combine the method from WebGL 3D Perspective and your matrix, the fudgeFactor has a 100 times factor (-0.2 in webgl but -0.002 in css). Further exploration shows that this factor is related to the image size, for a 200px * 200px DOM fudgeFactor is -0.1 in webgl while -0.002 in css.

var m4 = {

  projection: function(width, height, depth) {
    // Note: This matrix flips the Y axis so 0 is at the top.
    return [
       2 / width, 0, 0, 0,
       0, -2 / height, 0, 0,
       0, 0, 2 / depth, 0,
      -1, 1, 0, 1,
    ];
  },

  multiply: function(a, b) {
    var a00 = a[0 * 4 + 0];
    var a01 = a[0 * 4 + 1];
    var a02 = a[0 * 4 + 2];
    var a03 = a[0 * 4 + 3];
    var a10 = a[1 * 4 + 0];
    var a11 = a[1 * 4 + 1];
    var a12 = a[1 * 4 + 2];
    var a13 = a[1 * 4 + 3];
    var a20 = a[2 * 4 + 0];
    var a21 = a[2 * 4 + 1];
    var a22 = a[2 * 4 + 2];
    var a23 = a[2 * 4 + 3];
    var a30 = a[3 * 4 + 0];
    var a31 = a[3 * 4 + 1];
    var a32 = a[3 * 4 + 2];
    var a33 = a[3 * 4 + 3];
    var b00 = b[0 * 4 + 0];
    var b01 = b[0 * 4 + 1];
    var b02 = b[0 * 4 + 2];
    var b03 = b[0 * 4 + 3];
    var b10 = b[1 * 4 + 0];
    var b11 = b[1 * 4 + 1];
    var b12 = b[1 * 4 + 2];
    var b13 = b[1 * 4 + 3];
    var b20 = b[2 * 4 + 0];
    var b21 = b[2 * 4 + 1];
    var b22 = b[2 * 4 + 2];
    var b23 = b[2 * 4 + 3];
    var b30 = b[3 * 4 + 0];
    var b31 = b[3 * 4 + 1];
    var b32 = b[3 * 4 + 2];
    var b33 = b[3 * 4 + 3];
    return [
      b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30,
      b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31,
      b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32,
      b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33,
      b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30,
      b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31,
      b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32,
      b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33,
      b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30,
      b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31,
      b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32,
      b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33,
      b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30,
      b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31,
      b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32,
      b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33,
    ];
  },

  translation: function(tx, ty, tz) {
    return [
       1,  0,  0,  0,
       0,  1,  0,  0,
       0,  0,  1,  0,
       tx, ty, tz, 1,
    ];
  },

  xRotation: function(angleInRadians) {
    var c = Math.cos(angleInRadians);
    var s = Math.sin(angleInRadians);

    return [
      1, 0, 0, 0,
      0, c, s, 0,
      0, -s, c, 0,
      0, 0, 0, 1,
    ];
  },

  yRotation: function(angleInRadians) {
    var c = Math.cos(angleInRadians);
    var s = Math.sin(angleInRadians);

    return [
      c, 0, -s, 0,
      0, 1, 0, 0,
      s, 0, c, 0,
      0, 0, 0, 1,
    ];
  },

  zRotation: function(angleInRadians) {
    var c = Math.cos(angleInRadians);
    var s = Math.sin(angleInRadians);

    return [
       c, s, 0, 0,
      -s, c, 0, 0,
       0, 0, 1, 0,
       0, 0, 0, 1,
    ];
  },

  scaling: function(sx, sy, sz) {
    return [
      sx, 0,  0,  0,
      0, sy,  0,  0,
      0,  0, sz,  0,
      0,  0,  0,  1,
    ];
  },

  translate: function(m, tx, ty, tz) {
    return m4.multiply(m, m4.translation(tx, ty, tz));
  },

  xRotate: function(m, angleInRadians) {
    return m4.multiply(m, m4.xRotation(angleInRadians));
  },

  yRotate: function(m, angleInRadians) {
    return m4.multiply(m, m4.yRotation(angleInRadians));
  },

  zRotate: function(m, angleInRadians) {
    return m4.multiply(m, m4.zRotation(angleInRadians));
  },

  scale: function(m, sx, sy, sz) {
    return m4.multiply(m, m4.scaling(sx, sy, sz));
  },

};
function initShaders(gl, vshader, fshader) {
    let program = createProgram(gl, vshader, fshader);
    if (!program) {
      return false;
    }

    gl.useProgram(program);
    gl.program = program;

    return true;
  }


  function createProgram(gl, vshader, fshader) {
    let vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
    let fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
    if (!vertexShader || !fragmentShader) {
      return null;
    }

    let program = gl.createProgram();
    if (!program) {
      return null;
    }

    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);

    let linked = gl.getProgramParameter(program, gl.LINK_STATUS);
    if (!linked) {
      let error = gl.getProgramInfoLog(program);
      gl.deleteProgram(program);
      gl.deleteShader(fragmentShader);
      gl.deleteShader(vertexShader);
      return null;
    }
    return program;
  }

  function loadShader(gl, type, source) {
    let shader = gl.createShader(type);
    if (shader == null) {
      return null;
    }

    gl.shaderSource(shader, source);
    gl.compileShader(shader);

    let compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
    if (!compiled) {
      let error = gl.getShaderInfoLog(shader);
      console.error('Failed to compile shader: ' + error);
      gl.deleteShader(shader);
      return null;
    }

    return shader;
  }

  let canvas = document.querySelector('#canvas');
  let gl = canvas.getContext('webgl');
  gl.clearColor(0.0, 0.0, 0.0, 0.0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  let vSource = `attribute vec4 a_position;
        attribute vec4 a_color;
        varying vec4 v_color;
        uniform mat4 u_matrix;

        void main() {
         gl_Position = u_matrix * a_position;
         v_color = a_color;
        }`;

  let fSource = `precision mediump float;

    varying vec4 v_color;

    void main() {
        gl_FragColor = v_color;
    }`;
  if (!initShaders(gl, vSource, fSource)) {
    console.error('init error');
  }

  let pointBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, pointBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    -0.5, -0.5,
    0.5, -0.5,
    -0.5, 0.5,
    0.5, -0.5,
    -0.5, 0.5,
    0.5, 0.5,
  ]), gl.STATIC_DRAW);
  let a_position = gl.getAttribLocation(gl.program, 'a_position');
  gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_position);

  let colorBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    0, 0, 1, 1,
    0, 0, 1, 1,
    1, 0, 0, 1,
    0, 0, 1, 1,
    1, 0, 0, 1,
    1, 0, 0, 1,
  ]), gl.STATIC_DRAW);
  let a_color = gl.getAttribLocation(gl.program, 'a_color');
  gl.vertexAttribPointer(a_color, 4, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_color);
  let u_matrix = gl.getUniformLocation(gl.program, 'u_matrix');
  
  function makeZToWMatrix(fudgeFactor) {
    return [
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, fudgeFactor,
      0, 0, 0, 1,
    ];
  }

  // same matrix as css above
  gl.uniformMatrix4fv(u_matrix, false, new Float32Array(m4.multiply(makeZToWMatrix(-100/200), m4.yRotate([
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1,
    ], 80 / 180 * Math.PI))));
  gl.drawArrays(gl.TRIANGLES, 0, 6);
#canvas {
 position: absolute;
 left: -50px;
 top: -50px;
}
<canvas id="canvas" style="height: 200px;width: 200px"></canvas>
<div style="position: absolute; left: 0; top: 0; opacity: 0.5; display:inline-block;background: blue;width:100px;height:100px;transform:perspective(200px) rotateY(80deg)"></div>

Related