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