用OpenSceneGraph实现的NeHe OpenGL教程 - 第三十五课

osg::AnimationPath osg::AnimationPath::ControlPoint osg::AnimationPathCallback 使用     osg::AnimationPath* animationPath = new osg::AnimationPath;    //设置循环,还可设成摇摆:SWING 或仅动作1次:NO_LOOPING     animationPath->setLoopMode(osg::AnimationPath::LOOP);    //插入时间,和对应的位置和 阅读详情
  • 简介

这节课介绍了如何在三维场景中类似播放一段视频的效果。我们都知道视频实际上是由一帧帧的图片来构成的,因此我们将视频中的图片解析成图片格式,并将这些图片赋给几何体表面,让它们在很短的间隔内进行切换就实现了动态的效果。

  • 实现

本课中使用了Windows解析视频格式AVI文件的API,代码中这部分内容都来自NeHe课程中的相应代码,读者可以查阅MSDN了解这些内容:

首先创建播放AVI文件的几何体(类似于电影的大屏幕)

osg::Geode*	createBackGroundGeode(osg::Texture2D *text)
{
    osg::Geode *geode = new osg::Geode;
    osg::Geometry *geometry = new osg::Geometry;
    geometry->setUpdateCallback(new DynamicTextureUpdateCallback);
 ...
	geometry->setVertexArray(vertexArray);
	geometry->setTexCoordArray(0, textureArray, osg::Array::BIND_PER_VERTEX);
	geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, vertexArray->size()));
	geometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, text);
	geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, false);
	geode->addDrawable(geometry);

	return geode;
}
动态的纹理回调DynamicTextureUpdateCallback实现了动态修改纹理:

class DynamicTextureUpdateCallback : public osg::Drawable::UpdateCallback
{
public:
	DynamicTextureUpdateCallback() : _lastUpdate(0), _frame(0), _passedTimeInMillSecond(0)
	{
		_startTime = osg::Timer::instance()->tick(); 
		_currentTime = _startTime;
	}

	void update(osg::NodeVisitor* nv, osg::Drawable* drawable)
	{
		osg::Geometry *geometry = dynamic_cast<osg::Geometry*>(drawable);
		if (!geometry)
...
        }
};
接着创建场景中的几何体:

osg::Node*	createBox()
osg::Node*	createSphere()
osg::Node*	createCylinder()
这部分内容与 第二十三课的内容很类似

将上面所有的几何体添加到一个Switch开关节点中实现切换操作:

在与键盘的交互中实现切换纹理方式和切换节点的效果:

		case(osgGA::GUIEventAdapter::KEYDOWN):
			{
				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Space)
				{
					if (!g_SwitchNode)
						return false;

					static int i = 0;
					++i;
					if (i > 2)
						i = 0;
					g_SwitchNode->setSingleChildOn(i);
				}
				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_E)
				{
					static bool flag = true;
					if(flag){
						g_SwitchNode->getOrCreateStateSet()->setTextureMode(0, GL_TEXTURE_GEN_S, osg::StateAttribute::OFF);
						g_SwitchNode->getOrCreateStateSet()->setTextureMode(0, GL_TEXTURE_GEN_T, osg::StateAttribute::OFF);
					}else{
						g_SwitchNode->getOrCreateStateSet()->setTextureMode(0, GL_TEXTURE_GEN_S, osg::StateAttribute::ON);
						g_SwitchNode->getOrCreateStateSet()->setTextureMode(0, GL_TEXTURE_GEN_T, osg::StateAttribute::ON);
					}
					flag = !flag;
				}
将以上所有内容添加到场景根节点下。编译运行程序:


附:本课源码(源码中可能存在错误和不足,仅供参考)

#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/ShapeDrawable>

#include <osg/Switch>
#include <osg/AnimationPath>

#include <osg/TexGen>
#include <osg/TexGenNode>

#include <vfw.h>


AVISTREAMINFO		psi;					
PAVISTREAM			pavi;						
PGETFRAME			pgf;							
BITMAPINFOHEADER	bmih;
long				lastframe;								
int					width;								
int					height;								
char					*pdata;								
int					mpf;									

HDRAWDIB hdd;									
HBITMAP hBitmap;
HDC hdc = CreateCompatibleDC(0);		
unsigned char* data = 0;								

osg::Texture2D *g_Texture2D = NULL;
osg::Image *g_TextureImage = NULL;
osg::Switch *g_SwitchNode = NULL;

void flipIt(void* buffer)								
{
	void* b = buffer;	
	__asm												
	{
		mov ecx, 256*256
			mov ebx, b							
label:												
		mov al,[ebx+0]						
		mov ah,[ebx+2]						
		mov [ebx+2],al						
			mov [ebx+0],ah					

			add ebx,3								
			dec ecx									
			jnz label									
	}
}

void OpenAVI(LPCSTR szFile)
{
	TCHAR	title[100];

	AVIFileInit();
	if (AVIStreamOpenFromFileA(&pavi, szFile, streamtypeVIDEO, 0, OF_READ, NULL) !=0)
	{
		return;
	}

	AVIStreamInfo(pavi, &psi, sizeof(psi));
	width=psi.rcFrame.right-psi.rcFrame.left;
	height=psi.rcFrame.bottom-psi.rcFrame.top;

	lastframe=AVIStreamLength(pavi);

	mpf=AVIStreamSampleToTime(pavi,lastframe)/lastframe;

	bmih.biSize = sizeof (BITMAPINFOHEADER);
	bmih.biPlanes = 1;
	bmih.biBitCount = 24;
	bmih.biWidth = 256;
	bmih.biHeight = 256;
	bmih.biCompression = BI_RGB;

	hBitmap = CreateDIBSection (hdc, (BITMAPINFO*)(&bmih), DIB_RGB_COLORS, (void**)(&data), NULL, NULL);
	SelectObject (hdc, hBitmap);

	pgf=AVIStreamGetFrameOpen(pavi, NULL);
	if (pgf==NULL)
	{
		return;
	}
}


void GrabAVIFrame(int frame)
{
	LPBITMAPINFOHEADER lpbi;
	lpbi = (LPBITMAPINFOHEADER)AVIStreamGetFrame(pgf, frame);
	pdata=(char *)lpbi+lpbi->biSize+lpbi->biClrUsed * sizeof(RGBQUAD);

	DrawDibDraw (hdd, hdc, 0, 0, 256, 256, lpbi, pdata, 0, 0, width, height, 0);
	flipIt(data);

	g_TextureImage->setImage(256, 256, 1, 3, GL_RGB, GL_UNSIGNED_BYTE, data, osg::Image::NO_DELETE);
	g_Texture2D->setImage(g_TextureImage);
}


void CloseAVI(void)
{
	DeleteObject(hBitmap);
	DrawDibClose(hdd);
	AVIStreamGetFrameClose(pgf);
	AVIStreamRelease(pavi);
	AVIFileExit();
}


void initialize()
{
	hdd = DrawDibOpen();
	g_Texture2D = new osg::Texture2D;
	g_Texture2D->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
	g_Texture2D->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST);
	g_TextureImage = new osg::Image;
	g_TextureImage->allocateImage(256, 256, 1, GL_RGB, GL_UNSIGNED_BYTE);
	OpenAVI("Data/face3.avi");
}


class DynamicTextureUpdateCallback : public osg::Drawable::UpdateCallback
{
public:
	DynamicTextureUpdateCallback() : _lastUpdate(0), _frame(0), _passedTimeInMillSecond(0)
	{
		_startTime = osg::Timer::instance()->tick(); 
		_currentTime = _startTime;
	}

	void update(osg::NodeVisitor* nv, osg::Drawable* drawable)
	{
		osg::Geometry *geometry = dynamic_cast<osg::Geometry*>(drawable);
		if (!geometry)
			return;
		osg::Texture2D *texture2D = dynamic_cast<osg::Texture2D *>(geometry->getOrCreateStateSet()->getTextureAttribute(
																											0, osg::StateAttribute::TEXTURE));
		if (!texture2D)
			return;

		if (nv->getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR &&
			nv->getFrameStamp() &&
			nv->getFrameStamp()->getFrameNumber() != _lastUpdate) 
		{
			_lastUpdate = nv->getFrameStamp()->getFrameNumber();
			_currentTime = osg::Timer::instance()->tick();
			float t = osg::Timer::instance()->delta_m(_startTime, _currentTime);
			_passedTimeInMillSecond += t;
			
			if (_passedTimeInMillSecond >= mpf)
			{
				GrabAVIFrame(_frame);
				_frame++;

				if (_frame >= lastframe)
				{
					_frame = 0;
				}
				_passedTimeInMillSecond = 0;
			}
		}
	}

	osg::Timer_t	_startTime;
	osg::Timer_t	_currentTime;
	unsigned int		_lastUpdate;
	unsigned int	    _frame;
	int					_passedTimeInMillSecond;
};


//////////////////////////////////////////////////////////////////////////
///////////////////////绘制几何体/////////////////////////////////
//////////////////////////////////////////////////////////////////////////
osg::Geode*	createBackGroundGeode(osg::Texture2D *text)
{
	osg::Geode *geode = new osg::Geode;
	osg::Geometry *geometry = new osg::Geometry;
	geometry->setUpdateCallback(new DynamicTextureUpdateCallback);
	osg::Vec3Array *vertexArray = new osg::Vec3Array;
	osg::Vec2Array *textureArray = new osg::Vec2Array;

	vertexArray->push_back(osg::Vec3(11.0f,  8.3f, -20.0f));
	vertexArray->push_back(osg::Vec3(-11.0f,  8.3f, -20.0f));
	vertexArray->push_back(osg::Vec3(-11.0f, -8.3f, -20.0f));
	vertexArray->push_back(osg::Vec3(11.0f, -8.3f, -20.0f));

	textureArray->push_back(osg::Vec2(1.0f, 1.0f));
	textureArray->push_back(osg::Vec2(0.0f, 1.0f));
	textureArray->push_back(osg::Vec2(0.0f, 0.0f));
	textureArray->push_back(osg::Vec2(1.0f, 0.0f));

	geometry->setVertexArray(vertexArray);
	geometry->setTexCoordArray(0, textureArray, osg::Array::BIND_PER_VERTEX);
	geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, vertexArray->size()));
	geometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, text);
	geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, false);
	geode->addDrawable(geometry);

	return geode;
}

osg::Node*	createBox(/*osg::Texture2D *text*/)
{
	osg::MatrixTransform *mtX = new osg::MatrixTransform;
	mtX->addUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(), osg::X_AXIS, 1.5));
	osg::MatrixTransform *mtY = new osg::MatrixTransform;
	mtY->addUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(), osg::Y_AXIS, 2.5));
	osg::MatrixTransform *mtZ = new osg::MatrixTransform;
	mtZ->addUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(), osg::Z_AXIS, 3.5));

	osg::Geode *geode = new osg::Geode;
	osg::Geometry *geometry = new osg::Geometry;
	osg::Vec3Array *vertexArray = new osg::Vec3Array;
	osg::Vec2Array *textureArray = new osg::Vec2Array;
	osg::Vec3Array *normalArray = new osg::Vec3Array;
	
	for (int i = 0; i < 4; ++i)
		normalArray->push_back(osg::Vec3(0.0f, 0.0f, 0.5f));
	textureArray->push_back(osg::Vec2(0.0f, 0.0f));
	textureArray->push_back(osg::Vec2(1.0f, 0.0f));
	textureArray->push_back(osg::Vec2(1.0f, 1.0f));
	textureArray->push_back(osg::Vec2(0.0f, 1.0f));
	vertexArray->push_back(osg::Vec3(-1.0f, -1.0f,  1.0f));
	vertexArray->push_back(osg::Vec3(1.0f, -1.0f,  1.0f));
	vertexArray->push_back(osg::Vec3(1.0f,  1.0f,  1.0f));
	vertexArray->push_back(osg::Vec3(-1.0f,  1.0f,  1.0f));
	
	for (int i = 0; i < 4; ++i)
		normalArray->push_back(osg::Vec3(0.0f, 0.0f,-0.5f));
	textureArray->push_back(osg::Vec2(1.0f, 0.0f));
	textureArray->push_back(osg::Vec2(1.0f, 1.0f));
	textureArray->push_back(osg::Vec2(0.0f, 1.0f));
	textureArray->push_back(osg::Vec2(0.0f, 0.0f));
	vertexArray->push_back(osg::Vec3(-1.0f, -1.0f,  -1.0f));
	vertexArray->push_back(osg::Vec3(-1.0f, 1.0f,  -1.0f));
	vertexArray->push_back(osg::Vec3(1.0f,  1.0f, - 1.0f));
	vertexArray->push_back(osg::Vec3(1.0f,  -1.0f,  -1.0f));

	for (int i = 0; i < 4; ++i)
		normalArray->push_back(osg::Vec3(0.0f, 0.5f,0.0f));
	textureArray->push_back(osg::Vec2(0.0f, 1.0f));
	textureArray->push_back(osg::Vec2(0.0f, 0.0f));
	textureArray->push_back(osg::Vec2(1.0f, 0.0f));
	textureArray->push_back(osg::Vec2(1.0f, 1.0f));
	vertexArray->push_back(osg::Vec3(-1.0f, 1.0f,  -1.0f));
	vertexArray->push_back(osg::Vec3(-1.0f, 1.0f,  1.0f));
	vertexArray->push_back(osg::Vec3(1.0f,  1.0f,  1.0f));
	vertexArray->push_back(osg::Vec3(1.0f,  1.0f,  -1.0f));

	for (int i = 0; i < 4; ++i)
		normalArray->push_back(osg::Vec3(0.0f, -0.5f,0.0f));
	textureArray->push_back(osg::Vec2(1.0f, 1.0f));
	textureArray->push_back(osg::Vec2(0.0f, 1.0f));
	textureArray->push_back(osg::Vec2(0.0f, 0.0f));
	textureArray->push_back(osg::Vec2(1.0f, 0.0f));
	vertexArray->push_back(osg::Vec3(-1.0f, -1.0f,  -1.0f));
	vertexArray->push_back(osg::Vec3(1.0f, -1.0f,  -1.0f));
	vertexArray->push_back(osg::Vec3(1.0f,  -1.0f,  1.0f));
	vertexArray->push_back(osg::Vec3(-1.0f,  -1.0f,  1.0f));

	for (int i = 0; i < 4; ++i)
		normalArray->push_back(osg::Vec3(0.5f, 0.0f,0.0f));
	textureArray->push_back(osg::Vec2(1.0f, 0.0f));
	textureArray->push_back(osg::Vec2(1.0f, 1.0f));
	textureArray->push_back(osg::Vec2(0.0f, 1.0f));
	textureArray->push_back(osg::Vec2(0.0f, 0.0f));
	vertexArray->push_back(osg::Vec3(1.0f, -1.0f,  -1.0f));
	vertexArray->push_back(osg::Vec3(1.0f, 1.0f,  -1.0f));
	vertexArray->push_back(osg::Vec3(1.0f,  1.0f,  1.0f));
	vertexArray->push_back(osg::Vec3(1.0f,  -1.0f,  1.0f));

	for (int i = 0; i < 4; ++i)
		normalArray->push_back(osg::Vec3(-0.5f, 0.0f,0.0f));
	textureArray->push_back(osg::Vec2(0.0f, 0.0f));
	textureArray->push_back(osg::Vec2(1.0f, 0.0f));
	textureArray->push_back(osg::Vec2(1.0f, 1.0f));
	textureArray->push_back(osg::Vec2(0.0f, 1.0f));
	vertexArray->push_back(osg::Vec3(-1.0f, -1.0f,  -1.0f));
	vertexArray->push_back(osg::Vec3(-1.0f, -1.0f,  1.0f));
	vertexArray->push_back(osg::Vec3(-1.0f,  1.0f,  1.0f));
	vertexArray->push_back(osg::Vec3(-1.0f,  1.0f,  -1.0f));

	geometry->setVertexArray(vertexArray);
	geometry->setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX);
	geometry->setTexCoordArray(0, textureArray, osg::Array::BIND_PER_VERTEX);
	geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, vertexArray->size()));
	geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, false);
	geode->addDrawable(geometry);

	mtX->addChild(mtY);
	mtY->addChild(mtZ);
	mtZ->addChild(geode);

	return mtX;
}

osg::Node*	createSphere()
{
	osg::MatrixTransform *mtX = new osg::MatrixTransform;
	mtX->addUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(), osg::X_AXIS, 1.5));
	osg::MatrixTransform *mtY = new osg::MatrixTransform;
	mtY->addUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(), osg::Y_AXIS, 2.5));
	osg::MatrixTransform *mtZ = new osg::MatrixTransform;
	mtZ->addUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(), osg::Z_AXIS, 3.5));

	osg::Geode *sphere = new osg::Geode;
	osg::ShapeDrawable *sphereDrawable = new osg::ShapeDrawable(new osg::Sphere(osg::Vec3(0,0,0), 1.3));
	sphere->addDrawable(sphereDrawable);

	mtX->addChild(mtY);
	mtY->addChild(mtZ);
	mtZ->addChild(sphere);

	return mtX;
}

osg::Node*	createCylinder()
{
	osg::MatrixTransform *mtX = new osg::MatrixTransform;
	mtX->addUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(), osg::X_AXIS, 1.5));
	osg::MatrixTransform *mtY = new osg::MatrixTransform;
	mtY->addUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(), osg::Y_AXIS, 2.5));
	osg::MatrixTransform *mtZ = new osg::MatrixTransform;
	mtZ->addUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(), osg::Z_AXIS, 3.5));

	osg::Geode *cylinder = new osg::Geode;
	osg::ShapeDrawable *cylinderDrawable = new osg::ShapeDrawable(new osg::Cylinder(osg::Vec3(0,0,0), 0.5, 2.0));
	cylinder->addDrawable(cylinderDrawable);

	mtX->addChild(mtY);
	mtY->addChild(mtZ);
	mtZ->addChild(cylinder);


	return mtX;
}

//////////////////////////////////////////////////////////////////////////
/////////////////////////////场景交互//////////////////////////////
//////////////////////////////////////////////////////////////////////////
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;

		switch(ea.getEventType())
		{
		case(osgGA::GUIEventAdapter::KEYDOWN):
			{
				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Space)
				{
					if (!g_SwitchNode)
						return false;

					static int i = 0;
					++i;
					if (i > 2)
						i = 0;
					g_SwitchNode->setSingleChildOn(i);
				}
				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_E)
				{
					static bool flag = true;
					if(flag){
						g_SwitchNode->getOrCreateStateSet()->setTextureMode(0, GL_TEXTURE_GEN_S, osg::StateAttribute::OFF);
						g_SwitchNode->getOrCreateStateSet()->setTextureMode(0, GL_TEXTURE_GEN_T, osg::StateAttribute::OFF);
					}else{
						g_SwitchNode->getOrCreateStateSet()->setTextureMode(0, GL_TEXTURE_GEN_S, osg::StateAttribute::ON);
						g_SwitchNode->getOrCreateStateSet()->setTextureMode(0, GL_TEXTURE_GEN_T, osg::StateAttribute::ON);
					}
					flag = !flag;
				}
			}
		default: break;
		}
		return false;
	}
};
//////////////////////////////////////////////////////////////////////////



//////////////////////////////////////////////////////////////////////////
//////////////////////////osgNeHe框架////////////////////////////
//////////////////////////////////////////////////////////////////////////
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, 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, 0), osg::Vec3d(0, 0, -1), osg::Vec3d(0, 1, 0));

		this->setSceneData( scene );
		this->addEventHandler(new ManipulatorSceneHandler);

		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;
};



osg::Node*	buildScene()
{
	initialize();

	osg::Group *root = new osg::Group;
	
	osg::Switch *switchNode = new osg::Switch;
	g_SwitchNode = switchNode;
	switchNode->addChild(createBox());
	switchNode->addChild(createCylinder());
	switchNode->addChild(createSphere());
	switchNode->setSingleChildOn(0);

	osg::TexGen *texGen = new osg::TexGen;
	texGen->setMode(osg::TexGen::SPHERE_MAP);
	switchNode->getOrCreateStateSet()->setTextureAttributeAndModes(0, texGen);
	switchNode->getOrCreateStateSet()->setTextureAttributeAndModes(0, g_Texture2D);

	osg::MatrixTransform *zoomMT = new osg::MatrixTransform;
	zoomMT->setMatrix(osg::Matrix::translate(0.0f, 0.0f, -10.0f));
	zoomMT->addChild(switchNode);

	root->addChild(createBackGroundGeode(g_Texture2D));
	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();
}

OSG三维渲染引擎编程学习之九十:“第九章:OSG动画” 之 “9.1 路径动画”之“9.1.2 AnimationPathCallback动画路径回调” OSG;三维渲染引擎编程学习;第九章:OSG动画;9.1 路径动画;9.1.2 AnimationPathCallback动画路径回调 阅读详情

相关推荐

OpenSceneGraph三维渲染引擎编程指南.pdf

目 录 第1章 OpenSceneGraph概述 1 1.1 OpenSceneGraph简介 2 1.1.1 什么是OpenSceneGraph 2 1.1.2 OpenSceneGraph的历史和发展 2 1.1.3 OSG中国 3 1.1.4 OSG组成模块 3 1.2 开发的预备知识 5 1.3 OSG的安装及编译 6 1.3.1 OSG的获取及安装 6 1.3.2 编译OSG 7 1.3.3 OSG邮件列表 13 1.4 OSG基础 14 1.4.1 开发环境设置 14 1.4.2 OSG中的HelloWorld工程 14 1.4.3 OSG实用工具——场景浏览器osgViewer 17 1.4.4 OSG实用工具——版本信息查看器 osgVersion 21 1.4.5 OSG实用工具——场景图形压缩归档工具 osgArchive 21 1.4.6 OSG实用工具——数据转换工具 osgConv 23 第2章 OpenSceneGraph数学基础 27 2.1 坐标系统 28 2.2 坐标系变换 29 2.2.1 世界坐标系-物体坐标系变换 30 2.2.2 物体坐标系-世界坐标系变换 30 2.2.3 世界坐标系-屏幕坐标系变换 32 2.3 向量、矩阵及四元数 34 2.3.1 向量 34 2.3.2 矩阵 35 2.3.3 四元数 36 2.3.4 矩阵与四元数之间的转换 37 第3章 场景的组织及渲染 41 3.1 OSG场景树 42 3.1.1 OSG场景树节点 42 3.1.2 OSG中的父节点与子节点 42 3.2 Geode 43 3.2.1 Billboard节点 43 3.2.2 布告板示例 44 3.3 Group 48 3.3.1 位置变换节点 48 3.3.2 位置变换节点示例 49 3.3.3 矩阵变换节点 50 3.3.4 矩阵变换节点示例 51 3.3.5 自动对齐节点 53 3.3.6 自动对齐节点示例 53 3.3.7 开关节点 56 3.3.8 开关节点示例 57 3.3.9 细节层次节点 58 3.3.10 细节层次节点示例 59 3.3.11 分页细节层次节点 61 3.3.12 分页细节层次节点示例 61 3.3.13 替代节点 63 3.3.14 替代节点示例 64 3.3.15 遮挡裁剪节点 66 3.3.16 遮挡裁剪节点示例 67 3.3.17 坐标系节点 70 3.3.18 坐标系节点示例 71 3.4 场景中节点的拷贝—— osg::CopyOp类 73 3.4.1 自定义场景拷贝示例(一) 73 3.4.2 自定义场景拷贝示例(二) 79 第4章 OSG中几何体的绘制 83 4.1 场景基本绘图类 84 4.2 基本几何体的绘制 86 4.2.1 几何体类 86 4.2.2 基本几何体绘制示例 87 4.2.3 索引绑定几何体绘制示例 90 4.3 使用OSG中预定义的几何体 93 4.3.1 osg::Shape类 93 4.3.2 osg::ShapeDrawable类 94 4.3.3 网格化类 94 4.3.4 预定义几何体示例 95 4.4 多边形分格化 97 4.5 几何体操作 101 4.5.1 简化几何体 102 4.5.2 简化几何体示例 102 4.5.3 Delaunay三角网绘制 104 4.5.4 Delaunay三角网绘制示例 105 4.5.5 三角带绘制 107 4.5.6 三角带绘制示例 108 4.5.7 生成顶点法向量 111 4.5.8 生成顶点法向量示例 112 第5章 渲染状态、纹理与光照 115 5.1 渲染状态 116 5.1.1 osg::StateSet类 116 5.1.2 渲染属性和渲染模式 117 5.1.3 状态继承 118 5.1.4 渲染状态示例 119 5.2 纹理映射 121 5.2.1 二维纹理映射 123 5.2.2 二维纹理映射示例 126 5.2.3 多重纹理映射 129 5.2.4 多重纹理映射示例 129 5.2.5 Mipmap纹理映射 131 5.2.6 Mipmap纹理映射示例 132 5.2.7 TextureRectangle纹理映射 138 5.2.8 TextureRectangle纹理映射示例 139 5.2.9 自动生成纹理坐标 142 5.2.10 自动生成纹理坐标示例 142 5.2.11 计算纹理坐标 144 5.2.12 计算纹理坐标示例 145 5.2.13 立方图纹理 149 5.2.14 立方图纹理示例 150 5.2.15 渲染到纹理 155 5.2.16 渲染到纹理示例 155 5.2.17 一维纹理 162 5.2.18 一维纹理示例 162 5.2.19 三维纹理映射 164 5.2.20 三维纹理映射示例 166 5.3 光照 171 5.3.1 osg::Light类 171 5.3.2 osg::LightSource类 172 5.3.3 场景中使用光源 173 5.3.4 简单光源示例 174 5.3.5 聚光灯示例 176 5.4 材质 182 5.4.1 材质类 182 5.4.2 材质类示例 184 第6章 文件的读写 187 6.1 OSG支持的文件格式 188 6.1.1 三维模型文件格式 188 6.1.2 图片及视频文件格式 189 6.1.3 打包及网络传输格式 189 6.1.4 字体文件格式 190 6.1.5 伪插件文件格式 190 6.1.6 .osg文件和.ive文件 190 6.2 文件读取的流程 191 6.2.1 osgDB库 191 6.2.2 文件的读取与保存 191 6.2.3 文件读写示例 192 6.2.4 文件读写进度 194 6.2.5 文件读取进度示例 195 6.3 插件的工作机制 198 6.3.1 插件的搜索和注册 198 6.3.2 osgArchive读写流程 199 6.3.3 自定义文件插件 200 6.3.4 自定义文件格式读写插件示例 202 6.4 读写中文文件名及中文路径问题 206 6.5 osgEXP导出文件 209 第7章 场景图形的工作机制 213 7.1 内存管理 214 7.1.1 Referenced类 214 7.1.2 ref_ptr模板类 214 7.1.3 智能指针 215 7.2 访问器机制 215 7.2.1 访问器设计模式 215 7.2.2 osg::NodeVisitor类 216 7.2.3 顶点访问器示例 217 7.2.4 纹理访问器示例 220 7.2.5 节点访问器示例 223 7.3 回调机制 227 7.3.1 osg::NodeCallback类 227 7.3.2 节点回调示例 228 7.3.3 事件回调示例 230 7.3.4 文件读取回调示例 232 7.4 数据变量 235 第8章 场景图形管理 237 8.1 视图与相机 238 8.1.1 osg::Camera类 238 8.1.2 裁剪平面示例(一) 243 8.1.3 裁剪平面示例(二) 244 8.1.4 单视图与相机 246 8.1.5 宽屏变形示例 247 8.1.6 单视图多相机渲染示例 249 8.1.7 多视图与相机 252 8.1.8 多视图相机渲染示例 253 8.1.9 多视图多窗口渲染示例 257 8.2 场景交互与场景漫游 259 8.2.1 osgGA库 259 8.2.2 键盘事件消息处理 261 8.2.3 抓图示例 262 8.2.4 场景漫游 265 8.2.5 自定义操作器场景漫游示例 266 8.2.6 路径漫游 275 8.2.7 路径漫游示例 276 8.3 交运算与对象选取 277 8.3.1 交运算 278 8.3.2 显示位置及拾取示例 280 8.3.3 对象选取示例 285 第9章 OSG文字 289 9.1 osgText 290 9.1.1 osgText::Text类 290 9.1.2 osgText::Font类 293 9.1.3 显示汉字示例 294 9.1.4 各种文字效果(边框、阴影及颜色倾斜) 示例 296 9.1.5 HUD显示汉字示例 300 9.1.6 渐变文字 303 9.1.7 渐变文字示例 304 9.2 osgText3D 306 9.2.1 osgText::Text3D类 307 9.2.2 3D汉字显示示例 307 第10章 OSG动画与声音 311 10.1 路径动画 312 10.1.1 osg::AnimationPath类 312 10.1.2 osg::AnimationPathCallback类 314 10.1.3 路径动画控制及显示示例 314 10.1.4 路径的导出示例 318 10.1.5 路径的导入示例 321 10.2 帧动画 323 10.2.1 osg::Sequence类 324 10.2.2 帧动画显示与控制示例 325 10.3 骨骼动画osgCal 329 10.3.1 Cal3D简介及Cal3D导出 329 10.3.2 编译osgCal 330 10.3.3 骨骼动画osgCal示例 331 10.4 三维立体声音osgAL 336 10.4.1 OpenAL简介 336 10.4.2 osgAL编译 337 10.4.3 osgAL声音播放示例 338 10.4.4 osgAL声音动态加载及播放示例 343 第11章 OSG粒子系统与阴影 351 11.1 粒子系统 352 11.1.1 粒子系统的主要模块 352 11.1.2 粒子系统的模拟过程 354 11.1.3 雾效模拟示例 354 11.1.4 雪效模拟示例 357 11.1.5 雨效模拟示例 359 11.1.6 爆炸模拟示例 360 11.1.7 自定义粒子系统示例(一) 362 11.1.8 自定义粒子系统示例(二) 365 11.1.9 粒子系统的读取与保存 369 11.2 OSG阴影 372 11.2.1 osgShadow库 372 11.2.2 阴影示例 374 第12章 osgFX扩展库及osgSim 扩展库 381 12.1 osgFX扩展库 382 12.1.1 异性光照特效 382 12.1.2 异性光照特效示例 383 12.1.3 凹凸贴图特效 384 12.1.4 凹凸贴图特效示例 386 12.1.5 卡通渲染特效 387 12.1.6 卡通渲染特效示例 388 12.1.7 刻线特效 390 12.1.8 刻线特效示例 390 12.1.9 立方图镜面高光特效 392 12.1.10 立方图镜面高光特效示例 393 12.2 osgSim扩展库 394 12.2.1 DOFTransform类 394 12.2.2 osgSim::Impostor类 397 12.2.3 osgSim::ImpostorSprite类 398 12.2.4 osgSim::MultiSwitch类 399 12.2.5 osgSim::OverlayNode类 400 12.2.6 osgSim::VisibilityGroup类 402 第13章 OSG地形与地理信息 403 13.1 VirtualPlanetBuilder 404 13.1.1 VirtualPlanetBuilder简介 404 13.1.2 编译与参数说明 407 13.1.3 使用实例及说明 411 13.2 海量地形生成解决方案 412 13.3 osgGIS 414 13.3.1 osgGIS简介 414 13.3.2 osgGIS编译 414 13.4 osgGIS实用工具 415 13.4.1 osggis_build 415 13.4.2 osggis_mapper 416 13.4.3 osggis_makelayer 416 13.4.4 osggis_simple 418 13.4.5 osggis_viewer 419 13.5 GIS坐标系 419 13.5.1 GIS中坐标系的定义 419 13.5.2 地理坐标系与投影坐标系 420 参考文献 423

OSG 飞机路径动画控制

OSG 飞机路径动画控制 注:摘自《三维渲染引擎编程指南》 本文实现一个飞机,在地图上空盘旋,读者可以根据自己需要进行更改; 代码如下://2017. 8 .29 #include #include #include #include #include #include #include #include #include #include #

MissXy_的博客 4981

华为S5720交换机堆叠配置

S5720交换机,做下堆叠,两条线,交叉互联(即A交换机23口连到B交换机24口,A交换机24口连接B交换机23口)。PS:S5720堆叠可基于电口或者光口做,本次测试基于电口。 SWA配置 # 改名 [Huawei]sysname SWA # 最好先shutdown接口 [SWA]interface GigabitEthernet 0/0/23 [SWA-GigabitEthernet0/0/23]shutdown [SWA-GigabitEthernet0/0/23]interface G.

Markdown 6340

OSG中的示例程序简介

1.example_osganimate一)演示了路径动画的使用(AnimationPath、AnimationPathCallback),路径动画回调可以作用在Camera、CameraView、MatrixTransform、PositionAttitudeTransform等四种类型的节点上。二)演示了osgSim::OverlayNode的使用 2.example_osgani...

weixin_30367169的博客 1372

Unity中URP实现水体效果(泡沫)

在上篇文章中,我们实现了水体的深度。在这篇文章中,我们来使用上篇文章采样后的深度图来实现水中的泡沫效果。

楠溪泽岸的博客 3489

AnyGrasp实战指南:从环境配置到抓取检测与跟踪

本文详细介绍了AnyGrasp从环境配置到抓取检测与跟踪的实战指南。涵盖硬件要求、关键依赖安装避坑、抓取检测与跟踪的实战技巧,以及常见问题解决方案和性能优化建议,帮助开发者快速掌握这一高精度物体抓取检测与跟踪技术。

weixin_33712987的博客 274

OSG AnimationPath

今天闲的看了下AnimationPath源文件,在这里做个笔记吧~       AnimationPath封装了一个随时间变化的转型过程。可用于更新相机位置和模型对象的位置。AnimationPathCallback可以直接连接到变换的节点移动子图周围的景物。简单来说该类封装了一个旋转变化过程的对象,并且通过更新回调和指定节点绑定就可以实现旋转效果了。      该类旋转的矩阵参数:时间,多长

wzh-whu 3799

TESSY创建单元测试或集成测试工程

我们以tessy5.1为例,给大家展示单元测试和集成测试工程的创建过程。

空气中的告白专栏 1686

OpenSceneGraph实现的NeHe OpenGL教程 - 第三十五课

简介 这节课介绍了如何在三维场景中类似播放一段视频的效果。我们都知道视频实际上是由一帧帧的图片来构成的,因此我们将视频中的图片解析成图片格式,并将这些图片赋给几何体表面,让它们在很短的间隔内进行切换就实现了动态的效果。 实现 本课中使用了Windows解析视频格式AVI文件的API,代码中这部分内容都来自NeHe课程中的相应代码,读者可以查阅MSDN了解这些内容:

ALLENJIAO的专栏 752

使用Python爬虫抓取政府网站政务信息:公告、通知的自动化抓取与分析

通过结合Requests、BeautifulSoup、Selenium等技术,你能够高效地抓取政府网站的数据,并通过MySQL存储与Pandas分析这些数据。通过自动化手段抓取这些信息,不仅可以提高数据收集的效率,还能够实时获取最新的政务动态,为政策研究、舆情分析等提供宝贵数据。我们将通过Python的Requests、BeautifulSoup、Selenium等工具,结合最新的技术实现一个完整的爬虫系统,帮助你轻松抓取并分析政务信息。通过爬虫,我们可以自动化地从指定网站上抓取公告、新闻、通知等信息。

2201_76125261的博客 1907

大数据在电力行业的应用案例解析 -【电力技术】(十)电力数字化运营与大数据精准服务体系建设

电力数字化运营,是大数据在电力行业从技术场景走向业务价值的最终落点。它以数据为纽带,将用户、设备、服务、收益、风险全部纳入统一闭环,实现从 “管好电” 向 “运营好能源资产” 升级。在新型电力系统与市场化改革双重驱动下,谁能用好数据、建好闭环、做好运营,谁就能在服务、成本、效益、安全上建立长期优势。

getusushu的博客 69

用OpenSceneGraph实现的NeHe OpenGL教程 - 第三十五课_附录1

简介 第三十五课中

Frank的专栏 1717

手把手教你学Simulink--人形机器人控制场景实例:基于Simulink的多连杆人形机器人拉格朗日动力学建模仿真

matlab编辑% 连杆长度 (m)% 躯干(髋到CoM)% 大腿% 小腿% 质量 (kg)% 转动惯量(绕质心,简化为杆)matlab编辑% 躯干CoM(假设在髋上方L_body/2处)% 左大腿CoM% 左小腿CoM% (右腿类似,略)本文完成了基于Simulink的多连杆人形机器人拉格朗日动力学建模仿真,实现了:✅ 定义7-DOF平面人形结构✅ 使用Symbolic Math Toolbox自动推导动力学方程✅ 生成。

MHD0815的博客 146

OpenSceneGraph实现的NeHe OpenGL教程 - 第三十五课_附录1

简介 在OSG中支持动态纹理,因此不需要我们对视频文件进行大量的解析工作,这些内容都已经在osgPlugins中完成了,在OSG中通过纹理osg::Image的派生类支持这一特性 有了以上内容,在OSG中实现就十分简单了,使用过程类似于我们常见的添加osgTexture2D的过程: [cpp] view plain copy  

ALLENJIAO的专栏 613

BW 技术 InfoObject 初始化,一次把 0LANGU、0CURRENCY、0IOBJNM 这些底座对象讲透

摘要:BW/4HANA和S/4HANA嵌入式分析中最容易被忽视的关键技术InfoObject(如0LANGU、0UNIT等)是系统运行的基础骨架。这些对象虽不直接涉及业务,但若未正确激活会导致多维分析、查询运行等核心功能异常。BW/4HANA中应通过任务清单SAP_BW4_SETUP_SIMPLE或SAP_BW4_SET_OPERATION_SCOPE激活,而非旧事务码RSTCO_ADMIN。S/4HANA嵌入式分析仍需依赖这些底层BW组件,配置需参考SAP Note 2636754。项目实践中需特别关注这

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1385

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

用OpenSceneGraph实现的NeHe OpenGL教程 - 第一课

简介 OpenSceneGraph是一个开放源码,跨平台的图形开发包,它为诸如飞行器仿真,游戏,虚拟现实,科学计算可视化这样的高性能图形应用程序开发而设计。它基于场景图的概念,它提供一个在OpenGL之上的面向对象的框架,从而能把开发者从实现和优化底层图形的调用中解脱出来,并且它为图形应用程序的快速开发提供很多附加的实用工具。 NeHe教程是目前针对初学者来说最

Frank的专栏 1万+

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

用OpenSceneGraph实现的NeHe OpenGL教程 - 第二十四课

简介 NeHe教程在这节课中向我们介绍了如何读取显卡支持的OpenGL的扩展,如何使用Targa(TGA)图像文件作为纹理,以及如何利用OpenGL的剪裁区域来滚动屏幕文字。 在OpenInventor中没有相关的函数来获取OpenGL的扩展信息,我们只能通过直接调用OpenGL来获得这些信息。OpenInventor的纹理节点自动支持使用Targa(TGA)图像文件作为纹理,所以我们

Frank的专栏 2317

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

用OpenSceneGraph实现的NeHe OpenGL教程 - 第十一课

简介 这节课我们将创建一个以正弦波方式飘动的旗帜。本课所用到的知识在前面的课程中都有讲解,并没有什么新的内容 实现 首先创建我们的场景,关于旗帜的顶点坐标在NeHe教程中已经有非常详细的介绍,本文就不在赘述了。 float points[45][45][3]; for(int x=0; x<45; x++) { for(int y=0; y<45; y++

Frank的专栏 1579

用OpenSceneGraph实现的NeHe OpenGL教程 - 第三十八课

简介 这节课NeHe教我们怎样把图片

Frank的专栏 2303

OpenSceneGraph实现的NeHe OpenGL教程 - 第二十四课

简介 NeHe教程在这节课中向我们介绍了如何读取显卡支持的OpenGL的扩展,如何使用Targa(TGA)图像文件作为纹理,以及如何利用OpenGL的剪裁区域来滚动屏幕文字。osg支持tga格式的文件读取,因此不需要像NeHe课程那样解析TGA格式,有兴趣的读者可以查看osgPlugins库中的ReaderWriterTGA.cpp文件了解osg是如何读取TGA图片格式的。

ALLENJIAO的专栏 1110

用OpenSceneGraph实现的NeHe OpenGL教程 - 第二十五课

简介 NeHe教程在这节课中向我们介绍了如何从文件加载3D模型,并且平滑的从一个模型变换为另一个模型。两个模型之间平滑过渡的算法其实是很简单的,首先我们要保证两个模型要有相同数目的顶点,然后我们分别计算每个点从一个模型移动到另外一个模型时,中间的位置数据。我们将这个过程显示出来就会产生模型平滑过渡的效果。

Frank的专栏 1747

OpenSceneGraph实现的NeHe OpenGL教程 - 第十一课

简介 这节课我们将创建一个以正弦波方式飘动的旗帜。本课所用到的知识在前面的课程中都有讲解,并没有什么新的内容 实现 首先创建我们的场景,关于旗帜的顶点坐标在NeHe教程中已经有非常详细的介绍,本文就不在赘述了。 [cpp] view plain copy   float points[45][45][3];

ALLENJIAO的专栏 717

用OpenSceneGraph实现的NeHe OpenGL教程 - 第十二课

简介 本课NeHe教程主要是使用了OpenGL中的显示列表。OpenGL的显示列表是加速OpenGL性能的一种重要的手段。OpenGl的几何体绘制方法主要包括立即模式、顶点数组、显示列表和VBO。在OSG中显示列表是大部分OSG程序所使用的默认绘制优化行为,因此不需要我们去像OpenGL那样进行设置。 实现 首先创建整个场景,本课中我使用了OSG中预定义的几种基本形体,(os

Frank的专栏 7883

OpenSceneGraph实现的NeHe OpenGL教程 - 第一课

目录(?)[-] 简介创建一个osg窗口 简介 OpenSceneGraph是一个开放源码,跨平台的图形开发包,它为诸如飞行器仿真,游戏,虚拟现实,科学计算可视化这样的高性能图形应用程序开发而设计。它基于场景图的概念,它提供一个在OpenGL之上的面向对象的框架,从而能把开发者从实现和优化底层图形的调用中解脱出来,并且它为图形应用程序的快速

ALLENJIAO的专栏 1201

OpenSceneGraph实现的NeHe OpenGL教程 - 第三十课

简介 这节课NeHe课程实现了在场景中进行碰撞检测以及简单的物理作用(碰撞之后的反弹),碰撞检测三维图形学中一个比较高级的主题,本课只涉及了简单的规则几何体之间的碰撞检测,更复杂的实例需要读者去更深入的学习。 实现 本课中碰撞检测的算法包括线与平面的求交检测、线与圆柱体的求交、以及球体之间的碰撞检测,这些碰撞检测的原理可以参考NeHe中的介绍。 具体实现在如下几

ALLENJIAO的专栏 722

OpenSceneGraph实现的NeHe OpenGL教程 - 第三十二课

简介 本节课NeHe课程主要想我们介绍了OpenGL中的拾取(Selection)的方式来和场景中的物体进行交换,在OSG中并没有封装OpenGL中的拾取方式,而是用了更高效的一种方式来实现与场景中的模型进行求交的操作,对于OSG这样处理的原因可以参考OSG中的这个MailList: New to OSG, some questions (selection buffer, para

ALLENJIAO的专栏 541
上一篇: 用OpenSceneGraph实现的NeHe OpenGL教程 - 第三十四课
下一篇: 用OpenSceneGraph实现的NeHe OpenGL教程 - 第三十五课_附录1
csxiaoshui
博客等级 码龄16年 873粉丝 158原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值