下面要讲一些理论的东西了,请不要感到厌烦,因为如果没有这些知识,我们的三维教程将很难进行下去。为了方便的描述三维场景中物体的旋转、平移、缩放等空间变换操作,我们引入三维变换矩阵的概念。这是一个4X4的矩阵,当然单位矩阵的对角线上的值都是1了。看这貌似平凡的矩阵,里面却蕴藏着无数的神奇。比如在笛卡尔坐标系中有一个空间点,坐标是10, 10, 10,现在你想把这一点平移5, -2, 8个单位,那么你只需要将变换矩阵最后一行的前三列的值为别赋为5、-2和8再将空间点的坐标做为一个4X1的矩阵,最后一列补0再与变换矩阵求积(什么?你不会算矩阵相乘?!我倒!),得到的4X1矩阵的前三列值便是变换过的空间点坐标的X、Y和Z。同样的旋转、缩放也是大致的方法,区别仅在于变换矩阵里不同位置的值代表不同的含义。
现在我们将开始绘图。先确定一下视角:
// 设置模形矩阵 void SetModalMatrix( void ) { glMatrixMode( GL_MODELVIEW ); glLoadIdentity( ); // 单位化矩阵 // 这个函数是在OnIdle里被调用的,所以我们用下面的代码来实现物体的旋转 // 一个很容易理解的概念是,你绕着物体转和物体自己转在某些简单场景里的 // 的效果看起来是一样的,所以我们通过矩阵运算让眼睛点在一定高度做圆周 // 运动。知道圆的简化方程是:(sinα* r)^2 + (cosα* r)^2 = r^2,所以代码 // 很好理解。 static float fRadius = 0; fRadius += 0.01f; if ( fRadius > M_PI * 2 ) { fRadius = 0; } gluLookAt( cosf( fRadius ) * 30, sinf( fRadius ) * 30, 15.0, 0.0, 0.0, 0.0, // 向原点坐标看去 0.0, 0.0, 1.0 ); // 设置眼睛(摄影机)的方向向量,该向量表示眼表向上 }
// 设置透视矩阵 void SetProjMatrix( WORD wWidth, WORD wHeight ) { // 此函数将在WM_SIZE时被调用,所以应该设置一下glViewPort glViewport( 0, 0, wWidth, wHeight ); glMatrixMode( GL_PROJECTION ); glLoadIdentity( ); // 这和照象机很类似,第一个参数设置镜头广角度,第二个参数是长宽比,后面是远近剪切。 gluPerspective( 45.0, (double)wWidth / (double)wHeight, 1.0, 1000.0 ); }
|
然后我们在OnDraw里调用下面的代码:
// 先将上次渲染的残留物清为背景色 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glBegin( GL_QUADS ); // 设置绘制模式,我们画一个平面的四边形 glVertex2i( 5, 5 ); glVertex2i( 5, -5 ); glVertex2i( -5, -5 ); glVertex2i( -5, 5 ); SwapBuffers( g_hDC ); // 交换前后缓冲,双缓冲无闪烁 |
至此,GlTest.cpp中的代码因该是这个样子:
// GlTest.cpp : 定义应用程序的入口点。 //
#include "stdafx.h" #include "GlTest.h" #define MAX_LOADSTRING 100
// 全局变量: HINSTANCE hInst; // 当前实例 HWND g_hWnd; HDC g_hDC; HGLRC g_glRes;
TCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本 TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
// 此代码模块中包含的函数的前向声明: void OnCreate( HWND hWnd ); void OnCreated( void ); void OnDestroy( void ); void OnDraw( void ); void SetProjMatrix( WORD wWidth, WORD wHeight ); void SetModalMatrix( void ); void OnIdle( void );
ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // TODO: 在此放置代码。 MSG msg; HACCEL hAccelTable;
// 初始化全局字符串 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_GLTEST, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance);
// 执行应用程序初始化: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; }
hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_GLTEST);
// 主消息循环: while ( true ) { if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } continue; } if ( WM_QUIT == msg.message ) { break; } OnIdle(); }
return (int) msg.wParam; }
// // 函数:MyRegisterClass() // // 目的:注册窗口类。 // // 注释: // // 仅当希望在已添加到 Windows 95 的 // “RegisterClassEx”函数之前此代码与 Win32 系统兼容时, // 才需要此函数及其用法。调用此函数 // 十分重要,这样应用程序就可以获得关联的 // “格式正确的”小图标。 // ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = 0; wcex.lpfnWndProc = (WNDPROC)WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_GLTEST); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = (LPCTSTR)IDC_GLTEST; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
return RegisterClassEx(&wcex); }
// // 函数:InitInstance(HANDLE, int) // // 目的:保存实例句柄并创建主窗口 // // 注释: // // 在此函数中,我们在全局变量中保存实例句柄并 // 创建和显示主程序窗口。 // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { hInst = hInstance; // 将实例句柄存储在全局变量中
CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if ( !g_hWnd ) { return FALSE; } OnCreated();
ShowWindow( g_hWnd, nCmdShow ); UpdateWindow( g_hWnd);
return TRUE; }
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; switch (message) { case WM_CREATE: OnCreate( hWnd ); break; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // 分析菜单选择: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_SIZE: SetProjMatrix( LOWORD( lParam ), HIWORD( lParam ) ); break; case WM_DESTROY: OnDestroy(); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
// “关于”框的消息处理程序。 LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: return TRUE;
case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return TRUE; } break; } return FALSE; }
void OnCreate( HWND hWnd ) { g_hWnd = hWnd; }
void OnDestroy( void ) { ReleaseDC( g_hWnd, g_hDC ); wglDeleteContext( g_glRes ); }
void OnCreated( void ) { g_hDC = GetDC( g_hWnd ); PIXELFORMATDESCRIPTOR pfd; ZeroMemory( &pfd, sizeof(PIXELFORMATDESCRIPTOR) ); pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR ); pfd.nVersion = 1; pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 24; pfd.cDepthBits = 32;
SetPixelFormat( g_hDC, ChoosePixelFormat( g_hDC, &pfd ), &pfd );
g_glRes = wglCreateContext( g_hDC ); wglMakeCurrent( g_hDC, g_glRes );
glEnable( GL_CULL_FACE ); glCullFace( GL_BACK );
glEnable( GL_DEPTH_TEST ); glDepthFunc( GL_LEQUAL );
int LightPos[] = { 50, 50, 10, 1 }; float LightColor[] = { 0.3f, 0.3f, 0.3f, 1.0f };
glEnable(GL_LIGHTING); glLightiv( GL_LIGHT0, GL_POSITION, LightPos ); glLightfv( GL_LIGHT0, GL_AMBIENT, LightColor ); glLightfv( GL_LIGHT0, GL_DIFFUSE, LightColor ); glEnable( GL_LIGHT0 ); glEnable( GL_COLOR_MATERIAL ); glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );
glShadeModel( GL_SMOOTH );
glClearColor( 255.0f / 255.0f, 255.0f / 255.0f, 200.0f / 255.0f, 0.0 ); glColor3ub( 140, 200, 255 ); }
void OnDraw( void ) { glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glBegin( GL_QUADS ); // 设置绘制模式,我们画一个平面的四边形 glVertex2i( 5, 5 ); glVertex2i( -5, 5 ); glVertex2i( -5, -5 ); glVertex2i( 5, -5 ); glEnd(); SwapBuffers( g_hDC ); // 交换前后缓冲,双缓冲无闪烁 }
void SetModalMatrix( void ) { glMatrixMode( GL_MODELVIEW ); glLoadIdentity( ); static float fRadius = 0; fRadius += 0.01f; if ( fRadius > M_PI * 2 ) { fRadius = 0; } gluLookAt( cosf( fRadius ) * 30, sinf( fRadius ) * 30, 15.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0 ); }
void SetProjMatrix( WORD wWidth, WORD wHeight ) { glViewport( 0, 0, wWidth, wHeight ); glMatrixMode( GL_PROJECTION ); glLoadIdentity( ); gluPerspective( 45.0, (double)wWidth / (double)wHeight, 1.0, 1000.0 ); }
void OnIdle( void ) { SetModalMatrix(); OnDraw(); } |
现在程序可以运行了,告诉我你看到了什么?应该是一个旋转的平面四边形。你什么也没有看到?请仔细复查你上面写的程序,看是不是每一句都和我一样。如果你仍然得不到解决,可以发短消息给我来争取获得帮助的机会。但请注意,我并不会对每一个愚蠢的问题都做出答复,比如请不要问我为什么你的窗体创建不出来或VC.net在哪里下载。
|