OpenSceneGraph实现的NeHe OpenGL教程 - 第二十二课
来源:互联网 发布:淘宝的洗衣机中国质造 编辑:程序博客网 时间:2024/06/09 21:35
简介
这节课我们将讨论凹凸纹理技术,使用凹凸纹理可以让物体表面更有质感。本节课实现的其实是一种被称为浮雕纹理的技术(Emboss Bump Mapping),并不能算真正的凹凸纹理实现方式(It is a Hack)。
光滑的表面它上面各点法线的方向是一致的,凹凸纹理通过扰乱它表面的法线方向,在进行光照计算的时候(像素光照计算)会使得光滑表面产生明暗不同的颜色,从而"欺骗"我们的大脑,让我们以为它的表面是凹凸不平的。凹凸纹理最早是由James F. Blinn在1978年提的
NeHe课程中的实现原理可以参考凹凸纹理随笔, 在开始之前先说明一下NeHe课程中的实现方式:
NeHe教程中通过空格切换在三种模式下进行:
1. 未使用多重纹理(MultiTexture)技术进行凹凸纹理处理
2.使用多重纹理(MultiTexture)技术进行凹凸纹理处理
3.渲染不带凹凸纹理的场景
这三个过程分别对应doMesh1TexelUnits、doMesh1Texe2Units、doMeshNoBumps三个函数。本课只实现了在1情形下的凹凸纹理。2的实现也在源码中,但是未找到对应的OpenGL API,希望知道的读者指点一二。
下面简单说一下Emboss Bump Mapping的实现过程:
浮雕纹理需要对模型表面贴两次纹理,第一次纹理的纹理坐标不进行偏移、第二次的纹理坐标会在光照方向上进行偏移。偏移量的计算方法可以参考PPT Emboss Bump Mapping
实现
首先和NeHe教程一下初始化我们需要的纹理:
这里面需要使用osg中的Image类和处理Image的ImageUtil中的内容:部分代码如下:
- osg::Image *image1 = new osg::Image;
- image1 = osgDB::readImageFile("Data/Base.bmp");
- texture[0] = new osg::Texture2D;
- texture[0]->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
- texture[0]->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST);
- texture[0]->setImage(image1);
- texture[1] = new osg::Texture2D;
- texture[1]->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
- texture[1]->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
- texture[1]->setImage(image1);
- osg::Image *image2 = new osg::Image;
- image2 = osgDB::readImageFile("Data/Bump.bmp");
- osg::offsetAndScaleImage(image2, osg::Vec4(), osg::Vec4(0.5, 0.5, 0.5, 1)); // RGB值都减半
- bump[0] = new osg::Texture2D;
- bump[0]->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
- bump[0]->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST);
- bump[0]->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
- bump[0]->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
- bump[0]->setBorderColor(osg::Vec4(0.5, 0.5, 0.5, 1.0));
- bump[0]->setImage(image2);
- osg::Image *image3 = osgDB::readImageFile("Data/Bump.bmp");
- osg::offsetAndScaleImage(image3, osg::Vec4(), osg::Vec4(0.5,0.5,0.5,1));
- osg::offsetAndScaleImage(image3, osg::Vec4(-128, -128, -128, 1), osg::Vec4(-1,-1,-1,1));
- invbump[0] = new osg::Texture2D;
- invbump[0]->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
- invbump[0]->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST);
- invbump[0]->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
- invbump[0]->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
- invbump[0]->setBorderColor(osg::Vec4(0.5, 0.5, 0.5, 1.0));
- invbump[0]->setImage(image3);
为了进行凹凸纹理,我们先设置第一次纹理部分,代码如下:
可以看到这一次贴的纹理的纹理坐标并没有偏移,全部用到了data中的原始数据
- osg::Geode* createUpMappingCube()
- {
- osg::Geode *cubeGeode = new osg::Geode;
- osg::Geometry *cubeGeometry = new osg::Geometry;
- osg::Vec3Array *vertices = new osg::Vec3Array;
- osg::Vec3Array *normals = new osg::Vec3Array;
- osg::Vec2Array *texcoords = new osg::Vec2Array;
- for (unsigned i = 0; i < 24; ++i)
- {
- texcoords->push_back(osg::Vec2(data[5*i],data[5*i + 1])); //第一次贴纹理不需要偏移
- vertices->push_back(osg::Vec3(data[5*i + 2], data[5*i +3], data[5*i + 4]));
- }
- cubeGeometry->setVertexArray(vertices);
- cubeGeometry->setTexCoordArray(0, texcoords);
- cubeGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, vertices->size()));
- cubeGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, false);
- cubeGeometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, bump[1]);
- osg::Depth *depth = new osg::Depth(osg::Depth::LEQUAL);
- cubeGeometry->getOrCreateStateSet()->setAttributeAndModes(depth);
- cubeGeode->addDrawable(cubeGeometry);
- return cubeGeode;
- }
- //第二次贴纹理,这里面并没有纹理坐标,因为纹理坐标需要偏移,在回调中计算
- osg::Geode* createDownMappingCube()
- {
- osg::Geode *cubeGeode = new osg::Geode;
- osg::Geometry *cubeGeometry = new osg::Geometry;
- cubeGeometry->setUpdateCallback(new TextureCallback());
- osg::Vec3Array *vertices = new osg::Vec3Array;
- for (unsigned i = 0; i < 24; ++i)
- {
- vertices->push_back(osg::Vec3(data[5*i + 2], data[5*i +3], data[5*i + 4]));
- }
- cubeGeometry->setVertexArray(vertices);
- cubeGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, vertices->size()));
- cubeGeode->addDrawable(cubeGeometry);
- return cubeGeode;
- }
接下来看看回调实现:(只给出部分代码)
- virtual void update(osg::NodeVisitor* nv, osg::Drawable* drawable)
- {
- osg::Geometry *geometry = drawable->asGeometry();
- if (!geometry)
- return;
- osg::Vec2Array *textureArray = dynamic_cast<osg::Vec2Array*>(geometry->getTexCoordArray(0));
- if(textureArray)
- return;
- textureArray = new osg::Vec2Array;
- osg::Vec3Array *vertices = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray());
- osg::Vec3 lightPos(LightPosition[0], LightPosition[1], LightPosition[2]);
- //获取当前模型视图矩阵
- osg::Matrix currentModelViewMatrix = computeLocalToWorld(nv->getNodePath()) * g_viewer->getCamera()->getViewMatrix();
- //反转模型视图矩阵
- osg::Matrix currentInvModelViewerMatrix = osg::Matrix::inverse(currentModelViewMatrix);
- //将光源变换到物体建模坐标系下
- osg::Vec3 lightInObjectSpace = lightPos * currentInvModelViewerMatrix;
- /////////////////////////////////////////////////////////////////////////
- for (int i=0; i<4; i++) {
- osg::Vec3 pos = vertices->at(i);
- osg::Vec3 orientation = lightInObjectSpace - pos;
- orientation.normalize();
- //获取切空间中在S、T轴上的偏移量
- double deltaS = orientation * osg::Vec3(1,0,0) * MaxEmboss;
- double deltaT = orientation * osg::Vec3(0,1,0) * MaxEmboss;
- textureArray->push_back(osg::Vec2(data[5*i]+deltaS,data[5*i + 1]+deltaT));
- }
- osg::Switch *switchNode = new osg::Switch;
- g_switch = switchNode;
- rotateYAxis->addChild(switchNode);
- switchNode->addChild(createUpMappingCube());
- switchNode->addChild(createDownMappingCube());
- switchNode->addChild(createCube());
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_E)
- {
- if (g_switch->getValue(2) == true)
- {
- g_switch->setValue(2, false);
- }
- else
- {
- g_switch->setValue(2, true);
- }
- }
附:本课源码(源码中可能存在错误和不足,仅供参考)
- #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/LightSource>
- #include <osg/ImageUtils>
- #include <osg/Texture2D>
- #include <osg/BlendFunc>
- #include <osg/Depth>
- #include <osg/TexEnv>
- #include <osg/TexEnvCombine>
- //////////////////////////////////////////////////////////////////////////
- const float MaxEmboss = 0.008f;
- bool multitextureSupported = false;
- bool useMultitexture = true;
- osgViewer::View *g_viewer;
- osg::Switch *g_switch;
- osg::Switch *g_switchMulti;
- GLfloat xrot;
- GLfloat yrot;
- GLfloat xspeed;
- GLfloat yspeed;
- GLfloat z=-5.0f;
- GLuint filter=1;
- osg::Texture2D *texture[3];
- osg::Texture2D *bump[3];
- osg::Texture2D *invbump[3];
- osg::Texture2D *glLogo;
- osg::Texture2D *multiLogo;
- GLfloat LightAmbient[] = { 0.2f, 0.2f, 0.2f};
- GLfloat LightDiffuse[] = { 1.0f, 1.0f, 1.0f};
- GLfloat LightPosition[] = { 0.0f, 0.0f, 2.0f};
- GLfloat data[]=
- {
- // FRONT FACE
- 0.0f, 0.0f, -1.0f, -1.0f, +1.0f,
- 1.0f, 0.0f, +1.0f, -1.0f, +1.0f,
- 1.0f, 1.0f, +1.0f, +1.0f, +1.0f,
- 0.0f, 1.0f, -1.0f, +1.0f, +1.0f,
- // BACK FACE
- 1.0f, 0.0f, -1.0f, -1.0f, -1.0f,
- 1.0f, 1.0f, -1.0f, +1.0f, -1.0f,
- 0.0f, 1.0f, +1.0f, +1.0f, -1.0f,
- 0.0f, 0.0f, +1.0f, -1.0f, -1.0f,
- // Top Face
- 0.0f, 1.0f, -1.0f, +1.0f, -1.0f,
- 0.0f, 0.0f, -1.0f, +1.0f, +1.0f,
- 1.0f, 0.0f, +1.0f, +1.0f, +1.0f,
- 1.0f, 1.0f, +1.0f, +1.0f, -1.0f,
- // Bottom Face
- 1.0f, 1.0f, -1.0f, -1.0f, -1.0f,
- 0.0f, 1.0f, +1.0f, -1.0f, -1.0f,
- 0.0f, 0.0f, +1.0f, -1.0f, +1.0f,
- 1.0f, 0.0f, -1.0f, -1.0f, +1.0f,
- // Right Face
- 1.0f, 0.0f, +1.0f, -1.0f, -1.0f,
- 1.0f, 1.0f, +1.0f, +1.0f, -1.0f,
- 0.0f, 1.0f, +1.0f, +1.0f, +1.0f,
- 0.0f, 0.0f, +1.0f, -1.0f, +1.0f,
- // Left Face
- 0.0f, 0.0f, -1.0f, -1.0f, -1.0f,
- 1.0f, 0.0f, -1.0f, -1.0f, 1.0f,
- 1.0f, 1.0f, -1.0f, 1.0f, 1.0f,
- 0.0f, 1.0f, -1.0f, 1.0f, -1.0f
- };
- //////////////////////////////////////////////////////////////////////////
- //FindFirstNamedNodeVisitor用来遍历寻找与指定名称相同的节点
- class FindFirstNamedNodeVisitor : public osg::NodeVisitor
- {
- public:
- FindFirstNamedNodeVisitor(const std::string& name):
- osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
- _name(name), _foundNode(NULL) {}
- virtual void apply(osg::Node& node)
- {
- if (node.getName()==_name)
- {
- _foundNode = &node;
- return;
- }
- traverse(node);
- }
- std::string _name;
- osg::Node *_foundNode;
- };
- //////////////////////////////////////////////////////////////////////////
- //RotateCallback
- class RotateCallback : public osg::NodeCallback
- {
- public:
- RotateCallback(osg::Vec3d rotateAxis, double rotateSpeed) :
- osg::NodeCallback(),
- _rotateAxis(rotateAxis),
- _rotateSpeed(rotateSpeed),
- _rotateAngle(0.0)
- {
- //Nop
- }
- void setRotateSpeed(double speed)
- {
- _rotateSpeed = speed;
- }
- double getRotateSpeed() const
- {
- return _rotateSpeed;
- }
- virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
- {
- osg::MatrixTransform *currentMT = dynamic_cast<osg::MatrixTransform*>(node);
- if (currentMT)
- {
- //获取当前的平移位置
- osg::Vec3d currentTranslate = currentMT->getMatrix().getTrans();
- osg::Matrix newMatrix = osg::Matrix::rotate(_rotateAngle, _rotateAxis) * osg::Matrix::translate(currentTranslate);
- currentMT->setMatrix(newMatrix);
- _rotateAngle += _rotateSpeed;
- }
- traverse(node, nv);
- }
- private:
- osg::Vec3d _rotateAxis; //旋转轴
- double _rotateSpeed; //旋转速度
- double _rotateAngle; //当前旋转的角度
- };
- //////////////////////////////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////
- class ManipulatorSceneHandler : public osgGA::GUIEventHandler
- {
- public:
- ManipulatorSceneHandler()
- {
- }
- 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;
- osg::Group *root = viewer->getSceneData()->asGroup();
- switch(ea.getEventType())
- {
- case(osgGA::GUIEventAdapter::KEYDOWN):
- {
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Left)
- {
- FindFirstNamedNodeVisitor fnv("yRotMT");
- root->accept(fnv);
- osg::Node *mtNode = fnv._foundNode;
- osg::MatrixTransform *yRotMT = dynamic_cast<osg::MatrixTransform*>(mtNode);
- if (!yRotMT)
- return false;
- RotateCallback *rotCallback = dynamic_cast<RotateCallback*>(yRotMT->getUpdateCallback());
- if (!rotCallback)
- return false;
- double speed = rotCallback->getRotateSpeed();
- speed += 0.02;
- rotCallback->setRotateSpeed(speed);
- }
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Right)
- {
- FindFirstNamedNodeVisitor fnv("yRotMT");
- root->accept(fnv);
- osg::Node *mtNode = fnv._foundNode;
- osg::MatrixTransform *yRotMT = dynamic_cast<osg::MatrixTransform*>(mtNode);
- if (!yRotMT)
- return false;
- RotateCallback *rotCallback = dynamic_cast<RotateCallback*>(yRotMT->getUpdateCallback());
- if (!rotCallback)
- return false;
- double speed = rotCallback->getRotateSpeed();
- speed -= 0.02;
- rotCallback->setRotateSpeed(speed);
- }
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Up)
- {
- FindFirstNamedNodeVisitor fnv("xRotMT");
- root->accept(fnv);
- osg::Node *mtNode = fnv._foundNode;
- osg::MatrixTransform *xRotMT = dynamic_cast<osg::MatrixTransform*>(mtNode);
- if (!xRotMT)
- return false;
- RotateCallback *rotCallback = dynamic_cast<RotateCallback*>(xRotMT->getUpdateCallback());
- if (!rotCallback)
- return false;
- double speed = rotCallback->getRotateSpeed();
- speed += 0.02;
- rotCallback->setRotateSpeed(speed);
- }
- if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Down)
- {
- FindFirstNamedNodeVisitor fnv("xRotMT");
- root->accept(fnv);
- osg::Node *mtNode = fnv._foundNode;
- osg::MatrixTransform *xRotMT = dynamic_cast<osg::MatrixTransform*>(mtNode);
- if (!xRotMT)
- return false;
- RotateCallback *rotCallback = dynamic_cast<RotateCallback*>(xRotMT->getUpdateCallback());
- if (!rotCallback)
- return false;
- double speed = rotCallback->getRotateSpeed();
- speed -= 0.02;
- rotCallback->setRotateSpeed(speed);
- }
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_E)
- {
- if (g_switch->getValue(2) == true)
- {
- g_switch->setValue(2, false);
- }
- else
- {
- g_switch->setValue(2, true);
- }
- }
- }
- 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.5) );
- camera->setClearDepth(1.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));
- this->setSceneData( scene );
- addEventHandler(new ManipulatorSceneHandler);
- g_viewer = this;
- 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;
- };
- //////////////////////////////////////////////////////////////////////////
- //创建纹理:代码中只是用到第二个纹理bump[1]和invbmp[1]
- void initTextures()
- {
- osg::Image *image1 = new osg::Image;
- image1 = osgDB::readImageFile("Data/Base.bmp");
- texture[0] = new osg::Texture2D;
- texture[0]->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
- texture[0]->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST);
- texture[0]->setImage(image1);
- texture[1] = new osg::Texture2D;
- texture[1]->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
- texture[1]->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
- texture[1]->setImage(image1);
- texture[2] = new osg::Texture2D;
- texture[2]->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
- texture[2]->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_NEAREST);
- texture[2]->setImage(image1);
- osg::Image *image2 = new osg::Image;
- image2 = osgDB::readImageFile("Data/Bump.bmp");
- osg::offsetAndScaleImage(image2, osg::Vec4(), osg::Vec4(0.5, 0.5, 0.5, 1));
- bump[0] = new osg::Texture2D;
- bump[0]->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
- bump[0]->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST);
- bump[0]->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
- bump[0]->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
- bump[0]->setBorderColor(osg::Vec4(0.5, 0.5, 0.5, 1.0));
- bump[0]->setImage(image2);
- bump[1] = new osg::Texture2D;
- bump[1]->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
- bump[1]->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
- bump[1]->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
- bump[1]->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
- bump[1]->setBorderColor(osg::Vec4(0.5, 0.5, 0.5, 1.0));
- bump[1]->setImage(image2);
- bump[2] = new osg::Texture2D;
- bump[2]->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
- bump[2]->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_NEAREST);
- bump[2]->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
- bump[2]->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
- bump[2]->setBorderColor(osg::Vec4(0.5, 0.5, 0.5, 1.0));
- bump[2]->setImage(image2);
- osg::Image *image3 = osgDB::readImageFile("Data/Bump.bmp");
- osg::offsetAndScaleImage(image3, osg::Vec4(), osg::Vec4(0.5,0.5,0.5,1));
- osg::offsetAndScaleImage(image3, osg::Vec4(-128, -128, -128, 1), osg::Vec4(-1,-1,-1,1));
- invbump[0] = new osg::Texture2D;
- invbump[0]->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
- invbump[0]->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST);
- invbump[0]->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
- invbump[0]->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
- invbump[0]->setBorderColor(osg::Vec4(0.5, 0.5, 0.5, 1.0));
- invbump[0]->setImage(image3);
- invbump[1] = new osg::Texture2D;
- invbump[1]->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
- invbump[1]->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
- invbump[1]->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
- invbump[1]->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
- invbump[1]->setBorderColor(osg::Vec4(0.5, 0.5, 0.5, 1.0));
- invbump[1]->setImage(image3);
- invbump[2] = new osg::Texture2D;
- invbump[2]->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
- invbump[2]->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_NEAREST);
- invbump[2]->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
- invbump[2]->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
- invbump[2]->setBorderColor(osg::Vec4(0.5, 0.5, 0.5, 1.0));
- invbump[2]->setImage(image3);
- osg::Image *glLogoAlpha = osgDB::readImageFile("Data/OpenGL_ALPHA.bmp");
- osg::Image *glLogoImage = osgDB::readImageFile("Data/OpenGL.bmp");
- osg::Image *glLogoCombo = new osg::Image;
- glLogoCombo->allocateImage(glLogoImage->s(), glLogoImage->t(), 1, GL_RGBA, glLogoImage->getDataType());
- unsigned char *comboData = glLogoCombo->data();
- for (int i = 0; i < glLogoImage->s() * glLogoImage->t(); ++i)
- {
- comboData[4*i+3] = glLogoAlpha->data()[i*3];
- comboData[4*i] = glLogoImage->data()[i*3];
- comboData[4*i+1] = glLogoImage->data()[i*3+1];
- comboData[4*i+2] = glLogoImage->data()[i*3+2];
- }
- glLogo = new osg::Texture2D;
- glLogo->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
- glLogo->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
- glLogo->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
- glLogo->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
- glLogo->setBorderColor(osg::Vec4(0.5, 0.5, 0.5, 1.0));
- glLogo->setImage(glLogoCombo);
- osg::Image *glMultiAlpha = osgDB::readImageFile("Data/multi_on_alpha.bmp");
- osg::Image *glMultiImage = osgDB::readImageFile("Data/multi_on.bmp");
- osg::Image *glMultiCombo = new osg::Image;
- glMultiCombo->allocateImage(glLogoImage->s(), glLogoImage->t(), 1, GL_RGBA, glLogoImage->getDataType());
- unsigned char *multiComboData = glMultiCombo->data();
- for (int i = 0; i < glMultiImage->s() * glMultiImage->t(); ++i)
- {
- multiComboData[4*i+3] = glMultiAlpha->data()[i*3];
- multiComboData[4*i] = glMultiImage->data()[i*3];
- multiComboData[4*i+1] = glMultiImage->data()[i*3+1];
- multiComboData[4*i+2] = glMultiImage->data()[i*3+2];
- }
- multiLogo = new osg::Texture2D;
- multiLogo->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
- multiLogo->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
- multiLogo->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
- multiLogo->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
- multiLogo->setBorderColor(osg::Vec4(0.5, 0.5, 0.5, 1.0));
- multiLogo->setImage(glMultiCombo);
- }
- osg::Group *createLogo()
- {
- osg::Group *logoGroup = new osg::Group;
- osg::Depth *depth = new osg::Depth(osg::Depth::ALWAYS);
- osg::BlendFunc *blendFunc = new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE_MINUS_SRC_ALPHA);
- osg::Geode *logoGeode = new osg::Geode;
- osg::Geometry *logoGeometry = new osg::Geometry;
- osg::Vec3Array *logoVertices = new osg::Vec3Array;
- logoVertices->push_back(osg::Vec3(0.23f, -0.4f,-1.0f));
- logoVertices->push_back(osg::Vec3(0.53f, -0.4f,-1.0f));
- logoVertices->push_back(osg::Vec3(0.53f, -0.25f,-1.0f));
- logoVertices->push_back(osg::Vec3(0.23f, -0.25f,-1.0f));
- osg::Vec2Array *textureVertices = new osg::Vec2Array;
- textureVertices->push_back(osg::Vec2(0.0f,0.0f));
- textureVertices->push_back(osg::Vec2(1.0f,0.0f));
- textureVertices->push_back(osg::Vec2(1.0f,1.0f));
- textureVertices->push_back(osg::Vec2(0.0f,1.0f));
- logoGeometry->setVertexArray(logoVertices);
- logoGeometry->setTexCoordArray(0, textureVertices);
- logoGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));
- logoGeometry->getOrCreateStateSet()->setAttributeAndModes(depth);
- logoGeometry->getOrCreateStateSet()->setAttributeAndModes(blendFunc);
- logoGeometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, glLogo);
- logoGeode->addDrawable(logoGeometry);
- osg::Geode *multiGeode = new osg::Geode;
- osg::Geometry *multiGeometry = new osg::Geometry;
- osg::Vec3Array *multiVertices = new osg::Vec3Array;
- multiVertices->push_back(osg::Vec3(-0.53f, -0.4f,-1.0f));
- multiVertices->push_back(osg::Vec3(-0.33f, -0.4f,-1.0f));
- multiVertices->push_back(osg::Vec3(-0.33f, -0.3f,-1.0f));
- multiVertices->push_back(osg::Vec3(-0.53f, -0.3f,-1.0f));
- osg::Vec2Array *tVertices = new osg::Vec2Array;
- textureVertices->push_back(osg::Vec2(0.0f,0.0f));
- textureVertices->push_back(osg::Vec2(1.0f,0.0f));
- textureVertices->push_back(osg::Vec2(1.0f,1.0f));
- textureVertices->push_back(osg::Vec2(0.0f,1.0f));
- multiGeometry->setVertexArray(multiVertices);
- multiGeometry->setTexCoordArray(0, tVertices);
- multiGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));
- multiGeometry->getOrCreateStateSet()->setAttributeAndModes(depth);
- multiGeometry->getOrCreateStateSet()->setAttributeAndModes(blendFunc);
- multiGeometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, multiLogo);
- multiGeode->addDrawable(multiGeometry);
- logoGroup->addChild(logoGeode);
- logoGroup->addChild(multiGeode);
- return logoGroup;
- }
- osg::Geode* createCube()
- {
- osg::Geode *cubeGeode = new osg::Geode;
- osg::Geometry *cubeGeometry = new osg::Geometry;
- osg::Vec3Array *vertices = new osg::Vec3Array;
- osg::Vec3Array *normals = new osg::Vec3Array;
- osg::Vec2Array *texcoords = new osg::Vec2Array;
- for (unsigned i = 0; i < 24; ++i)
- {
- texcoords->push_back(osg::Vec2(data[5*i],data[5*i + 1]));
- vertices->push_back(osg::Vec3(data[5*i + 2], data[5*i +3], data[5*i + 4]));
- }
- normals->push_back(osg::Vec3(0.0f, 0.0f, +1.0f));
- normals->push_back(osg::Vec3(0.0f, 0.0f,-1.0f));
- normals->push_back(osg::Vec3(0.0f, 1.0f, 0.0f));
- normals->push_back(osg::Vec3(0.0f,-1.0f, 0.0f));
- normals->push_back(osg::Vec3(1.0f, 0.0f, 0.0f));
- normals->push_back(osg::Vec3(-1.0f, 0.0f, 0.0f));
- cubeGeometry->setVertexArray(vertices);
- cubeGeometry->setTexCoordArray(0, texcoords);
- cubeGeometry->setNormalArray(normals, osg::Array::BIND_PER_PRIMITIVE_SET);
- cubeGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, vertices->size()));
- cubeGeometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture[1]);
- osg::BlendFunc *blendFunc = new osg::BlendFunc(osg::BlendFunc::DST_COLOR, osg::BlendFunc::SRC_COLOR);
- cubeGeometry->getOrCreateStateSet()->setAttributeAndModes(blendFunc);
- osg::Depth *depth = new osg::Depth(osg::Depth::LEQUAL);
- cubeGeometry->getOrCreateStateSet()->setAttributeAndModes(depth);
- osg::TexEnv *texEnv = new osg::TexEnv(osg::TexEnv::MODULATE);
- cubeGeometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, texEnv);
- cubeGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
- cubeGeode->addDrawable(cubeGeometry);
- return cubeGeode;
- }
- osg::Geode* createUpMappingCube()
- {
- osg::Geode *cubeGeode = new osg::Geode;
- osg::Geometry *cubeGeometry = new osg::Geometry;
- osg::Vec3Array *vertices = new osg::Vec3Array;
- osg::Vec3Array *normals = new osg::Vec3Array;
- osg::Vec2Array *texcoords = new osg::Vec2Array;
- for (unsigned i = 0; i < 24; ++i)
- {
- texcoords->push_back(osg::Vec2(data[5*i],data[5*i + 1]));
- vertices->push_back(osg::Vec3(data[5*i + 2], data[5*i +3], data[5*i + 4]));
- }
- cubeGeometry->setVertexArray(vertices);
- cubeGeometry->setTexCoordArray(0, texcoords);
- cubeGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, vertices->size()));
- cubeGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, false);
- cubeGeometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, bump[1]);
- osg::Depth *depth = new osg::Depth(osg::Depth::LEQUAL);
- cubeGeometry->getOrCreateStateSet()->setAttributeAndModes(depth);
- cubeGeode->addDrawable(cubeGeometry);
- return cubeGeode;
- }
- class TextureCallback : public osg::Drawable::UpdateCallback
- {
- public:
- TextureCallback(){ }
- virtual void update(osg::NodeVisitor* nv, osg::Drawable* drawable)
- {
- osg::Geometry *geometry = drawable->asGeometry();
- if (!geometry)
- return;
- osg::Vec2Array *textureArray = dynamic_cast<osg::Vec2Array*>(geometry->getTexCoordArray(0));
- if(textureArray)
- return;
- textureArray = new osg::Vec2Array;
- osg::Vec3Array *vertices = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray());
- osg::Vec3 lightPos(LightPosition[0], LightPosition[1], LightPosition[2]);
- osg::Matrix currentModelViewMatrix = computeLocalToWorld(nv->getNodePath()) * g_viewer->getCamera()->getViewMatrix();
- osg::Matrix currentInvModelViewerMatrix = osg::Matrix::inverse(currentModelViewMatrix);
- osg::Vec3 lightInObjectSpace = lightPos * currentInvModelViewerMatrix;
- //////////////////////////////////////////////////////////////////////////
- for (int i=0; i<4; i++) {
- osg::Vec3 pos = vertices->at(i);
- osg::Vec3 orientation = lightInObjectSpace - pos;
- orientation.normalize();
- double deltaS = orientation * osg::Vec3(1,0,0) * MaxEmboss;
- double deltaT = orientation * osg::Vec3(0,1,0) * MaxEmboss;
- textureArray->push_back(osg::Vec2(data[5*i]+deltaS,data[5*i + 1]+deltaT));
- }
- for (int i=4; i<8; i++) {
- osg::Vec3 pos = vertices->at(i);
- osg::Vec3 orientation = lightInObjectSpace - pos;
- orientation.normalize();
- double deltaS = orientation * osg::Vec3(-1, 0, 0) * MaxEmboss;
- double deltaT = orientation * osg::Vec3(0,1,0) * MaxEmboss;
- textureArray->push_back(osg::Vec2(data[5*i]+deltaS,data[5*i + 1]+deltaT));
- }
- for (int i=8; i<12; i++) {
- osg::Vec3 pos = vertices->at(i);
- osg::Vec3 orientation = lightInObjectSpace - pos;
- orientation.normalize();
- double deltaS = orientation * osg::Vec3(1, 0, 0) * MaxEmboss;
- double deltaT = orientation * osg::Vec3(0,0,-1) * MaxEmboss;
- textureArray->push_back(osg::Vec2(data[5*i]+deltaS,data[5*i + 1]+deltaT));
- }
- for (int i=12; i<16; i++) {
- osg::Vec3 pos = vertices->at(i);
- osg::Vec3 orientation = lightInObjectSpace - pos;
- orientation.normalize();
- double deltaS = orientation * osg::Vec3(0, -1, 0) * MaxEmboss;
- double deltaT = orientation * osg::Vec3(-1,0,0) * MaxEmboss;
- textureArray->push_back(osg::Vec2(data[5*i]+deltaS,data[5*i + 1]+deltaT));
- }
- for (int i=16; i<20; i++) {
- osg::Vec3 pos = vertices->at(i);
- osg::Vec3 orientation = lightInObjectSpace - pos;
- orientation.normalize();
- double deltaS = orientation * osg::Vec3(0, 0, -1) * MaxEmboss;
- double deltaT = orientation * osg::Vec3(0,1,0) * MaxEmboss;
- textureArray->push_back(osg::Vec2(data[5*i]+deltaS,data[5*i + 1]+deltaT));
- }
- for (int i=20; i<24; i++) {
- osg::Vec3 pos = vertices->at(i);
- osg::Vec3 orientation = lightInObjectSpace - pos;
- orientation.normalize();
- double deltaS = orientation * osg::Vec3(0, 0, 1) * MaxEmboss;
- double deltaT = orientation * osg::Vec3(0,1,0) * MaxEmboss;
- textureArray->push_back(osg::Vec2(data[5*i]+deltaS,data[5*i + 1]+deltaT));
- }
- geometry->setTexCoordArray(0, textureArray);
- geometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, invbump[1]);
- geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, false);
- osg::BlendFunc *blendFunc = new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE);
- geometry->getOrCreateStateSet()->setAttributeAndModes(blendFunc);
- osg::Depth *depth = new osg::Depth(osg::Depth::LEQUAL);
- geometry->getOrCreateStateSet()->setAttributeAndModes(depth);
- }
- };
- osg::Geode* createDownMappingCube()
- {
- osg::Geode *cubeGeode = new osg::Geode;
- osg::Geometry *cubeGeometry = new osg::Geometry;
- cubeGeometry->setUpdateCallback(new TextureCallback());
- osg::Vec3Array *vertices = new osg::Vec3Array;
- for (unsigned i = 0; i < 24; ++i)
- {
- vertices->push_back(osg::Vec3(data[5*i + 2], data[5*i +3], data[5*i + 4]));
- }
- cubeGeometry->setVertexArray(vertices);
- cubeGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, vertices->size()));
- cubeGeode->addDrawable(cubeGeometry);
- return cubeGeode;
- }
- //////////////////////////////////////////////////////////////////////////
- //Method 2:Using MultiTexture to Render
- //
- //TODO:使用MultiTexture的方式:
- // TODO: 未在OpenGL文档中查询到
- // glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
- //glTexEnvf (GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE);
- //不清楚是否是新的扩展,请读者指点
- //////////////////////////////////////////////////////////////////////////
- #if 0
- class MultiTextureUpdateCallback : public osg::Drawable::UpdateCallback
- {
- public:
- virtual void update(osg::NodeVisitor* nv, osg::Drawable* drawable)
- {
- osg::Geometry *geometry = drawable->asGeometry();
- if (!geometry)
- return;
- osg::Vec2Array *textureArray = dynamic_cast<osg::Vec2Array*>(geometry->getTexCoordArray(1));
- if(textureArray)
- return;
- textureArray = new osg::Vec2Array;
- osg::Vec3Array *vertices = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray());
- osg::Vec3 lightPos(LightPosition[0], LightPosition[1], LightPosition[2]);
- osg::Matrix currentModelViewMatrix = computeLocalToWorld(nv->getNodePath()) * g_viewer->getCamera()->getViewMatrix();
- osg::Matrix currentInvModelViewerMatrix = osg::Matrix::inverse(currentModelViewMatrix);
- osg::Vec3 lightInObjectSpace = lightPos * currentInvModelViewerMatrix;
- //////////////////////////////////////////////////////////////////////////
- for (int i=0; i<4; i++) {
- osg::Vec3 pos = vertices->at(i);
- osg::Vec3 orientation = lightInObjectSpace - pos;
- orientation.normalize();
- double deltaS = orientation * osg::Vec3(1,0,0) * MaxEmboss;
- double deltaT = orientation * osg::Vec3(0,1,0) * MaxEmboss;
- textureArray->push_back(osg::Vec2(data[5*i]+deltaS,data[5*i + 1]+deltaT));
- }
- for (int i=4; i<8; i++) {
- osg::Vec3 pos = vertices->at(i);
- osg::Vec3 orientation = lightInObjectSpace - pos;
- orientation.normalize();
- double deltaS = orientation * osg::Vec3(-1, 0, 0) * MaxEmboss;
- double deltaT = orientation * osg::Vec3(0,1,0) * MaxEmboss;
- textureArray->push_back(osg::Vec2(data[5*i]+deltaS,data[5*i + 1]+deltaT));
- }
- for (int i=8; i<12; i++) {
- osg::Vec3 pos = vertices->at(i);
- osg::Vec3 orientation = lightInObjectSpace - pos;
- orientation.normalize();
- double deltaS = orientation * osg::Vec3(1, 0, 0) * MaxEmboss;
- double deltaT = orientation * osg::Vec3(0,0,-1) * MaxEmboss;
- textureArray->push_back(osg::Vec2(data[5*i]+deltaS,data[5*i + 1]+deltaT));
- }
- for (int i=12; i<16; i++) {
- osg::Vec3 pos = vertices->at(i);
- osg::Vec3 orientation = lightInObjectSpace - pos;
- orientation.normalize();
- double deltaS = orientation * osg::Vec3(0, -1, 0) * MaxEmboss;
- double deltaT = orientation * osg::Vec3(-1,0,0) * MaxEmboss;
- textureArray->push_back(osg::Vec2(data[5*i]+deltaS,data[5*i + 1]+deltaT));
- }
- for (int i=16; i<20; i++) {
- osg::Vec3 pos = vertices->at(i);
- osg::Vec3 orientation = lightInObjectSpace - pos;
- orientation.normalize();
- double deltaS = orientation * osg::Vec3(0, 0, -1) * MaxEmboss;
- double deltaT = orientation * osg::Vec3(0,1,0) * MaxEmboss;
- textureArray->push_back(osg::Vec2(data[5*i]+deltaS,data[5*i + 1]+deltaT));
- }
- for (int i=20; i<24; i++) {
- osg::Vec3 pos = vertices->at(i);
- osg::Vec3 orientation = lightInObjectSpace - pos;
- orientation.normalize();
- double deltaS = orientation * osg::Vec3(0, 0, 1) * MaxEmboss;
- double deltaT = orientation * osg::Vec3(0,1,0) * MaxEmboss;
- textureArray->push_back(osg::Vec2(data[5*i]+deltaS,data[5*i + 1]+deltaT));
- }
- geometry->setTexCoordArray(1, textureArray);
- geometry->getOrCreateStateSet()->setTextureAttributeAndModes(1, invbump[1]);
- osg::TexEnv *tex2Env = new osg::TexEnv(osg::TexEnv::ADD);
- geometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, tex2Env);
- geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, false);
- }
- };
- #endif
- osg::Node* buildScene()
- {
- initTextures();
- osg::Group *root = new osg::Group;
- osg::MatrixTransform *zoomMT = new osg::MatrixTransform;
- zoomMT->setMatrix(osg::Matrix::translate(0.0, 0.0, -5.0));
- osg::MatrixTransform *rotateXAxis = new osg::MatrixTransform;
- rotateXAxis->setName("xRotMT");
- RotateCallback *xRotCallback = new RotateCallback(osg::X_AXIS, 0.0);
- rotateXAxis->setUpdateCallback(xRotCallback);
- zoomMT->addChild(rotateXAxis);
- osg::MatrixTransform *rotateYAxis = new osg::MatrixTransform;
- rotateYAxis->setName("yRotMT");
- RotateCallback *yRotCallback = new RotateCallback(osg::Y_AXIS, 0.0);
- rotateYAxis->setUpdateCallback(yRotCallback);
- rotateXAxis->addChild(rotateYAxis);
- osg::Switch *switchNode = new osg::Switch;
- g_switch = switchNode;
- rotateYAxis->addChild(switchNode);
- switchNode->addChild(createUpMappingCube());
- switchNode->addChild(createDownMappingCube());
- switchNode->addChild(createCube());
- root->addChild(zoomMT);
- 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();
- }
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第二十二课
- OpenSceneGraph实现的NeHe OpenGL教程 - 第二十二课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第一课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第二课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第三课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第四课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第五课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第六课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第七课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第八课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第九课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第十课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第十一课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第十二课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第十三课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第十四课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第十五课
- 用OpenSceneGraph实现的NeHe OpenGL教程 - 第十六课
- Java 8 Optional类深度解析
- GIT获取远程分支
- iOS之隐藏单个页面的电池条方法(iOS7以后)
- 前端基础一之开发须知道的那些事儿
- wchar_t <-> char
- OpenSceneGraph实现的NeHe OpenGL教程 - 第二十二课
- Android基础面试题
- 谷歌浏览器播放器声音
- iOS实现超酷页面切换动画特效
- 步进电机S型曲线加减速算法与实现
- 回溯法求解N皇后问题(Java实现)
- bzoj3743(树形dp)
- 欢迎使用CSDN-markdown编辑器
- jsp自定义标签(时间格式化包括Long转时间)