WebGL绘制透视图卷

时间:2017-11-22 02:05:50

标签: graphics geometry webgl trigonometry

我正在尝试计算视图体积平面的8(4 + 4)个顶点:近和远。

view volume

我需要这个顶点在webGL中绘制相机的视图体积。

到目前为止,我设法通过从每个视角使用三角法来计算它们,但是当我绘制顶点时,结果似乎不准确。 到目前为止,我已经达到顶点方程式:

y = sqrt(斜边^ 2 - 平面^ 2)

x = sqrt(斜边^ 2 - 平面^ 2)

z =平面(近或远)

有人可以帮忙吗?提前谢谢。

1 个答案:

答案 0 :(得分:4)

您可以通过反投影矩阵投影标准立方体。

const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");

const vs = `
attribute vec4 position;
uniform mat4 u_worldViewProjection;
void main() {
  gl_Position = u_worldViewProjection * position;
}
`;

const fs = `
precision mediump float;
void main() {
  gl_FragColor = vec4(1, 0, 0, 1);
}
`;


const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

const arrays = {
   position: [
      -1,  1, -1,
       1,  1, -1,
       1, -1, -1,
      -1, -1, -1,

      -1,  1,  1,
       1,  1,  1,
       1, -1,  1,
      -1, -1,  1,
  ],
  indices: [
      0, 1, 1, 2, 2, 3, 3, 0,
      4, 5, 5, 6, 6, 7, 7, 4,
      0, 4, 1, 5, 2, 6, 3, 7,
  ],
};
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

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

  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  let projectionToViewWith;
  {  
    const fov = 30 * Math.PI / 180;
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const zNear = 0.5;
    const zFar = 100;
    projectionToViewWith = m4.perspective(fov, aspect, zNear, zFar); 
  }
  let projectionToBeViewed;
  {
    const fov = 30 * Math.PI / 180;
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const zNear = 2;
    const zFar = 10;
    projectionToBeViewed = m4.perspective(fov, aspect, zNear, zFar); 
  }
  const inverseProjectionToBeViewed = m4.inverse(projectionToBeViewed);
  
  const radius = 20;
  const eye = [Math.sin(time) * radius, 4, Math.cos(time) * radius];
  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(projectionToViewWith, view);
  
  const worldViewProjection = m4.multiply(
      viewProjection,
      inverseProjectionToBeViewed);

  gl.useProgram(programInfo.program);
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);

  twgl.setUniforms(programInfo, {
    u_worldViewProjection: worldViewProjection,
  });
  twgl.drawBufferInfo(gl, bufferInfo, gl.LINES);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

要获得8个角的点,您只需要进行反向投影。投影矩阵占据平截头体的空间,该平截头体具有高,fovy *宽度宽,从-zNear开始并在-zFar结束并将该空间转换为-1&lt; - &gt;透视划分后的+1框。

要向后移动并计算该框的点,我们只需通过反投影矩阵投影-1到+1框并再次进行分割(这正是上面例子中发生的情况,我们只是在做这一切都在GPU)

因此,我们将其从GPU中取出并在JavaScript中完成

[
  [-1,  1, -1],
  [ 1,  1, -1],
  [ 1, -1, -1],
  [-1, -1, -1],

  [-1,  1,  1],
  [ 1,  1,  1],
  [ 1, -1,  1],
  [-1, -1,  1],
].forEach((point) => {
  console.log(m4.transformPoint(inverseProjectionMatrix, point));
});

这是一个例子。

const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");

const vs = `
attribute vec4 position;
uniform mat4 u_worldViewProjection;
void main() {
  gl_Position = u_worldViewProjection * position;
  gl_PointSize = 10.;
}
`;

const fs = `
precision mediump float;
uniform vec4 u_color;
void main() {
  gl_FragColor = u_color;
}
`;


const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

const positions = [
    -1,  1, -1,
     1,  1, -1,
     1, -1, -1,
    -1, -1, -1,

    -1,  1,  1,
     1,  1,  1,
     1, -1,  1,
    -1, -1,  1,
];
const arrays = {
   position: positions,
   indices: [
      0, 1, 1, 2, 2, 3, 3, 0,
      4, 5, 5, 6, 6, 7, 7, 4,
      0, 4, 1, 5, 2, 6, 3, 7,
  ],
};
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

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

  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  let projectionToViewWith;
  {  
    const fov = 30 * Math.PI / 180;
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const zNear = 0.5;
    const zFar = 100;
    projectionToViewWith = m4.perspective(fov, aspect, zNear, zFar); 
  }
  let projectionToBeViewed;
  {
    const fov = 30 * Math.PI / 180;
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const zNear = 2;
    const zFar = 10;
    projectionToBeViewed = m4.perspective(fov, aspect, zNear, zFar); 
  }
  const inverseProjectionToBeViewed = m4.inverse(projectionToBeViewed);
  
  const radius = 20;
  const eye = [Math.sin(time) * radius, 4, Math.cos(time) * radius];
  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(projectionToViewWith, view);
  
  const worldViewProjection = m4.multiply(
      viewProjection,
      inverseProjectionToBeViewed);

  gl.useProgram(programInfo.program);
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);

  twgl.setUniforms(programInfo, {
    u_worldViewProjection: worldViewProjection,
    u_color: [1, 0, 0, 1],
  });
  twgl.drawBufferInfo(gl, bufferInfo, gl.LINES);
  
  // just because I'm lazy let's draw each point one at a time
  // note: since in our case the frustum is not moving we
  // could have computed these at init time. 
  const positionLoc = programInfo.attribSetters.position.location;
  gl.disableVertexAttribArray(positionLoc);

  for (let i = 0; i < positions.length; i += 3) {
    const point = positions.slice(i, i + 3);
    const worldPosition = m4.transformPoint(
        inverseProjectionToBeViewed, point);
    gl.vertexAttrib3f(positionLoc, ...worldPosition);
    twgl.setUniforms(programInfo, {
      u_color: [0, 1, 0, 1],
      u_worldViewProjection: viewProjection,
    });
    gl.drawArrays(gl.POINT, 0, 1);
  }

  requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

在评论中提到您想要在另一个画布的一个画布中显示相机的视锥。

除了canvasWhosFrustumWeWantToRender中的相机没有移动之外,这实际上是上面发生的事情。相反,它只是坐在原点向下看-Z轴向上+ Y向上。允许平截头体移动以显示它相对于相机的位置只需要添加相机矩阵

const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension("OES_standard_derivatives");

const vs = `
attribute vec4 position;
uniform mat4 u_worldViewProjection;
varying vec3 v_position;
void main() {
  gl_Position = u_worldViewProjection * position;
  v_position = position.xyz;  // for fake lighting
}
`;

const fs = `
#extension GL_OES_standard_derivatives : enable
precision mediump float;
varying vec3 v_position;
uniform vec4 u_color;
void main() {
  vec3 fdx = dFdx(v_position);
  vec3 fdy = dFdy(v_position);

  vec3 n = normalize(cross(fdx,fdy)); 
  float l = dot(n, normalize(vec3(1,2,-3))) * .5 + .5;  
  gl_FragColor = u_color;
  gl_FragColor.rgb *= l;
}
`;


const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

const arrays = {
   position: [
      -1,  1, -1,
       1,  1, -1,
       1, -1, -1,
      -1, -1, -1,

      -1,  1,  1,
       1,  1,  1,
       1, -1,  1,
      -1, -1,  1,
  ],
  indices: [
      0, 1, 1, 2, 2, 3, 3, 0,
      4, 5, 5, 6, 6, 7, 7, 4,
      0, 4, 1, 5, 2, 6, 3, 7,
  ],
};
const concat = twgl.primitives.concatVertices;
const reorient = twgl.primitives.reorientVertices;
const wireCubeBufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
const solidCubeBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);
const cameraBufferInfo = twgl.createBufferInfoFromArrays(gl,
  concat([
     reorient(twgl.primitives.createCubeVertices(2), 
              m4.translation([0, 0, 1])),
     reorient(twgl.primitives.createTruncatedConeVertices(0, 1, 2, 12, 1),
              m4.rotationX(Math.PI * -.5)),
  ])
);

const black = [0, 0, 0, 1];
const blue = [0, 0, 1, 1];

function drawScene(viewProjection, clearColor) {
  gl.clearColor(...clearColor);
  gl.clear(gl.COLOR_BUFFER_BIT);

  const numCubes = 10;
  for (let i = 0; i < numCubes; ++i) {
    const u = i / numCubes;
    let mat = m4.rotationY(u * Math.PI * 2);
    mat = m4.translate(mat, [0, 0, 10]);
    mat = m4.scale(mat, [1, 1 + u * 23 % 1, 1]);
    mat = m4.translate(mat, [0, .5, 0]);
    mat = m4.multiply(viewProjection, mat);
    drawModel(solidCubeBufferInfo, mat, [u, u * 3 % 1, u * 7 % 1,1]);
  }
}

function drawModel(bufferInfo, worldViewProjection, color, mode) {
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  twgl.setUniforms(programInfo, {
    u_worldViewProjection: worldViewProjection,
    u_color: color,
  });
  twgl.drawBufferInfo(gl, bufferInfo, mode);  
}

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

  gl.disable(gl.SCISSOR_TEST);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  gl.enable(gl.DEPTH_TEST);

  let projectionToViewWith;  // the projection on the right
  {  
    const fov = 60 * Math.PI / 180;
    const aspect = gl.canvas.clientWidth / 2 / gl.canvas.clientHeight;
    const zNear = 0.5;
    const zFar = 100;
    projectionToViewWith = m4.perspective(fov, aspect, zNear, zFar); 
  }
  let projectionToBeViewed;  // the projeciton on the left
  {
    const fov = 60 * Math.PI / 180;
    const aspect = gl.canvas.clientWidth / 2 / gl.canvas.clientHeight;
    const zNear = 1.5;
    const zFar = 15;
    projectionToBeViewed = m4.perspective(fov, aspect, zNear, zFar); 
  }
  const inverseProjectionToBeViewed = m4.inverse(projectionToBeViewed);
  
  let cameraViewingScene;  // camera for right view
  {
    const t1 = 0;
    const radius = 30;
    const eye = [Math.sin(t1) * radius, 4, Math.cos(t1) * radius];
    const target = [0, 0, 0];
    const up = [0, 1, 0];
    cameraViewingScene = m4.lookAt(eye, target, up);
  }
  
  let cameraInScene;  // camera for left view
  {
    const t1 = time;
    const t2 = time + .4;
    const r1 = 10 + Math.sin(t1);
    const r2 = 10 + Math.sin(t2) * 2;
    const eye = [Math.sin(t1) * r1, 0 + Math.sin(t1) * 4, Math.cos(t1) * r1];
    const target = [Math.sin(t2) * r2, 1 + Math.sin(t2), Math.cos(t2) * r2];
    const up = [0, 1, 0];
    cameraInScene = m4.lookAt(eye, target, up);
  }

  // there's only one shader program so just set it once
  gl.useProgram(programInfo.program);
  
  // draw only on left half of canvas
  gl.enable(gl.SCISSOR_TEST);
  gl.scissor(0, 0, halfWidth, height);
  gl.viewport(0, 0, halfWidth, height);
  
  // draw the scene on the left using the camera inside the scene
  {
     const view = m4.inverse(cameraInScene);
     const viewProjection = m4.multiply(projectionToBeViewed, view);
     drawScene(viewProjection, [.9, 1, .9, 1]);
  }
  
  // draw only on right half of canvas
  gl.scissor(halfWidth, 0, halfWidth, height);
  gl.viewport(halfWidth, 0, halfWidth, height);
  
  // draw the same scene on the right using the camera outside the scene
  {
    const view = m4.inverse(cameraViewingScene);
    const viewProjection = m4.multiply(projectionToViewWith, view);
    drawScene(viewProjection, [.9, 1, 1, 1]);
  
    // draw the in scene camera's frustum
    {
      const world = m4.multiply(cameraInScene, inverseProjectionToBeViewed);
      const worldViewProjection = m4.multiply(viewProjection, world);
      drawModel(wireCubeBufferInfo, worldViewProjection, black, gl.LINES);
    }
    
    // draw the in scene camera's camera model
    {
       const worldViewProjection = m4.multiply(viewProjection, cameraInScene);
       drawModel(cameraBufferInfo, worldViewProjection, blue);
    }
  }  

  requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>