用OpenInventor实现的NeHe OpenGL教程-第九课
这节课我们将讨论如何在3D空间中移动物体,物体之间如何以透明方式显示。这节课我们还将首次讨论如何在OpenInventor中调用OpenGL函数。
在程序开始的部分我们新定义了一些变量,这些变量的作用和NeHe教程中定义的变量有相同的作用。
SoTranslation* g_pZoomTrans = NULL; //
用于放大/缩小场景
SoRotation* g_pTiltRotation = NULL;//
倾斜场景中的物体
SoRotation* g_pAntiTiltRotation = NULL;//
抵消掉场景中的倾斜
SoRotation* g_pStarSpinRotation = NULL;//
旋转星星
double
tilt = 3.1415926 / 2.0;//
初始的倾斜角度
double
zoom = -15.0;//
初始Z轴位置
double
spin = 0.0f;
const
int iStartNums = 50; //
定义星星的总数
和NeHe教程类似,我们也创建一个用来描述星星位置、颜色等信息的结构体
typedef
struct tag_starts
{
SoMaterial *pStarColor; //
用来指定星星的颜色
SoTranslation *pStarPos; //
用来指定星星距离中心的位置
SoRotation *pStarDir; //
用来指定星星当前的角度
SoRotation *pAntiStarDir; //
用来抵消掉星星当前角度产生的影响
float dist; //
星星距离中心的位置
float angle; //
星星当前的角度
} STAR;
STAR stars[iStartNums]; //
定义一个星星的数组变量
在函数BuildScene中,我们编写如下的代码。
void
BuildScene(void)
{
NeHe
教程在这节课使用了一种 glBlendFunc(GL_SRC_ALPHA,GL_ONE) 的混合透明计算方法。这种透明方式在OpenInventor提供的透明类型中没有提供。不过没有关系,因为OpenInventor允许在程序中调用OpenGL的命令。当然我们不能直接调用OpenGL,因为我们知道在调用OpenGL函数的时候,当前线程必须要有一个合法的OpenGL Context,我们在编写OpenInventor程序的时候,根本就不需要考虑OpenGL Context的事情,这些事情都是OpenInventor内部的事情。如何在OpenInventor中使用OpenGL是一个比较高级的内容,读者可以查阅《The Inventor Mentor》一书中第17节课的内容。本节课我们使用SoCallback节点的方法来调用OpenGL。
首先我们要向场景中增加一个SoCallback节点,这个节点的作用是可以在它的回调函数中调用OpenGL函数。
SoCallback *pGlCallback = new SoCallback();
pGlCallback->setCallback(GlCB, NULL); //
设置用户自定义回调函数GLCB
g_pOivSceneRoot->addChild(pGlCallback);
向场景中增加纹理对象
SoTexture2 *pTexture = new SoTexture2;
pTexture->filename.setValue("../Data/Star.png");
g_pOivSceneRoot->addChild(pTexture);
向场景增加一个平移节点,主要用于放大/缩小场景中的物体。
g_pZoomTrans = new SoTranslation;
g_pZoomTrans->translation.setValue(0,0,zoom);//
在Z轴方向上移动
g_pOivSceneRoot->addChild(g_pZoomTrans);
向场景中增加一个旋转节点,使星星始终面向用户显示,作用和NeHe中的tilt作用一样。
g_pTiltRotation = new SoRotation;
g_pTiltRotation->rotation.setValue(SbVec3f(1,0,0),tilt);
g_pOivSceneRoot->addChild(g_pTiltRotation);
g_pAntiTiltRotation = new SoRotation;
g_pAntiTiltRotation->rotation.setValue(SbVec3f(1,0,0),-tilt);
g_pStarSpinRotation = new SoRotation;
g_pStarSpinRotation->rotation.setValue(SbVec3f(0,0,1),spin);
定义一个垂直于Z轴的平面,因为前面已经增加上一个纹理节点,所以纹理将贴在这个平面上,这个平面就显示成一个星星的图案。因为所有的星星都一样,不同的只是位置和旋转方向,所以我们可以定义一个Separator节点,将定义一个星星的所有节点都包含在这个Separator节点中,以后再共享使用这个节点。
SoSeparator *pStarFaceSetSep = new SoSeparator;
const float StarVertices[][3] = { {-1.0f, -1.0f, 0.0f}, {1.0f,-1.0f,0.0f}, {1.0f,1.0f,0.0f}, {-1.0f, 1.0f, 0.0f} };
SoCoordinate3 *StarCoords = new SoCoordinate3;
StarCoords->point.setValues(0, 4, StarVertices);
pStarFaceSetSep->addChild(StarCoords);
pStarFaceSetSep->addChild(new SoFaceSet);
下面就要开始创建iStartNums个星星
for(int i = 0; i < iStartNums; i++)
{
定义一个SoSeparator节点,用来保存当前这个星星的所有节点,这样做的目的是为了防止星星之间的节点会互相干扰。
SoSeparator *pStarSep = new SoSeparator;
g_pOivSceneRoot->addChild(pStarSep);
随机定义星星的颜色
stars[i].pStarColor = new SoMaterial;
stars[i].pStarColor->diffuseColor.setValue((rand() % 256) / 256.0,(rand() % 256) / 256.0,(rand() % 256) / 256.0);
stars[i].pStarColor->transparency = 0.3;
pStarSep->addChild(stars[i].pStarColor);
定义星星的旋转角度,初始都为0。注意OpenInventor使用角度的单位为弧度
stars[i].angle = 0.0f;
stars[i].pStarDir = new SoRotation;
stars[i].pStarDir->rotation.setValue(SbVec3f(0,1,0),stars[i].angle);
pStarSep->addChild(stars[i].pStarDir);
定义星星的位置
stars[i].dist = (float(i) / (float)iStartNums) * 5.0f;
stars[i].pStarPos = new SoTranslation;
stars[i].pStarPos->translation.setValue(stars[i].dist,0,0);
pStarSep->addChild(stars[i].pStarPos);
取消掉前面设置的旋转,平移。只有这样,星星才能始终面向用户显示
stars[i].pAntiStarDir = new SoRotation;
stars[i].pAntiStarDir->rotation.setValue(SbVec3f(0,1,0),-stars[i].angle);
pStarSep->addChild(stars[i].pAntiStarDir);
pStarSep->addChild(g_pAntiTiltRotation);
pStarSep->addChild(g_pStarSpinRotation);
pStarSep->addChild(pStarFaceSetSep);
}
定义键盘回调节点,响应用户按键事件
SoEventCallback* pEventCallback = new SoEventCallback;
pEventCallback->addEventCallback(SoKeyboardEvent::getClassTypeId(),KeyboardEventCB,g_pOivSceneRoot);
g_pOivSceneRoot->addChild(pEventCallback);
在NeHe教程中,星星是不断旋转的。NeHe教程是通过在消息空闲的时候来旋转星星的。这种方式的显示效果比较好,但特别占用CPU的时间。读者可以运行所有NeHe的教程例子代码,可以发现所有的例子代码程序CPU占用时间均为100%.我们这里不采用NeHe的方式。我们使用一个定时器的方式来旋转星星。定时器可以使用Windows的SetTimer函数来创建。不过OpenInventor也提供定时器节点,这里我们使用OpenInventor提供的定时器节点来旋转星星。关于定时器节点,读者可以查阅《The Inventor Mentor》一书中第12章的内容。
SoTimerSensor * Timer = new SoTimerSensor(TimerSensorCB, NULL);
Timer->setInterval(0.001);
Timer->schedule();
}
下面我们编写SoCallback节点的响应函数,在OpenInventor遍历到SoCallback节点时会调用这个函数。在这个函数中,我们可以调用OpenGL命令,因为这时OpenGL Context是合法的。
void
GlCB(void *data, SoAction *action)
{
if (action->isOfType(SoGLRenderAction::getClassTypeId()))
{
glBlendFunc(GL_SRC_ALPHA,GL_ONE);//
定义混合计算公式
glEnable(GL_BLEND); //
启动混合运算
glDisable(GL_DEPTH_TEST); //
禁止深度检测
}
}
下面的代码是定时器响应函数,定时器会每个一段时间调用一次这个函数,我们在这个函数中修改星星的位置、方向和颜色。计算方法和NeHe教程中是相同的。
void
TimerSensorCB(void * data, SoSensor *)
{
for(int i = 0; i < iStartNums; i++)
{
stars[i].dist -= 0.01f;
stars[i].angle += (float(i) / (float)iStartNums) * 3.1415926 / 180.0;
if(stars[i].dist < 0.0f)
{
stars[i].dist += 5.0f;
stars[i].pStarColor->diffuseColor.setValue((rand() % 256) / 256.0,(rand() % 256) / 256.0,(rand() % 256) / 256.0);
}
stars[i].pStarPos->translation.setValue(stars[i].dist,0,0);
stars[i].pStarDir->rotation.setValue(SbVec3f(0,1,0),stars[i].angle);
stars[i].pAntiStarDir->rotation.setValue(SbVec3f(0,1,0),-stars[i].angle);
spin += 0.01f * 3.1415926 / 180.0;
g_pStarSpinRotation->rotation.setValue(SbVec3f(0,0,1),spin);
}
}
下面是键盘响应函数,我们在这个函数中响应上下箭头按键,Page_Up/Page_Down按键
void
KeyboardEventCB(void *userData, SoEventCallback *pEventCB)
{
const SoEvent *pEvent = pEventCB->getEvent();
if(SO_KEY_PRESS_EVENT(pEvent,UP_ARROW))
{
绕X轴旋转场景,注意,角度是弧度。
tilt -= 0.5f * 3.1415926 / 180.0;
g_pTiltRotation->rotation.setValue(SbVec3f(1,0,0),tilt);
g_pAntiTiltRotation->rotation.setValue(SbVec3f(1,0,0),-tilt);
}
else
if(SO_KEY_PRESS_EVENT(pEvent,DOWN_ARROW))
{
tilt += 0.5f * 3.1415926 / 180.0;
g_pTiltRotation->rotation.setValue(SbVec3f(1,0,0),tilt);
g_pAntiTiltRotation->rotation.setValue(SbVec3f(1,0,0),-tilt);
}
else
if(SO_KEY_PRESS_EVENT(pEvent,PAGE_UP))
{
放大场景
zoom -= 0.2f;
g_pZoomTrans->translation.setValue(0,0,zoom);
}
else
if(SO_KEY_PRESS_EVENT(pEvent,PAGE_DOWN))
{
缩小场景
zoom += 0.2f;
g_pZoomTrans->translation.setValue(0,0,zoom);
}
pEventCB->setHandled();
}
剩下的代码和以前的代码都一样了。
现在编译运行我们程序,屏幕上显示一群不断旋转的星星,效果很cool。读者可以试试按下左右方向键和PnUp/PnDn键,观察星星产生的变化。效果和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协议的条件下自由使用、修改本文的代码。水平的原因,代码可能不是最优化的,我随时期待读者的指正和交流。转载请注明。谢谢。
我的联系方式:
Blog: < http://blog.csdn.net/RobinHao >
Site: < http://www.openinventor.cn >