5、爆炸粒子系统
#include "glparticle.h"
const vec3 PARTICLE_VELOCITY (0.0f, 2.0f, 0.0f);
const vec3 VELOCITY_VARIATION (4.0f, 4.0f, 4.0f);
const vec3 PARTICLE_ACCELERATION (0.0f, -5.0f, 0.0f);
const float PARTICLE_SIZE = 3.0f;//5.0f;
const float SIZE_VARIATION = 0.3f;//2.0f;
#define FRAND (((float)rand()-(float)rand())/RAND_MAX)
class glExplosion : public glParticleSystem
{
public:
glExplosion(int maxParticles, vec3 origin, float spread, GLuint texture);
void Update(float elapsedTime);
void Render();
bool IsDead() { return m_numParticles == 0; }
void InitializeParticle(int index);
float m_spread;
GLuint m_texture;
};
glExplosion::glExplosion(int numParticles, vec3 origin, float spread, GLuint texture)
: m_texture(texture), m_spread(spread), glParticleSystem(numParticles, origin)
{
srand(timeGetTime());
glParticleSystem::InitializeSystem();
Emit(numParticles);
}
void glExplosion::InitializeParticle(int index)//爆炸初始化
{
m_particleList[index].m_pos.x = m_origin.x + FRAND * m_spread;
m_particleList[index].m_pos.y = m_origin.y + FRAND * m_spread;
m_particleList[index].m_pos.z = m_origin.z + FRAND * m_spread;
m_particleList[index].m_size = PARTICLE_SIZE + FRAND * SIZE_VARIATION;
m_particleList[index].m_velocity.x = PARTICLE_VELOCITY.x + FRAND * VELOCITY_VARIATION.x;
m_particleList[index].m_velocity.y = PARTICLE_VELOCITY.y + FRAND * VELOCITY_VARIATION.y;
m_particleList[index].m_velocity.z = PARTICLE_VELOCITY.z + FRAND * VELOCITY_VARIATION.z;
m_particleList[index].m_acceleration = PARTICLE_ACCELERATION;
m_particleList[index].m_color[0] = 1.0;
m_particleList[index].m_color[1] = 0.5f + FRAND * 0.5f;
m_particleList[index].m_color[2] = 0.01f;
m_particleList[index].m_color[3] = 1.0;
m_particleList[index].m_energy = 1.5f + FRAND / 2.0f;
m_particleList[index].m_colorDelta[0] = 0.0;
m_particleList[index].m_colorDelta[1] = -(m_particleList[index].m_color[1] / 2.0f) / m_particleList[index].m_energy;
m_particleList[index].m_colorDelta[2] = 0.0;
m_particleList[index].m_colorDelta[3] = -1.0f / m_particleList[index].m_energy;
m_particleList[index].m_sizeDelta = -m_particleList[index].m_size / m_particleList[index].m_energy;
}
void glExplosion::Update(float elapsedTime)//清除爆炸动画
{
for (int i = 0; i < m_numParticles;)
{
//更新位置s=vt;
m_particleList[i].m_pos = m_particleList[i].m_pos + m_particleList[i].m_velocity * elapsedTime;
//更新速度v=at;
m_particleList[i].m_velocity = m_particleList[i].m_velocity + m_particleList[i].m_acceleration * elapsedTime;
//能量随着时间流逝
m_particleList[i].m_energy -= elapsedTime;
//size以及颜色随着时间变换
m_particleList[i].m_size += m_particleList[i].m_sizeDelta * elapsedTime;
m_particleList[i].m_color[3] += m_particleList[i].m_colorDelta[3] * elapsedTime;
m_particleList[i].m_color[1] += m_particleList[i].m_colorDelta[1] * elapsedTime;
//如果当前粒子的能量<=0,说明没有了
//则将最后一个粒子放入当前位置
if (m_particleList[i].m_energy <= 0.0)
{
m_particleList[i] = m_particleList[--m_numParticles];
}
else
{
++i;
}
}
}
void glExplosion::Render()//爆炸动画
{
glPushAttrib(GL_CURRENT_BIT);//保存现有颜色属性 glPopAttrib();//恢复前一属性
float viewMatrix[16];
glGetFloatv(GL_MODELVIEW_MATRIX, viewMatrix);
vec3 right(viewMatrix[0], viewMatrix[4], viewMatrix[8]);
vec3 up(viewMatrix[1], viewMatrix[5], viewMatrix[9]);
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, m_texture);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glBegin(GL_QUADS);
for (int i = 0; i < m_numParticles; ++i)
{
GLfloat size = m_particleList[i].m_size / 3;
vec3 pos = m_particleList[i].m_pos;
glColor4fv(m_particleList[i].m_color);
glTexCoord2f(0.0, 0.0); glVertex3fv((pos + (right + up) * -size).v);
glTexCoord2f(1.0, 0.0); glVertex3fv((pos + (right - up) * size).v);
glTexCoord2f(1.0, 1.0); glVertex3fv((pos + (right + up) * size).v);
glTexCoord2f(0.0, 1.0); glVertex3fv((pos + (up - right) * size).v);
}
glEnd();
//glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
glPopAttrib();//恢复前一属性
}
6、由于整个系统大部分都是圆形以及像土星一样带光圈的图形,因此在opengl中,二次曲面对象最适合这种显示
void drawInit();
void drawSphere(double radius , int slices, int stack,bool texture);
void drawDisk( GLdouble innerRadius,
GLdouble outerRadius,
GLint slices);
void drawDeInit();
GLUquadricObj * m_quad = NULL;
void drawInit()
{
m_quad = gluNewQuadric();
}
void drawDeInit()
{
gluDeleteQuadric(m_quad);
}
void drawSphere(double radius , int slices, int stack ,bool texture)
{
gluQuadricTexture(m_quad, true);
gluSphere(m_quad,radius,slices,stack);
}
void drawDisk(GLdouble innerRadius,
GLdouble outerRadius,
GLint slices )
{
gluDisk(m_quad,innerRadius,
outerRadius,
slices,
true);
}
7、纹理载入,使用lodepng库读取png图像,该库最大的好处是只有单独一个文件,不需要依赖zlib和libpng等库就能惊醒png读写
class glTexture
{
public:
GLuint m_tex;
glTexture(){m_tex=0;}
glTexture(const char* fname,
bool make_mipmaps=true);
~glTexture();
void MakeCurrent();
};
glTexture::glTexture(const char *fname,
bool make_mipmaps)
{
std::vector<unsigned char> image;
unsigned int width, height;
unsigned int error = lodepng::decode(image, width, height, fname);
if (error != 0)
{
std::cout << "error " << error << ": " << lodepng_error_text(error) << std::endl;
return;
}
m_tex = 0;
glEnable(GL_TEXTURE_2D);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
//Get a texture space and make it active
glGenTextures(1, &m_tex);
glBindTexture(GL_TEXTURE_2D, m_tex);
//Set default(and fastest) texture propoties
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
//Determine if mipmaps are used
if (make_mipmaps)
{
gluBuild2DMipmaps(GL_TEXTURE_2D, 4, width, height, GL_RGBA, GL_UNSIGNED_BYTE, &image[0]);
}
else
{
double xPow2, yPow2;
int ixPow2, iyPow2;
int xSize2, ySize2;
unsigned char* pData = NULL;
GLint glMaxTexDim;
//Get the maximum texture size
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glMaxTexDim);
//Get the powers of 2 that correspond to the width and height of the original
//or of the maxmaximum texture size if widthor height is larger than the maxmaximum texture size
if (width <= glMaxTexDim)
xPow2 = log((double)width) / log((double)2.0);
else
xPow2 = log((double)glMaxTexDim) / log((double)2.0);
if (height <= glMaxTexDim)
yPow2 = log((double)height) / log((double)2.0);
else
yPow2 = log((double)glMaxTexDim) / log((double)2.0);
//round the power of 2 up to the nearest power of 2
ixPow2 = (int)xPow2;
iyPow2 = (int)yPow2;
if (xPow2 != (double)ixPow2)
ixPow2++;
if (yPow2 != (double)iyPow2)
iyPow2++;
//convert power to actual value
xSize2 = 1 << ixPow2;
ySize2 = 1 << iyPow2;
//if the new sizes are different than the old ones
//resize and scale the "RGBAImage"
if (xSize2 != width ySize2 != height)
{
pData = (unsigned char*)malloc(xSize2 * ySize2 * 4 * sizeof(unsigned char));
if (!pData)
return;
gluScaleImage(GL_RGBA,
width,
height,
GL_UNSIGNED_BYTE,
&image[0],
xSize2,
ySize2,
GL_UNSIGNED_BYTE,
pData);
width = xSize2;
height = ySize2;
}
glTexImage2D(GL_TEXTURE_2D,
0, 4, width,height,
0, GL_RGB, GL_UNSIGNED_BYTE,pData);
if (pData)
{
free(pData);
pData = NULL;
}
}
}
void glTexture::MakeCurrent()
{
glBindTexture(GL_TEXTURE_2D, m_tex);
}
glTexture::~glTexture()
{
glDeleteTextures(1, &m_tex);
}
8、行星结构的定义
class planet
{
public:
float aroundRotatedSpeed;//公转
float selfRotatedSpeed;//自转
float radius;//行星的半径,影响绘制的大小啦
//下面这个变量代表当前行星离父亲之间的的距离拉
//1.太阳系以太阳为中心进行运动
//2.除地球外其他行星相对于太阳进行自转和公转,因此对于其他行星来说,太阳就是父亲,其他行星就是儿子
//3比较特殊的是地球,地球也是相对太阳进行自转和公转的,但是地球还有一个儿子,既月球,因此对于月球来说,他的父亲是地球,他的父亲的父亲是太阳
//层次关系总结如下:
// 太阳
// 除地球和月球外的其他行星
// 地球
// 月球
vec3 pos;
glTexture *texture;
public:
planet(const char* texname,float as,float ss,float radius,vec3 pos)
{
texture=new glTexture(texname,true);
this->aroundRotatedSpeed =as;
this->selfRotatedSpeed =ss;
this->radius =radius;
this->pos =pos;
}
~planet()
{
if (!texture)
{
delete texture;
texture = NULL;
}
}
};
9、录制与回放,采取最简单的方式,记录当前的angle,所有的星球运动都是基于当前anlge变量,因此其他运动都是当前角度的某个映射关系,因此只要记录当前的angle,就能实现录制和回放功能(其实由上述代码可见,纯粹是为了完成任务,因此采取最简洁有效方式,哈哈哈)
int angle = 0; //核心变量,所有行星的移动和转动的速度是以angle为基础的啦,改变angle旋转速度会影响所有行星的运动速度,包括角速度和距离,所有行星都是按照angle相对比例进行运动
void record()
{
if (filesys.beginWrite("test.txt"))
{
filesys.writeInt(angle);
filesys.closeFile();
}
}
void rePlay()
{
if (filesys.beginRead("test.txt"))
{
filesys.readInt(&angle);
filesys.closeFile();
}
}
10、main.cpp
//全局变量声明
bool lighting = true;
fileSystem filesys;//record/replay系统使用
int frontViewOnOff;//开关变量,前视图和正视图转换
glTexture *boxtex;//天空盒纹理贴图,理论上天空盒需要六张无缝拍摄的纹理,现在就用一张贴在天空盒六个面上,简化一下
glTexture *parttex;
glExplosion * glexp = new glExplosion(1, vec3(0, 0, 0), 1.0f, 0);
bool isexp = true;
int numexp;
float px, py, pz;
glTrail* trail;
//行星系统
planet *sun; //太阳
planet *sx; //水星
planet *jx; //金星
planet *hx; //火星
planet *mx; //木星
planet *tx; //土星
planet *twx; //天王星
planet *hwx; //海王星
planet * earth; //地球
planet * moon; //月亮
glCamera camera(vec3(0.0f, 0.0f, 10.0f)); //摄像机初始位置,w/s键控制摄像机前后移动,a/d控制摄像机左右移动,鼠标左键按下拖动控制摄像机的pitch/yaw旋转
int angle = 0; //核心变量,所有行星的移动和转动的速度是以angle为基础的啦,改变angle旋转速度会影响所有行星的运动速度,包括角速度和距离,所有行星都是按照angle相对比例进行运动
void SetLight(bool b)
{
float amb[4] = { 1.0, 0.8, 0.8, 1 };
float dif[4] = { 1.0, 1.0, 1.0, 1 };
float pos[4] = { 0, 10, 0, 1 };
glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
glLightfv(GL_LIGHT0, GL_SPECULAR, dif);
glLightfv(GL_LIGHT0, GL_POSITION, pos);
glColorMaterial(GL_FRONT, GL_DIFFUSE);
if (b)
{
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
}
else
{
glDisable(GL_LIGHTING);
glDisable(GL_LIGHT0);
}
glShadeModel(GL_SMOOTH);
}
void init()
{
boxtex = new glTexture("星空图.png", true);
sun = new planet("太阳.png", 0.0f, 1.0f, 1.0f, vec3(0.0f, 0.0f, 0.0f));
sx = new planet("水星.png", 0.5f, 0.5f, 0.2f, vec3(1.4f, 0.4f, 0.0f));
jx = new planet("金星.png", 0.5f, 0.5f, 0.2f, vec3(3.0f, -0.4f, 0.0f));
earth = new planet("地球.png", 1.0f, 2.0f, 0.5f, vec3(5.0f, 2.0f, 8.0f));
moon = new planet("月亮.png", 0.5f, 0.5f, 0.2f, vec3(1.5f, 0.0f, 0.0f));
hx = new planet("火星.png", 0.2f, 0.3f, 0.3f, vec3(7.0f, 0.0f, 0.0f));
mx = new planet("木星.png", 0.4f, 1.0f, 0.5f, vec3(10.0f, 0.0f, 0.0f));
tx = new planet("土星.png", -0.4f, 0.2f, 1.0f, vec3(15.0f, 1.0f, 0.0f));
twx = new planet("天王星.png", 0.8f, 0.5f, 0.3f, vec3(17.0f, 0.0f, 0.0f));
hwx = new planet("海王星.png", 0.6f, 0.5f, 0.4f, vec3(19.0f, 0.8f, 0.0f));
parttex = new glTexture("particle.png");
trail = new glTrail("spawnflash.png");
trail->setPath(vec3(0, 0, 0));
glFrontFace(GL_CCW);
glCullFace(GL_BACK);
//初始化二次曲面对象
drawInit();
}
void deinit()
{
delete boxtex;
delete sun;
delete sx;
delete jx;
delete earth;
delete moon;
delete hx;
delete mx;
delete tx;
delete twx;
delete hwx;
delete parttex;
delete trail;
delete glexp;
drawDeInit();
}
void record()
{
if (filesys.beginWrite("test.txt"))
{
filesys.writeInt(angle);
filesys.closeFile();
}
}
void rePlay()
{
if (filesys.beginRead("test.txt"))
{
filesys.readInt(&angle);
filesys.closeFile();
}
}
static void DrawEarthAndMoon(planet *earth, planet *moon)
{
glPushMatrix();//地球公转+自转(围绕太阳)
earth->texture->MakeCurrent();
glRotatef(angle*earth->aroundRotatedSpeed, 0.0f, 1.0f, 0.0f);
glTranslatef(earth->pos.x, earth->pos.y, earth->pos.z);
glRotatef(angle*earth->selfRotatedSpeed, 0.0f, 1.0f, 0.0f);
drawSphere(earth->radius, 20, 20, true);
glPushMatrix();//月球公转+自转(围绕地球)
moon->texture->MakeCurrent();
glRotatef(angle*moon->aroundRotatedSpeed, 0.0f, 1.0f, 0.0f);
glTranslatef(moon->pos.x, moon->pos.y, moon->pos.z);
glRotatef(angle*moon->selfRotatedSpeed, 0.0f, 1.0f, 0.0f);
drawSphere(moon->radius, 20, 20, true);
glPopMatrix();
glPopMatrix();
}
static void DrawOtherPlanet(planet * p)
{
glPushMatrix();
p->texture->MakeCurrent();
glRotatef(angle*p->aroundRotatedSpeed, 0.0f, 1.0f, 0.0f);
glTranslatef(p->pos.x, p->pos.y, p->pos.z);
glRotatef(angle*p->selfRotatedSpeed, 0.0f, 1.0f, 0.0f);
drawSphere(p->radius, 20, 20, true);
glPopMatrix();
}
static void DrawTrail(planet * p)
{
glPushMatrix();
glRotatef(angle*p->aroundRotatedSpeed*2.0f, 0.0f, 1.0f, 0.0f);
glTranslatef(p->pos.x + 0.4f, p->pos.y + 0.3, p->pos.z);
glPushMatrix();
glRotatef(-90.0f, 0.0f, 1.0f, 0.0f);
trail->draw(camera.pos);
glPopMatrix();
glRotatef(angle*p->selfRotatedSpeed, 0.0f, 1.0f, 0.0f);
drawSphere(p->radius, 20, 20, true);
glPopMatrix();
}
static void DrawOtherPlanet2(planet * p)
{
glPushMatrix();
p->texture->MakeCurrent();
glRotatef(angle*p->aroundRotatedSpeed, 0.0f, 1.0f, 0.0f);
glTranslatef(p->pos.x, p->pos.y, p->pos.z);
glRotatef(angle*p->selfRotatedSpeed, 0.0f, 1.0f, 0.0f);
drawSphere(p->radius, 20, 20, true);
glPushMatrix();
glRotatef(85, 1.0f, 0.0f, 0.0f);
drawDisk(1.5, 3, 20);
glPopMatrix();
glPopMatrix();
}
void DrawJX()
{
glPushMatrix();
jx->texture->MakeCurrent();
glRotatef(angle*jx->aroundRotatedSpeed * 2, 1.0, 1.0, 1.0);
glTranslatef(5.0f, 0.0, 0.0f);
glRotatef(angle*jx->aroundRotatedSpeed * 2, 0.0f, 0.0f, 1.0f);
drawSphere(jx->radius, 20, 20, true);
glPopMatrix();
}
void DrawHX()
{
glPushMatrix();
hx->texture->MakeCurrent();
glRotatef(angle*hx->aroundRotatedSpeed * 2, -1.0, -1.0, 0.0);
glTranslatef(5.0f, 0.0, 0.0f);
glRotatef(angle*hx->aroundRotatedSpeed * 2, 0.0f, 0.0f, 1.0f);
drawSphere(hx->radius, 20, 20, true);
glPopMatrix();
}
void testdraw1(planet *p)
{
glPushMatrix();
p->texture->MakeCurrent();
glRotatef(45, 1.0, 0.0, 0.0);
glRotatef(angle*p->aroundRotatedSpeed * 2, 0.0f, 1.0f, 0.0f);
glTranslatef(5.0f, 0, 0.0f);
glRotatef(angle*p->aroundRotatedSpeed * 2, 0.0f, 0.0f, 1.0f);
drawSphere(p->radius, 20, 20, true);
glPopMatrix();
}
void DrawSolarSystem(planet *sun)
{
sun->texture->MakeCurrent();
SetLight(lighting);
glTranslatef(sun->pos.x, sun->pos.y, sun->pos.z);
glRotatef(angle*sun->selfRotatedSpeed, 0.0f, 1.0f, 0.0f);
drawSphere(sun->radius, 50, 50, true);
DrawEarthAndMoon(earth, moon);//地月绘制
DrawOtherPlanet(sx);
DrawOtherPlanet(jx);
DrawOtherPlanet(twx);
DrawOtherPlanet(hwx);
//带光圈绘制
DrawOtherPlanet2(mx);
DrawOtherPlanet2(tx);
DrawTrail(jx);
}
void myKeyboardFunc(unsigned char key, int x, int y)
{
switch (key)
{
case 27: //ESC
exit(0); //退出系统
break;
case 'w'://摄像机向前运动
camera.pos -= camera.forward*0.5;
glutPostRedisplay();
break;
case 's'://摄像机向后运动
camera.pos += camera.forward*0.5;
glutPostRedisplay();
break;
case 'a'://摄像机向左运动
camera.pos -= camera.right *0.5;
glutPostRedisplay();
break;
case 'd'://摄像机向右运动
camera.pos += camera.right*0.5;
glutPostRedisplay();
break;
case 'f'://正视图和顶视图切换
frontViewOnOff ^= 1;
glutPostRedisplay();
break;
case 'r'://记录
record();
glutPostRedisplay();
break;
case 'p'://回放
rePlay();
glutPostRedisplay();
break;
case 'l'://灯源开关
lighting = !lighting;
glutPostRedisplay();
break;
}
}
void myReshape(int w, int h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60, GLfloat(w) / h, 0.1, 1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
glClearDepth(1.0f);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glShadeModel(GL_SMOOTH);
}
void creatExplosion(float x, float y, float z, int num, float spread)
{
if (glexp != NULL) { delete glexp; glexp = NULL; }
glexp = new glExplosion(num, vec3(0, 0, 0), spread, parttex->m_tex);
px = x, pz = z, py = y;
isexp = false;
numexp = 0;
}
void drawExplosion()
{
glPushMatrix();
glTranslatef(px, py, pz);
if (isexp == false)
{
glexp->Render();
isexp = true;
}
if (isexp)
{
glexp->Update(0.03f);
isexp = false;
}
glPopMatrix();
}
void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT);
camera.update();
//如果顶视图的话,沿着x轴旋转90度
glRotatef(frontViewOnOff*90.0f, 1.0f, 0.0f, 0.0f);
//绘制天空盒
glDrawSkyBox(boxtex, 0.0f, 0.0f, 0.0f, 1000.0f, 1000.0f, 1000.0f);
DrawJX();
DrawHX();
DrawSolarSystem(sun);
if (numexp > 100)
{
creatExplosion(1.0f, 1.0f, 1.0f, 100, 1.5f);
}
drawExplosion();
angle += 2;
numexp++;
glutSwapBuffers();
}
void myTimerFunc(int val)
{
myDisplay();
glutTimerFunc(25, myTimerFunc, 0);
}
int main(int argc, char *argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA GLUT_DOUBLE GLUT_DEPTH);
glutInitWindowPosition(100, 100);
glutInitWindowSize(800, 600);
glutCreateWindow("太阳系 w/s/a/d控制摄像机运动 r/录制 p/回放 f/切换正顶视图 l/光源开关");
init();
glutDisplayFunc(&myDisplay);
glutReshapeFunc(&myReshape);
glutKeyboardFunc(&myKeyboardFunc);
glutTimerFunc(25, myTimerFunc, 0);
glutMainLoop();
deinit();
return 0;
}
11、源码下载以及参考文档
完整的源代码可以到 https://github.com/jackyblf/SolarSystem-openGL- 进行下载。
源码中包含了billboardingtut.pdf的文档,讲解了所有的billboard类型以及适用范围,基于opengl描述。强烈推荐这篇文档
点击查看更多内容
4人点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦