用OpenInventor實現的NeHe OpenGL教程-第二十課
這節課我們將討論蒙板技術。所謂蒙板技術可能很多人在2D繪圖程序中已經使用過了。例如,我們希望在背景圖片上繪製一個人物(精靈)。因爲人物的圖片是矩形的,而人物本身又是不規則圖形,所以矩形圖片中會有一些空白的部分。如果我們不將這些空白的部分去掉,直接繪製人物圖片的話,程序的效果肯定會很差。這時我們就需要使用蒙板技術了,首先要產生一個和人物圖片一樣的黑白掩碼圖片,然後先讓這幅黑白掩碼圖片與背景圖片進行異或操作,然後再將真正的人物圖像與背景圖片進行與操作。這樣在背景圖片上就會顯示出一個“乾淨”的人物了。
在3D程序中使用的蒙板技術和2D類似,主要是用在紋理映射上。其原理是相同的。
下面的代碼首先定義一些全局變量。
SoTextureCoordinate2* g_pLogTexCoord = NULL;//背景紋理座標
SoTextureCoordinate2* g_pScene1MaskTexCoord = NULL;//場景1掩碼紋理座標
SoTextureCoordinate2* g_pScene1TexCoord = NULL;//場景1紋理座標
SoSeparator* g_pTopSceneSep = NULL;//場景節點
int g_iWhichScene = 0;//指定顯示那個場景。和NeHe教程中的變量含義相同
bool g_bMasking = true;//指定是否顯示掩碼。和NeHe教程中的變量含義相同
下面的函數用來創建背景場景。背景場景只是一個帶有紋理的正方形平面。
SoSeparator* BuildLogSep(void)
{
SoSeparator *pLogSep = new SoSeparator;
SoComplexity *pTextureComplexity = new SoComplexity;
pTextureComplexity->textureQuality = 1.0;
pLogSep->addChild(pTextureComplexity);
SoTexture2 *pLogoTexture = new SoTexture2;
pLogoTexture->filename.setValue("../Data/logo.png");
pLogoTexture->model = SoTexture2::DECAL;
pLogSep->addChild(pLogoTexture);
g_pLogTexCoord = new SoTextureCoordinate2;
g_pLogTexCoord->point.set1Value(0,0,0);
g_pLogTexCoord->point.set1Value(1,3,0);
g_pLogTexCoord->point.set1Value(2,3,3);
g_pLogTexCoord->point.set1Value(3,0,3);
pLogSep->addChild(g_pLogTexCoord);
SoCoordinate3 *pLogCoord = new SoCoordinate3;
pLogCoord->point.set1Value(0,-1.1f,-1.1f,0.0f);
pLogCoord->point.set1Value(1,1.1f,-1.1f,0.0f);
pLogCoord->point.set1Value(2,1.1f,1.1f,0.0f);
pLogCoord->point.set1Value(3,-1.1f,1.1f,0.0f);
pLogSep->addChild(pLogCoord);
pLogSep->addChild(new SoFaceSet);
return pLogSep;
}
下面的函數用來創建場景1。場景1也是一個帶有紋理的正方形平面。不過它可以根據bMasking變量來決定是否創建掩碼正方形。
SoSeparator* BuildScene1Sep(bool bMasking)
{
SoSeparator *pScene1Sep = new SoSeparator;
if (bMasking)
{
SoTexture2 *pScene1MaskTexture = new SoTexture2;
pScene1MaskTexture->filename.setValue("../Data/mask1.png");
pScene1MaskTexture->model = SoTexture2::DECAL;
pScene1Sep->addChild(pScene1MaskTexture);
g_pScene1MaskTexCoord = new SoTextureCoordinate2;
g_pScene1MaskTexCoord->point.set1Value(0,0,0);
g_pScene1MaskTexCoord->point.set1Value(1,4,0);
g_pScene1MaskTexCoord->point.set1Value(2,4,4);
g_pScene1MaskTexCoord->point.set1Value(3,0,4);
pScene1Sep->addChild(g_pScene1MaskTexCoord);
SoCoordinate3 *pScene1MaskCoord = new SoCoordinate3;
pScene1MaskCoord->point.set1Value(0,-1.1f,-1.1f,0.0f);
pScene1MaskCoord->point.set1Value(1,1.1f,-1.1f,0.0f);
pScene1MaskCoord->point.set1Value(2,1.1f,1.1f,0.0f);
pScene1MaskCoord->point.set1Value(3,-1.1f,1.1f,0.0f);
pScene1Sep->addChild(pScene1MaskCoord);
pScene1Sep->addChild(new SoFaceSet);
}
SoCallback *pOneGlCallback = new SoCallback();
pOneGlCallback->setCallback(GlRenderCB, (void *)2);
pScene1Sep->addChild(pOneGlCallback);
SoTexture2 *pScene1Texture = new SoTexture2;
pScene1Texture->filename.setValue("../Data/image1.png");
pScene1Sep->addChild(pScene1Texture);
g_pScene1TexCoord = new SoTextureCoordinate2;
g_pScene1TexCoord->point.set1Value(0,0,0);
g_pScene1TexCoord->point.set1Value(1,4,0);
g_pScene1TexCoord->point.set1Value(2,4,4);
g_pScene1TexCoord->point.set1Value(3,0,4);
pScene1Sep->addChild(g_pScene1TexCoord);
SoCoordinate3 *pScene1Coord = new SoCoordinate3;
pScene1Coord->point.set1Value(0,-1.1f,-1.1f,0.0f);
pScene1Coord->point.set1Value(1,1.1f,-1.1f,0.0f);
pScene1Coord->point.set1Value(2,1.1f,1.1f,0.0f);
pScene1Coord->point.set1Value(3,-1.1f,1.1f,0.0f);
pScene1Sep->addChild(pScene1Coord);
pScene1Sep->addChild(new SoFaceSet);
return pScene1Sep;
}
下面的函數用來創建場景2。場景2的內容和場景1類似
SoSeparator* BuildScene2Sep(bool bMasking)
{
SoSeparator *pScene2Sep = new SoSeparator;
SoTranslation *pInitTrans = new SoTranslation;
pInitTrans->translation.setValue(0,0,-1);
pScene2Sep->addChild(pInitTrans);
SoRotor *pRotor = new SoRotor;
pRotor->speed = 0.1;
pScene2Sep->addChild(pRotor);
if (bMasking)
{
SoTexture2 *pScene2MaskTexture = new SoTexture2;
pScene2MaskTexture->filename.setValue("../Data/mask2.png");
pScene2MaskTexture->model = SoTexture2::DECAL;
pScene2Sep->addChild(pScene2MaskTexture);
SoTextureCoordinate2 *pScene2MaskTexCoord = new SoTextureCoordinate2;
pScene2MaskTexCoord->point.set1Value(0,0,0);
pScene2MaskTexCoord->point.set1Value(1,1,0);
pScene2MaskTexCoord->point.set1Value(2,1,1);
pScene2MaskTexCoord->point.set1Value(3,0,1);
pScene2Sep->addChild(pScene2MaskTexCoord);
SoCoordinate3 *pScene2MaskCoord = new SoCoordinate3;
pScene2MaskCoord->point.set1Value(0,-1.1f,-1.1f,0.0f);
pScene2MaskCoord->point.set1Value(1,1.1f,-1.1f,0.0f);
pScene2MaskCoord->point.set1Value(2,1.1f,1.1f,0.0f);
pScene2MaskCoord->point.set1Value(3,-1.1f,1.1f,0.0f);
pScene2Sep->addChild(pScene2MaskCoord);
pScene2Sep->addChild(new SoFaceSet);
}
SoCallback *pOneGlCallback = new SoCallback();
pOneGlCallback->setCallback(GlRenderCB, (void *)2);
pScene2Sep->addChild(pOneGlCallback);
SoTexture2 *pScene2Texture = new SoTexture2;
pScene2Texture->filename.setValue("../Data/image2.png");
pScene2Sep->addChild(pScene2Texture);
SoTextureCoordinate2 *pScene2TexCoord = new SoTextureCoordinate2;
pScene2TexCoord->point.set1Value(0,0,0);
pScene2TexCoord->point.set1Value(1,1,0);
pScene2TexCoord->point.set1Value(2,1,1);
pScene2TexCoord->point.set1Value(3,0,1);
pScene2Sep->addChild(pScene2TexCoord);
SoCoordinate3 *pScene2Coord = new SoCoordinate3;
pScene2Coord->point.set1Value(0,-1.1f,-1.1f,0.0f);
pScene2Coord->point.set1Value(1,1.1f,-1.1f,0.0f);
pScene2Coord->point.set1Value(2,1.1f,1.1f,0.0f);
pScene2Coord->point.set1Value(3,-1.1f,1.1f,0.0f);
pScene2Sep->addChild(pScene2Coord);
pScene2Sep->addChild(new SoFaceSet);
return pScene2Sep;
}
開始構造完整的場景
void BuildScene(void)
{
//增加鍵盤響應節點
SoEventCallback* pEventCallback = new SoEventCallback;
pEventCallback->addEventCallback(SoKeyboardEvent::getClassTypeId(),KeyboardEventCB,g_pOivSceneRoot);
g_pOivSceneRoot->addChild(pEventCallback);
//整個場景向Z軸負方向移動2個單位
SoTranslation *pInitTrans = new SoTranslation;
pInitTrans->translation.setValue(0,0,-2);
g_pOivSceneRoot->addChild(pInitTrans);
//增加上背景場景
g_pOivSceneRoot->addChild(BuildLogSep());
//定義GL渲染方式
SoCallback *pEnableGlCallback = new SoCallback();
pEnableGlCallback->setCallback(GlRenderCB, 0);
g_pOivSceneRoot->addChild(pEnableGlCallback);
//創建場景1
g_pTopSceneSep = new SoSeparator;
g_pOivSceneRoot->addChild(g_pTopSceneSep);
g_pTopSceneSep->addChild(BuildScene1Sep(g_bMasking));
SoCallback *pDisableGlCallback = new SoCallback();
pDisableGlCallback->setCallback(GlRenderCB, (void *)1);
g_pOivSceneRoot->addChild(pDisableGlCallback);
//增加定時器節點,用來移動場景中紋理座標
SoTimerSensor * texttimer = new SoTimerSensor(TimerSensorCB, NULL);
texttimer->setInterval(0.001);
texttimer->schedule();
}
GL渲染回調函數,渲染方式和NeHe教程中的相同。
void GlRenderCB(void *data, SoAction *action)
{
if (action->isOfType(SoGLRenderAction::getClassTypeId()))
{
switch((int)data)
{
case 0:
glEnable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
if (g_bMasking)
glBlendFunc(GL_DST_COLOR,GL_ZERO); // Blend Screen Color With Zero (Black)
break;
case 1:
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
break;
case 2:
glBlendFunc(GL_ONE, GL_ONE);
break;
}
}
}
時間回調函數,用來修改紋理座標,產生動畫效果
void TimerSensorCB(void * data, SoSensor *)
{
static float roll = 0;
roll += 0.002f; // Increase Our Texture Roll Variable
if (roll > 1.0f) // Is Roll Greater Than One
roll -= 1.0f; // Subtract 1 From Roll
g_pLogTexCoord->point.set1Value(0,0,-roll + 0);
g_pLogTexCoord->point.set1Value(1,3,-roll + 0);
g_pLogTexCoord->point.set1Value(2,3,-roll + 3);
g_pLogTexCoord->point.set1Value(3,0,-roll + 3);
if(g_iWhichScene == 0)
{
if (g_bMasking)
{
g_pScene1MaskTexCoord->point.set1Value(0,roll + 0,0);
g_pScene1MaskTexCoord->point.set1Value(1,roll + 4,0);
g_pScene1MaskTexCoord->point.set1Value(2,roll + 4,4);
g_pScene1MaskTexCoord->point.set1Value(3,roll + 0,4);
}
g_pScene1TexCoord->point.set1Value(0,roll + 0,0);
g_pScene1TexCoord->point.set1Value(1,roll + 4,0);
g_pScene1TexCoord->point.set1Value(2,roll + 4,4);
g_pScene1TexCoord->point.set1Value(3,roll + 0,4);
}
}
鍵盤響應函數,用來切換場景
void KeyboardEventCB(void *userData, SoEventCallback *pEventCB)
{
const SoEvent *pEvent = pEventCB->getEvent();
if(SO_KEY_PRESS_EVENT(pEvent,SPACE))
{
g_iWhichScene = (++g_iWhichScene) % 2;
g_pTopSceneSep->removeAllChildren();
if (g_iWhichScene == 0)
g_pTopSceneSep->addChild(BuildScene1Sep(g_bMasking));
else
g_pTopSceneSep->addChild(BuildScene2Sep(g_bMasking));
}
else
if(SO_KEY_PRESS_EVENT(pEvent,M))
{
g_bMasking = !g_bMasking;
g_pTopSceneSep->removeAllChildren();
if (g_iWhichScene == 0)
g_pTopSceneSep->addChild(BuildScene1Sep(g_bMasking));
else
g_pTopSceneSep->addChild(BuildScene2Sep(g_bMasking));
}
pEventCB->setHandled();
}
現在編譯運行我們程序,屏幕上顯示出向上移動的背景圖片和一個向左移動的鏤空的前景圖片。按下空格鍵,將變換場景。按下M鍵會取消掩碼效果。程序運行的效果和NeHe第二十課是相同的。
本課的完整代碼 下載。(VC 2003 + Coin2.5)
後記
OpenInventor是一種基於OpenGL的面向對象的三維圖形軟件開發包。使用這個開發包,程序員可以快速、簡潔地開發出各種類型的交互式三維圖形軟件。這裏不對OpenInventor做詳細的介紹,讀者如果感興趣,可以閱讀我的blog中的這篇文章《OpenInventor 簡介》。
NeHe教程是目前針對初學者來說最好的OpenGL教程,它可以帶領讀者由淺入深,循序漸進地掌握OpenGL編程技巧。到目前爲止(2007年11月),NeHe教程一共有48節。我的計劃是使用OpenInventor來實現所有48節課程同樣的效果。目的是複習和鞏固OpenGL的知識,同時與各位讀者交流OpenInventor的使用技巧。
因爲篇幅的限制,我不會介紹NeHe教程中OpenGL的實現過程,因爲NeHe的教程已經講解的很清楚了,目前網絡中也有NeHe的中文版本。我將使用VC 2003作爲主要的編譯器。程序框架採用和NeHe一樣的Win32程序框架,不使用MFC。程序也可以在VC Express,VC 2005/2008中編譯。我採用的OpenInventor開發環境是Coin,這是一個免費開源的OpenInventor開發庫。文章 《OpenInventor-Coin3D開發環境》 介紹瞭如何在VC中使用Coin。我使用的Coin版本是2.5。讀者可以到 www.coin3d.org 中免費下載。
讀者可以在遵循GNU協議的條件下自由使用、修改本文的代碼。水平的原因,代碼可能不是最優化的,我隨時期待讀者的指正和交流。轉載請註明。謝謝。
我的聯繫方式:
E-mail: < [email protected] > < [email protected] >
Blog: < http://blog.csdn.net/RobinHao >
Site: < http://www.openinventor.cn >