如何在WebGL中旋转单个形状

时间:2019-02-07 19:10:52

标签: javascript webgl

过去一周来,我一直在努力尝试理解WebGL中的旋转形状。我正在绘制从它们自己的函数调用的3个形状。函数的基本结构有点像这样:

function drawShape(vertices) {

    var a = vertices.length / 2;
    gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    var vPosition = gl.getAttribLocation(program, "vPosition");
    
    gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(vPosition);


    gl.drawArrays(gl.TRIANGLE_FAN, 0, a);
  }

现在我已经渲染了每个形状函数被调用的地方。有点像这样:

function render() {
    angleInRadians += 0.1;
    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);   
 
    drawShape1();
    drawShape2();
  
    matrix = mat.rotation(angleInRadians);  
    
    gl.uniformMatrix3fv(matrixLocation, false, matrix);

    requestAnimFrame( render );

}

旋转功能为:

rotation: function(angle) {
      var a = Math.cos(angle);
      var b = Math.sin(angle);
      return [
        a,-b, 0,
        b, a, 0,
        0, 0, 1,
      ];
    },

我一直试图从3个形状中仅旋转1个。我尝试在函数drawArrays之前使用匀速3fv来绘制要旋转的形状,但所有形状都会随之旋转。如何只旋转一种形状?

2 个答案:

答案 0 :(得分:2)

首先,通常更常见的是在初始化时上传顶点数据,然后在渲染时使用它。您发布的代码是在渲染时上传顶点数据。在初始化时间而不是渲染时间查找属性位置也将更为常见。

换句话说,你有这个

function drawShape(vertices) {

    var a = vertices.length / 2;
    gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    var vPosition = gl.getAttribLocation(program, "vPosition");

    gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(vPosition);


    gl.drawArrays(gl.TRIANGLE_FAN, 0, a);
  }

但是做这样的事情会更常见

const attribs = {
  vPosition: gl.getAttribLocation(program, "vPosition"),
};

const shape = createShape(vertices);

...

drawShape(attribs, shape);

function createShape(vertices) {
  const bufferId = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
  return {
    bufferId,
    numVertices: vertices.length / 2,
  };
}

function drawShape(attribs, shape) {
  gl.bindBuffer(gl.ARRAY_BUFFER, shape.bufferId);
  gl.vertexAttribPointer(attribs.vPosition, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(attrib.vPosition);

  gl.drawArrays(gl.TRIANGLE_FAN, 0, shape.numVertices);
}

或类似的内容。

您还可能会在初始化时查找制服,然后传递制服并将其设置在drawShape内(或不设置在内)。通常,关键是通常在初始化时间调用gl.bufferDatagl.getUniformLocationgl.getAttribLocation

接下来,您需要每个形状设置一次制服。

matrix = mat.rotation(angleInRadiansForShape1);  
gl.uniformMatrix3fv(matrixLocation, false, matrix);
drawShape1();

matrix = mat.rotation(angleInRadiansForShape2);  
gl.uniformMatrix3fv(matrixLocation, false, matrix);
drawShape2();

但是您不仅需要添加旋转,否则所有形状都将出现在同一位置。

旋转矩阵绕原点(0,0)旋转。如果要围绕其他点旋转,请平移顶点。您可以通过将旋转矩阵乘以转换矩阵来实现。

由于它需要一个数学库并且每个库的使用方法都不相同,因此很难说明如何做到这一点。

This article介绍了如何使用本文中创建的函数对矩阵求和。

要移动旋转点,请执行此操作。

var matrix = m3.projection(gl.canvas.clientWidth, gl.canvas.clientHeight);    
matrix = m3.translate(matrix, whereToDrawX, whereToDrawY);
matrix = m3.rotate(matrix, angleToRotate);
matrix = m3.translate(matrix, offsetForRotationX, offsetForRotationY);

我通常从下至上阅读该书,

  1. m3.translate(matrix, offsetForRotationX, offsetForRotationY) =平移顶点,以便它们的原点是我们要旋转的位置。例如,如果我们有一个框,框的范围从0到10,向下为0到20,并且我们想在右下角旋转,那么我们需要将右下角移到0,0,这意味着我们需要平移-10,-20所以右下角在原点。

  2. m3.rotate(matrix, angleToRotate) =旋转

  3. m3.translate(matrix, whereToDrawX, whereToDrawY) =将其翻译到我们实际希望绘制的位置。

  4. m3.projection(gl.canvas.clientWidth, gl.canvas.clientHeight) =从像素转换为剪辑空间

示例:

"use strict";

function main() {
  // Get A WebGL context
  /** @type {HTMLCanvasElement} */
  var canvas = document.getElementById("canvas");
  var gl = canvas.getContext("webgl");
  if (!gl) {
    return;
  }

  // setup GLSL program
  var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);

  // look up where the vertex data needs to go.
  var positionLocation = gl.getAttribLocation(program, "a_position");

  // lookup uniforms
  var colorLocation = gl.getUniformLocation(program, "u_color");
  var matrixLocation = gl.getUniformLocation(program, "u_matrix");

  // Create a buffer to put positions in
  var positionBuffer = gl.createBuffer();
  // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  // Put geometry data into buffer
  setGeometry(gl);

  const shapes = [
    { 
      translation: [50, 75],
      scale: [0.5, 0.5],
      rotationOffset: [0, 0], // top left corner of F
      angleInRadians: 0,
      color: [1, 0, 0, 1], // red
    },
    { 
      translation: [100, 75],
      scale: [0.5, 0.5],
      rotationOffset: [-50, -75], // center of F
      angleInRadians: 0,
      color: [0, 1, 0, 1], // green
    },
    { 
      translation: [150, 75],
      scale: [0.5, 0.5],
      rotationOffset: [0, -150], // bottom left corner of F
      angleInRadians: 0,
      color: [0, 0, 1, 1], // blue
    },
    { 
      translation: [200, 75],
      scale: [0.5, 0.5],
      rotationOffset: [-100, 0], // top right corner of F
      angleInRadians: 0,
      color: [1, 0, 1, 1],  // magenta
    },
  ];

  requestAnimationFrame(drawScene);

  // Draw the scene.
  function drawScene(time) {
    time *= 0.001;  // seconds

    webglUtils.resizeCanvasToDisplaySize(gl.canvas);

    // Tell WebGL how to convert from clip space to pixels
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    // Clear the canvas.
    gl.clearColor(0, 0, 0, 0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    
    // draw a single black line to make the pivot clearer
    gl.enable(gl.SCISSOR_TEST);
    gl.scissor(0, 75, 300, 1);
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.disable(gl.SCISSOR_TEST);

    // Tell it to use our program (pair of shaders)
    gl.useProgram(program);

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

    // Bind the position buffer.
    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(
        positionLocation, size, type, normalize, stride, offset);

    for (const shape of shapes) {
      shape.angleInRadians = time;
      
      // set the color
      gl.uniform4fv(colorLocation, shape.color);

      // Compute the matrices
      var matrix = m3.projection(gl.canvas.clientWidth, gl.canvas.clientHeight);
      matrix = m3.translate(matrix, shape.translation[0], shape.translation[1]);
      matrix = m3.scale(matrix, shape.scale[0], shape.scale[1]);
      matrix = m3.rotate(matrix, shape.angleInRadians);
      matrix = m3.translate(matrix, shape.rotationOffset[0], shape.rotationOffset[1]);

      // Set the matrix.
      gl.uniformMatrix3fv(matrixLocation, false, matrix);

      // Draw the geometry.
      var primitiveType = gl.TRIANGLES;
      var offset = 0;
      var count = 18;  // 6 triangles in the 'F', 3 points per triangle
      gl.drawArrays(primitiveType, offset, count);
    }
    requestAnimationFrame(drawScene);
  }
}

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

  identity: function() {
    return [
      1, 0, 0,
      0, 1, 0,
      0, 0, 1,
    ];
  },

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

  rotation: function(angleInRadians) {
    var c = Math.cos(angleInRadians);
    var s = Math.sin(angleInRadians);
    return [
      c,-s, 0,
      s, c, 0,
      0, 0, 1,
    ];
  },

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

  multiply: function(a, b) {
    var a00 = a[0 * 3 + 0];
    var a01 = a[0 * 3 + 1];
    var a02 = a[0 * 3 + 2];
    var a10 = a[1 * 3 + 0];
    var a11 = a[1 * 3 + 1];
    var a12 = a[1 * 3 + 2];
    var a20 = a[2 * 3 + 0];
    var a21 = a[2 * 3 + 1];
    var a22 = a[2 * 3 + 2];
    var b00 = b[0 * 3 + 0];
    var b01 = b[0 * 3 + 1];
    var b02 = b[0 * 3 + 2];
    var b10 = b[1 * 3 + 0];
    var b11 = b[1 * 3 + 1];
    var b12 = b[1 * 3 + 2];
    var b20 = b[2 * 3 + 0];
    var b21 = b[2 * 3 + 1];
    var b22 = b[2 * 3 + 2];
    return [
      b00 * a00 + b01 * a10 + b02 * a20,
      b00 * a01 + b01 * a11 + b02 * a21,
      b00 * a02 + b01 * a12 + b02 * a22,
      b10 * a00 + b11 * a10 + b12 * a20,
      b10 * a01 + b11 * a11 + b12 * a21,
      b10 * a02 + b11 * a12 + b12 * a22,
      b20 * a00 + b21 * a10 + b22 * a20,
      b20 * a01 + b21 * a11 + b22 * a21,
      b20 * a02 + b21 * a12 + b22 * a22,
    ];
  },

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

  rotate: function(m, angleInRadians) {
    return m3.multiply(m, m3.rotation(angleInRadians));
  },

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

// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl) {
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
          // left column
          0, 0,
          30, 0,
          0, 150,
          0, 150,
          30, 0,
          30, 150,

          // top rung
          30, 0,
          100, 0,
          30, 30,
          30, 30,
          100, 0,
          100, 30,

          // middle rung
          30, 60,
          67, 60,
          30, 90,
          30, 90,
          67, 60,
          67, 90,
      ]),
      gl.STATIC_DRAW);
}

main();
<canvas id="canvas"></canvas>
<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform mat3 u_matrix;

void main() {
  // Multiply the position by the matrix.
  gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

uniform vec4 u_color;

void main() {
   gl_FragColor = u_color;
}
</script>
<!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See http://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and http://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>

如果您不想移动旋转中心,只需删除与rotationOffset相关的最后一步

"use strict";

function main() {
  // Get A WebGL context
  /** @type {HTMLCanvasElement} */
  var canvas = document.getElementById("canvas");
  var gl = canvas.getContext("webgl");
  if (!gl) {
    return;
  }

  // setup GLSL program
  var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);

  // look up where the vertex data needs to go.
  var positionLocation = gl.getAttribLocation(program, "a_position");

  // lookup uniforms
  var colorLocation = gl.getUniformLocation(program, "u_color");
  var matrixLocation = gl.getUniformLocation(program, "u_matrix");

  // Create a buffer to put positions in
  var positionBuffer = gl.createBuffer();
  // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  // Put geometry data into buffer
  setGeometry(gl);

  const shapes = [
    { 
      translation: [50, 75],
      scale: [0.5, 0.5],
      rotationOffset: [0, 0], // top left corner of F
      angleInRadians: 0,
      color: [1, 0, 0, 1], // red
    },
    { 
      translation: [100, 75],
      scale: [0.5, 0.5],
      rotationOffset: [-50, -75], // center of F
      angleInRadians: 0,
      color: [0, 1, 0, 1], // green
    },
    { 
      translation: [150, 75],
      scale: [0.5, 0.5],
      rotationOffset: [0, -150], // bottom left corner of F
      angleInRadians: 0,
      color: [0, 0, 1, 1], // blue
    },
    { 
      translation: [200, 75],
      scale: [0.5, 0.5],
      rotationOffset: [-100, 0], // top right corner of F
      angleInRadians: 0,
      color: [1, 0, 1, 1],  // magenta
    },
  ];

  requestAnimationFrame(drawScene);

  // Draw the scene.
  function drawScene(time) {
    time *= 0.001;  // seconds

    webglUtils.resizeCanvasToDisplaySize(gl.canvas);

    // Tell WebGL how to convert from clip space to pixels
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    // Clear the canvas.
    gl.clearColor(0, 0, 0, 0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    
    // draw a single black line to make the pivot clearer
    gl.enable(gl.SCISSOR_TEST);
    gl.scissor(0, 75, 300, 1);
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.disable(gl.SCISSOR_TEST);

    // Tell it to use our program (pair of shaders)
    gl.useProgram(program);

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

    // Bind the position buffer.
    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(
        positionLocation, size, type, normalize, stride, offset);

    for (const shape of shapes) {
      shape.angleInRadians = time;
      
      // set the color
      gl.uniform4fv(colorLocation, shape.color);

      // Compute the matrices
      var matrix = m3.projection(gl.canvas.clientWidth, gl.canvas.clientHeight);
      matrix = m3.translate(matrix, shape.translation[0], shape.translation[1]);
      matrix = m3.scale(matrix, shape.scale[0], shape.scale[1]);
      matrix = m3.rotate(matrix, shape.angleInRadians);
      //matrix = m3.translate(matrix, shape.rotationOffset[0], shape.rotationOffset[1]);

      // Set the matrix.
      gl.uniformMatrix3fv(matrixLocation, false, matrix);

      // Draw the geometry.
      var primitiveType = gl.TRIANGLES;
      var offset = 0;
      var count = 18;  // 6 triangles in the 'F', 3 points per triangle
      gl.drawArrays(primitiveType, offset, count);
    }
    requestAnimationFrame(drawScene);
  }
}

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

  identity: function() {
    return [
      1, 0, 0,
      0, 1, 0,
      0, 0, 1,
    ];
  },

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

  rotation: function(angleInRadians) {
    var c = Math.cos(angleInRadians);
    var s = Math.sin(angleInRadians);
    return [
      c,-s, 0,
      s, c, 0,
      0, 0, 1,
    ];
  },

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

  multiply: function(a, b) {
    var a00 = a[0 * 3 + 0];
    var a01 = a[0 * 3 + 1];
    var a02 = a[0 * 3 + 2];
    var a10 = a[1 * 3 + 0];
    var a11 = a[1 * 3 + 1];
    var a12 = a[1 * 3 + 2];
    var a20 = a[2 * 3 + 0];
    var a21 = a[2 * 3 + 1];
    var a22 = a[2 * 3 + 2];
    var b00 = b[0 * 3 + 0];
    var b01 = b[0 * 3 + 1];
    var b02 = b[0 * 3 + 2];
    var b10 = b[1 * 3 + 0];
    var b11 = b[1 * 3 + 1];
    var b12 = b[1 * 3 + 2];
    var b20 = b[2 * 3 + 0];
    var b21 = b[2 * 3 + 1];
    var b22 = b[2 * 3 + 2];
    return [
      b00 * a00 + b01 * a10 + b02 * a20,
      b00 * a01 + b01 * a11 + b02 * a21,
      b00 * a02 + b01 * a12 + b02 * a22,
      b10 * a00 + b11 * a10 + b12 * a20,
      b10 * a01 + b11 * a11 + b12 * a21,
      b10 * a02 + b11 * a12 + b12 * a22,
      b20 * a00 + b21 * a10 + b22 * a20,
      b20 * a01 + b21 * a11 + b22 * a21,
      b20 * a02 + b21 * a12 + b22 * a22,
    ];
  },

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

  rotate: function(m, angleInRadians) {
    return m3.multiply(m, m3.rotation(angleInRadians));
  },

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

// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl) {
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
          // left column
          0, 0,
          30, 0,
          0, 150,
          0, 150,
          30, 0,
          30, 150,

          // top rung
          30, 0,
          100, 0,
          30, 30,
          30, 30,
          100, 0,
          100, 30,

          // middle rung
          30, 60,
          67, 60,
          30, 90,
          30, 90,
          67, 60,
          67, 90,
      ]),
      gl.STATIC_DRAW);
}

main();
<canvas id="canvas"></canvas>
<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform mat3 u_matrix;

void main() {
  // Multiply the position by the matrix.
  gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

uniform vec4 u_color;

void main() {
   gl_FragColor = u_color;
}
</script>
<!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See http://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and http://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>

您可能还会发现this article有用

答案 1 :(得分:1)

您必须为每个形状设置一个单独的旋转矩阵(模型矩阵)。为每个形状定义一个单独的角度:

例如

var angleShape1 = 0.0; 
var angleShape2 = 0.0;

计算每种形状的旋转矩阵并设置统一变量,绘制形状之前:

function render() {

    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);   

    matrix1 = mat.rotation(angleShape1);
    gl.uniformMatrix3fv(matrixLocation, false, matrix1);
    drawShape1();

    matrix2 = mat.rotation(angleShape2);
    gl.uniformMatrix3fv(matrixLocation, false, matrix2);
    drawShape2();

    angleShape1 += 0.1;
    angleShape2 += 0.2;

    requestAnimFrame( render );
}

这将导致形状以不同的模型转换和单独的方向进行渲染。