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

  • 简介

这节课我们将使用osg实现一个简单的粒子系统。从NeHe的教程中可以看到,制作一个简单的粒子系统并没有想象中的那么困难,不过粒子系统涉及的细节计算还是有些繁琐的。粒子系统主要用来模拟三维场景中的雨雪、爆炸、火焰、喷泉等效果。主要是对大量的可绘制几何体(如简单的三角形、四边形、三角形条带等)进行它们位置、方向、显示时间(生命)的一些设置。

  • 实现

首先我们按照NeHe教程中,定义了一个粒子的结构体,代表场景中产生的一个粒子的参数:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. struct Particle  
  2. {  
  3.     bool active;  //是否是激活状态  
  4.     float life;      //存活时间  
  5.     float fade;    //消失的速度  
  6.     float r;          
  7.     float g;  
  8.     float b;  
  9.     float x;  
  10.     float y;  
  11.     float z;  
  12.     float xi;      
  13.     float yi;      
  14.     float zi;  
  15.     float xg;  
  16.     float yg;  
  17.     float zg;  
  18. };  
同样在初始化的时候给所有的粒子(1000个)一些初始值

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. for (int i=0;  i<MaxParticles; i++)  
  2. {  
  3.     particles[i].active=true;  
  4.     particles[i].life=1.0f;  
  5.     particles[i].fade=float(rand()%100)/1000.0f+0.003f;   
  6.     particles[i].r=colors[int(i*(12.0/MaxParticles))][0];  
  7.     particles[i].g=colors[int(i*(12.0/MaxParticles))][1];  
  8.     particles[i].b=colors[int(i*(12.0/MaxParticles))][2];  
  9.     particles[i].xi=float((rand()%50)-26.0f)*10.0f;  
  10.     particles[i].yi=float((rand()%50)-25.0f)*10.0f;  
  11.     particles[i].zi=float((rand()%50)-25.0f)*10.0f;  
  12.     particles[i].xg=0.0f;     
  13.     particles[i].yg=-0.8f;  
  14.     particles[i].zg=0.0f;     
  15. }  

粒子系统中的每一个粒子实际上就是一个简单的可绘制几何体(Geometry),设置它的各项参数如下:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. osg::Geometry *particle = new osg::Geometry();  
  2.   
  3. float x=particles[i].x;  
  4. float y=particles[i].y;  
  5. float z=particles[i].z+zoom;  
  6. //设置顶点  
  7. osg::Vec3Array *vertexArray = new osg::Vec3Array;  
  8. vertexArray->push_back(osg::Vec3(x+0.5f,y+0.5f,z));  
  9. vertexArray->push_back(osg::Vec3(x-0.5f,y+0.5f,z));  
  10. vertexArray->push_back(osg::Vec3(x+0.5f,y-0.5f,z));  
  11. vertexArray->push_back(osg::Vec3(x-0.5f,y-0.5f,z));  
  12. //设置纹理坐标  
  13. osg::Vec2Array *texArray = new osg::Vec2Array;  
  14. texArray->push_back(osg::Vec2(1,1));  
  15. texArray->push_back(osg::Vec2(0,1));  
  16. texArray->push_back(osg::Vec2(1,0));  
  17. texArray->push_back(osg::Vec2(0,0));  
  18. //设置颜色  
  19. osg::Vec4Array *colorArray = new osg::Vec4Array;  
  20. colorArray->push_back(osg::Vec4(particles[i].r,particles[i].g,particles[i].b,particles[i].life));  
  21. colorArray->setBinding(osg::Array::BIND_OVERALL);  
  22. //设置纹理  
  23. osg::Texture2D *texture = new osg::Texture2D;  
  24. texture->setImage(osgDB::readImageFile("Data/Particle.bmp"));  
  25. texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);  
  26. texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);  
  27.   
  28. particle->setVertexArray(vertexArray);  
  29. particle->setTexCoordArray(0, texArray);  
  30. particle->setColorArray(colorArray);  
  31. particle->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_STRIP, 0, 4));  
  32. particle->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture);  
  33. osg::BlendFunc *blendFunc = new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE);  
  34. particle->getOrCreateStateSet()->setAttributeAndModes(blendFunc,osg::StateAttribute::ON);  
  35. particle->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);  
  36. particle->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);  
  37. particle->setUseDisplayList(false);  
  38. particle->setUpdateCallback(new ParticleDrawableCallback(i));  
注意我们需要设置混合的模式和禁用深度测试,这样纹理图片的背景黑色才能被清除掉

在每一帧中需要更新粒子的位置和颜色等参数,我们设置一个Geode的更新回调用来完成对所有粒子参数的更新

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. class ParticleUpdateCallback : public osg::NodeCallback  
另外在绘制粒子的时候需要用到更新的新数据,我们在粒子对象的更新回调中完成它坐标位置、颜色的更新

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. class ParticleDrawableCallback : public osg::Drawable::UpdateCallback  
  2. {  
  3. public:  
  4.   
  5.     ParticleDrawableCallback(int index) : _index(index){ }  
每一个粒子的序号绑定到_index之上

最后在ParticleEventHandler之中完成粒子系统中一些参数的交互,这部分和NeHe教程中是一样的:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. class ParticleEventHandler : public osgGA::GUIEventHandler  
  2. {  
代码我只是贴出了一部分,详细的代码参考后面的完整代码部分。

编译运行程序:

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

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include "../osgNeHe.h"  
  2.   
  3. #include <QtCore/QTimer>  
  4. #include <QtGui/QApplication>  
  5. #include <QtGui/QVBoxLayout>  
  6.   
  7. #include <osgViewer/Viewer>  
  8. #include <osgDB/ReadFile>  
  9. #include <osgQt/GraphicsWindowQt>  
  10.   
  11. #include <osg/MatrixTransform>  
  12.   
  13. #include <osg/Texture2D>  
  14. #include <osg/BlendFunc>  
  15. #include <osg/PrimitiveSet>  
  16.   
  17. //  
  18.   
  19. const int MaxParticles = 1000;  
  20. float slowdown = 2.0f;  
  21. float xspeed;  
  22. float yspeed;  
  23. float zoom = -40.0f;  
  24. GLuint delay;  
  25. int col;  
  26.   
  27. struct Particle  
  28. {  
  29.     bool active;  //是否是激活状态  
  30.     float life;      //存活时间  
  31.     float fade;    //消失的速度  
  32.     float r;          
  33.     float g;  
  34.     float b;  
  35.     float x;  
  36.     float y;  
  37.     float z;  
  38.     float xi;      
  39.     float yi;      
  40.     float zi;  
  41.     float xg;  
  42.     float yg;  
  43.     float zg;  
  44. };  
  45.   
  46. Particle particles[MaxParticles];  
  47.   
  48.   
  49. static GLfloat colors[12][3] =  
  50. {  
  51.     {1.0f,0.5f,0.5f},{1.0f,0.75f,0.5f},{1.0f,1.0f,0.5f},{0.75f,1.0f,0.5f},  
  52.     {0.5f,1.0f,0.5f},{0.5f,1.0f,0.75f},{0.5f,1.0f,1.0f},{0.5f,0.75f,1.0f},  
  53.     {0.5f,0.5f,1.0f},{0.75f,0.5f,1.0f},{1.0f,0.5f,1.0f},{1.0f,0.5f,0.75f}  
  54. };  
  55.   
  56. //  
  57. //Particle Manipulator  
  58. class ParticleEventHandler : public osgGA::GUIEventHandler  
  59. {  
  60.   
  61. public:  
  62.     ParticleEventHandler(){}  
  63.   
  64.     virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)  
  65.     {  
  66.         osgViewer::Viewer *viewer = dynamic_cast<osgViewer::Viewer*>(&aa);  
  67.         if (!viewer)  
  68.             return false;  
  69.         if (!viewer->getSceneData())  
  70.             return false;  
  71.         if (ea.getHandled())   
  72.             return false;  
  73.   
  74.         osg::Group *root = viewer->getSceneData()->asGroup();  
  75.   
  76.         switch(ea.getEventType())  
  77.         {  
  78.   
  79.         case(osgGA::GUIEventAdapter::KEYDOWN):  
  80.             {  
  81.                 if (ea.getKey() == osgGA::GUIEventAdapter::KEY_8)  
  82.                 {  
  83.                     for(int i = 0; i < MaxParticles; ++i)  
  84.                         if ((particles[i].yg<1.5f))   
  85.                             particles[i].yg+=0.01f;  
  86.                 }  
  87.   
  88.                 if (ea.getKey() == osgGA::GUIEventAdapter::KEY_2)  
  89.                 {  
  90.                     for(int i = 0; i < MaxParticles; ++i)  
  91.                         if ((particles[i].yg>-1.5f))   
  92.                             particles[i].yg-=0.01f;  
  93.                 }  
  94.   
  95.                 if (ea.getKey() == osgGA::GUIEventAdapter::KEY_6)  
  96.                 {  
  97.                     for(int i = 0; i < MaxParticles; ++i)  
  98.                         if (particles[i].xg<1.5f)   
  99.                             particles[i].xg+=0.01f;  
  100.                 }  
  101.   
  102.                 if (ea.getKey()== osgGA::GUIEventAdapter::KEY_4)  
  103.                 {  
  104.                     for(int i = 0; i < MaxParticles; ++i)  
  105.                         if (particles[i].xg>-1.5f)   
  106.                             particles[i].xg-=0.01f;  
  107.                 }  
  108.   
  109.                 if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Tab)  
  110.                 {     
  111.                     for(int i = 0; i < MaxParticles; ++i)  
  112.                     {  
  113.                         particles[i].x=0.0f;  
  114.                         particles[i].y=0.0f;  
  115.                         particles[i].z=0.0f;  
  116.                         particles[i].xi=float((rand()%50)-26.0f)*10.0f;  
  117.                         particles[i].yi=float((rand()%50)-25.0f)*10.0f;  
  118.                         particles[i].zi=float((rand()%50)-25.0f)*10.0f;  
  119.                     }  
  120.                 }  
  121.   
  122.                 if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Page_Up)  
  123.                 {  
  124.                     zoom += 0.1;  
  125.                 }  
  126.   
  127.                 if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Page_Down)  
  128.                 {     
  129.                     zoom -= 0.1;  
  130.                 }  
  131.   
  132.                 if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Plus)  
  133.                 {  
  134.                     if (slowdown>1.0f)   
  135.                         slowdown-=0.01f;  
  136.                 }  
  137.   
  138.                 if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Minus)  
  139.                 {  
  140.                     if (slowdown<4.0f)   
  141.                         slowdown+=0.01f;  
  142.                 }  
  143.   
  144.                 if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Up)  
  145.                 {  
  146.                     if ((yspeed<200))   
  147.                         yspeed+=1.0f;             
  148.                 }  
  149.   
  150.                 if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Down)  
  151.                 {  
  152.                     if ((yspeed>-200))   
  153.                         yspeed-=1.0f;  
  154.                 }  
  155.   
  156.                 if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Left)  
  157.                 {  
  158.                     if ((xspeed>-200))   
  159.                         xspeed-=1.0f;  
  160.                 }  
  161.   
  162.                 if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Right)  
  163.                 {  
  164.                     if ((xspeed<200))   
  165.                         xspeed+=1.0f;  
  166.                 }  
  167.             }  
  168.         defaultbreak;  
  169.         }  
  170.         return false;  
  171.     }  
  172. };  
  173.   
  174.   
  175. class ViewerWidget : public QWidget, public osgViewer::Viewer  
  176. {  
  177. public:  
  178.     ViewerWidget(osg::Node *scene = NULL)  
  179.     {  
  180.         QWidget* renderWidget = getRenderWidget( createGraphicsWindow(0,0,100,100), scene);  
  181.   
  182.         QVBoxLayout* layout = new QVBoxLayout;  
  183.         layout->addWidget(renderWidget);  
  184.         layout->setContentsMargins(0, 0, 0, 1);  
  185.         setLayout( layout );  
  186.   
  187.         connect( &_timer, SIGNAL(timeout()), this, SLOT(update()) );  
  188.         _timer.start( 10 );  
  189.     }  
  190.   
  191.     QWidget* getRenderWidget( osgQt::GraphicsWindowQt* gw, osg::Node* scene )  
  192.     {  
  193.         osg::Camera* camera = this->getCamera();  
  194.         camera->setGraphicsContext( gw );  
  195.   
  196.         const osg::GraphicsContext::Traits* traits = gw->getTraits();  
  197.   
  198.         camera->setClearColor( osg::Vec4(0.0, 0.0, 0.0, 0.0) );  
  199.         camera->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) );  
  200.         camera->setProjectionMatrixAsPerspective(45.0f, static_cast<double>(traits->width)/static_cast<double>(traits->height), 0.1f, 100.0f );  
  201.         camera->setViewMatrixAsLookAt(osg::Vec3d(0, 0, 1), osg::Vec3d(0, 0, 0), osg::Vec3d(0, 1, 0));  
  202.   
  203.         this->setSceneData( scene );  
  204.         addEventHandler(new ParticleEventHandler);  
  205.   
  206.         return gw->getGLWidget();  
  207.     }  
  208.   
  209.     osgQt::GraphicsWindowQt* createGraphicsWindow( int x, int y, int w, int h, const std::string& name=""bool windowDecoration=false )  
  210.     {  
  211.         osg::DisplaySettings* ds = osg::DisplaySettings::instance().get();  
  212.         osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;  
  213.         traits->windowName = name;  
  214.         traits->windowDecoration = windowDecoration;  
  215.         traits->x = x;  
  216.         traits->y = y;  
  217.         traits->width = w;  
  218.         traits->height = h;  
  219.         traits->doubleBuffer = true;  
  220.         traits->alpha = ds->getMinimumNumAlphaBits();  
  221.         traits->stencil = ds->getMinimumNumStencilBits();  
  222.         traits->sampleBuffers = ds->getMultiSamples();  
  223.         traits->samples = ds->getNumMultiSamples();  
  224.   
  225.         return new osgQt::GraphicsWindowQt(traits.get());  
  226.     }  
  227.   
  228.     virtual void paintEvent( QPaintEvent* event )  
  229.     {   
  230.         frame();   
  231.     }  
  232.   
  233. protected:  
  234.   
  235.     QTimer _timer;  
  236. };  
  237.   
  238.   
  239. //  
  240. //Particle Geode's UpdateCallback  
  241. class ParticleUpdateCallback : public osg::NodeCallback  
  242. {  
  243. public:  
  244.     virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)  
  245.     {  
  246.         for(int i = 0; i < MaxParticles; ++i)  
  247.         {  
  248.             particles[i].x+=particles[i].xi/(slowdown*1000);  
  249.             particles[i].y+=particles[i].yi/(slowdown*1000);  
  250.             particles[i].z+=particles[i].zi/(slowdown*1000);  
  251.   
  252.             particles[i].xi+=particles[i].xg;  
  253.             particles[i].yi+=particles[i].yg;  
  254.             particles[i].zi+=particles[i].zg;  
  255.             particles[i].life-=particles[i].fade;  
  256.   
  257.             if (particles[i].life<0.0f)  
  258.             {  
  259.                 if (col > 11)  
  260.                 {  
  261.                     col = 0;  
  262.                 }  
  263.                 ++col;  
  264.                 particles[i].life=1.0f;  
  265.                 particles[i].fade=float(rand()%100)/1000.0f+0.003f;  
  266.                 particles[i].x=0.0f;  
  267.                 particles[i].y=0.0f;  
  268.                 particles[i].z=0.0f;  
  269.                 particles[i].xi=xspeed+float((rand()%60)-32.0f);  
  270.                 particles[i].yi=yspeed+float((rand()%60)-30.0f);  
  271.                 particles[i].zi=float((rand()%60)-30.0f);  
  272.                 particles[i].r=colors[col][0];  
  273.                 particles[i].g=colors[col][1];  
  274.                 particles[i].b=colors[col][2];  
  275.             }  
  276.         }  
  277.   
  278.     }  
  279. };  
  280.   
  281.   
  282. //  
  283. //ParticleDrawableCallback  
  284. class ParticleDrawableCallback : public osg::Drawable::UpdateCallback  
  285. {  
  286. public:  
  287.   
  288.     ParticleDrawableCallback(int index) : _index(index){ }  
  289.   
  290.     virtual void update(osg::NodeVisitor*, osg::Drawable* drawable)   
  291.     {  
  292.         osg::Geometry *geometry = dynamic_cast<osg::Geometry*>(drawable);  
  293.         if (!geometry)  
  294.         {  
  295.             return;  
  296.         }  
  297.         osg::Vec4Array *colorArray = dynamic_cast<osg::Vec4Array*>(geometry->getColorArray());  
  298.         if (colorArray)  
  299.         {  
  300.             colorArray->clear();  
  301.             colorArray->push_back(osg::Vec4(particles[_index].r,particles[_index].g,particles[_index].b,particles[_index].life));  
  302.             colorArray->dirty();  
  303.         }  
  304.         osg::Vec3Array *vertexArray = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray());  
  305.         if (vertexArray)  
  306.         {  
  307.             float x=particles[_index].x;  
  308.             float y=particles[_index].y;  
  309.             float z=particles[_index].z+zoom;  
  310.   
  311.             vertexArray->clear();  
  312.             vertexArray->push_back(osg::Vec3(x+0.5f,y+0.5f,z));  
  313.             vertexArray->push_back(osg::Vec3(x-0.5f,y+0.5f,z));  
  314.             vertexArray->push_back(osg::Vec3(x+0.5f,y-0.5f,z));  
  315.             vertexArray->push_back(osg::Vec3(x-0.5f,y-0.5f,z));  
  316.             vertexArray->dirty();  
  317.         }  
  318.     }  
  319.   
  320.     int _index;  
  321. };  
  322.   
  323. ///  
  324. ///  
  325. osg::Node*  buildScene()  
  326. {  
  327.     for (int i=0;  i<MaxParticles; i++)  
  328.     {  
  329.         particles[i].active=true;  
  330.         particles[i].life=1.0f;  
  331.         particles[i].fade=float(rand()%100)/1000.0f+0.003f;   
  332.         particles[i].r=colors[int(i*(12.0/MaxParticles))][0];  
  333.         particles[i].g=colors[int(i*(12.0/MaxParticles))][1];  
  334.         particles[i].b=colors[int(i*(12.0/MaxParticles))][2];  
  335.         particles[i].xi=float((rand()%50)-26.0f)*10.0f;  
  336.         particles[i].yi=float((rand()%50)-25.0f)*10.0f;  
  337.         particles[i].zi=float((rand()%50)-25.0f)*10.0f;  
  338.         particles[i].xg=0.0f;     
  339.         particles[i].yg=-0.8f;  
  340.         particles[i].zg=0.0f;     
  341.     }  
  342.   
  343.     osg::Geode *particleGeode = new osg::Geode;  
  344.   
  345.     for (int i = 0; i < MaxParticles; ++i)  
  346.     {  
  347.         osg::Geometry *particle = new osg::Geometry();  
  348.   
  349.         float x=particles[i].x;  
  350.         float y=particles[i].y;  
  351.         float z=particles[i].z+zoom;  
  352.         //设置顶点  
  353.         osg::Vec3Array *vertexArray = new osg::Vec3Array;  
  354.         vertexArray->push_back(osg::Vec3(x+0.5f,y+0.5f,z));  
  355.         vertexArray->push_back(osg::Vec3(x-0.5f,y+0.5f,z));  
  356.         vertexArray->push_back(osg::Vec3(x+0.5f,y-0.5f,z));  
  357.         vertexArray->push_back(osg::Vec3(x-0.5f,y-0.5f,z));  
  358.         //设置纹理坐标  
  359.         osg::Vec2Array *texArray = new osg::Vec2Array;  
  360.         texArray->push_back(osg::Vec2(1,1));  
  361.         texArray->push_back(osg::Vec2(0,1));  
  362.         texArray->push_back(osg::Vec2(1,0));  
  363.         texArray->push_back(osg::Vec2(0,0));  
  364.         //设置颜色  
  365.         osg::Vec4Array *colorArray = new osg::Vec4Array;  
  366.         colorArray->push_back(osg::Vec4(particles[i].r,particles[i].g,particles[i].b,particles[i].life));  
  367.         colorArray->setBinding(osg::Array::BIND_OVERALL);  
  368.         //设置纹理  
  369.         osg::Texture2D *texture = new osg::Texture2D;  
  370.         texture->setImage(osgDB::readImageFile("Data/Particle.bmp"));  
  371.         texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);  
  372.         texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);  
  373.   
  374.         particle->setVertexArray(vertexArray);  
  375.         particle->setTexCoordArray(0, texArray);  
  376.         particle->setColorArray(colorArray);  
  377.         particle->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_STRIP, 0, 4));  
  378.         particle->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture);  
  379.         osg::BlendFunc *blendFunc = new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE);  
  380.         particle->getOrCreateStateSet()->setAttributeAndModes(blendFunc,osg::StateAttribute::ON);  
  381.         particle->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);  
  382.         particle->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);  
  383.         particle->setUseDisplayList(false);  
  384.         particle->setUpdateCallback(new ParticleDrawableCallback(i));  
  385.   
  386.         particleGeode->addDrawable(particle);  
  387.     }  
  388.     particleGeode->addUpdateCallback(new ParticleUpdateCallback);  
  389.   
  390.     osg::Group *root = new osg::Group;  
  391.     root->addChild(particleGeode);  
  392.   
  393.     return root;  
  394. }  
  395.   
  396. int main( int argc, char** argv )  
  397. {  
  398.     QApplication app(argc, argv);  
  399.     ViewerWidget* viewWidget = new ViewerWidget(buildScene());  
  400.     viewWidget->setGeometry( 100, 100, 640, 480 );  
  401.     viewWidget->show();  
  402.     return app.exec();  
  403. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值