点击这里:Win32 OpenGL编程(7) 3D视图变换——真3D的关键

  照相机比喻

  在OpenGL Programming Guide中将所有3D变换统个有意思现实世界模型照相机比喻

  1.确定照相机位置过程对应于“视图变换”(Viewing Transformations)

  2.确定物体位置过程对应于“模型变换”(Modeling Transformations)

  3.确定照相机放大倍数过程对应于“投影变换”(Projection Transformations)

  4.确定照片大小过程对应于“视口变换”(Viewport Transformations)

  实际照相过程遵循这个过程在我们处理3D图形时候也遵循这个过程其中上述4个变换就是我今天准备介绍

  视图变换——确定视角

  现实生活中物体从区别角度观察我们看到东西是不这还引发了历史上著名“金银盾事件”(实际上是寓言-_-!)面看是金盾面看是银盾从中间看是面金面银

  以Win32 OpenGL编程(6) 踏入3D世界(以后简称XO6该系列文章类似)例中最后最复杂 3角锥为例此例中是 3角锥本身在旋转我们观察角度并没有变现在我们反过来 3角锥不动我们自己移动自己位置看看 3角锥区别方向样子现实生活中你看雕塑可不是总能让别人扛着雕塑旋转吧-_-!这时候总得自己走动走动这个时候我们变化是观察角度但是看到确是雕塑区别侧面见下例我们可以从区别观察角度来观察这个简单 3角锥见下例:

// 观察者位置
GLfloat gViewPosX;
GLfloat gViewPosY;
GLfloat gViewPosZ = 1.0;
// 观察者视角方向
GLfloat gViewDirX = 0.0;
GLfloat gViewDirY = 0.0;
GLfloat gViewDirZ = 0.0;
GLfloat gViewUpDirX = 0.0;
GLfloat gViewUpDirY = 1.0;
GLfloat gViewUpDirZ = 0.0;
// 是改变位置还是视角
bool gbChangePos = true;

 
//这里进行所有绘图工作
void SceneShow(GLvoid)        
{
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(1.0, 0.0, 0.0);
    glPushMatrix;
    DrawSmoothColorPyramid(0.5);
    glPopMatrix;
    glLoadIdentity;
    gluLookAt(gViewPosX, gViewPosY, gViewPosZ, 0.0, 0.0, 0.0, gViewDirX, gViewDirY, gViewDirZ);
    glFlush;
}  

 Game_Main(void *parms = NULL,  num_parms = 0)
{
    DWORD dwStartTime;
    dwStartTime = GetTickCount;
    // this is the  loop of the game, do all your processing
    // here
    // for now test  user is hitting ESC and send WM_CLOSE
     (KEYDOWN(VK_ESCAPE))
        SendMessage(ghWnd,WM_CLOSE,0,0);
    (gbChangePos)
    {
         (KEYDOWN(VK_UP))
        {
            gViewPosY  0.01;
            gViewPosZ = sqrt( 1.0 - gViewPosY * gViewPosY);
        }
         (KEYDOWN(VK_DOWN))
        {
            gViewPosY -= 0.01;
            gViewPosZ = sqrt( 1.0 - gViewPosY * gViewPosY);
        }
         (KEYDOWN(VK_LEFT))
        {
            gViewPosX  0.01;
            gViewPosZ = sqrt( 1.0 - gViewPosX * gViewPosX);
        }
         (KEYDOWN(VK_RIGHT))
        {
            gViewPosX -= 0.01;
            gViewPosZ = sqrt( 1.0 - gViewPosX * gViewPosX);
        }
    }
    
    {
         (KEYDOWN(VK_UP))
        {
            gViewDirY  0.01;
        }
         (KEYDOWN(VK_DOWN))
        {
            gViewDirY -= 0.01;
        }
         (KEYDOWN(VK_LEFT))
        {
            gViewDirX  0.01;
        }
         (KEYDOWN(VK_RIGHT))
        {
            gViewDirX -= 0.01;
        }
    }
     (KEYDOWN(VK_NUMPAD8))
    {
        gViewUpDirY  0.01;
    }
     (KEYDOWN(VK_NUMPAD2))
    {
        gViewUpDirY -= 0.01;
    }
     (KEYDOWN(VK_NUMPAD6))
    {
        gViewUpDirX  0.01;
    }
     (KEYDOWN(VK_NUMPAD4))
    {
        gViewUpDirX -= 0.01;
    }
    SceneShow;

    // 控制帧率
    while(GetTickCount - dwStartTime < TIME_IN_FRAME)
    {
        Sleep(1);
    }

    //  success or failure or your own  code here
    (1);
} // end Game_Main

 
// enter  event loop
while(TRUE)
{
    // test  there is a message in queue,  so get it
     (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
    { 
        // test  this is a quit
         (msg.message  WM_QUIT)
            ;
         (msg.message  WM_KEYDOWN)
        {
            (msg.wParam  VK_INSERT)
            {
                gbChangePos = !gbChangePos;
            }
        }
        // translate any accelerator keys
        TranslateMessage(&msg);
        // send the message to the window proc
        DispatchMessage(&msg);
    } // end 
    //  game processing goes here
    Game_Main;
} // end while
// closedown game here
Game_Shutdown;
//  to Windows like this
(msg.wParam);
 end WinMain


  代码虽然很长但是主要是为了演示所有参数其实主体并不复杂主要就是gluLookAt作用如其名就是制定你是从哪里(Where look from,前 3个参数)看哪去(where look at,中间 3个参数)如何看(how to look at最后 3个参数)上面例子中可以分别演示各个参数变化后图形响应变化

  OpenGL Reference Manual:

    gluLookAt —  a viewing transformation
    C Specication
    void gluLookAt(    GLdouble      eyeX,
         GLdouble      eyeY,
         GLdouble      eyeZ,
         GLdouble      centerX,
         GLdouble      centerY,
         GLdouble      centerZ,
         GLdouble      upX,
         GLdouble      upY,
         GLdouble      upZ);
    Parameters
    eyeX, eyeY, eyeZ
                            Species the position of the eye po.
    centerX, centerY, centerZ
                            Species the position of the reference po.
    upX, upY, upZ
                            Species the direction of the up vector.


  此例中通过全局变量保存观察者位置并通过上下左右键改变方向以从区别角度观察 3角锥作为演示我开始时锁定了观察方向直是朝向观察 3角锥并且通过计算让观察者总是保持和观察物品距离相等这样最能看到观察位置改变带来效果但是目前只实现了在物体正面时方向改变当越过正面后由于象限改变实际上增加数值需要编程减小然后将另外个辅助变量正负号更改比如直向上按时Y坐标不停增长但是到 Y=1.0时候就需要变成减小了Z坐标也变成了负值此时已经到了物体后方

  当按下INSERT键后再按上下左右改变就是看哪里方向了尝试下会发现当观察方向向右时物体向左移动直到移出屏幕以外这点很像你开始面对着屏幕然后不停将头向右偏那么屏幕也就向左移动你头偏角度足够大时候已经就看不见屏幕了

  而当通过小键盘8426方向控制时改变是观察向上角度就像你看屏幕时偏着脑袋看具体演示效果就大家自己去看了这里提供个截图.

  

  图片看不清楚?请点击这里查看原图(大图)

  image

  此例强烈建议通过运行去感受也算是体会在游戏中3D转换是如何回事儿其实也就是这么回事儿既然都已经是3D图形了那为什么有游戏要限制你观察角度呢?还冒出了2.5D2.8D等新鲜词汇(其实我也不太懂如何命名)但是学了视图变换后就会明白限制角度对处理简化作用了就像我此处虽然已经是个真正3D 3角锥了我还是没有提供720度随意旋转当移动到物体面时处理方式有些改变老是变来变去会比较麻烦要知道2D平面有4个象限而3D呢?8个象限可不少了每个象限在视图变换时计算方式可是不太因此而简化了很多而那些2.XD游戏自然简化更多罗

  多边形表面显示方式

  在确定了视角后会发现原来 3角锥有问题了背面都显示出来了-_-!见上面截图这个可是不行问题出在我们没有告诉OpenGL那个面是背面那个面是正面而且OpenGL默认现实方式是不管正面背面样处理自然我们得告诉OpenGL按照我们想要处理方式处理才行比如我们想要正面显示背面不显示(这是多么正常需求啊)那么我们可以通过glPolygonMode指明

  OpenGL Reference Manual:

    glPolygonMode — select a polygon rasterization mode
    C Specication
    void glPolygonMode(    GLenum      face,
         GLenum      mode);
    Parameters
    face
                            Species the polygons that mode applies to.
                            Must be
                            GL_FRONT for front-facing polygons,
                            GL_BACK for back-facing polygons,
                            or GL_FRONT_AND_BACK for front- and back-facing polygons.
    mode
                            Species how polygons will be rasterized.
                            Accepted values are
                            GL_POINT,
                            GL_LINE, and
                            GL_FILL.
                            The initial value is GL_FILL for both front- and back-facing polygons.


  要达到我们要求只需要这样:

glPolygonMode(GL_FRONT, GL_FILL);
glPolygonMode(GL_BACK, GL_LINE);


  这样指示以后背面就不会填充了但是OpenGL如何知道哪个是正面哪个是背面呢?通过glFrontFace可以指定

  OpenGL Reference Manual:

    glFrontFace —  front- and back-facing polygons
    C Specication
    void glFrontFace(    GLenum      mode);
    Parameters
    mode
                            Species the orientation of front-facing polygons.
                            GL_CW and GL_CCW are accepted.
                            The initial value is GL_CCW.


  OpenGL自然不是傻到要你具体个面去指定哪个面是正面哪个面是方面它是通过顶点绘制方向来决定OpenGL通过此指定了什么方向时多边形表示正反面默认是是逆时针为正面顺时针为方面想想这样好处指定起来还是方便点绘制顶点时候考虑好就行了不用额外再次指定然后确还有效正面看是逆时针东西到了反面还真是顺时针-_-!(废话)

  如下例所示:

void DrawSmoothColorPyramid(GLfloat adSize)
{
     GLfloat fPyramidDatas = {    0.0, 1.0, 0.0,    //  3角锥上顶点
        -1.0, 0.0, 1.0,    // 底面左前顶点
        1.0, 0.0, 1.0,    // 底面右前下顶点
        0.0, 0.0, -1.0}; // 底面后下顶点
    GLfloat fPyramidSizeDatas[(fPyramidDatas)/(GLfloat)] = {0};
    // 计算大小
    for(  i = 0; i < 12; i)
    {
        fPyramidSizeDatas[i] = fPyramidDatas[i] * adSize;
    }
     GLfloat fPyramidColors = { 0.0, 0.0, 0.0,
        1.0, 0.0, 0.0,
        0.0, 1.0, 0.0,
        0.0, 0.0, 1.0};
    // GLu ubyIndices = {    0, 1, 2,    // 正面
    //    0, 1, 3,    // 左侧面
    //    0, 2, 3,    // 右侧面
    //    1, 2, 3};    // 底面
     GLu ubyIndices = {    0, 1, 2,    // 正面
        0, 3, 1,    // 左侧面
        0, 2, 3,    // 右侧面
        1, 3, 2};    // 底面

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    glVertexPoer(3, GL_FLOAT, 0, fPyramidSizeDatas);
    glColorPoer(3, GL_FLOAT, 0, fPyramidColors);
    glPolygonMode(GL_FRONT, GL_FILL);
    glPolygonMode(GL_BACK, GL_LINE);
    for( i = 0; i < 4; i)
    {
        glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, ubyIndices+i*3);
    }
}


  这就是个比较正常 3角锥显示效果了注释掉是原来顶点顺序是正确方向大家可以对比原来左侧面和底面顺序出问题了也可以尝试运行下出问题代码运行效果效果就是正面可以看到背面移动到应该可以看到左侧面和底面时候发现空了-_-!倒是可以作为实现容器个效果但是真要面不要显示不添加顶点数据就完了嘛……..

  多边形表面剔除

  另外事实上我们这里是指定了背面绘制轮廓(即GL_LINE)但是既然是背面为啥我们定要轮廓呢?完全可以剔除的glCullFace就是干这个

  OpenGL Reference Manual:

    glCullFace — specy whether front- or back-facing facets can be culled
    C Specication
    void glCullFace(    GLenum      mode);
    Parameters
    mode
                            Species whether front- or back-facing facets are candidates for culling.
                            Symbolic constants
                            GL_FRONT, GL_BACK, and GL_FRONT_AND_BACK are accepted.
                            The initial value is GL_BACK.


  既然背面都指定好了使用起来就非常简单了无非就是glCullFace(GL_BACK)表示剔除背面多边形(虽然你可以剔除正面或者全部都剔除了)并且需要注意剔除时需要用glEnable(GL_CULL_FACE)开启剔除功能为了速度像这种功能OpenGL般都是默认关闭我们也习惯了需要添加代码实在也就简单了:



//OpenGL化开始
void SceneInit( w, h)
{
    GLenum err = glewInit;
    (err != GLEW_OK)
    {
        MessageBox(NULL, _T("Error"), _T("Glew init failed."), MB_OK);
        exit(-1);
    }
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
}


  但是需要介绍说明背面剔除后在转换视角到背面变成正面瞬间会需要重新渲染背面此时效率较低事实上这么个简单再没有开启背面剔除时从正面到背面转换非常流畅开启后转换会有明显小小迟缓但是好处就是背面没有需要显示时候是不占用资源孰优孰劣开启和否那就只能看情况把握了



Tags:  win32编程 点击这里

延伸阅读

最新评论

发表评论