在现代计算机图形学中,3D图形的渲染和交互是两个非常重要的领域。本文将介绍如何使用MFC(Microsoft Foundation Classes)和OpenGL来实现3D图形的显示、渲染加速、线框叠加、平滑细分以及鼠标交互。
VRML(Virtual Reality Modeling Language)是一种用于描述三维交互式矢量图形的语言。在MFC应用程序中,可以通过解析VRML文件来构建3D场景。这个过程涉及到读取文件、解析文件中的节点和属性,以及将这些信息转换为OpenGL可以理解的格式。
显示列表是OpenGL提供的一种优化渲染性能的技术。通过将一系列的OpenGL命令编译成一个列表,然后在渲染循环中调用这个列表,可以显著提高渲染效率。
创建显示列表的一般步骤如下:
        int list = glGenLists(1); // 请求一个空闲的ID号
        glNewList(list, GL_COMPILE_AND_EXECUTE); // 开始编译列表
        glBegin(GL_TRIANGLES); // 开始绘制三角形
        // 标准OpenGL调用,填充顶点、法线、颜色等
        glEnd();
        glEndList(); // 结束列表
    
使用显示列表的一般步骤如下:
        if(glIsList(list) == GL_TRUE) {
            glCallList(list); // 调用显示列表
        }
    
在3D网格类中,每个网格都包含一个列表编号,当网格被修改时,可以通过标志位来重新构建列表。
有时候,希望在平滑着色的网格上叠加线框,以便于观察网格的结构。这可以通过OpenGL的glPolygonOffset命令来实现,该命令可以创建一个z-buffer偏移。
渲染场景时,如果需要叠加线框,可以进行两次渲染:第一次使用光照平滑模式渲染网格,第二次关闭光照,设置线框模式,并设置z-buffer偏移,然后再次绘制网格。
为了改善3D网格的几何外观,可以使用Charles Loop平滑细分算法。该算法将每个三角形细分为四个三角形,并通过过滤函数来平滑网格。
在文档中,可以通过调用CMeshDoc类的OnMeshLoop方法来实现平滑细分。
        void CMeshDoc::OnMeshLoop() {
            BeginWaitCursor();
            int NbObject = m_SceneGraph.NbObject();
            for(int i = 0; i < NbObject; i++) {
                CObject3d *pObject3d = m_SceneGraph[i];
                if(pObject3d->GetType() == TYPE_MESh2D) {
                    CMesh2d *pMesh = (CMesh2d *)pObject3d;
                    pMesh->SubdivisionLoop();
                }
            }
            UpdateAllViews(NULL);
            EndWaitCursor();
        }
    
通过平滑细分,可以看到网格的视觉效果得到了显著提升。
在3D视图中,鼠标交互是非常重要的。可以通过在视图中插入一些变量和命令来实现鼠标交互。
例如,左键按下时可以进行x/y平移,右键按下时可以进行z轴平移。鼠标移动时,如果同时按下左右键,可以进行旋转。
        void CMeshView::OnLButtonDown(UINT nFlags, CPoint point) {
            m_LeftButtonDown = TRUE;
            m_LeftDownPos = point;
            SetCapture();
            CView::OnLButtonDown(nFlags, point);
        }
        void CMeshView::OnMouseMove(UINT nFlags, CPoint point) {
            if(m_LeftButtonDown && m_RightButtonDown) {
                // 旋转操作
            }
            else if(m_LeftButtonDown) {
                // x/y平移操作
            }
            else if(m_RightButtonDown) {
                // z轴平移操作
            }
            CView::OnMouseMove(nFlags, point);
        }