可视化视锥体
撰写时间:2023-09-13
修订时间:2026-05-07
问题的提出
我们知道,当应用了投影矩阵后,其本质就是确定了一个视锥体。视锥体范围内的所有物体将得到渲染,而视锥体范围之外的所有物体则不会被渲染。
而视锥体又与观察者位置、相机视域、视口宽高比、近平面、远平面等诸多因素相关,如下图所示。

因此,当这些相关因素其中之一设置不当时,将导致在场景中不能正确地渲染网格物体。
例如,此应用的右上角视口中的立方体因不能完全落于视锥体范围内而导致立方体在渲染时被不当裁剪,从而出现了很怪异的效果。一是立方体的前面被穿透了,我们位于立方体的内部,看到了立方体的背面。二是随着在右上角视口中旋转场景,立方体被视锥体裁剪得似乎没有规律可循。
而另外3个视口的视图,它们都通过可视化视锥体,非常直观地显示出立方体与视锥体的相互位置关系,我们得以从上帝视角清楚地看到立方体的哪些部位落在视锥体的范围之外。
应用程序功能分析
此应用程序与其他应用程序不一样的地方,在于右上角视口使用了自定义的投影投影矩阵。
之前,Viewport设置默认的投影矩阵的代码如下:
近平面无限近,而远平面无限远,因此视锥体的范围无限宽广,网格物体不容易出现不当被裁剪的问题。
本应用的客户端代码:
import {
WebGLApp,
VIEWPORT_LAYOUT,
Camera, CAMERA_TYPE,
GridFloorMesh, CubeMesh, ViewingVolumnMesh,
mat4, glMatrix,
} from './js/esm/index-independent.js';
let app = new WebGLApp();
let eyePos = [0, 0, 3];
let perspective = {
fovy: 30,
aspect: null,
near: 3,
far: 5
};
app.doInInitViewports((cameraManager, viewportManager) => {
cameraManager.eyeDist = 10;
viewportManager.useLayout(VIEWPORT_LAYOUT.Left_2_Right_2);
let targetViewport = viewportManager.viewports[1];
let camera = new Camera(perspective.fovy, eyePos, CAMERA_TYPE.USER_VIEW);
targetViewport.camera = camera;
targetViewport.updateViewportRect();
perspective.aspect = targetViewport.rect.width / targetViewport.rect.height;
targetViewport.updateProjectionMatrix = function() {
mat4.perspective(this.projectionMatrix, glMatrix.toRadian(this.camera.fov), this.rect.width / this.rect.height, perspective.near, perspective.far);
};
});
app.doInInitMeshes((scene) => {
let cube = new CubeMesh(1);
scene.add(cube);
let viewVolMesh = new ViewingVolumnMesh(eyePos, perspective);
scene.add(viewVolMesh);
let floor = new GridFloorMesh(0.5, 20);
scene.add(floor);
});
app.run();
我们将与视锥体有关的因素存储到eyePos及perspective两个变量中,在4个视口布局的右上角视口中,以这两个变量的值设置了一个自定义相机,并依此相机设置单独的投影矩阵,并且,依据变量perspective来设置其近平面与远平面。这样,一旦网格物体落在视锥体范围之外,其在渲染结果中将直接被裁剪。
运行应用。
此例可以清晰地看出,如若视锥体设置不当,将无法看到物体的全貌。正因视锥体的设置比较麻烦,因此,我们所设计的Camera类不使用glMatrix的lookAt方法来实现,而是在确定好眼睛位置后,使相机上下仰动或左右摇头即可,从而简化了视锥体的设置,有利于相机的精准取景。