1. 程式人生 > >OpenGL實現滑鼠繞任意軸旋轉/平移/縮放

OpenGL實現滑鼠繞任意軸旋轉/平移/縮放

       剛剛學opengl的童鞋肯定有個苦惱的麻煩,只會繪製一個三角形,但是想像那些三維軟體那樣用滑鼠控制視角還是有點困難的,所以我就封裝了一個場景漫遊類:RoamingScenceManager,這個類使用非常方便,跟介面沒有半毛錢關係,可以在Qt,原生OpenGL,MFC用,下面的內容是簡單介紹怎麼用,然後就給出上述三個環境的具體例子。

       RoamingScenceManager類

<span style="font-size:14px;">void init(); //初始化
void render();//開始渲染
void executeRotateOperation(int x, int y); //執行旋轉操作
void executeScaleOperation(float factor); //執行縮放操作
void executeTranslateOperation(int x,int y);//執行平移操作
void getInitPos(int x, int y); //獲取滑鼠按下時候的座標</span><span style="font-weight: bold; font-size: 18px;">
</span>

      就是很簡單運用上面六個函式就可以快速構建開放環境,下面說說怎麼在三個平臺用:

        Qt平臺:

#include"GL/glew.h"
#include"GL/glut.h"

#include"GLMainWidget.h"
#include"RoamingScenceManager.h"

#include<QMouseEvent>
#include<QWheelEvent>
#include<QDebug>

GLMainWidget::GLMainWidget(QWidget *parent) :
    QGLWidget(parent)
{
    this->setFocusPolicy(Qt::StrongFocus);
    manager=new RoamingScenceManager();
}

GLMainWidget::~GLMainWidget()
{
    delete manager;
}

void GLMainWidget::initializeGL()
{
    manager->init();
}

void GLMainWidget::resizeGL(int w, int h)
{
    glViewport(0,-(w-h)/2,w,w);
}
void GLMainWidget::paintGL()
{
    manager->render();
}

void GLMainWidget::mousePressEvent(QMouseEvent *e)
{
    manager->getInitPos(e->x(),e->y());
}

void GLMainWidget::mouseMoveEvent(QMouseEvent *e)
{
    if(e->buttons()&Qt::MiddleButton)
    {
        if(e->modifiers()==Qt::CTRL)
        {
            manager->executeTranslateOperation(e->x(),e->y());
        }
        else
        {
            manager->executeRotateOperation(e->x(),e->y());
        }
    }
    updateGL();
}

void GLMainWidget::wheelEvent(QWheelEvent *e)
{
    if(e->delta()>=0)
    {
        manager->executeScaleOperation(-0.1);
    }else
    {
        manager->executeScaleOperation(0.1);
    }
    updateGL();
}

     Win32平臺(原生OpenGL介面)

<span style="font-size:14px;">// OpenGLScence_raw.cpp : 定義控制檯應用程式的入口點。
//
#include "stdafx.h"
#include"GL/glut.h"
#include<memory> 
#include "RoamingScenceManager.h"

void MousePressEvent(int button, int state, int x, int y);
void MouseMoveEvent(int x,int y);
void ProcessSpecialKeys(int key, int x, int y);

void reshape(int w, int h);
void display();

bool leftButtonState;
bool rightButtonState;

//#define  GLUT_WHEEL_UP 3           //定義滾輪操作  
//#define  GLUT_WHEEL_DOWN 4

std::auto_ptr<RoamingScenceManager> manager(new RoamingScenceManager());


int main(int argc, char ** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 600);
	glutInitWindowPosition(100, 100);
	glutCreateWindow("原生OpenGL");
	manager->init();
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutMouseFunc(MousePressEvent);
	glutMotionFunc(MouseMoveEvent);
	glutSpecialFunc(ProcessSpecialKeys);
	glutMainLoop();
	return 0;

}

void display()
{
	manager->render();
	glFlush();
}

void reshape(int w,int h)
{
	glViewport(0, -(w - h) / 2, w, w);
}

void MousePressEvent(int button, int state, int x, int y)
{
	if (button == GLUT_LEFT_BUTTON)
	{
		if (state == GLUT_DOWN)
		{
			manager->getInitPos(x, y);
			leftButtonState = true;
		}
		else
		{
			leftButtonState = false;
		}
	}

	if (button == GLUT_RIGHT_BUTTON)
	{
		if (state == GLUT_DOWN)
		{
			manager->getInitPos(x, y);
			rightButtonState = true;
		}
		else
		{
			rightButtonState = false;
		}
	}

}

void MouseMoveEvent(int x, int y)
{
	if (leftButtonState)
	{
		manager->executeRotateOperation(x, y);
	}
	if (rightButtonState)
	{
	    manager->executeTranslateOperation(x, y);
	} 
	   display();
}
	
void ProcessSpecialKeys(int key, int x, int y)
{
	if (key == GLUT_KEY_UP)
	{
		manager->executeScaleOperation(-0.1);
	}
	if (key == GLUT_KEY_DOWN)
	{
		manager->executeScaleOperation(0.1);
	}
	display();
}</span><span style="font-weight: bold; font-size: 18px;">
</span>


       MFC平臺:
// OpenGLScence_MFCView.cpp : COpenGLScence_MFCView 類的實現
//

#include "stdafx.h"
// SHARED_HANDLERS 可以在實現預覽、縮圖和搜尋篩選器控制代碼的
// ATL 專案中進行定義,並允許與該專案共享文件程式碼。
#ifndef SHARED_HANDLERS
#include "OpenGLScence_MFC.h"
#endif

#include"GL/glew.h"
#include"GL/glut.h"
#include "OpenGLScence_MFCDoc.h"
#include "OpenGLScence_MFCView.h"
#include "RoamingScenceManager.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// COpenGLScence_MFCView

IMPLEMENT_DYNCREATE(COpenGLScence_MFCView, CView)

BEGIN_MESSAGE_MAP(COpenGLScence_MFCView, CView)
	// 標準列印命令
	ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, &COpenGLScence_MFCView::OnFilePrintPreview)
	ON_WM_CONTEXTMENU()
	ON_WM_RBUTTONUP()
	ON_WM_CREATE()
	ON_WM_SIZE()
	ON_WM_DESTROY()
	ON_WM_PAINT()
	ON_WM_MBUTTONDOWN()
	ON_WM_MBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_MOUSEWHEEL()
//	ON_WM_KEYDOWN()
END_MESSAGE_MAP()

// COpenGLScence_MFCView 構造/析構

COpenGLScence_MFCView::COpenGLScence_MFCView()
{
	// TODO:  在此處新增構造程式碼
	this->m_GLPixelIndex = 0;
	this->m_hGLContext = NULL;
	midButtonState = false;
	ctrlKeyState = false;
	manager = new RoamingScenceManager();

}

COpenGLScence_MFCView::~COpenGLScence_MFCView()
{

}

BOOL COpenGLScence_MFCView::PreCreateWindow(CREATESTRUCT& cs)
{
	// TODO:  在此處通過修改
	//  CREATESTRUCT cs 來修改視窗類或樣式
	cs.style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN;// 在客戶區繪製

	return CView::PreCreateWindow(cs);
}

// COpenGLScence_MFCView 繪製

void COpenGLScence_MFCView::OnDraw(CDC* /*pDC*/)
{
	COpenGLScence_MFCDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	// TODO:  在此處為本機資料新增繪製程式碼
}


// COpenGLScence_MFCView 列印


void COpenGLScence_MFCView::OnFilePrintPreview()
{
#ifndef SHARED_HANDLERS
	AFXPrintPreview(this);
#endif
}

BOOL COpenGLScence_MFCView::OnPreparePrinting(CPrintInfo* pInfo)
{
	// 默認準備
	return DoPreparePrinting(pInfo);
}

void COpenGLScence_MFCView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
	// TODO:  新增額外的列印前進行的初始化過程
}

void COpenGLScence_MFCView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
	// TODO:  新增列印後進行的清理過程
}

bool COpenGLScence_MFCView::SetWindowPixelFormat(HDC hDC)
{
	//定義視窗的畫素格式
	PIXELFORMATDESCRIPTOR pixelDesc =
	{
		sizeof(PIXELFORMATDESCRIPTOR),           //nSize結構長度
		1,                                       //nVersion結構版本
		PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |
		PFD_DOUBLEBUFFER | PFD_SUPPORT_GDI,        //dwFlags告訴OpenGL如何處理畫素
		/*
		wFlags能接收以下標誌:
		PFD_DRAW_TO_WINDOW 使之能在視窗或者其他裝置視窗畫圖;
		PFD_DRAW_TO_BITMAP 使之能在記憶體中的點陣圖畫圖;
		PFD_SUPPORT_GDI 使之能呼叫GDI函式(注:如果指定了PFD_DOUBLEBUFFER,這個選項將無效);
		PFD_SUPPORT_OpenGL 使之能呼叫OpenGL函式;
		PFD_GENERIC_FORMAT 假如這種象素格式由Windows GDI函式庫或由第三方硬體裝置驅動程式支援,則需指定這一項;
		PFD_NEED_PALETTE 告訴緩衝區是否需要調色盤,本程式假設顏色是使用24或 32位色,並且不會覆蓋調色盤;
		PFD_NEED_SYSTEM_PALETTE 這個標誌指明緩衝區是否把系統調色盤當作它自身調色盤的一部分;
		PFD_DOUBLEBUFFER 指明使用了雙緩衝區(注:GDI不能在使用了雙緩衝區的視窗中畫圖);
		PFD_STEREO 指明左、右緩衝區是否按立體影象來組織。
		PFD_SWAP_LAYER_BUFFERS
		*/
		PFD_TYPE_RGBA,  //iPixelType,顏色模式,包括兩種PFD_TYPE_RGBA意味著每一位(bit)組代表著rgb各分量的值。PFD_TYPE_COLORINDEX意味著每一位組代表著在彩色查詢表中的索引值
		24,   //cColorBits定義了指定一個顏色的位數。對RGBA來說,位數是在顏色中紅、綠、藍各分量所佔的位數。對顏色的索引值來說,指的是表中的顏色數。
		0, 0, 0, 0, 0, 0,  //cRedBits、cRedShifts、cGreenBits、cGreenShifts、cBlueBits、cBlueShifts用,基本不被採用,一般置0
		//cRedBits、cGreenBits、cBlueBits用來表明各相應分量所使用的位數。
		//cRedShift、cGreenShift、cBlue-Shift用來表明各分量從顏色開始的偏移量所佔的位數。
		0,                                       //cAlphaBits,RGB顏色快取中Alpha的位數                            
		0,                                 //cAlphaShift,已經不被採用,置0                   
		0,                                       //cAcuumBits累計快取的位數
		0, 0, 0, 0,                                 //cAcuumRedBits/cAcuumGreenBits/cAcuumBlueBits/cAcuumAlphaBits,基本不被採用,置0
		32,                                      //cDepthBits深度快取的位數
		0,                                       //cStencilBits,模板快取的位數
		0,                                       //cAuxBuffers,輔助快取的位數,一般置0
		PFD_MAIN_PLANE,                          //iLayerType,說明層面的型別,可忽略置0,是早期的版本,包括
		//PFD_MAIN_PLANE,PFD_OVER_LAY_PLANE,PFD_UNDERLAY_PLANE
		0, 0, 0, 0                                  //bReserved,dwLayerMask,dwVisibleMask,dwDamageMask,必須置0
	};

	this->m_GLPixelIndex = ChoosePixelFormat(hDC, &pixelDesc);//選擇最相近的畫素格式
	/*
	ChoosePixelFormat接受兩個引數:一個是hDc,另一個是一個指向PIXELFORMATDESCRIPTOR結構的指標&pixelDesc
	該函式返回此畫素格式的索引值,如果返回0則表示失敗。
	假如函式失敗,我們只是把索引值設為1並用 DescribePixelFormat得到畫素格式描述。
	假如你申請一個沒得到支援的畫素格式,則ChoosePixelFormat將會返回與你要求的畫素格式最接近的一個值
	一旦我們得到一個畫素格式的索引值和相應的描述,我們就可以呼叫SetPixelFormat設定畫素格式,並且只需設定一次。
	*/
	if (this->m_GLPixelIndex == 0)
	{//選擇失敗
		this->m_GLPixelIndex = 1;//預設的畫素格式
		//用預設的畫素格式進行設定
		//
		if (DescribePixelFormat(hDC, this->m_GLPixelIndex, sizeof(PIXELFORMATDESCRIPTOR), &pixelDesc) == 0)
		{
			return FALSE;
		}
	}

	if (SetPixelFormat(hDC, this->m_GLPixelIndex, &pixelDesc) == FALSE)
	{
		return FALSE;
	}
	return TRUE;

}

bool COpenGLScence_MFCView::CreateViewGLContext(HDC hDC)
{

	//WglCreateContext函式建立一個新的OpenGL渲染描述表(RC)
	//此描述表必須適用於繪製到由hdc返回的裝置
	//這個渲染描述表將有和裝置上下文(dc)一樣的畫素格式.
	this->m_hGLContext = wglCreateContext(hDC);//建立RC

	if (this->m_hGLContext == NULL)
	{//建立失敗
		return FALSE;
	}

	/*
	wglMakeCurrent 函式設定OpenGL當前執行緒(執行緒相關性)的渲染環境。
	以後這個執行緒所有的OpenGL呼叫都是在這個hdc標識的裝置上繪製。
	你也可以使用wglMakeCurrent 函式來改變呼叫執行緒的當前渲染環境
	使之不再是當前的渲染環境。
	*/
	if (wglMakeCurrent(hDC, this->m_hGLContext) == FALSE)
	{//選為當前RC失敗
		return FALSE;
	}
	return TRUE;

}

bool COpenGLScence_MFCView::InitGL(void)
{
	manager->init();
	return true;
}

int COpenGLScence_MFCView::DrawGLScene(void)
{
	manager->render();
	glFlush();
	return TRUE;                                
}

void COpenGLScence_MFCView::OnRButtonUp(UINT /* nFlags */, CPoint point)
{
	ClientToScreen(&point);
	OnContextMenu(this, point);
}

void COpenGLScence_MFCView::OnContextMenu(CWnd* /* pWnd */, CPoint point)
{
#ifndef SHARED_HANDLERS
	theApp.GetContextMenuManager()->ShowPopupMenu(IDR_POPUP_EDIT, point.x, point.y, this, TRUE);
#endif
}


// COpenGLScence_MFCView 診斷

#ifdef _DEBUG
void COpenGLScence_MFCView::AssertValid() const
{
	CView::AssertValid();
}

void COpenGLScence_MFCView::Dump(CDumpContext& dc) const
{
	CView::Dump(dc);
}

COpenGLScence_MFCDoc* COpenGLScence_MFCView::GetDocument() const // 非除錯版本是內聯的
{
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(COpenGLScence_MFCDoc)));
	return (COpenGLScence_MFCDoc*)m_pDocument;
}
#endif //_DEBUG


// COpenGLScence_MFCView 訊息處理程式


int COpenGLScence_MFCView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;

	// TODO:  在此新增您專用的建立程式碼
	//得到一個視窗物件(CWnd的派生物件)指標的控制代碼(HWND)  
	HWND hWnd = this->GetSafeHwnd();
	//GetDC該函式檢索一指定視窗的客戶區域或整個螢幕的顯示裝置上下文環境的控制代碼
	//以後可以在GDI函式中使用該控制代碼來在裝置上下文環境中繪圖。
	HDC hDC = ::GetDC(hWnd);

	if (this->SetWindowPixelFormat(hDC) == FALSE)
	{//設定畫素格式
		return 0;
	}
	if (this->CreateViewGLContext(hDC) == FALSE)
	{//建立RC並選為所用
		return 0;
	}
	if (!this->InitGL())
	{//初始化openGL
		return 0;
	}
	return 0;
}


void COpenGLScence_MFCView::OnSize(UINT nType, int cx, int cy)
{
	CView::OnSize(nType, cx, cy);
	glViewport(0, -(cx - cy) / 2, cx, cx);
}


void COpenGLScence_MFCView::OnDestroy()
{
	CView::OnDestroy();

	// TODO:  在此處新增訊息處理程式程式碼
	if (wglGetCurrentContext() != NULL)
	{
		wglMakeCurrent(NULL, NULL);
	}
	if (this->m_hGLContext != NULL)
	{
		wglDeleteContext(this->m_hGLContext);
		this->m_hGLContext = NULL;
	}

	delete manager;
	manager = NULL;
}


void COpenGLScence_MFCView::OnPaint()
{
	CPaintDC dc(this); // device context for painting
	// TODO:  在此處新增訊息處理程式程式碼
	// 不為繪圖訊息呼叫 CView::OnPaint()
	this->DrawGLScene();
	SwapBuffers(dc.m_hDC);
}


void COpenGLScence_MFCView::OnMButtonDown(UINT nFlags, CPoint point)
{
	// TODO:  在此新增訊息處理程式程式碼和/或呼叫預設值

	CView::OnMButtonDown(nFlags, point);
	manager->getInitPos(point.x, point.y);
	midButtonState = true;
}


void COpenGLScence_MFCView::OnMButtonUp(UINT nFlags, CPoint point)
{
	// TODO:  在此新增訊息處理程式程式碼和/或呼叫預設值

	CView::OnMButtonUp(nFlags, point);
	midButtonState = false;
}


void COpenGLScence_MFCView::OnMouseMove(UINT nFlags, CPoint point)
{
	// TODO:  在此新增訊息處理程式程式碼和/或呼叫預設值

	CView::OnMouseMove(nFlags, point);
	if (midButtonState)
	{
		if (ctrlKeyState)
		{
			manager->executeTranslateOperation(point.x, point.y);
		} 
		else
		{
			manager->executeRotateOperation(point.x, point.y);
		}
		
		OnPaint();
	}
}


BOOL COpenGLScence_MFCView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	// TODO:  在此新增訊息處理程式程式碼和/或呼叫預設值
	if (zDelta>=0)
	{
		manager->executeScaleOperation(-0.1);
	}
	else
	{
		manager->executeScaleOperation(0.1);
	}
	OnPaint();

	return CView::OnMouseWheel(nFlags, zDelta, pt);
}



BOOL COpenGLScence_MFCView::PreTranslateMessage(MSG* pMsg)
{
	// TODO:  在此新增專用程式碼和/或呼叫基類
	if (pMsg->message == WM_KEYDOWN)
	{

		if (pMsg->wParam == VK_CONTROL)//直接用虛碼代替就可以響應所指鍵
		{
			ctrlKeyState = true;
		}

	}

	if (pMsg->message == WM_KEYUP)
	{

		if (pMsg->wParam == VK_CONTROL)//直接用虛碼代替就可以響應所指鍵
		{
			ctrlKeyState = false;
		}

	}
	return CView::PreTranslateMessage(pMsg);
}

         

下面是看看效果如何:

Qt平臺:中鍵旋轉,ctrl+中鍵平移,滑輪縮放


Win32平臺:左鍵旋轉,右鍵平移, Up/Down縮放(沒有滾輪事件)


MFC平臺:中鍵旋轉,ctrl+中鍵平移,滑輪縮放


         其實看那麼多也沒有用,直接下載程式碼好了,下載:http://download.csdn.net/detail/trustguan/9453751  環境分別是Qt5.4.2和VS2013,如果你們不是這些版本,重新編譯一次就行了。