一、
MFC
框架下使用
OpenGL
繪圖演示平移、旋轉、縮放等操作的具體實現
二、
實現算法
繪制的圖形以各種頂點及頂點之間的線、面構成
要想對這些圖形進行操作,只需要對各種頂點在坐標系中的坐標進行轉換即可
頂點在
OpenGL
中的坐標以四維形式表示:(
X,Y,Z,W
)
其中
X,Y,Z
對應三維系統中
X
,
Y
,
Z
三個柚;
W
默認為
1
,一般為了方便各種操作時坐標換算而將三維坐標系提升為四次,即齊次坐標系。
①
平移操作——對應
glTranslate
函數:
初始點:(
X,Y,Z,W
)
使用轉換矩陣
TMatrix
:
1, 0, 0, Tx
0, 1, 0, Ty
0, 0, 1, Tz
0, 0, 0, 1
(X’, Y’, Z’) =
TMatrix * (X, Y, Z),
其中
Tx
,
Ty
,
Tz
對應
glTranslate
函數中的三個參數,分別表示三個方向的的平移量;原始坐標右乘平移矩陣后即得到新的坐標
③
縮放操作——對應
glScale
函數:
使用轉換矩陣為
SMatrix
:
Sx, 0, 0, 0
0, Sy, 0, 0
0, 0, Sz, 0
0, 0, 0, 1
(X’, Y’, Z’) =
SMatrix * (X, Y, Z)
此次自己實現的縮放函數與
OpenGL
有所不同,我實現的是與圖形的中心點為基準進行縮放,所以進行矩陣變換前需要計算各個坐標的位置并求幾何中心。
②
旋轉操作——對應
glRotate
函數(逆時針方向):
在三維坐標系統中,旋轉操作與二維情況有所不同,因為三維中的一個點可以繞不同的方向進行旋轉,所以除了要指定旋轉角度外,還要指定旋轉軸
當所取旋轉軸為
X
,
Y
或
Z
三個坐標軸時,與二維情況相似
即所選取的軸那一方向上的坐標不變,在其它兩個軸所成的平面上進行二維旋轉變換
根據這個規律可以把三維中繞任意軸旋轉的操作分為幾個操作來完成
步驟:
先將待旋轉的坐標與旋轉軸平移,使旋轉軸經過原點
進行若干次旋轉操作,例旋轉軸與
Z
軸正向重合
依據對坐標軸旋轉的方法將待旋轉坐標進行旋轉操作
應用逆旋轉使旋轉軸回到原方向
應用逆平移使旋轉軸回到原位置
二維情況下旋轉矩陣為:
cosQ, -sinQ, 0
sinQ, cosQ, 0
0, 0, 1
此次采用先繞
X
轉做旋轉
Rx
,再繞
Y
轉旋轉
Ry
使旋轉軸與
Z
轉正向重合,再繞
Z
軸做
Rz
旋轉
Q
度,再進行逆操作還原位置
即
: (X’, Y’, Z’) = Rx*Ry*Rz(Q)*(Ry
-1
)*(Rx-1)
其中
Q
是需要旋轉的角度,
逆矩陣
A
-1
= A*/|A|
代數余子式:
(-1)^(i+j)Aij
三、
程序結構
使用
MFC
生成程序界面,
OpenGL
負責繪制圖形
文檔
/
視圖結構
文件數據為,圖形的類型以及各個頂點坐標
從菜單中選擇要進行的操作:平移,旋轉,縮放后出現對話框輸入參數
平移——
3
個參數,分別表示
X,Y,Z
各個軸向的平移量
旋轉——
4
個參數,第一個角度
Angle,
后三個表示轉軸向量
縮放——
3
個參數,表示
X,Y,Z
軸方向上的縮放比例,可以為負數
文件數據為:
一個
CArray<Point,
Point> m_PointArray
保存圖形的各個頂點信息
一個表示圖形形狀的
int m_nShape
表示
glBegin
函數里的參數
四、
MFC
中使用
OpenGL
繪圖
具體還沒有研究,只是照搬網上教程,以后補上
View
頭文件中加入
HGLRC m_hRC; //Rendering
Context
CDC* m_pDC; //Device
Context
BOOL InitializeOpenGL(); //Initialize OpenGL
BOOL SetupPixelFormat(); //Set up the Pixel Format
實現部分:
InitializeOpenGL
BOOL CHomeWork1View::InitializeOpenGL()
{
//
Get a DC for the Client Area
m_pDC
=
new
CClientDC(
this
);
//
Failure to Get DC
if
(m_pDC
==
NULL)
{
MessageBox(_T(
"
Error Obtaining DC
"
));
return
FALSE;
}
//
Failure to set the pixel format
if
(
!
SetupPixelFormat())
{
return
FALSE;
}
//
Create Rendering Context
m_hRC
=
::wglCreateContext (m_pDC
->
GetSafeHdc ());
//
Failure to Create Rendering Context
if
(m_hRC
==
0
)
{
MessageBox(_T(
"
Error Creating RC
"
));
return
FALSE;
}
//
Make the RC Current
if
(::wglMakeCurrent (m_pDC
->
GetSafeHdc (), m_hRC)
==
FALSE)
{
MessageBox(_T(
"
Error making RC Current
"
));
return
FALSE;
}
//
Specify Black as the clear color
::glClearColor(
0.0f
,
0.0f
,
0.0f
,
0.0f
);
//
Specify the back of the buffer as clear depth
::glClearDepth(
1.0f
);
//
Enable Depth Testing
::glEnable(GL_DEPTH_TEST);
return
TRUE;
}
SetupPixelFormat
BOOL CHomeWork1View::SetupPixelFormat()
{
static
PIXELFORMATDESCRIPTOR pfd
=
{
sizeof
(PIXELFORMATDESCRIPTOR),
//
size of this pfd
1
,
//
version number
PFD_DRAW_TO_WINDOW
|
//
support window
PFD_SUPPORT_OPENGL
|
//
support OpenGL
PFD_DOUBLEBUFFER,
//
double buffered
PFD_TYPE_RGBA,
//
RGBA type
24
,
//
24-bit color depth
0
,
0
,
0
,
0
,
0
,
0
,
//
color bits ignored
0
,
//
no alpha buffer
0
,
//
shift bit ignored
0
,
//
no accumulation buffer
0
,
0
,
0
,
0
,
//
accum bits ignored
16
,
//
16-bit z-buffer
0
,
//
no stencil buffer
0
,
//
no auxiliary buffer
PFD_MAIN_PLANE,
//
main layer
0
,
//
reserved
0
,
0
,
0
//
layer masks ignored
};
int
m_nPixelFormat
=
::ChoosePixelFormat(m_pDC
->
GetSafeHdc(),
&
pfd);
if
( m_nPixelFormat
==
0
)
{
return
FALSE;
}
if
( ::SetPixelFormat(m_pDC
->
GetSafeHdc(), m_nPixelFormat,
&
pfd)
==
FALSE)
{
return
FALSE;
}
return
TRUE;
}
此外需要將
View
類中的
OnEraseBkgnd
消息屏蔽掉,使之直接返回
TRUE
,因為消除背景的工作已經將由
OpenGL
來做,不需要由這個函數再來做一遍。
在
PreCreateWindow
函數中修改
cs.style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
保證
OpenGL
不會繪制到其它窗口中去
在
OnCreate
函數中調用
InitializeOpenGL
函數就可以在
View
中進行繪制了
另外,為了適應窗口大小變化,還需要在
OnSize
函數中加入高速視域的代碼
OnSize
void
CHomeWork1View::OnSize(UINT nType,
int
cx,
int
cy)
{
CView::OnSize(nType, cx, cy);
//
TODO: 在此處添加消息處理程序代碼
GLdouble aspect_ratio;
//
width/height ratio
if
(
0
>=
cx
||
0
>=
cy )
{
return
;
}
//
select the full client area
::glViewport(
0
,
0
, cx, cy);
//
compute the aspect ratio
//
this will keep all dimension scales equal
aspect_ratio
=
(GLdouble)cx
/
(GLdouble)cy;
//
select the projection matrix and clear it
::glMatrixMode(GL_PROJECTION);
::glLoadIdentity();
//
select the viewing volume
::gluPerspective(
120
, aspect_ratio,
1.0f
,
150.0f
);
//
switch back to the modelview matrix and clear it
::glMatrixMode(GL_MODELVIEW);
::glLoadIdentity();
}
做好一切準備工作后,就可以在
OnDraw
中使用
OpenGL
函數進行繪制了,不過別忘了程序最后要進行資源的釋放,在
OnDestroy
中加入清理工作
OnDestroy
void
CHomeWork1View::OnDestroy()
{
CView::OnDestroy();
//
TODO: 在此處添加消息處理程序代碼
//
Make the RC non-current
if
(::wglMakeCurrent (
0
,
0
)
==
FALSE)
{
MessageBox(_T(
"
Could not make RC non-current
"
));
}
//
Delete the rendering context
if
(::wglDeleteContext (m_hRC)
==
FALSE)
{
MessageBox(_T(
"
Could not delete RC
"
));
}
//
Delete the DC
if
(m_pDC)
{
delete m_pDC;
}
//
Set it to NULL
m_pDC
=
NULL;
}
最后程序演示結果:
五、
編程中遇到的問題總結
1.
復習了操作符重載的相關內容
2.
浮點數的比較,這個比較重要,以前在課上只是聽說,沒有特別印象,在耗費了我
2
個小時的調試之后,以后應該再也不會忘了。
Fabs
(
f1-f2
)
<=
精度要求
3.
此次寫的工具類
Matrix
勝于處理向量,矩陣的相關操作,因為偷懶還不太完善,尤其是矩陣的一些操作比較求模,求逆都沒有實現,以后補上
http://www.shnenglu.com/Files/sunshinealike/HomeWork1.rar