【问题标题】:WebGL draw perspective view volumeWebGL 绘制透视图体积
【发布时间】:2017-11-22 02:05:50
【问题描述】:

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

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

到目前为止,我设法从各个角度使用三角函数来计算它们,但不知何故,当我绘制顶点时,结果似乎并不准确。 到目前为止,我已经达到了这个顶点方程:

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

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

z = 平面(近或远)

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

【问题讨论】:

    标签: graphics geometry webgl trigonometry


    【解决方案1】:

    您可以通过逆投影矩阵投影一个标准立方体。

    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 高、fovy * 纵横比,从-zNear 开始,到-zFar 结束,并在透视分割后将该空间转换为-1 +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>

    【讨论】:

    • 哇,非常感谢。这实际上在概念上更容易。我试图实现它,但我有一些麻烦的想法。我有两个画布,我想在 canvasA 中绘制 canvasB 相机的平截头体。我尝试了以下方法(如果我错了,请纠正我):如您所说,我创建了一个标准立方体模型。然后,对于该模型的每个点 V,我乘以 canvasB 的 V * InverseProjectionMatrix。这给了我一个新的 V'' 点,它对应于 canvasB 的视图体积的限制。我现在将拥有所有正确的点,并且可以将它们发送到顶点缓冲区。这给了我一个平行六面体物体。
    • 你除以W了吗? temp = V * inverseProjectionMatrix; newV = temp.xyz / temp.w
    • 成功了!我以错误的顺序相乘。我不能感谢你。顺便说一句,我一直在关注你关于 webgl 的教程,它们是黄金。特维姆!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-01
    相关资源
    最近更新 更多