OpenGl 游戏编程笔记 第三章: OpenGL 状态和图元 (一)

OpenGl 游戏编程笔记 第二章: 创建一个简单的 OpenGL 程序

皮贝贝 posted @ 2010年3月25日 22:10 in opengl with tags OpenGL game 游戏 , 4618 阅读

 

OpenGl 游戏编程笔记 第二章: 创建一个简单的 OpenGL 程序

本章可以学到:

  • WGL 和 Windows 平台相关的 OpenGL 函数
  • 像素格式
  • 在 Windows 平台上如何使用 OpenGL
  • 全屏的 OpenGL

1 WGL 介绍

WGL 是 Windows 平台相关的 OpenGL 的一个 API 子集. 有以下内容:

  1. 创建和选择一个渲染上下文 [本章]
  2. 在 OpenGL 中使用 Windows 的字体 [第 11 章: 显示文字]
  3. 加载 OpenGL 的扩展 [第 8 章: OpenGL 扩展]

2 渲染上下文

windows 上的 GDI 用 dc (设备上下文) 来记住诸如绘制模式和命令. 同样的在 OpenGl 中, 使用 渲染上 下文( render context) 来区分彼此. 不过要记住, rc 不能替代 dc, 在windows上, 必须先创建 dc, 然 后再创建相匹配像素格式的rc.

我们可以在一个应用程序中创建多个 rc, 这在 3d 编程中很常见, 这样你可以拥有许多个窗口或视口, 并 且每一个窗口或视口都能跟踪他们独立的信息.

有几个重要的rc 相关的 wgl 函数:

  • 创建: HGLRC wglCreateContext (HDC hDC);
  • 销毁: BOOL wglDeleteContext (HGLRC hRC);
  • 选择: BOOl wglMakeCurrent (HDC hDC, HGLRC hRC);
  • 获取: HGLRC wglGetCurrentContext();

这让我们很直接的用 dc 的使用函数联系起来: create, delete, select, get. 意思一样, 创建, 选择, 反选择, 不用了再销毁.

wglCreateContext() 和 wglMakeCurrent() 函数应该在你的应用程序的初始化部分调用, 例如 windows 过程函数的 WM_CREATE 消息来临时. wglDeleteContext() 函数应该在窗口被销毁之前调用, 例如 windows 过程函数的 WM_DESTROY 消息来临时. 在销毁 rc 前, 好的习惯是先用 wglMakeCurrent() 来取消选择, (虽然 wglDeleteContext() 函数内进行了相关操作). 模板如下:

 

LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
        static HGLRC hRC;
        static HDC hDC;

        switch(message)
        {
                case WM_CREATE:
                        hDC = GetDC(hWnd);
                        hRC = wglCreateContext(hDC);
                        wglMakeCurrent(hDC, hRC);
                        break;
                case WM_DESTROY:
                        wglMakeCurrent(hDC, NULL);
                        wglDeleteContext(hRC);
                        PostMessage(0);
                        break;
        }
}

 

3 像素格式 (pixel format)

在创建一个 rc 之前, 必须选择一个合适的像素格式. opengl 预先提供了几种像素格式, 定义了颜色模式, 深度缓冲区, 每个像素的位数, 窗口是否使用双缓冲等.

首先需要使用 PIXELFORMATDESCRIPTOR 结构来定义字符集和窗口相关的行为. 结构 PIXELFORMATDESCRIPTOR 的定义:

 

/* Pixel format descriptor */
typedef struct tagPIXELFORMATDESCRIPTOR
{
    WORD  nSize;    // sizeof(PIXELFORMATDESCRIPTOR)
    WORD  nVersion;
    DWORD dwFlags; // 指定缓冲区的属性:
    BYTE  iPixelType; // 指定像素点的类型: RGBA或是颜色序列值
    BYTE  cColorBits; // 指定每个像素点的位数
    BYTE  cRedBits;
    BYTE  cRedShift;
    BYTE  cGreenBits;
    BYTE  cGreenShift;
    BYTE  cBlueBits;
    BYTE  cBlueShift;
    BYTE  cAlphaBits;
    BYTE  cAlphaShift;
    BYTE  cAccumBits;
    BYTE  cAccumRedBits;
    BYTE  cAccumGreenBits;
    BYTE  cAccumBlueBits;
    BYTE  cAccumAlphaBits;
    BYTE  cDepthBits;
    BYTE  cStencilBits;
    BYTE  cAuxBuffers;
    BYTE  iLayerType;
    BYTE  bReserved;
    DWORD dwLayerMask;
    DWORD dwVisibleMask;
    DWORD dwDamageMask;
} PIXELFORMATDESCRIPTOR, *PPIXELFORMATDESCRIPTOR, FAR *LPPIXELFORMATDESCRIPTOR;

 

3.1 设置像素格式

填充好 PIXELFORMATDESCRIPTOR 结构以后, 下一步就是调用:

int ChoosePixelFormat( HDC hDC, CONST PIXELFORMATDESCRIPTOR *ppfd);

这个函数会根据你的 PIXELFORMATDESCRIPTOR 结构内容来匹配找到 OpenGL 预设的一个像素格式.若找不到, 则会选择一个临近的. 这会改变你得 PIXELFORMATDESCRIPTOR 结构体内容. 函数返回值是预设像素的标识ID. 你通过这个 id 来调用这个函数:

BOOL SetPixelFormat (HDC hDC, int pixelFormat, const PIXELFORMATDESCRIPTOR *ppfd);

这样就为 dc 和其相关联的窗口设置好了像素格式. 注意这个函数只能为一个窗口调用一次, 所以如果你想更改, 必须先销毁窗口再重新创建窗口.

一个设置像素格式的模板:

 

PIXELFORMATDESCRIPTOR pfd;
memset (&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
pfd.nSize = sizeof (PIXELFORMATDESCRIPTOR);
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.nVersion = 1;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 24;
pfd.iLayerType = PFD_MAIN_PLANE;

int pixelFormat = ChoosePixelFormat (hDC, &pfd);

SetPixelFormat (hDC, pixelFormat &pfd);

 

 

4 OpenGL的基本框架

基于上面的内容, 现在我们可以构建一个 OpenGL 的基本框架了.

 

// winmain.cpp

#define WIN32_LEAN_AND_MEAN
#define WIN32_EXTRA_LEAN

#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>

#include "CGfxOpenGL.h"

bool exiting = false;           // 程序退出标志
long windowWidth = 800;         // 窗口大小
long windowHeight = 600;
long windowBits = 32;           // 窗口每个像素位数
bool fullscreen = false;        // 全屏标志
HDC hDC;                        // 窗口的dc


CGfsOpenGL *g_glRender = NULL;

 

 

上面的程序主要定义了程序中使用到的全局变量. 其中 CGfsOpenGL 是我封装好的一个可移植的类. 你可以拿来用在 Linux 上.

 

void SetupPixelFormat (HDC hDC)
{
    int pixelFormat;

    PIXELFORMATDESCRIPTOR pfd =
    {
        sizeof(PIXELFORMATDESCRIPTOR); // size
        1,                             // version
        PFD_SUPPORT_OPENGL |           // OpenGL 窗口
        PFD_DRAW_TO_WINDOW |           // 渲染到窗口
        PFD_DOUBLEBUFFER,              // 双缓冲支持
        PFD_TYPE_RGBA,                 // 颜色类型
        32,                            // 预设颜色深度
        0, 0, 0, 0, 0, 0,              // 颜色位数(忽略)
        0,                             // 无 alpha 缓冲区
        0,                             // alpha 位数(忽略)
        0,                             // 
        0,0,0,0,
        16,
        0,
        0,
        PFD_MAIN_PLANE,
        0,
        0,0,0,
    };
    pixelFormat = ChoosePixelFormat (hDC, &pfs);
    SetPixelFormat (hDC, pixelFormat, &pfd);
}

 

 

SetPixelFormat() 根据 dc 来设置像素格式.

 

LRESULT CALLBACK MainWindowProc (hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HDC hDC;
    static HGLRC hRC;
    int height, width;

    // 消息处理
    switch (uMsg )
    {
    case WM_CREATE:             // 窗口创建
        hDC = GetDC (hWnd);
        SetupPixelFormat(hDC);
        hRC = wglCreateContext(hDC);
        wglMakeCurrent(hDC,hRC);
        break;

    case WM_DESTROY:            // 窗口销毁
    case WM_QUIT:
    case WM_CLOSE:              // 窗口关闭
        wglMakeCurrent(hDC, NULL);
        wglDeleteContext (hRC);

        PostQuitMessage (0);
        break;
    case WM_SIZE:               // 大小变化
        height = HIWORD(lParam);
        width = LOWORD (lParam);
        g_glRender->SetupProjection(width, height);
        break;

    case WM_KEYDOWN:
        int fwKeys;
        LPARAM keyData;
        fwKeys = (int)wParam;
        keyData = lParam;
        switch (fwKeys)
        {
        case VK_ESCAPE:
            PostQuitMessage(0);
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }

    return DefWindowProc (hWnd, uMsg, wParam. lParam);
}

 

 

 

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    WNDCLASSEX windowClass;
    HWND hWnd;
    MSG msg;
    DWORD dwExStyle;
    DWORD dwStyle;
    RECT windowRect;

    g_glRender = new CGfsOpenGL;

    windowRect.left = (long)0;
    windowRect.right = (long)windowWidth;
    windowRect.top = (long)0;
    windowRect.bottom = (long)windowHeight;

    // 填充 windowClass 结构
    windowClass.cbSize = sizeof(WNDCLASSEX);
    windowClass.style = CS_HREDRAW | CS_VREDRAW;
    windowClass.lpfnWndProc = MainWindowProc;
    windowClass.cbClsExtra = 0;
    windowClass.cbWndExtra = 0;
    windowClass.hInstance = hInstance;
    windowClass.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    windowClass.hCursor = LOAD_CURSOR(NULL, IDC_ARROW);
    windowClass.hbrBackground = NULL;
    windowClass.lpszMenuName = NULL;
    windowClass.lpszClassName = "GLClass";
    windowClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO);

    // 注册窗口类
    if (!RegisterClassEx (&windowClass))
        return 0;

    // 全屏设置
    if (fullscreen)
    {
        DEVMODE dmScreenSettings;
        DEVMODE devMode;
        memset (&devMode, 0, sizeof(DEVMODE));
        devMode.dmSize = sizeof (DEVMODE);
        devmode.dmBitsPerPel = g_screenBpp;
        devMode.dmPelsWidth = g_screenWidth;
        devMode.dmPelsHeight = g_screenHeight;
        devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;

        if (ChangeDisplaySettings (&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
        {
            MessageBox (NULL, "全屏显示模式失败", NULL, MB_OK);
            fullscreen = false;
        }
    }

    if (fullscreen)
    {
        dwExStyle = WS_EX_APPWINDOW;
        dwStyle = WS_POPUP;
        ShowCursor (FALSE);
    }
    else
    {
        dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
        dwStyle = WS_OVERLAPPEDWINDOW;
    }

    // 计算所给样式的窗口大小
    AdjustWindowRect (&windowRect, dwStyle, FALSE, dwExStyle);
    
    // 创建窗口
    hwnd = CreateWindowEx (NULL,
                           "GLClass",
                           "BOGLGP - Chapter 2 - OpenGL Application",
                           dwStyle | WS_CLIPCHILDREN |
                           WS_CLIPSIBLINGS,
                           0,0,
                           windowRect.right - windowRect.left,
                           windowRect.bottom -windowRect.top,
                           NULL,
                           NULL,
                           hInstance,
                           NULL
        );
    hDC = GetDC (hWnd);

    //
    if (!hWnd)
        return 0;

    ShowWindow (hWnd, SW_SHOW);
    UpdateWindow (hwnd);

    g_glRender->Init ();

    while (!exiting)
    {
        g_glRender->Prepare(0.0f);
        g_glRender->Render ();
        SwapBuffers (hDC);

        while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE))
        {
            if (!GetMessage (&msg, NULL, 0, 0))
            {
                exiting = true;
                break;
            }
            TranslateMessage (&msg);
            DispatchMessage (&msg );
        }
    }

    delete g_glRender;
    if (fullscreen)
    {
        ChangeDisplaySetting(NULL, 0);
        ShowCursor(TRUE);
    }

    return (int)msg.wParam;
}

 

 

主要的部分是 CGfsOpenGL 类:

 

// CGfxOpenGL.h

class CGfxOpenGL
{
private:
    int m_windowWidth;
    int m_windowHeight;

    float m_angle;

public:
    CGfxOpenGL();
    virtual ~CGfxOpenGL();

    bool Init ();
    bool Shutdown ();

    void SetupProjection (int width, int height);

    void Prepare (float dt);
    void Render ();

};

 

 

这个类 CGfxOpenGL 使用起来很简单. Init() 方法用来初始化你的 OpenGL. Shutdown() 用来关闭 你的 OpenGL, SetupProjection() 用来为你的窗口设置好项目矩阵. Prepare() 为下一帧提供数据更新, Render() 是主要函数, 用来渲染你的场景. 本书将围绕具体的应用, 对这个类进行扩展.

实现代码文件: CGfxOpenGL.cpp

 

// CGfxOpenGL.cpp

#ifdef _WINDOWS
#include <windows.h>
#endif

#include <gl/gl.h>
#include <gl/glu.h>
#include <math.h>
#include "CGfxOpenGL.h"

// 禁止float 到double的转换警告
#pragma warning(disable:4305)

CGfxOpenGL::CGfxOpenGL()
{
}

CGfxOpenGL::~CGfxOpenGL()
{
}

bool CGfxOpenGL::Init ()
{
    // 清空背景
    glClearColor (0.0, 0.0, 0.0, 0.0);

    m_angle = 0.0f;
    return true;
}

bool CGfxOpenGL::Shutdown()
{
    return true;
}

void CGfxOpenGL::SetupProjection (int width, int height )
{
    if (height == 0) height = 1;

    glViewport (0, 0, width, height);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity();

    gluPerspective (52.0, (GLfloat)width / (GLfloat)height, 1.0f, 1000.0f);

    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();

    m_windowWidth = width;
    m_windowHeight = height;
}

void CGfxOpenGL::Prepare (floag dt)
{
    m_angle += 0.1f;
}


void CGfxOpenGL::Render ()
{
    // 清除屏幕和深度缓存
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    glTranslatef(0.0, 0.0, -5.0f);
    glRotatef(m_angle, 1.0f, 0.0f, 0.0f);
    glRotatef(m_angle, 0.0f, 1.0f, 0.0f);
    glRotatef(m_angle, 0.0f, 0.0f, 1.0f);

    glColor2f(0.7f, 1.0f, 0.3f);

    glBegin(GL_TRIANGLES);
    glVertex3f(1.0f, -1.0f, 0.0f);
    glVertex3f(-1.0f, -1.0f, 0.0f);
    glVertex3f(0.0, 1.0, 0.0f);
    glEnd();
}


 

 

如你所见, 我将所有的 OpenGL 具体相关的代码封装在了 CGfxOpenGL 类内. Init() 方法中使用

 glClearColor() 函数将背景颜色置成黑色(0,0,0), 并且初始化成员变量 m_angle, 这个变量将

在 Render() 的 glRotatef() 函数中指定旋转角度. 

 

 

5 全屏

欲进入全屏模式, 须使用 DEVMODE 数据结构. 这个结构体有些大, 不过还好, 只有几个成员要我们关心:


Field Description
dmSize sizeof(DEVMODE)
dmBitsPerPel 每个像素点的位数
dmPelsWidth 屏幕宽度
dmPelsHeight 屏幕高度
dmFields 一个位集合用于指定当前结构体哪些成员有效, 本表中的域标志: DM_BITSPERPEL, DM_PELSWIDTH, DM_PERLHEIGHT

初始化完了 DEVMODE 结构体, 然后调用:

LONG ChangeDisplaySettings (LPDEVMODE pDevMode, DWORD dwFlags);

这个函数第二个参数是一个位集合, 用于指定你要执行的功能. 本全屏功能需要传入 CDS_FULLSCREEN 标志, 这样会从屏幕上移除任务栏, 进入新的显示模式. 若函数调用成功, 返回 DISP_CHANGE_SUCCESSFUL. 你可以通过指定 函数的两个参数为 NULL 和 0 来切换到默认的屏幕显示模式.

对上节中的代码添加如下, 来实现全屏功能:

 

DEVMODE devMode;
memset (&devMode, 0, sizeof(DEVMODE));
devMode.dmSize = sizeof (DEVMODE);
devmode.dmBitsPerPel = g_screenBpp;
devMode.dmPelsWidth = g_screenWidth;
devMode.dmPelsHeight = g_screenHeight;
devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;

if (ChangeDisplaySetttings (&devMode, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
    g_fullScreen = false;

 

 

例子中使用了一个全局标志变量来控制是否启用全屏模式.

在切换到全屏模式时, 要注意几个问题. 首先你必须确认 DEVMODE 中指定的高度和宽度与你创建的窗口的高度 和宽度一致. 最简单的方法是两个操作使用相同的高度和宽度变量. 其次, 必须在窗口创建之前更改显示的设置( ChangeDisplaySetttings()).

全屏模式下的风格设置与普通窗口不同, 所以你必须对两种情况进行分别处理. 若不启用全屏, 风格设置成普通的 窗口风格. 否则, 你必须使用 WSEXAPPWINDOW 扩展风格, 和 WSPOPUP 普通窗口风格. WSPOPUP 标志 指定了窗口没有边缘. 另一点是在全屏模式下屏蔽鼠标的光标显示, 这可以通过 API ShowCursor() 来设定. 下面是一个处理框架:

 

if (g_fullScreen)
{
    extendedWindowStyle = WS_EX_APPWINDOW; // 隐藏前边窗口
    windowStyle = WS_POPUP;                // 没有窗口边缘
    ShowCursor (FALSE);                    // 隐藏光标
}
else
{
    extendedWindowStyle = NULL;
    windowStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE |
        WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
}

 

 

6 小结

本章你学会了怎样在windows平台创建一个简单的 OpenGL 程序. 讲述了 OpenGL 的渲染上下文以及相关 的 wgl 函数: wglCreateContext(), wglDeleteContext(), wglMakeCurrent() 和 wglGetCurrentContext(). 像素格式以及怎样设置像素格式. 最后我们通过一个例子运用了上面的几个 知识点, 并且讲解了如何在 OpenGL 中设置全屏模式.


 

Date: 2010-03-25 13:55:18

HTML generated by org-mode 6.30c in emacs 23

 


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter