丁 智,肖 宇
OpenGL是圖形硬件的軟件接口[1-2],由于其具有穩(wěn)定性好、可移植性強等特點,目前已成為廣泛應用的跨平臺三維圖形繪制引擎,也是當前事實上應用最廣泛的三維圖形標準[1].OpenGL的一個顯著特點是它獨立于操作系統(tǒng)、窗口系統(tǒng)和硬件系統(tǒng)環(huán)境,這種運行平臺的無關性造就了OpenGL的成功,但同時也為OpenGL應用開發(fā)帶來了不便.OpenGL程序員必須利用不同操作系統(tǒng)平臺提供的圖形用戶界面(Graphics User Interface,GUI)支持函數(shù),才能開發(fā)OpenGL應用[3],而掌握相關既定窗口系統(tǒng)的接口功能函數(shù)和可視化編程方法,通常需要較長時間的學習[4].為了解決初學者的這一困難,美國Silicon Graphics公司的MARK K開發(fā)了一個簡易的窗口系統(tǒng)工具包GLUT(OpenGL Utility Toolkit)[5].GLUT是 一 個跨平臺的輕量級窗口系統(tǒng),能夠滿足一般圖形應用的開發(fā)要求.由于幾乎所有操作系統(tǒng)上都標配了GLUT包,因此以GLUT為基礎的OpenGL程序可以不加修改地運行于不同平臺,如Windows、Linux和Mac等.
然而,GLUT僅提供了簡單的事件處理功能,并不支持單復選按鈕、拖動條、組合框、文本框等控件,更沒有提供類似于下拉菜單和對話框等圖形用戶界面元素[5].這給基于OpenGL和GLUT開發(fā)圖形應用程序的用戶帶來了不便.在通常情形下,大多數(shù)圖形應用程序需要通過GUI實現(xiàn)對圖形繪制過程的交互式控制[6],以增加相關程序的靈活性和界面友好性.
另一方面,OpenGL具有強大的圖形處理能力,能夠勝任GUI所需的所有繪制功能.因此可以借助GLUT的窗口事件處理功能,并配合OpenGL繪制功能實現(xiàn)自己所需的圖形控件,為應用程序提供個性化的圖形用戶界面.
本文在分析GLUT事件處理過程和OpenGL繪制功能的基礎上,以實例討論如何基于二者來構造圖形控件的問題.所有的控件均采用C++類進行封裝,并提供必要的接口函數(shù)對它們進行控制.本文實現(xiàn)的圖形控件C++類具有高可復用性.
現(xiàn)代操作系統(tǒng)均為用戶提供了易于操作、用戶友好的圖形用戶界面[3,7].運行于其上的應用一般提供了按鈕、滑動條、文本框、單選和復選按鈕等,使用戶可以通過拖動鼠標等簡單操作完成對相關程序運行參數(shù)的選擇或調(diào)節(jié).
對用戶而言,其對程序的控制主要是通過圖形控件提供的界面來實現(xiàn)的;在一般用戶的認識中,應用程序在屏幕上繪制出的有形控件便是程序本身.本節(jié)以滑動條、單選和復選按鈕三種控件討論如何基于OpenGL實現(xiàn)控件的圖形界面設計.
圖1(a)為Windows系統(tǒng)提供的滑動條控件的外觀,其由固定不動的滑動導軌和能夠在導軌上左右滑動的控制滑塊組成.本節(jié)先討論如何模仿Windows系統(tǒng)的滑動條圖形效果.通過觀察不難發(fā)現(xiàn),滑動條導軌實質(zhì)上是由一條黑色線段下襯一條等長的白色線段構成,兩條平行線段的兩頭又繪制了兩條黑色和白色短線段進行修飾.由于人類視覺上的原因,最終看到的滑動導軌呈現(xiàn)出一種下凹立體效果.滑動控制塊是一個用背景色繪制的矩形塊,為了使其呈現(xiàn)出凸出于背景的立體效果,在矩形塊的頂端和左側(cè)各繪制了一條白色線段,再在其右側(cè)和底端分別繪制出一條黑色線段.控制塊的位置可通過鼠標的拖動進行控制.
圖1 標準Windows操作系統(tǒng)圖形控件的示例
在知道具體位置的情況下,滑動條控件可以直接運用OpenGL中繪制長方形和線段的命令實現(xiàn).如下列指令可繪制線段.
圖2中,右下側(cè)為本文模仿Windows系統(tǒng)中的滑動條繪制出的控件外觀效果,左側(cè)為垂直方向放置的滑動條繪制效果.在設計控件時,實現(xiàn)者可以根據(jù)自己的需要,設計具有個性化外觀的滑動條.如在圖2的右上角,滑動控制塊采用了與Windows系統(tǒng)中不同的外觀款式,其中滑動導軌也更寬了.
圖2 基于OpenGL繪制出各種滑動條的外觀
圖1(b)為單選控件的可視化界面,它由一組單選按鈕及其名稱和分組框等部分組成.相對于滑動條的界面外觀而言,單選和復選按鈕的繪制過程要稍微復雜一點.這里介紹Windows系統(tǒng)下的單選和復選按鈕在OpenGL下的實現(xiàn),其原理同樣適用于其他個性化按鈕的繪制.
用戶將Windows系統(tǒng)下的單選和復選按鈕進行放大,可以發(fā)現(xiàn)兩種按鈕的位圖表示,參見圖3.一旦得到位圖,便可以用OpenGL的位圖繪制函數(shù)來實現(xiàn)控件外觀的繪制.
令a∈[0, 255],并用{}a3表示{a,a,a},則{}a3表示整數(shù)值的RGB顏色三元組,即為某種灰度.這時,可以通過分析圖3得到相關位圖的數(shù)組表示,其中圖3(a)的位圖可以表示為二維向量數(shù)組icon.
圖3 Windows操作系統(tǒng)中單選和復選按鈕的位圖
需要注意的是,為了適應OpenGL的數(shù)據(jù)格式要求,圖3(a)中位圖最后一行的數(shù)據(jù)位于數(shù)組icon的第一行,位圖倒數(shù)第二行的數(shù)據(jù)位于數(shù)組的第二行,依次類推.
圖3(a)為被選中狀態(tài)的單選按鈕圖標,圖3(b)為被選中狀態(tài)的復選按鈕圖標,兩者所對應的未被選中狀態(tài)的按鈕圖標可以通過用顏色{200}3分別替換位圖中央的“十”字和對勾標志處的位圖像素得到.
得到相關位圖的數(shù)組表示,便可利用OpenGL的位圖繪制函數(shù)進行繪制.具體實現(xiàn)方式如下:
其中,xpos和ypos為放置圖標的窗口坐標值.每個單、復選按鈕后面都有一個標記選項內(nèi)容的文字標簽,繪制文字標簽的任務可由如下OpenGL和GLUT函數(shù)完成.
圖4為采用以上方法繪制出來的單選和復選按鈕圖標及相關控件,其中圖4(c)為方形單選按鈕,中間劃“×”表示該按鈕處為“選中”狀態(tài).
圖4 用本文方法繪制出的單選按鈕及控件
為使用戶能夠借助鼠標等設備通過控件界面與應用程序進行互相操作,需將控件界面與窗口事件關聯(lián)起來[8].本節(jié)以滑動條為例進行說明.
滑動條控件通常由鼠標設備控制,并且通過鼠標按鍵的按下并移動拖動滑塊在滑軌上運動改變控件所調(diào)節(jié)的數(shù)值.因此,每當鼠標鍵(設為左鍵)按下時,控件必須檢測當前的鼠標光標是否落在屏幕滑動條控件所在的區(qū)域內(nèi).如果鼠標光標未落在該區(qū)域內(nèi),滑動條控件將不對該事件進行響應;如果鼠標光標落在了該區(qū)域內(nèi),則說明用戶當前按下鼠標的行為是想拖動滑動條的滑塊運動,這時將滑動條控件設置為激活狀態(tài).激活狀態(tài)將一直維持到鼠標左鍵抬起為止.鼠標左鍵在滑動條區(qū)域內(nèi)按下時,控件被激活;鼠標左鍵松開抬起時,控件由激活狀態(tài)轉(zhuǎn)為睡眠狀態(tài).
當滑動條控件處在激活狀態(tài)(鼠標左鍵被按下)時,如果用戶移動鼠標,說明用戶欲通過移動滑塊調(diào)整滑動條表示的值.在基于GLUT的應用程序中,鼠標運動時GLUT窗口系統(tǒng)將會截獲該鼠標運動事件,并將該事件交給先前由glutMouseMotionFunc()所注冊的回調(diào)函數(shù)[9]處理.該回調(diào)函數(shù)將會得到由系統(tǒng)傳給它的包含當前鼠標光標在應用程序窗口中坐標的反饋信息.為了使控件能夠針對鼠標的運動情況調(diào)節(jié)滑塊的位置,需將控件響應鼠標運動的函數(shù)放在該回調(diào)函數(shù)中進行調(diào)用,并僅在控件處在激活狀態(tài)時執(zhí)行調(diào)節(jié)滑塊的功能(即對左鍵未按下時的鼠標運動不作響應).由于該響應函數(shù)可以得到鼠標光標在屏幕上的實時位置,故其可通過該光標位置信息修改滑塊當前位置所表示的數(shù)值.
當鼠標滑塊表示的值被修改時,控件將觸發(fā)一個窗口重繪的系統(tǒng)請求,該事件使窗口系統(tǒng)進一步調(diào)用控件圖形界面的繪制函數(shù),重新繪制控件界面,并在新的位置繪制控件滑塊.于是對用戶而言,界面所產(chǎn)生的效果即為其按下鼠標鍵的操作“拖動”控件滑塊運行,并通過調(diào)節(jié)滑塊的位置確定自己所要的數(shù)值.
滑動條所調(diào)節(jié)數(shù)值的范圍,以及滑塊變動所引起數(shù)值變化的幅度,均是由封裝該控件的類內(nèi)部變量決定.控件外部可以通過該類所提供的公共成員函數(shù)引用或修改這些變量以及滑塊表示的值,從而最終達到使應用程序相關變量改變的目的.
本文給出的滑動條控件類的定義如下,相關變量和成員函數(shù)的意義在程序的注釋中給出.
以set-開頭的函數(shù)的主要功能是設置滑動條內(nèi)部參數(shù)的值,如函數(shù)setPosition()用來設定滑動條左上角位置在窗口坐標系中的坐標值,setLength()用來設置繪制出的滑動條長度,setRange()用來設置滑動條調(diào)節(jié)的數(shù)值范圍;函數(shù)value()返回當前滑塊位置所對應的調(diào)節(jié)值.show()、button()、slide()三個函數(shù)用來響應鼠標事件[1,2,6],其中show()用來繪制滑動條的可視化外觀,其在基于GLUT的應用程序中,由glutDisplayFunc()所注冊的繪制回調(diào)函數(shù)調(diào)用,而button()和slide()則分別由經(jīng)glutMouseFunc()和glutMotionFunc()所注冊的響應鼠標事件的回調(diào)函數(shù)調(diào)用.
其他控件均可以采用類似方法實現(xiàn).由于采用了面向?qū)ο蠓椒▽λ锌丶謩e進行C++類封裝,故可由每一控件類定義若干實體對象,各對象之間相對獨立而不會相互干擾,對外為用戶提供了與應用程序進行交互的界面和接口;同時,面向?qū)ο蠓椒ㄒ彩惯@些控件能夠在同一應用或不同應用中得到高度復用.
設計人員基于OpenGL和GLUT,在Visual C++6.0環(huán)境下開發(fā)并實現(xiàn)了標簽(Label)、滑動條、單選和復選、文本框等一系列控件.所有控件均以C++類的形式進行封裝,并對外提供簡單易用接口.
在圖5中,設計人員給出了兩張以本文控件作為圖形用戶界面的程序運行效果截圖.程序主窗口的工作區(qū)被分為兩部分:左側(cè)大部分為圖形繪制區(qū),右側(cè)部分為控件繪制區(qū).其中圖形繪制區(qū)為主窗口的子窗口,它注冊了屬于自己的回調(diào)函數(shù),子窗口對各類事件的響應均由它自己的回調(diào)函數(shù)完成;控件繪制區(qū)中的所有控件均由主窗口所注冊的相關回調(diào)函數(shù)驅(qū)動,主窗口通過這些控件的接口函數(shù)獲取其當前狀態(tài)參數(shù),并適時向子窗口傳遞(可通過全局變量或公共緩沖區(qū)等實現(xiàn)),控制圖形繪制區(qū)的繪圖行為.因此,除了相關參數(shù)的傳遞外,主窗口和子窗口的事件處理過程是絕緣的,這種處理簡化了程序開發(fā)過程,降低了圖形用戶界面開發(fā)的難度和復雜性.
圖5 配備本文控件的應用程序界面
圖5(a)和圖5(b)右側(cè)的控件繪制區(qū)中,從上至下依次放置了標簽、單選、水平和垂直滑動條3類控件.其中單選控件用來確定繪制圖形的形狀,可分別選擇茶壺、球體、圓環(huán)等6種形體;三個水平滑動條分別用來調(diào)節(jié)圖形繪制區(qū)背景顏色RGB分量的值,取值范圍均為[0,1],當前RGB值進一步由控件繪制區(qū)最上面的三個標簽控件以文字形式進行顯示;而三個垂直滑動條控件分別用來控制所繪制的圖形繞坐標系的X、Y、Z軸的旋轉(zhuǎn)角度,取值范圍均為[0,360].在圖5(a)中,程序在白色背景上繪制了一個茶壺模型;而在圖5(b)中,通過單選控件選擇了繪制圓環(huán),并通過調(diào)節(jié)控制顏色和旋轉(zhuǎn)的滑動條改變了工作區(qū)中的背景和繪制圖形的旋轉(zhuǎn)角度.
本文給出一種基于OpenGL和GLUT圖形用戶界面可視化控件實驗的實現(xiàn)方法,討論了控件的外觀繪制方法和控件的事件驅(qū)動機制,并實現(xiàn)了相關控件面向?qū)ο蟮脑O計和實現(xiàn),并以實例展示了部分控件的應用.實驗表明,本文方法制作的控件響應窗口事件的速度快捷,易學易用,易于操作,且可復用性好,適用于輕量級交互式圖形應用程序的開發(fā).