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

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

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

 

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

 

Avatar_small
meine aktivitäten an 说:
2023年7月19日 23:22

Meine Aktivitäten ist das Hauptportal, in dem Sie Ihre Google-Aktivitäten sehen können, darunter Suchanfragen, besuchte Websites und angesehene Filme. Beachten Sie dies bei der Nutzung von Websites, Anwendungen und Diensten von Google. meine aktivitäten anzeigen Sie werden auf unbestimmte Zeit in Ihrem Google-Konto gespeichert. Sie können den Google-Verlauf Meine Aktivitäten anzeigen oder löschen, indem Sie zu Meine Aktivitäten gehen. Es ist auch jederzeit möglich, die Speicherung des Großteils Ihrer Aktivität einzustellen.

Avatar_small
KVS 1st Class Books 说:
2023年9月26日 18:42

KVS keeps on Updating the Text Books with the help of the Latest Question Papers of each year. The KVS Text Books 2023 for Class 1 are not only Trending but also comes with the Updated syllabus and curriculum.KVS 1st Class Books 2024 are KVS 1st Class Books 2024 one of the Perfect study Materials you can Choose for Efficient Preparation ahead, KVS 1st Books are Specifically Focused on the Syllabus which Suits All Indian Education Boards, Students Download KVS Books 2024 for 1st Class Online our Web portal Provide Subject Wise and Medium Wise Pdf Format File Complete KVS Curriculum Complete Text Books 2024

Avatar_small
jnanabhumiap.in 说:
2024年1月19日 23:20

JNANABHUMI AP provides all the latest educational updates and many more. The main concept or our aim behind this website has been the will to provide resources with full information on each topic jnanabhumiap.in which can be accessed through the Internet. To ensure that every reader gets what is important and worthy about the topic they search and link to hear from us.


登录 *


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