用OpenInventor实现的NeHe OpenGL教程-第九课

用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协议的条件下自由使用、修改本文的代码。水平的原因,代码可能不是最优化的,我随时期待读者的指正和交流。转载请注明。谢谢。
我的联系方式:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值