在本节中,并没有新的知识,只是通过glTranslatef()和glRotatef()函数对星星纹理映射的矩形的位置变换以及glColor4ub()函数对星星颜色改变达到混合效果实现星星自转、绕z轴旋转和闪烁的效果,可以说是对之前知识的回顾。
NEHEOpenGL例程的代码注释率达到90%以上,而51CTO翻译版的NEHE教程注释较少,上午又下载了NEHE例程源码,并将其英文注释全部翻译了一遍,算是对知识的一次巩固。
NeHe原版例程下载地址OpenGL教程Nehe版下载地址
#include "OpenGLSB.h"
#include <stdio.h>
#include <stdlib.h>
#include "glaux.h"
HDC hDC = NULL; // 私有GDI设备环境
HGLRC hRC = NULL; // 永久透视环境
HWND hWnd = NULL; // 保持窗口句柄
HINSTANCE hInstance; // 保持应用实例
bool keys[256]; // 键盘活动存储数组
bool active = TRUE; // 窗口有效标志默认设置为TRUE
bool fullscreen = TRUE; // 全屏标志默认设置为全屏
bool twinkle; // 闪烁的星星
bool tp; // ‘T’键按下?
const int num = 50; //要画的星星的个数
typedef struct // 创建一个星星的结构体
{
int r, g, b; // 星星的颜色
GLfloat dist, // 星星距离中心的距离
angle; // 星星当前角度
}
stars;
stars star[num]; // 需要保持对'num'个星星的追踪
GLfloat zoom = -15.0f; // 星星远离的距离
GLfloat tilt = 90.0f; // 视角倾斜
GLfloat spin; // 旋转星星
GLuint loop; // 通用循环变量
GLuint texture[1]; // 用于存储一个纹理
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // WndProc函数声明
AUX_RGBImageRec *LoadBMP(char *Filename) // 载入一个位图图像
{
FILE *File = NULL; //文件句柄
if (!Filename) // 确保文件名给出
{
return NULL; // 如果不返回NULL
}
File = fopen(Filename, "r"); // 检查文件是否存在
if (File) //文件存在否?
{
fclose(File); // 关闭句柄
return auxDIBImageLoad(Filename); // 加载位图并返回一个指针
}
return NULL; // 如果加载失败则返回NULL
}
int LoadGLTextures() //加载位图并转换成纹理
{
int Status = FALSE; //状态指示器
AUX_RGBImageRec *TextureImage[1]; // 创建一个纹理存储空间
memset(TextureImage, 0, sizeof(void *) * 1); // 将指针指向NULL
// 加载位图,检查错误,如果文图不存在则退出
if (TextureImage[0] = LoadBMP("Data/Star.bmp"))
{
Status = TRUE; // 将状态设置未TRUE
glGenTextures(1, &texture[0]); // 创建一个纹理
// 创建线性滤波纹理
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
}
if (TextureImage[0]) // 如果纹理存在
{
if (TextureImage[0]->data) // 如果纹理图像存在
{
free(TextureImage[0]->data); // 释放纹理图像内存
}
free(TextureImage[0]); // 释放图像结构体
}
return Status; // 返回状态
}
GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // 调整GL窗口并初始化
{
if (height == 0) // 避免A被0除
{
height = 1; // 让高度等于1
}
glViewport(0, 0, width, height); // 重置当前视角
glMatrixMode(GL_PROJECTION); // 选择投射矩阵
glLoadIdentity(); // 重置投射矩阵
// 计算窗口方向比
gluPerspective(45.0f, (GLfloat)width / (GLfloat)height, 0.1f, 100.0f);
glMatrixMode(GL_MODELVIEW); // 选择模型视图矩阵
glLoadIdentity(); // 重置模型视图矩阵
}
int InitGL(GLvoid) // 这儿开始OpenGL的所有设置
{
if (!LoadGLTextures()) //调用纹理加载历程
{
return FALSE; // 如果纹理未能加载返回FALSE
}
glEnable(GL_TEXTURE_2D); // 启用纹理映射
glShadeModel(GL_SMOOTH); // 启用平滑阴影
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // 黑色背景
glClearDepth(1.0f); // 深度缓存启动
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // 真实的nice级的场景计算
glBlendFunc(GL_SRC_ALPHA, GL_ONE); // 设置半透明混合函数
glEnable(GL_BLEND);
for (loop = 0; loop<num; loop++)
{
star[loop].angle = 0.0f;
star[loop].dist = (float(loop) / num)*5.0f;
star[loop].r = rand() % 256;
star[loop].g = rand() % 256;
star[loop].b = rand() % 256;
}
return TRUE; // 初始化OK
int DrawGLScene(GLvoid) // 这儿是我们所做的所有的绘制
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕和深度缓存
glBindTexture(GL_TEXTURE_2D, texture[0]); // 选择我们的纹理
for (loop = 0; loop<num; loop++) // 循环所有的星星
{
glLoadIdentity(); // 我们画每个星星前重置模型观察矩阵
glTranslatef(0.0f, 0.0f, zoom); // 深入屏幕(使用zoom里的数值)
glRotatef(tilt, 1.0f, 0.0f, 0.0f); //倾斜视角(使用tilt里的数值)
glRotatef(star[loop].angle, 0.0f, 1.0f, 0.0f); // 旋转至当前星星的角度
glTranslatef(star[loop].dist, 0.0f, 0.0f); // 在x平面前移(注:在观察者视角应是向右移动)
glRotatef(-star[loop].angle, 0.0f, 1.0f, 0.0f); // 取消当前星星角度
glRotatef(-tilt, 1.0f, 0.0f, 0.0f); // 取消屏幕倾斜
if (twinkle) //启用闪烁效果
{
//第一遍绘制静止的星星
glColor4ub(star[(num - loop) - 1].r, star[(num - loop) - 1].g, star[(num - loop) - 1].b, 255);
glBegin(GL_QUADS); //开始绘制纹理映射的四边形
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
glEnd();
}
//第二遍绘制绕z轴旋转的星星
glRotatef(spin, 0.0f, 0.0f, 1.0f);
glColor4ub(star[loop].r, star[loop].g, star[loop].b, 255);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
glEnd();
spin += 0.01f;
star[loop].angle += float(loop) / num;
star[loop].dist -= 0.01f;
if (star[loop].dist<0.0f) //星星到达中心
{
star[loop].dist += 5.0f;
star[loop].r = rand() % 256;
star[loop].g = rand() % 256;
star[loop].b = rand() % 256;
}
}
return TRUE; //一切OK
}
GLvoid KillGLWindow(GLvoid) //恰当地消除窗口
{
if (fullscreen) //我们在全屏模式吗
{
ChangeDisplaySettings(NULL, 0); // 如果是,我们切换回桌面
ShowCursor(TRUE); // 显示鼠标指针
}
if (hRC) //我们拥有一个渲染描述表吗
{
if (!wglMakeCurrent(NULL, NULL)) // 我们能释放DC(设备描述表)或RC(渲染描述表)吗
{
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; // 将hRC设置为空
}
if (hDC && !ReleaseDC(hWnd, hDC)) // 我们能释放设备描述表吗
{
MessageBox(NULL, "Release Device Context Failed.", "SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
hDC = NULL; // 将DC设置为空
}
if (hWnd && !DestroyWindow(hWnd)) // 我们能清除窗口吗
{
MessageBox(NULL, "Could Not Release hWnd.", "SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
hWnd = NULL; // 将窗口句柄设置为空
}
if (!UnregisterClass("OpenGL", hInstance)) // 我们能注销类吗
{
MessageBox(NULL, "Could Not Unregister Class.", "SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
hInstance = NULL; // 将实例句柄设置为空
}
}
/* This Code Creates Our OpenGL Window. Parameters Are: *
* title - Title To Appear At The Top Of The Window *
* width - Width Of The GL Window Or Fullscreen Mode *
* height - Height Of The GL Window Or Fullscreen Mode *
* bits - Number Of Bits To Use For Color (8/16/24/32) *
* fullscreenflag - Use Fullscreen Mode (TRUE) Or Windowed Mode (FALSE) */
/* 这段代码创建我们地OpenGL窗口,参数为: *
* title -标题出现在窗口地最顶端 *
* width -GL窗口或全屏模式的宽度 *
* height -GL窗口或全屏模式的高度 *
* bits -用于表示颜色的位数(8/16/24/32) *
* fullscreenflag -使用全屏模式(TRUE)或窗口模式(FLASE) *
*/
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
GLuint PixelFormat; // 搜索到匹配项后保持结果
WNDCLASS wc; // Windows类结构
DWORD dwExStyle; // 窗口拓展类型
DWORD dwStyle; // 窗口类型
RECT WindowRect; // 取得矩形左上/右下值
WindowRect.left = (long)0; // 左值设为0
WindowRect.right = (long)width; // 右值设置为要求的宽度
WindowRect.top = (long)0; // 上值设置为0
WindowRect.bottom = (long)height; // 下值设置为要求的高度
fullscreen = fullscreenflag; //设置全局全屏标志
hInstance = GetModuleHandle(NULL); //取得我们窗口的一个实例
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // 重绘尺寸和窗口自己的设备描述表
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, "Failed To Register The Window Class.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
return FALSE; //返回FALSE
}
if (fullscreen) //试图全屏模式?
{
DEVMODE dmScreenSettings; // 设备模式
memset(&dmScreenSettings, 0, sizeof(dmScreenSettings)); //确保内存清空
dmScreenSettings.dmSize = sizeof(dmScreenSettings); //开发模式结构体大小
dmScreenSettings.dmPelsWidth = width; // 选定屏幕宽度
dmScreenSettings.dmPelsHeight = height; // 选定屏幕高度
dmScreenSettings.dmBitsPerPel = bits; // 选定像素色彩深度
dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
// 试图设置选定的模式并返回结果 提示: CDS_FULLSCREEN没有Start Bar.
if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
{
// 如果模式失败,提供两个选项,退出或使用窗口模式
if (MessageBox(NULL, "The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?", "NeHe GL", MB_YESNO | MB_ICONEXCLAMATION) == IDYES)
{
fullscreen = FALSE; //选定窗口模式. Fullscreen = FALSE
}
else
{
//弹出一个消息盒让用户知道程序正在关闭
MessageBox(NULL, "Program Will Now Close.", "ERROR", MB_OK | MB_ICONSTOP);
return FALSE; // 返回FALSE
}
}
}
if (fullscreen) //我们仍在全屏模式?
{
dwExStyle = WS_EX_APPWINDOW; //窗口扩展类型
dwStyle = WS_POPUP; // 窗口类型
ShowCursor(FALSE); // 隐藏鼠标指针
}
else
{
dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // 窗口拓展类型
dwStyle = WS_OVERLAPPEDWINDOW; // 窗口类型
}
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); // 将窗口调整到正确的所需尺寸
// 创建窗口
if (!(hWnd = CreateWindowEx(dwExStyle, // 窗口拓展类型
"OpenGL", // 类名
title, // 窗口名称
dwStyle | // 定义的窗口类型
WS_CLIPSIBLINGS | //所需的窗口类型
WS_CLIPCHILDREN, //所需的窗口类型
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; //返回FALSE
}
static PIXELFORMATDESCRIPTOR pfd = // pfd(像素格式描述符)告诉窗口我们想让东西变成什么样
{
sizeof(PIXELFORMATDESCRIPTOR), // pfd大小
1, // 版本号
PFD_DRAW_TO_WINDOW | // 格式必须支持窗口
PFD_SUPPORT_OPENGL | // 格式必须支持OpenGL
PFD_DOUBLEBUFFER, // 必须支持双缓存
PFD_TYPE_RGBA, // 需要RGBA格式(红绿蓝Alpha)
bits, // 选择我们的色彩深度
0, 0, 0, 0, 0, 0, // 忽略的色彩位
0, // 无Alpha缓存
0, // 忽略的移位
0, // 无累加缓存
0, 0, 0, 0, // 忽略的累加缓存
16, // 16Bit Z-Buffer (深度缓存)
0, // 无蒙版缓存
0, // 无辅助缓存
PFD_MAIN_PLANE, // 主绘图层
0, // 预留项
0, 0, 0 // 忽略层遮罩
};
if (!(hDC = GetDC(hWnd))) //我们获得设备描述表了吗?
{
KillGLWindow(); // 重置显示
MessageBox(NULL, "Can't Create A GL Device Context.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
return FALSE; // 返回错误
}
if (!(PixelFormat = ChoosePixelFormat(hDC, &pfd))) // windows找到匹配的像素格式了吗?
{
KillGLWindow(); // 重置显示
MessageBox(NULL, "Can't Find A Suitable PixelFormat.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
return FALSE; // 返回FALSE
}
if (!SetPixelFormat(hDC, PixelFormat, &pfd)) //我们能设置像素格式吗?
{
KillGLWindow(); //重置显示
MessageBox(NULL, "Can't Set The PixelFormat.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
return FALSE; // 返回 FALSE
}
if (!(hRC = wglCreateContext(hDC))) //我们能获取渲染描述表吗?
{
KillGLWindow(); // 重置显示
MessageBox(NULL, "Can't Create A GL Rendering Context.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
return FALSE; //返回 FALSE
}
if (!wglMakeCurrent(hDC, hRC)) // 试图激活像素描述表
{
KillGLWindow(); // R重置显示
MessageBox(NULL, "Can't Activate The GL Rendering Context.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
return FALSE; // 返回FALSE
}
ShowWindow(hWnd, SW_SHOW); //显示窗口
SetForegroundWindow(hWnd); //微微提升优先级
SetFocus(hWnd); //设置键盘焦点于此窗口
ReSizeGLScene(width, height); //设置透视GL屏幕
if (!InitGL()) // 初始化我们新创建的GL窗口
{
KillGLWindow(); // 重置显示
MessageBox(NULL, "Initialization Failed.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
return FALSE; // 返回 FALSE
}
return TRUE; // 成功
}
LRESULT CALLBACK WndProc(HWND hWnd, // 解决这个窗口
UINT uMsg, // 这个窗口的消息
WPARAM wParam, // 额外消息信息
LPARAM lParam) // 额外消息信息
{
switch (uMsg) //检查消息
{
case WM_ACTIVATE: //监视窗口激活消息
{
if (!HIWORD(wParam)) // 检查最低状态
{
active = TRUE; // 程序被激活
}
else
{
active = FALSE; //程序不再激活
}
return 0; // 返回到消息循环
}
case WM_SYSCOMMAND: // 系统中断命令
{
switch (wParam) //检查系统调用
{
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
return 0; // 返回
}
case WM_SIZE: //重设OpenGL窗口尺寸
{
ReSizeGLScene(LOWORD(lParam), HIWORD(lParam)); // LoWord=Width, HiWord=Height
return 0; // 返回
}
}
//所有未解决的消息传送到DefWindowProc
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, // 实例
HINSTANCE hPrevInstance, // 之前的实例
LPSTR lpCmdLine, // 命令行参数
int nCmdShow) // 窗口显示状态
{
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("NeHe's Animated Blended Textures Tutorial", 640, 480, 16, fullscreen))
{
return 0; // 窗口未被创建退出
}
while (!done) // 当done=FALSE循环
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) //有消息等待?
{
if (msg.message == WM_QUIT) //我们收到一个退出的消息?
{
done = TRUE; //如果是done=TRUE
}
else // 如果不是,处理窗口消息
{
TranslateMessage(&msg); // 翻译消息
DispatchMessage(&msg); // 传递消息
}
}
else //没有消息
{
//绘制场景.监视来自DrawGLScene()的ESC键和退出消息
if ((active && !DrawGLScene()) || keys[VK_ESCAPE]) // 激活? 或 有一个退出收到?
{
done = TRUE; // ESC or DrawGLScene标记一个退出
}
else // 未退出,更行场景 (原例程标注为not time to quit)
{
SwapBuffers(hDC); //转换缓存(双缓存)
if (keys['T'] && !tp)
{
tp = TRUE;
twinkle = !twinkle;
}
if (!keys['T'])
{
tp = FALSE;
}
if (keys[VK_UP])
{
tilt -= 0.5f;
}
if (keys[VK_DOWN])
{
tilt += 0.5f;
}
if (keys[VK_PRIOR])
{
zoom -= 0.2f;
}
if (keys[VK_NEXT])
{
zoom += 0.2f;
}
if (keys[VK_F1]) //F1按下?
{
keys[VK_F1] = FALSE; //如果是,让Key为FALSE
KillGLWindow(); // 关闭当前窗口
fullscreen = !fullscreen; // 开启 全屏 / 窗口模式
// 重新创建我们的OpenGL窗口
if (!CreateGLWindow("NeHe's Animated Blended Textures Tutorial", 640, 480, 16, fullscreen))
{
return 0; // 如果窗口没有被创建退出
}
}
}
}
}
// 关闭
KillGLWindow(); //关闭窗口
return (msg.wParam); // 退出程序
}