这节课讨论的是蒙板技术。所谓蒙板技术可能很多人在2D绘图程序中已经使用过了。例如,我们希望在背景图片上绘制一个人物(精灵)。因为人物的图片是矩形的,而人物本身又是不规则图形,所以矩形图片中会有一些空白的部分。如果我们不将这些空白的部分去掉,直接绘制人物图片的话,程序的效果肯定会很差。这时我们就需要使用蒙板技术了,首先要产生一个和人物图片一样的黑白掩码图片,然后先让这幅黑白掩码图片与背景图片进行异或操作,然后再将真正的人物图像与背景图片进行与操作。这样在背景图片上就会显示出一个“干净”的人物了。在3D程序中使用的蒙板技术和2D类似,主要是用在纹理映射上,其原理是相同的。
这节课中的蒙版的方式是采用OpenGL中的混合(Blend)的方法实现的,主要用到了osg中osg::BlendFunc这个类来实现,这节课相当于第八课混合的一种应用,需要理解OpenGL中混合的作用原理。
首先我们先创建背景,也就是一张一直在滚动的纹理图片,设置代码如下:
osg::Geometry *quadGeometry = new osg::Geometry; //顶点位置坐标 osg::Vec3Array *quadVertexArray = new osg::Vec3Array; quadVertexArray->push_back(osg::Vec3(-1.1f, -1.1f, 0.0f)); quadVertexArray->push_back(osg::Vec3(1.1f, -1.1f, 0.0f)); quadVertexArray->push_back(osg::Vec3(1.1f,1.1f, 0.0f)); quadVertexArray->push_back(osg::Vec3(-1.1f,1.1f, 0.0f)); quadGeometry->setVertexArray(quadVertexArray); //顶点纹理坐标 osg::Vec2Array *quadTextureArray = new osg::Vec2Array; quadTextureArray->push_back(osg::Vec2(0,0)); quadTextureArray->push_back(osg::Vec2(3,0)); quadTextureArray->push_back(osg::Vec2(3,3)); quadTextureArray->push_back(osg::Vec2(0,3)); //纹理贴图 osg::Texture2D *quadTexture = new osg::Texture2D; quadTexture->setImage(osgDB::readImageFile("Data/Logo.bmp")); quadTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); quadTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); //设置WRAP方式是重复S和T方向 quadTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); quadTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); quadGeometry->setTexCoordArray(0, quadTextureArray); quadGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4)); quadGeometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, quadTexture); quadGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); quadGeometry->setUseDisplayList(false); //动态改变纹理坐标的回调 quadGeometry->setUpdateCallback(new TextureLogoCallback);代码和之前课程的很多代码都相似,主要一个不同的地方是需要设置纹理的WRAP方式,我们让纹理在几何体上重复贴图。
纹理的坐标需要在程序运行中动态的变化以实现向上的滚动,这个实现是在TextureLogoCallback这个更新回调中进行的
class TextureLogoCallback : public osg::Drawable::UpdateCallback { public: void update(osg::NodeVisitor*, osg::Drawable* drawable) { ...... } };接着需要创建两个场景,当按下空格键的时候在二者之间切换。对于其中的任一场景,当按下M键的时候需要在打开和关闭Mask之间切换,为了做到这一效果,本课使用的是Switch节点的方式。Switch节点在 第十八课中也有应用,具体代码如下:
osg::Switch* createScene1() { ...... } osg::Group* createScene2() { osg::Switch *toggleMask34 = new osg::Switch; toggleMask34->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); ...... }在创建场景的过程中,为了实现Mask的效果,最重要的部分是设置混合参数的地方。查看NeHeOpenGL中的设置方式,对于Mask纹理图片需要设置:
//geometry3是贴有Mask的几何体 geometry3->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture3); osg::BlendFunc *blendFunc3 = new osg::BlendFunc(osg::BlendFunc::DST_COLOR, osg::BlendFunc::ZERO); geometry3->getOrCreateStateSet()->setAttributeAndModes(blendFunc3);对于需要绘制的几何体本身也要设置混合方式:
//geometry4是需要绘制的几何体 geometry4->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture4); osg::BlendFunc *blendFuc4 = new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE); geometry4->getOrCreateStateSet()->setAttributeAndModes(blendFuc4);这样在OpenGL混合的作用之下,可以实现蒙版的效果。
最后在键盘交互中(自定义的EventHandler)实现Switch的切换:
case(osgGA::GUIEventAdapter::KEYDOWN): { static int index = 1; if (index > 1) { index = 0; } if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Space) { g_scene->setSingleChildOn(index); ++index; } if (ea.getKey() == osgGA::GUIEventAdapter::KEY_M) { if (index == 0) //第二个场景 { if (g_scene2->getValue(0) == true) { //Mask启用 g_scene2->setSingleChildOn(1); }else{ //Mask未启用 g_scene2->setAllChildrenOn(); } } else //第一个场景 { if (g_scene1->getValue(0) == true) { //Mask启用 g_scene1->setSingleChildOn(1); }else{ //Mask未启用 g_scene1->setAllChildrenOn(); } } } }编译运行程序:
附:本课源码(源码中可能存在错误和不足,仅供参考)
#include "../osgNeHe.h" #include <QtCore/QTimer> #include <QtGui/QApplication> #include <QtGui/QVBoxLayout> #include <osgViewer/Viewer> #include <osgDB/ReadFile> #include <osgQt/GraphicsWindowQt> #include <osg/MatrixTransform> #include <osg/Texture2D> #include <osg/Switch> #include <osg/BlendFunc> #include <osg/AnimationPath> osg::Switch *g_scene, *g_scene1, *g_scene2; class SceneEventHandler : public osgGA::GUIEventHandler { public: SceneEventHandler(){} virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) { osgViewer::Viewer *viewer = dynamic_cast<osgViewer::Viewer*>(&aa); if (!viewer) return false; if (!viewer->getSceneData()) return false; if (ea.getHandled()) return false; switch(ea.getEventType()) { case(osgGA::GUIEventAdapter::KEYDOWN): { static int index = 1; if (index > 1) { index = 0; } if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Space) { g_scene->setSingleChildOn(index); ++index; } if (ea.getKey() == osgGA::GUIEventAdapter::KEY_M) { if (index == 0) //第二个场景 { if (g_scene2->getValue(0) == true) { //Mask启用 g_scene2->setSingleChildOn(1); }else{ //Mask未启用 g_scene2->setAllChildrenOn(); } } else //第一个场景 { if (g_scene1->getValue(0) == true) { //Mask启用 g_scene1->setSingleChildOn(1); }else{ //Mask未启用 g_scene1->setAllChildrenOn(); } } } } default: break; } return false; } }; class ViewerWidget : public QWidget, public osgViewer::Viewer { public: ViewerWidget(osg::Node *scene = NULL) { QWidget* renderWidget = getRenderWidget( createGraphicsWindow(0,0,100,100), scene); QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(renderWidget); layout->setContentsMargins(0, 0, 0, 1); setLayout( layout ); connect( &_timer, SIGNAL(timeout()), this, SLOT(update()) ); _timer.start( 10 ); } QWidget* getRenderWidget( osgQt::GraphicsWindowQt* gw, osg::Node* scene ) { osg::Camera* camera = this->getCamera(); camera->setGraphicsContext( gw ); const osg::GraphicsContext::Traits* traits = gw->getTraits(); camera->setClearColor( osg::Vec4(0.0, 0.0, 0.0, 0.0) ); camera->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); camera->setProjectionMatrixAsPerspective(45.0f, static_cast<double>(traits->width)/static_cast<double>(traits->height), 0.1f, 100.0f ); camera->setViewMatrixAsLookAt(osg::Vec3d(0, 0, 1), osg::Vec3d(0, 0, 0), osg::Vec3d(0, 1, 0)); addEventHandler(new SceneEventHandler); this->setSceneData( scene ); return gw->getGLWidget(); } osgQt::GraphicsWindowQt* createGraphicsWindow( int x, int y, int w, int h, const std::string& name="", bool windowDecoration=false ) { osg::DisplaySettings* ds = osg::DisplaySettings::instance().get(); osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits; traits->windowName = name; traits->windowDecoration = windowDecoration; traits->x = x; traits->y = y; traits->width = w; traits->height = h; traits->doubleBuffer = true; traits->alpha = ds->getMinimumNumAlphaBits(); traits->stencil = ds->getMinimumNumStencilBits(); traits->sampleBuffers = ds->getMultiSamples(); traits->samples = ds->getNumMultiSamples(); return new osgQt::GraphicsWindowQt(traits.get()); } virtual void paintEvent( QPaintEvent* event ) { frame(); } protected: QTimer _timer; }; //纹理坐标更新 class TextureLogoCallback : public osg::Drawable::UpdateCallback { public: void update(osg::NodeVisitor*, osg::Drawable* drawable) { osg::Geometry *geometry = dynamic_cast<osg::Geometry*>(drawable); if (!geometry) { return; } osg::Vec2Array *textureArray = dynamic_cast<osg::Vec2Array *>(geometry->getTexCoordArray(0)); if (!textureArray) { return; } static float roll = 0; roll += 0.002f; if (roll > 1.0f) roll -= 1.0f; textureArray->at(0).set(0,-roll + 0); textureArray->at(1).set(3,-roll + 0); textureArray->at(2).set(3,-roll + 3); textureArray->at(3).set(0,-roll + 3); textureArray->dirty(); } }; class TextureImageCallback : public osg::Drawable::UpdateCallback { public: void update(osg::NodeVisitor*, osg::Drawable* drawable) { osg::Geometry *geometry = dynamic_cast<osg::Geometry*>(drawable); if (!geometry) { return; } osg::Vec2Array *textureArray = dynamic_cast<osg::Vec2Array *>(geometry->getTexCoordArray(0)); if (!textureArray) { return; } static float rolling = 0.0; rolling += 0.002f; if (rolling > 1.0f) rolling -= 1.0f; textureArray->at(0).set(-rolling + 0, 0); textureArray->at(1).set(rolling + 4, 0); textureArray->at(2).set(rolling + 4, 4); textureArray->at(3).set(rolling + 0, 4); textureArray->dirty(); } }; osg::Switch* createScene1() { osg::Switch *toggleMask12 = new osg::Switch; toggleMask12->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); //叶节点1 osg::Geode *geode1 = new osg::Geode; osg::Geometry *geometry1 = new osg::Geometry; osg::Vec3Array *vertexArray1 = new osg::Vec3Array; vertexArray1->push_back(osg::Vec3(-1.1f, -1.1f, 0.0f)); vertexArray1->push_back(osg::Vec3(1.1f, -1.1f, 0.0f)); vertexArray1->push_back(osg::Vec3(1.1f,1.1f, 0.0f)); vertexArray1->push_back(osg::Vec3(-1.1f,1.1f, 0.0f)); geometry1->setVertexArray(vertexArray1); osg::Vec2Array *textureArray1 = new osg::Vec2Array; textureArray1->push_back(osg::Vec2(0,0)); textureArray1->push_back(osg::Vec2(4,0)); textureArray1->push_back(osg::Vec2(4,4)); textureArray1->push_back(osg::Vec2(0,4)); osg::Texture2D *texture1 = new osg::Texture2D; texture1->setImage(osgDB::readImageFile("Data/Mask1.bmp")); texture1->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); texture1->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); texture1->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); texture1->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); geometry1->setTexCoordArray(0, textureArray1); geometry1->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4)); geometry1->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture1); geometry1->setUseDisplayList(false); osg::BlendFunc *blendFunc1 = new osg::BlendFunc(osg::BlendFunc::DST_COLOR, osg::BlendFunc::ZERO); geometry1->getOrCreateStateSet()->setAttributeAndModes(blendFunc1); geometry1->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); geometry1->setUpdateCallback(new TextureImageCallback); geode1->addDrawable(geometry1); //叶节点2 osg::Geode *geode2 = new osg::Geode; osg::Geometry *geometry2 = new osg::Geometry; osg::Vec3Array *vertexArray2 = new osg::Vec3Array; vertexArray2->push_back(osg::Vec3(-1.1f, -1.1f, 0.0f)); vertexArray2->push_back(osg::Vec3(1.1f, -1.1f, 0.0f)); vertexArray2->push_back(osg::Vec3(1.1f,1.1f, 0.0f)); vertexArray2->push_back(osg::Vec3(-1.1f,1.1f, 0.0f)); geometry2->setVertexArray(vertexArray2); osg::Vec2Array *textureArray2 = new osg::Vec2Array; textureArray2->push_back(osg::Vec2(0,0)); textureArray2->push_back(osg::Vec2(4,0)); textureArray2->push_back(osg::Vec2(4,4)); textureArray2->push_back(osg::Vec2(0,4)); osg::Texture2D *texture2 = new osg::Texture2D; texture2->setImage(osgDB::readImageFile("Data/Image1.bmp")); texture2->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); texture2->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); texture2->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); texture2->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); geometry2->setTexCoordArray(0, textureArray2); geometry2->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4)); geometry2->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture2); osg::BlendFunc *blendFuc2 = new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE); geometry2->getOrCreateStateSet()->setAttributeAndModes(blendFuc2); geometry2->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); geometry2->setUseDisplayList(false); geometry2->setUpdateCallback(new TextureImageCallback); geode2->addDrawable(geometry2); toggleMask12->addChild(geode1); toggleMask12->addChild(geode2); toggleMask12->setAllChildrenOn(); g_scene1 = toggleMask12; return toggleMask12; } osg::Group* createScene2() { osg::Switch *toggleMask34 = new osg::Switch; toggleMask34->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); osg::MatrixTransform *zoomMT = new osg::MatrixTransform; zoomMT->setMatrix(osg::Matrix::translate(0, 0, -0.9)); osg::MatrixTransform *rotateMT = new osg::MatrixTransform; rotateMT->setMatrix(osg::Matrix::rotate(0, osg::Z_AXIS)); rotateMT->addUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(), osg::Z_AXIS, 1.5)); //叶节点3 osg::Geode *geode3 = new osg::Geode; osg::Geometry *geometry3 = new osg::Geometry; osg::Vec3Array *vertexArray3 = new osg::Vec3Array; vertexArray3->push_back(osg::Vec3(-1.1f, -1.1f, 0.0f)); vertexArray3->push_back(osg::Vec3(1.1f, -1.1f, 0.0f)); vertexArray3->push_back(osg::Vec3(1.1f,1.1f, 0.0f)); vertexArray3->push_back(osg::Vec3(-1.1f,1.1f, 0.0f)); geometry3->setVertexArray(vertexArray3); osg::Vec2Array *textureArray3 = new osg::Vec2Array; textureArray3->push_back(osg::Vec2(0,0)); textureArray3->push_back(osg::Vec2(1,0)); textureArray3->push_back(osg::Vec2(1,1)); textureArray3->push_back(osg::Vec2(0,1)); osg::Texture2D *texture3 = new osg::Texture2D; texture3->setImage(osgDB::readImageFile("Data/Mask2.bmp")); texture3->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); texture3->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); geometry3->setTexCoordArray(0, textureArray3); geometry3->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4)); geometry3->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture3); osg::BlendFunc *blendFunc3 = new osg::BlendFunc(osg::BlendFunc::DST_COLOR, osg::BlendFunc::ZERO); geometry3->getOrCreateStateSet()->setAttributeAndModes(blendFunc3); geometry3->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); geode3->addDrawable(geometry3); //叶节点4 osg::Geode *geode4 = new osg::Geode; osg::Geometry *geometry4 = new osg::Geometry; osg::Vec3Array *vertexArray4 = new osg::Vec3Array; vertexArray4->push_back(osg::Vec3(-1.1f, -1.1f, 0.0f)); vertexArray4->push_back(osg::Vec3(1.1f, -1.1f, 0.0f)); vertexArray4->push_back(osg::Vec3(1.1f,1.1f, 0.0f)); vertexArray4->push_back(osg::Vec3(-1.1f,1.1f, 0.0f)); geometry4->setVertexArray(vertexArray4); osg::Vec2Array *textureArray4 = new osg::Vec2Array; textureArray4->push_back(osg::Vec2(0,0)); textureArray4->push_back(osg::Vec2(1,0)); textureArray4->push_back(osg::Vec2(1,1)); textureArray4->push_back(osg::Vec2(0,1)); osg::Texture2D *texture4 = new osg::Texture2D; texture4->setImage(osgDB::readImageFile("Data/Image2.bmp")); texture4->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); texture4->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); geometry4->setTexCoordArray(0, textureArray4); geometry4->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4)); geometry4->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture4); osg::BlendFunc *blendFuc4 = new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE); geometry4->getOrCreateStateSet()->setAttributeAndModes(blendFuc4); geometry4->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); geode4->addDrawable(geometry4); zoomMT->addChild(rotateMT); rotateMT->addChild(toggleMask34); toggleMask34->addChild(geode3); toggleMask34->addChild(geode4); toggleMask34->setAllChildrenOn(); g_scene2 = toggleMask34; return zoomMT; } osg::Node* buildScene() { osg::Group *root = new osg::Group; osg::MatrixTransform *quadMT = new osg::MatrixTransform; quadMT->setMatrix(osg::Matrix::translate(0.0, 0.0, -1.0)); osg::Geometry *quadGeometry = new osg::Geometry; //顶点位置坐标 osg::Vec3Array *quadVertexArray = new osg::Vec3Array; quadVertexArray->push_back(osg::Vec3(-1.1f, -1.1f, 0.0f)); quadVertexArray->push_back(osg::Vec3(1.1f, -1.1f, 0.0f)); quadVertexArray->push_back(osg::Vec3(1.1f,1.1f, 0.0f)); quadVertexArray->push_back(osg::Vec3(-1.1f,1.1f, 0.0f)); quadGeometry->setVertexArray(quadVertexArray); //顶点纹理坐标 osg::Vec2Array *quadTextureArray = new osg::Vec2Array; quadTextureArray->push_back(osg::Vec2(0,0)); quadTextureArray->push_back(osg::Vec2(3,0)); quadTextureArray->push_back(osg::Vec2(3,3)); quadTextureArray->push_back(osg::Vec2(0,3)); //纹理贴图 osg::Texture2D *quadTexture = new osg::Texture2D; quadTexture->setImage(osgDB::readImageFile("Data/Logo.bmp")); quadTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); quadTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); //设置WRAP方式是重复S和T方向 quadTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); quadTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); quadGeometry->setTexCoordArray(0, quadTextureArray); quadGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4)); quadGeometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, quadTexture); quadGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); quadGeometry->setUseDisplayList(false); //动态改变纹理坐标的回调 quadGeometry->setUpdateCallback(new TextureLogoCallback); osg::Geode *quadGeode = new osg::Geode; quadGeode->addDrawable(quadGeometry); quadMT->addChild(quadGeode); osg::Switch *toggleScene = new osg::Switch; toggleScene->addChild(createScene1()); toggleScene->addChild(createScene2()); toggleScene->setSingleChildOn(0); g_scene = toggleScene; root->addChild(quadMT); root->addChild(toggleScene); return root; } int main( int argc, char** argv ) { QApplication app(argc, argv); ViewerWidget* viewWidget = new ViewerWidget(buildScene()); viewWidget->setGeometry( 100, 100, 640, 480 ); viewWidget->show(); return app.exec(); }