OpenGL初探

原创 2006年05月25日 17:28:00

这学期有一门课是计算机辅助造型与设计,OpenGL是其中的一小块。前几天刚刚开始试着去使用OpenGL,参考资料是NeHe OpenGL。最初,没有原版的资料,看的是CKER翻译后的版本,但是看着总是没有原版的明白。所以,今天找来了原版,虽然是英文的,但写得非常浅显,很容易阅读。已经看到了Lesson4,把到目前为止的一些心得写一下,给自己,也给那些跟我一样刚开始学习OpenGL的朋友们。

首先,有一个自己觉得需要澄清一下的问题。在具体学习之前,觉得OpenGL既然是一个库,肯定需要先下载一个OpenGL的库才能够使用。其实,OpenGL的库在VC里面本身就带着的了,不需要另外下载。在VC里面,有OpenGL的三个库文件,分别是opengl32.lib,glu32.lib,glaux.lib,对应的头文件分别是gl/gl.h,gl/glu.h,gl/glaux.h。我们需要做的是,在新建了一个OpenGL的工程后,在Project的Setting下的Link选项中,将OpenGL库的三个库文件加进去。注意,必须将opengl32.lib,glu32.lib,glaux.lib添加到Link中,不然会产生链接错误。

在NeHe的教程中,有一个基于Window SDK的基本的OpenGL的Framework,我觉得这个框架虽然看起来有些繁琐,但确实对各种错误处理都比较全面,代码比较强壮。但使用这个框架,需要一定的Win32编程基础,我就由于对Win32的思路不够细致,犯了一个错误,找了两天才搞定,结果只修改了一个字母,晕阿~~~犯错的主要原因是没有搞清楚窗口创建过程。在我们CreateWindow之前,需要先注册一个窗口类。而且,一旦窗口类注册成功之后,会先调用WndProc(HWND, UINT, WPARAM, LPARAM)(消息处理函数),其中DefWindowProc(hwnd, uMsg, wParam, lParam)中的hwnd必须是WinProc当中的传进来的参数,而不能是其他全局的什么参数。我犯的错误就是将hwnd写成了hWnd,而hWnd是全局的一个窗口句柄,由于窗口还没有创建成功,所以这时hWnd是一个空值,于是,由于WndProc的调用失败导致了CreateWindow的失败。。。晕啊,这种错误写都写不清楚啊。。。

下面我就把到目前为止的我敲进去的代码贴出来一些,里面有比较详细的注释。

OpenGLDrawSth_1.cpp

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

HGLRC hRC = NULL;
HDC hDC = NULL;
HWND hWnd = NULL;
HINSTANCE hInstance;

bool keys[256];
bool active = TRUE;
bool fullscreen = TRUE;

LRESULT CALLBACK WndProc(
  HWND hWnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
);

// 下面的代码的作用是重新设置OpenGL场景的大小
// OpenGL场景的尺寸将被设置成它显示时所在窗口的大小
GLvoid ReSizeGLScene(GLsizei width, GLsizei height) {
 if (height == 0) {     // 防止被零除
  height = 1;
 }
 glViewport(0, 0, width, height); // 重置当前的视口(Viewport)

 // 为透视图设置屏幕。意味着越远的东西看起来越小
 // 这么做创建了一个现实外观的场景。此处透视按照基于窗口宽度和高度的45度视角来计算
 // 0.1f,100.0f是我们在场景中所能绘制深度的起点和终点
 glMatrixMode(GL_PROJECTION);     // 选择投影矩阵
 glLoadIdentity();        // 重置投影矩阵
 // 计算窗口的外观比例
 gluPerspective(45.0f, (GLfloat)width/(GLfloat)height, 0.1f, 100.0f);
 glMatrixMode(GL_MODELVIEW);      // 选择模型观察矩阵
 glLoadIdentity();        // 重置模型观察矩阵
}

// 对OpenGL进行所有的设置
// 我们将设置清除屏幕所用的颜色,打开深度缓存,启用smooth shading(阴影平滑),等等
int InitGL(GLvoid) {
 glShadeModel(GL_SMOOTH);        // 启用阴影平滑
 // 色彩值的范围从0.0f到1.0f。0.0f代表最黑的情况,1.0f就是最亮的情况
 // 采用RGBA模式
 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);     // 黑色背景
 // 将深度缓存设想为屏幕后面的层
 // 深度缓存不断的对物体进入屏幕内部有多深进行跟踪
 // 几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存
 // 它的排序决定那个物体先画。这样您就不会将一个圆形后面的正方形画到圆形上来
 glClearDepth(1.0f);          // 设置深度缓存
 glEnable(GL_DEPTH_TEST);        // 启用深度测试
 glDepthFunc(GL_LEQUAL);         // 所作深度测试的类型
 // 告诉OpenGL我们希望进行最好的透视修正
 // 这会十分轻微的影响性能。但使得透视图看起来好一点。
 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  // 真正精细的透视修正

 return TRUE;
}

GLfloat rtri;
GLfloat rquad;

// 任何您所想在屏幕上显示的东东都将在此段代码中出现
int DrawGLScene(GLvoid) {         // 从这里开始进行所有的绘制
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // 清除屏幕和深度缓存
 glLoadIdentity();          // 重置当前的模型观察矩阵

 //////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////
 glTranslatef(-1.5f, 0.0f, -6.0f);
 glRotatef(rtri, 0.0f, 1.0f, 0.0f);
 glBegin(GL_TRIANGLES);
  glColor3f(1.0f, 0.0f, 0.0f);
  glVertex3f(0.0f, 1.0f, 0.0f);
  glColor3f(0.0f, 1.0f, 0.0f);
  glVertex3f(-1.0f, -1.0f, 0.0f);
  glColor3f(0.0f, 0.0f, 1.0f);
  glVertex3f(1.0f, -1.0f, 0.0f);
 glEnd();
 
 glLoadIdentity();
 glTranslatef(1.5f, 0.0f, -6.0f);
 glRotatef(rquad, 1.0f, 0.0f, 0.0f);
 glBegin(GL_QUADS);
  glVertex3f(-1.0f, 1.0f, 0.0f);
  glVertex3f(1.0f, 1.0f, 0.0f);
  glVertex3f(1.0f, -1.0f, 0.0f);
  glVertex3f(-1.0f, -1.0f, 0.0f);
 glEnd();

 rtri += 10.0f;
 rquad -= 10.0f;
 //////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////

 return TRUE;
}

GLvoid KillGLWindow(GLvoid) {
 // 检查我们是否处于全屏模式。如果是,我们要切换回桌面
 if (fullscreen) {
  // 将NULL作为第一个参数,0作为第二个参数传递
  // 强制Windows使用当前存放在注册表中的值(缺省的分辨率、色彩深度、刷新频率,等等)
  // 来有效的恢复我们的原始桌面
  ChangeDisplaySettings(NULL, 0);
  ShowCursor(TRUE);
 }

 if (hRC) {
  // 查看我们能否释放它(将 hRC从hDC分开)
  if (!wglMakeCurrent(NULL, NULL)) {
   MessageBox(NULL, "Release Of DC And RC Failed.", "SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
  }
  // 试着删除着色描述表
  if (!wglDeleteContext(hRC)) {
   MessageBox(NULL, "Release Rendering Context Failed.", "SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
  }
  hRC = NULL;
 }

 // 查看是否存在设备描述表,如果有尝试释放它
 if (hDC && !ReleaseDC(hWnd, hDC)) {
  MessageBox(NULL, "Release Device Context Failed.", "SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
  hDC = NULL;
 }

 // 查看是否存在窗口句柄,我们调用 DestroyWindow( hWnd )来尝试销毁窗口
 if (hWnd && !DestroyWindow(hWnd)) {
  MessageBox(NULL, "Release hWnd Failed.", "SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
  hWnd = NULL;
 }

 // 注销我们的窗口类
 if (!UnregisterClass("OpenGL", hInstance)) {
  MessageBox(NULL, "Unregister Class Failed.", "SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
  hInstance = NULL;
 }
}

BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag) {
 GLuint PixelFormat;  // 保存相匹配的象素格式的模式值的结果

 WNDCLASS wc;  // 窗口类结构

 DWORD dwExStyle; // 扩展窗口风格
 DWORD dwStyle;  // 窗口风格

 RECT WindowRect;      // 取得矩形的左上角和右下角的坐标值
 WindowRect.left = (long)0;    // 将Left设为0
 WindowRect.right = (long)width;   // 将Right设为要求的宽度
 WindowRect.top = (long)0;    // 将Top设为0
 WindowRect.bottom = (long)height;  // 将Bottom设为要求的高度

 fullscreen = fullscreenflag; // 设置全局全屏标志

 hInstance = GetModuleHandle(NULL);     // 取得我们窗口的实例
 wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;  // 移动时重画,并为窗口取得DC
 wc.lpfnWndProc = (WNDPROC)WndProc;     // WndProc处理消息
 wc.cbClsExtra = 0;         // 无额外窗口数据
 wc.cbWndExtra = 0;         // 无额外窗口数据
 wc.hInstance = hInstance;       // 设置实例
 wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);    // 装入缺省图标
 wc.hCursor = LoadCursor(NULL, IDC_ARROW);   // 装入鼠标指针
 wc.hbrBackground = NULL;       // GL不需要背景
 wc.lpszMenuName = NULL;        // 不需要菜单
 wc.lpszClassName = "OpenGL";      // 设定类名字

 // 尝试注册窗口类
 if (!RegisterClass(&wc)) {
  MessageBox(NULL, "Register Class Error.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
  return FALSE;
 }

 // 如果应该是全屏模式的话,我们将尝试设置全屏模式
 if (fullscreen) {
  // 全屏模式下所用的宽度和高度等同于窗口模式下的宽度和高度
  // 最最重要的是要在创建窗口之前设置全屏模式
  // 分配了用于存储视频设置的空间
  // 设定了屏幕的宽,高,色彩深度
  DEVMODE dmScreenSettings;         // 设备模式
  memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));  // 确保内存分配
  dmScreenSettings.dmSize = sizeof(dmScreenSettings);   // Devmode 结构的大小
  dmScreenSettings.dmPelsWidth = width;      // 所选屏幕宽度
  dmScreenSettings.dmPelsHeight = height;      // 所选屏幕高度
  dmScreenSettings.dmBitsPerPel = bits;      // 每象素所选的色彩深度
  dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

  // 尝试切换成与dmScreenSettings所匹配模式
  if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) {
   // 若模式失败,提供两个选项:退出或在窗口内运行
   if (MessageBox(NULL, "The Requested Fullscreen Mode Is Not Supported By/n"
    "Your Video Card. Use Windowed Instead?", "EXCEPTION", MB_YESNO | MB_ICONEXCLAMATION)
    == IDYES) {
    fullscreen = false;
   } else {
    MessageBox(NULL, "Program Will Now Close.", "ERROR", MB_OK | MB_ICONSTOP);
    return FALSE;
   }
  }
 }

 // 由于全屏模式可能失败,用户可能决定在窗口下运行
 // 我们需要在设置屏幕/窗口之前,再次检查fullscreen的值是TRUE或FALSE
 if (fullscreen) {
  // 设置扩展窗体风格为WS_EX_APPWINDOW,这将强制我们的窗体可见时处于最前面
  dwExStyle = WS_EX_APPWINDOW;
  // 窗体的风格设为WS_POPUP。这个类型的窗体没有边框,使我们的全屏模式得以完美显示
  dwStyle = WS_POPUP;
  // 在全屏模式下禁用鼠标指针通常是个好主意
  ShowCursor(FALSE);
 } else {
  // 在扩展窗体风格中增加了 WS_EX_WINDOWEDGE,增强窗体的3D感观
  dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
  // 窗体风格改用 WS_OVERLAPPEDWINDOW
  // 创建一个带标题栏、可变大小的边框、菜单和最大化/最小化按钮的窗体
  dwStyle = WS_OVERLAPPEDWINDOW;
 }

 // 通常边框会占用窗口的一部分
 // 使用AdjustWindowRectEx 后,我们的OpenGL场景就不会被边框盖住
 // 实际上窗口变得更大以便绘制边框。全屏模式下,此命令无效
 AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);  // 调整窗口达到真正要求的大小
   
 // 检查看窗口是否正常创建
 // 如果成功, hWnd保存窗口的句柄。如果失败,弹出消息窗口,并退出程序
 if (!(hWnd = CreateWindowEx(dwExStyle,    // 扩展窗体风格
  "OpenGL",          // 类名字
  title,           // 窗口标题
  // 要让OpenGL正常运行,这两个属性是必须的
  // 他们阻止别的窗体在我们的窗体内/上绘图
  WS_CLIPSIBLINGS | WS_CLIPCHILDREN |    // 必须的窗体风格属性
  dwStyle,          // 选择的窗体属性
  0, 0,           // 窗口位置
  WindowRect.right - WindowRect.left,    // 计算调整好的窗口宽度
  WindowRect.bottom - WindowRect.top,    // 计算调整好的窗口高度
  NULL,           // 无父窗口
  NULL,           // 无菜单
  hInstance,          // 实例
  NULL)))           // 不向WM_CREATE传递任何东东
 {
  KillGLWindow();
  MessageBox(NULL, "Window Creation Error.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
  return FALSE;
 }

 //下面的代码描述象素格式
 // 我们选择了通过RGBA(红、绿、蓝、alpha通道)支持OpenGL和双缓存的格式
 static PIXELFORMATDESCRIPTOR pfd = {
  sizeof(PIXELFORMATDESCRIPTOR),   // 上诉格式描述符的大小
  1,          // 版本号
  PFD_DRAW_TO_WINDOW |     // 格式必须支持窗口
  PFD_SUPPORT_OPENGL |     // 格式必须支持OpenGL
  PFD_DOUBLEBUFFER,      // 必须支持双缓冲
  PFD_TYPE_RGBA,       // 申请 RGBA 格式
  bits,         // 选定色彩深度
  0, 0, 0, 0, 0, 0,      // 忽略的色彩位
  0,          // 无Alpha缓存
  0,          // 忽略Shift Bit
  0,          // 无聚集缓存
  0, 0, 0, 0,        // 忽略聚集位
  16,          // 16位 Z-缓存 (深度缓存)
  0,          // 无模板缓存
  0,          // 无辅助缓存
  PFD_MAIN_PLANE,       // 主绘图层
  0,          // 保留
  0, 0, 0         // 忽略层遮罩
 };

 // 尝试取得OpenGL设备描述表。若无法取得DC,弹出错误消息程序退出
 if (!(hDC = GetDC(hWnd))) {
  KillGLWindow();
  MessageBox(NULL, "Can't Create A GL Device Context.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
  return FALSE;
 }

 // 为OpenGL窗口取得设备描述表后,我们尝试找到对应与此前我们选定的象素格式的象素格式
 // 如果Windows不能找到的话,弹出错误消息,并退出程序
 if (!(PixelFormat = ChoosePixelFormat(hDC, &pfd))) {
  KillGLWindow();
  MessageBox(NULL, "Can't Find A Suitable PixelFormat.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
  return FALSE;
 }

 // Windows 找到相应的象素格式后,尝试设置象素格式
 // 如果无法设置,弹出错误消息,并退出程序
 if (!SetPixelFormat(hDC, PixelFormat, &pfd)) {
  KillGLWindow();
  MessageBox(NULL, "Can't Set Pixel Format.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
  return FALSE;
 }

 // 正常设置象素格式后,尝试取得着色描述表
 // 如果不能取得着色描述表的话,弹出错误消息,并退出程序
 if (!(hRC = wglCreateContext(hDC))) {
  KillGLWindow();
  MessageBox(NULL, "Can't Create A GL Rendering Context", "ERROR", MB_OK | MB_ICONEXCLAMATION);
  return FALSE;
 }

 // 如果到现在仍未出现错误的话,我们已经设法取得了设备描述表和着色描述表
 // 接着要做的是激活着色描述表。如果无法激活,弹出错误消息,并退出程序
 if (!wglMakeCurrent(hDC, hRC)) {
  KillGLWindow();
  MessageBox(NULL, "Can't Active GL Rendering Context.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
  return FALSE;
 }

 ShowWindow(hWnd, SW_SHOW);   // 显示窗口
 SetForegroundWindow(hWnd);   // 略略提高优先级
 SetFocus(hWnd);      // 设置键盘的焦点至此窗口

 // 调用ReSizeGLScene 将屏幕的宽度和高度设置给透视OpenGL屏幕
 ReSizeGLScene(width, height);  // 设置透视 GL 屏幕

 // 跳转至 InitGL(),这里可以设置光照、纹理、等等任何需要设置的东东
 if (!InitGL()) {
  KillGLWindow();
  MessageBox(NULL, "Initilization Failed.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
  return FALSE;
 }

 return TRUE;
}

// 下面的代码处理所有的窗口消息
// 当我们注册好窗口类之后,程序跳转到这部分代码处理窗口消息。
LRESULT CALLBACK WndProc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
  )
{
 switch (uMsg) {       // 检查Windows消息
 case WM_ACTIVATE:      // 监视窗口激活消息
  {
   if (!HIWORD(wParam)) {   // 检查最小化状态
    // 如果窗口已被激活,变量active的值为TRUE
    active = TRUE;    // 程序处于激活状态
   } else {
    // 如果窗口已被最小化,将变量active设为FALSE
    active = FALSE;    // 程序不再激活
   }

   return 0;      // 返回消息循环
  }
 case WM_SYSCOMMAND:      // 中断系统命令Intercept System Commands
  {
   switch (wParam) {    // 检查系统调用Check System Calls
   case SC_SCREENSAVE:    // 屏保要运行?
   case SC_MONITORPOWER:   // 显示器要进入节电模式?
    return 0;     // 阻止发生 返回消息循环
   }
   break;
  }
 case WM_CLOSE:
  {
   PostQuitMessage(0);
   return 0;
  }
 case WM_KEYDOWN:
  {
   keys[wParam] = TRUE;   // 如果有键按下,设为TRUE
   return 0;
  }
 case WM_KEYUP:
  {
   keys[wParam] = FALSE;   // 如果有键放开,设为FALSE
  }
 case WM_SIZE:       // 调整OpenGL窗口大小
  {
   ReSizeGLScene(LOWORD(lParam), HIWORD(lParam));  // LoWord=Width,HiWord=Height
   return 0;
  }
 }

 return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(
  HINSTANCE hInstance,      // handle to current instance
  HINSTANCE hPrevInstance,  // handle to previous instance
  LPSTR lpCmdLine,          // command line
  int nCmdShow              // show state
)
{
 MSG msg;
 BOOL done = FALSE;  // 用来退出循环的Bool 变量

 // 提示用户选择运行模式
 if (MessageBox(NULL, "Would You Like To Run In Fullscreen Mode?",
  "Start Fullscreen?", MB_YESNO | MB_ICONQUESTION) == IDNO) {
  fullscreen = FALSE;
 }

 // 创建OpenGL窗口
 if (!CreateGLWindow("OpenGL DrawSth_1", 640, 480, 16, fullscreen)) {
  return 0;  // 失败退出
 }

 while (!done) {
  if (PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE)) {
   if (msg.message == WM_QUIT) {
    done = TRUE;
   } else {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
   }
  } else { // 如果没有消息,绘制我们的OpenGL场景
     // 代码的第一行查看窗口是否激活
     // 如果按下ESC键,done变量被设为TRUE,程序将会退出。
   if (active) {
    if (keys[VK_ESCAPE]) {
     done = TRUE;
    } else {
     // 绘制场景并交换缓存(使用双缓存可以实现无闪烁的动画)。
     DrawGLScene();
     SwapBuffers(hDC);
    }
   }

   // 允许用户按下F1键在全屏模式和窗口模式间切换
   if (keys[VK_F1]) {
    keys[VK_F1] = FALSE;
    KillGLWindow();
    fullscreen = !fullscreen;
    if (!CreateGLWindow("OpenGL DrawSth_1", 640, 480, 16, fullscreen)) {
     return 0;
    }
   }
  }
 }

 KillGLWindow();
 return (msg.wParam);
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

OpenGL初探:光照

OpenGL光照基础。

OpenGL初探:三维迷宫游戏(一)——场景漫游

使用OpenGL实现的一个简单迷宫游戏。

OpenGL初探:材料

OpenGL材料

初探 Qt Opengl【2】

最近在研究QOPengl QGraphicsView QGraphicsItemQGraphicsScene不过也只是皮毛,也不是做什么技术贴,就是记录一下自己在其中遇到的问题,和自己新学到的东西。 ...

OpenGL 实例化 初探 之 实例化绘制行星带

http://learnopengl-cn.readthedocs.io/zh/latest/04%20Advanced%20OpenGL/10%20Instancing/http://blog.cs...

Android 初探OpenGL ES

GLSurfaceview是内嵌的surface专门负责OpenGL渲染,主要特性如下: 1.管理EGLDisplay,它表示一个屏幕 2.管理Surface(一块内存区域) 3.GLSurfa...

OpenGL 实例化 初探 之 非实例化绘制行星带

http://learnopengl-cn.readthedocs.io/zh/latest/04%20Advanced%20OpenGL/10%20Instancing/http://blog.cs...

初探uCOS-II

  • 2014-10-17 09:59
  • 255KB
  • 下载

设计模式初探-迭代器模式

迭代器模式(ITERATOR),又称游标(Cursor),提供了一种方法,用于顺序访问一个聚合对象中的各个元素,而不需暴露该对象的内部表示。迭代器模式通过将对聚合对象(通常为列表)的访问和遍历从聚合对...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)