WebGL - 在视线中移动对象

时间:2017-05-20 10:21:41

标签: camera webgl

我定义了我的模型 - 视图矩阵,定义了一个函数lookAt,它代表了摄像机的眼睛,我所代表的对象的位置以及摄像机的“向上”矢量。如何在相机的视线范围内移动物体?有小费吗?如果我定义了指向物体位置的矢量并从相机的眼睛开始(因此,如果我定义了视线),我该如何使用它来使物体沿这个方向移动?

这是我的lookAt功能

  function lookAt( eye, at, up )
{
    if ( !Array.isArray(eye) || eye.length != 3) {
        throw "lookAt(): first parameter [eye] must be an a vec3";
    }

    if ( !Array.isArray(at) || at.length != 3) {
        throw "lookAt(): first parameter [at] must be an a vec3";
    }

    if ( !Array.isArray(up) || up.length != 3) {
        throw "lookAt(): first parameter [up] must be an a vec3";
    }

    if ( equal(eye, at) ) {
        return mat4();
    }

    var v = normalize( subtract(at, eye) );  // view direction vector
    var n = normalize( cross(v, up) );       // perpendicular vector
    var u = normalize( cross(n, v) );        // "new" up vector

    v = negate( v );

    var result = mat4(
        vec4( n, -dot(n, eye) ),
        vec4( u, -dot(u, eye) ),
        vec4( v, -dot(v, eye) ),
        vec4()
    );

    return result;
}

1 个答案:

答案 0 :(得分:0)

老实说,我不明白你看起来的功能。它并没有像大多数人那样设置翻译功能。

这是一个不同的lookAt函数,它可以生成一个摄像机矩阵,一个将摄像机定位在世界中的矩阵。这与生成视图矩阵的lookAt函数形成对比,后者是一个矩阵,可以在摄像机前移动世界上的所有内容。

function lookAt(eye, target, up) {
  const zAxis = v3.normalize(v3.subtract(eye, target));
  const xAxis = v3.normalize(v3.cross(up, zAxis));
  const yAxis = v3.normalize(v3.cross(zAxis, xAxis));

  return [
    ...xAxis, 0,
    ...yAxis, 0,
    ...zAxis, 0,
    ...eye, 1,
  ];
}

这是an article with some detail about a lookAt matrix

我发现相机矩阵更有用,可以查看矩阵,因为相机矩阵(或lookAt矩阵)并用于使头查看其他内容。枪炮目标,眼睛兴趣,因为视图矩阵几乎只能用于一件事。你可以通过逆向获得另一个。但是由于一个有炮塔,眼睛和人物头部跟踪事物的场景可能需要50多个lookAt矩阵,因此生成这种矩阵并采用1个逆或视图矩阵比生成50+视图矩阵更有用。除了其中一个之外,其他所有人都会被反转。

您可以通过拍摄相机矩阵的轴并乘以某个标量来移动相对于相机方向的任何物体。 xAxis将垂直于摄像机左右移动,yAxis垂直于摄像机上下移动,zAxs朝摄像机方向前进/后退。

相机矩阵的轴是

+----+----+----+----+
| xx | xy | xz |    |  xaxis
+----+----+----+----+
| yx | yy | yz |    |  yaxis
+----+----+----+----+
| zx | zy | zz |    |  zaxis
+----+----+----+----+
| tx | ty | tz |    |  translation
+----+----+----+----+

换句话说

const camera = lookAt(eye, target, up);
const xaxis = camera.slice(0, 3);
const yaxis = camera.slice(4, 7);
const zaxis = camera.slice(8, 11);

现在您可以使用

向前或向后翻译
matrix = mult(matrix, zaxis);  // moves 1 unit away from camera

将zaxis乘以您要移动的金额

moveVec = [zaxis[0] * moveAmount, zaxis[1] * moveAmount, zaxis[2] * moveAmount];
matrix = mult(matrix, moveVec);  // moves moveAmount units away from camera

或者如果您将翻译存储在其他地方,只需在

中添加zaxis即可
// assuming tx, ty, and tz are our translation
tx += zaxis[0] * moveAmount;
ty += zaxis[1] * moveAmount;
tz += zaxis[2] * moveAmount;

const vs = `
uniform mat4 u_worldViewProjection;

attribute vec4 position;
attribute vec2 texcoord;

varying vec4 v_position;
varying vec2 v_texcoord;

void main() {
  v_texcoord = texcoord;
  gl_Position = u_worldViewProjection * position;
}
`;
const fs = `
precision mediump float;

varying vec2 v_texcoord;

uniform sampler2D u_texture;

void main() {
  gl_FragColor = texture2D(u_texture, v_texcoord);
}
`;


"use strict";
const m4 = twgl.m4;
const v3 = twgl.v3;
const gl = document.getElementById("c").getContext("webgl");

// compiles shaders, links program, looks up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for positions, texcoords
const bufferInfo = twgl.primitives.createCubeBufferInfo(gl);

// calls gl.createTexture, gl.bindTexture, gl.texImage2D, gl.texParameteri
const tex = twgl.createTexture(gl, {
  min: gl.NEAREST,
  mag: gl.NEAREST,
  src: [
    255, 64, 64, 255,
    64, 192, 64, 255,
    64, 64, 255, 255,
    255, 224, 64, 255,
  ],
});

const settings = {
  xoff: 0,
  yoff: 0,
  zoff: 0,
};

function render(time) {
  time *= 0.001;
  twgl.resizeCanvasToDisplaySize(gl.canvas);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  gl.enable(gl.DEPTH_TEST);
  gl.enable(gl.CULL_FACE);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  const fov = 45 * Math.PI / 180;
  const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  const zNear = 0.01;
  const zFar = 100;
  const projection = m4.perspective(fov, aspect, zNear, zFar);

  const eye = [3, 4, -6];
  const target = [0, 0, 0];
  const up = [0, 1, 0];

  const camera = m4.lookAt(eye, target, up);
  const view = m4.inverse(camera);
  const viewProjection = m4.multiply(projection, view);

  gl.useProgram(programInfo.program);

  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  const t = time * .1;
  for (let z = -1; z <= 1; ++z) {
    for (let x = -1; x <= 1; ++x) {
      const world = m4.identity();
      m4.translate(world, v3.mulScalar(camera.slice(0, 3), settings.xoff), world);
      m4.translate(world, v3.mulScalar(camera.slice(4, 7), settings.yoff), world);
      m4.translate(world, v3.mulScalar(camera.slice(8, 11), settings.zoff), world);
      m4.translate(world, [x * 1.4, 0, z * 1.4], world);
      m4.rotateY(world, t + z + x, world);

      // calls gl.uniformXXX
      twgl.setUniforms(programInfo, {
        u_texture: tex,
        u_worldViewProjection: m4.multiply(viewProjection, world),
      });

      // calls gl.drawArrays or gl.drawElements
      twgl.drawBufferInfo(gl, bufferInfo);
    }
  }

  requestAnimationFrame(render);
}
requestAnimationFrame(render);

setupSlider("#xSlider", "#xoff", "xoff");
setupSlider("#ySlider", "#yoff", "yoff");
setupSlider("#zSlider", "#zoff", "zoff");

function setupSlider(sliderId, labelId, property) {
  const slider = document.querySelector(sliderId);
  const label = document.querySelector(labelId);

  function updateLabel() {
    label.textContent = settings[property].toFixed(2);
  }

  slider.addEventListener('input', e => {
    settings[property] = (parseInt(slider.value) / 100 * 2 - 1) * 5;
    updateLabel();
  });

  updateLabel();
  slider.value = (settings[property] / 5 * .5 + .5) * 100;
}
body { margin: 0; }
canvas { display: block; width: 100vw; height: 100vh; }
#ui { 
  position: absolute; 
  left: 10px; 
  top: 10px; 
  z-index: 2; 
  background: rgba(255, 255, 255, 0.9);
  padding: .5em;
}
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas id="c"></canvas>
<div id="ui">
  <div><input id="xSlider" type="range" min="0" max="100"/><label>xoff: <span id="xoff"></span></label></div>
  <div><input id="ySlider" type="range" min="0" max="100"/><label>yoff: <span id="yoff"></span></label></div>
  <div><input id="zSlider" type="range" min="0" max="100"/><label>zoff: <span id="zoff"></span></label></div>
</div>