当前位置: 首页 > news >正文

电商网站 设计方案国际新闻最新消息

电商网站 设计方案,国际新闻最新消息,海南省城乡住房建设厅网站首页,做网站排版一、简介 本文介绍了如何使用 OpenGL 中的 Transform Feedback 实现粒子效果,最终可以实现下图的效果: 本文的粒子系统实现参考了modern-opengl-tutorial, ogldev-tutorial28 和 粒子系统–喷泉 [OpenGL-Transformfeedback]。 二、使用 TransformFeed…

一、简介

本文介绍了如何使用 OpenGL 中的 Transform Feedback 实现粒子效果,最终可以实现下图的效果:
在这里插入图片描述
本文的粒子系统实现参考了modern-opengl-tutorial, ogldev-tutorial28 和 粒子系统–喷泉 [OpenGL-Transformfeedback]。

二、使用 TransformFeedback 实现效果

1. Transform Feedback 简介

Transform Feedback 是 OpenGL 中用于获取 vertex shader 和 geometry shader 处理后的顶点数据的一种机制,可以在 GPU 上将 vertex shader, geometry shader 处理后的数据存储到以一个 buffer 中,而不进行接下来的 clipper, Rasterizer 和 Fragment Shader 阶段。
Transform Feedback Buffer 在渲染管线中所处的位置如下图所示:
在这里插入图片描述

基于 Transform feedback,我们可以在 GPU 上对多个顶点数据行进并行运算,粒子系统 就是 Transform feedback 的一个典型应用。

2. 粒子系统实现

在实现粒子系统时,使用 update Shader 和 render Shader 两个 着色器:

  • update shader 用来更新粒子的状态,包括更新粒子状态、生成新粒子、消灭旧粒子。
  • render shader 用来将粒子显示在屏幕上。

粒子系统的实现流程如下:
流程
上图展示了使用 Update shader 和 Render shader 实现粒子系统的流程。图中左侧黄色虚线内为使用 Update shader 更新粒子,右侧蓝色虚线内为使用 Render shader 将粒子渲染到屏幕上,然后再进入下一帧的Update-Render流程。
在 Update shader 中,输入为 Update input VBO,输出为 Update output VBO。在 Render shader 中,Update output VBO 又作为渲染时的输入,Render input VBO。由于 Transform Feedback 中的在读 一个 VBO 时,不能同时写该 VBO ,及Update input VBOUpdate output VBO 不能是同一个 buffer object。因此在代码实现使用两个 VBO 交替作为 一个Update-Render流程中的Update input VBO Update output VBO
例如,渲染一个n帧的结果,其 Update input VBOUpdate output VBO 所代表的 buffer 变换如下所示:
在这里插入图片描述

3. 部分代码讲解

3.1. Particle 类

struct Particle
{float Type; // 0: launch, 1: shell, 2 : second shellglm::vec3 Pos;glm::vec3 Velocity;float Life;
};

系统中粒子的类型分为三类, launch, shell 和 second shell。

  • launch 类粒子相当于一个发射器,其位置、速度一直保持不变,在 Life 到达一定的数值时生成 shell 类粒子;
  • shell 类粒子由 launch 类粒子生成后,获得一个初始的速度,假设只受到重力,根据牛顿第二定律更新自己的 速度、位置。并且 shell 粒子的 Life 在到达一定数值时生成 second shell 类粒子;
  • second shell 类粒子初始时于生成该粒子的父粒子具有相同的位置,但是速度不同。 second shell 粒子的 Life 到达一定数值后死亡。

3.2. PaticleSystem 类

a. PaticleSystem类的变量

class ParticleSystem
{
public:
...
private:bool m_isFirst; // 标记 是否时第一次调用 Render()GLuint m_VAO[2];                // 两个 VAO 分别用于 update 和 render 的输入unsigned int m_update_input_id; // update input id,unsigned int m_render_input_id; // render input id, update output idGLuint m_VBO_TFB[2]; // 两个顶点缓冲区 , 交替作为 update / render bufferGLuint m_TFO[2];     // 两个 transform feedback 对象 TFOShader m_updateShader;     // particle update shaderShader m_renderShader;     // particle render shaderTexture m_randomTexture;   // 随机数纹理Texture m_particleTexture; // 粒子的纹理float m_time;              // 系统运行的总时间...
}

b. InitParticleSystem() 初始化 ParticleSystem

class ParticleSystem
{public:...bool InitParticleSystem(const glm::vec3 &Pos){// 1. 生成 初始粒子Particle Particles[MAX_PARTICLES];Particles[0].Type = 0;Particles[0].Pos = Pos;Particles[0].Velocity = glm::vec3(0.0f, 0.01f, 0.0f);Particles[0].Life = 0.0f;// 2. 初始化  VAO, TFO, VBOglGenVertexArrays(2, m_VAO);       // 生成 两个 VAOglGenTransformFeedbacks(2, m_TFO); // 生成 两个 TFOglGenBuffers(2, m_VBO_TFB);        // 生成 两个 buffer (TFB), 分别绑定到 对应的 VAO 和 TFO 上for (unsigned int i = 0; i < 2; i++){// VAO[i] <- VBO[i]// TFO[i] <- VBO[i]glBindVertexArray(m_VAO[i]);glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_TFO[i]);glBindBuffer(GL_ARRAY_BUFFER, m_VBO_TFB[i]);glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_VBO_TFB[i]);glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(Particles), Particles, GL_DYNAMIC_DRAW);glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_VBO_TFB[i]);}// 3. 初始化 update shader, render shader// update shaderconst char *feedbackVaryings[] = {"Type1", "Position1", "Velocity1", "Age1"};m_updateShader = Shader("../resources/particleUpdate.vert", "../resources/particleUpdate.frag","../resources/particleUpdate.geom", feedbackVaryings);m_updateShader.use();m_updateShader.setFloat("gLauncherLifetime", 100.0f);m_updateShader.setFloat("gShellLifetime", 10000.0f);m_updateShader.setFloat("gSecondaryShellLifetime", 500.f);// 初始化 render shaderm_renderShader = Shader("../resources/particleRender.vert", "../resources/particleRender.frag","../resources/particleRender.geom");m_renderShader.use();m_renderShader.setFloat("gBillboardSize", 0.01f);// 4. 初始化 纹理// 随机数纹理m_randomTexture.id = TextureFromRand();m_randomTexture.path = "random";m_randomTexture.type = "texture_diffuse";// 粒子纹理m_particleTexture.id = TextureFromFile("particle.png", "../resources/textures/");m_particleTexture.path = "../resources/textures/particle.png";m_particleTexture.type = "texture_diffuse";return true;};...
}

c. Render() 调用 update shader 和 Render shader 进行更新粒子、渲染粒子

class ParticleSystem
{
public:
...void Render(float DeltaTimeMillis, const glm::mat4 &VP, const glm::vec3 &CameraPos){m_time += DeltaTimeMillis;// 更新 粒子updateParticles(DeltaTimeMillis);// 渲染 粒子renderParticles(VP, CameraPos);// 交换 update shader 使用的 VAO 和 TFO// 0 -> 1 -> 0 -> 1 -> 0 -> ...m_update_input_id = (m_update_input_id + 1) % 2;// 交换 render shader 使用的 VAO// 1 -> 0 -> 1 -> 0 -> 1 -> ...m_render_input_id = (m_render_input_id + 1) % 2;};...
}

d. updateParticles() 更新粒子

class ParticleSystem
{
public:
...void updateParticles(float DelatTimeMillis){// 1. 设置 update shader 中的 uniform 变量以及纹理变量m_updateShader.use();m_updateShader.setFloat("gTime", m_time);m_updateShader.setFloat("gDeltaTimeMillis", 1.0f * DelatTimeMillis);glActiveTexture(GL_TEXTURE0); // 激活纹理单元 0glUniform1i(glGetUniformLocation(m_updateShader.ID, "gRandomTexture"),0); // 将纹理单元0 与着色器的 sampler 变量 gRandomTexture 关联glBindTexture(GL_TEXTURE_1D, m_randomTexture.id); // 将 纹理对象 绑定到当前的纹理单元 GL_SAMPLER_1D 纹理上// 2. 绑定 VAO, TFB// 绑定VAO, 作为 update shader 的输入glBindVertexArray(m_VAO[m_update_input_id]);// 根据 update shader 设置 VAO 中不同属性的读取方式glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, sizeof(Particle), 0);                               // typeglVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid *)(sizeof(float))); // positionglVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Particle),(const GLvoid *)(sizeof(float) + sizeof(glm::vec3))); // velocityglVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, sizeof(Particle),(const GLvoid *)((sizeof(float) + sizeof(glm::vec3)) + sizeof(glm::vec3))); // lifetimeglEnableVertexAttribArray(0);glEnableVertexAttribArray(1);glEnableVertexAttribArray(2);glEnableVertexAttribArray(3);// 绑定 TFO, 作为 update shader 的输出glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_TFO[m_render_input_id]);// 3. 开始使用 update shader 更新粒子glEnable(GL_RASTERIZER_DISCARD); // 跳过光栅化以及之后的阶段glBeginTransformFeedback(GL_POINTS);if (m_isFirst){ // 第一次 运行 update shader, 只有一个 粒子glDrawArrays(GL_POINTS, 0, 1);m_isFirst = false;}else{ // 之后运行 update shader, 粒子个数不确定, 由 opengl 根据 transform feedback object 自行确定粒子个数glDrawTransformFeedback(GL_POINTS, m_TFO[m_update_input_id]);}glEndTransformFeedback();glDisable(GL_RASTERIZER_DISCARD); // 开启光栅化以及之后的阶段glDisableVertexAttribArray(0);glDisableVertexAttribArray(1);glDisableVertexAttribArray(2);glDisableVertexAttribArray(3);};...
}

e. renderParticles()渲染粒子

class ParticleSystem
{
public:
...void renderParticles(const glm::mat4 &VP, const glm::vec3 &CameraPos){// 1. 设置渲染状态glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT |GL_DEPTH_BUFFER_BIT); // 使用 (0.2,0.3,0.3,1.0) 清空 color texture, 清空 depth bufferglEnable(GL_BLEND);           // 启用 blendglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // blend 模式为 D = alpha*S + (1-alpha)*DglEnable(GL_PROGRAM_POINT_SIZE);// 2. 设置 render shader 中的 uniform 变量以及纹理变量m_renderShader.use();m_renderShader.setVec3("gCameraPos", CameraPos);m_renderShader.setMat4("gVP", VP);glActiveTexture(GL_TEXTURE1); // 激活纹理单元 1glUniform1i(glGetUniformLocation(m_renderShader.ID, "gColorMap"), 1);glBindTexture(GL_TEXTURE_2D, m_particleTexture.id); // 将 纹理对象 绑定到当前的纹理单元的 GL_SAMPLER_1D 纹理上// 3. 绑定 VAO// 绑定VAO, 作为 render shader 的输入glBindVertexArray(m_VAO[m_render_input_id]);// 根据 render shader 设置 VAO 中不同属性的读取方式glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (void *)(sizeof(float))); // positionglEnableVertexAttribArray(0);// 4. 开始使用 render shader 渲染粒子glDisable(GL_RASTERIZER_DISCARD); // 开启 光栅化 以及之后的阶段glDrawTransformFeedback(GL_POINTS, m_TFO[m_render_input_id]);};...
}

3.3. Update shader

a. Vertex shader

#version 410layout(location = 0) in float Type;
layout(location = 1) in vec3 Position;
layout(location = 2) in vec3 Velocity;
layout(location = 3) in float Age;out float Type0;
out vec3 Position0;
out vec3 Velocity0;
out float Age0;void main() {Type0 = Type;Position0 = Position;Velocity0 = Velocity;Age0 = Age;
}

b. Geometer shader

#version 410layout(points) in;
layout(points, max_vertices = 30) out;/* 从 vertex shader 输入的 point 的属性 */
in float Type0[];
in vec3 Position0[];
in vec3 Velocity0[];
in float Age0[];/* 输出到 fragment shader 的 point 的属性*/
out float Type1;
out vec3 Position1;
out vec3 Velocity1;
out float Age1;/* 用于更新 particle 的变量 */
uniform float gDeltaTimeMillis;        // 时间间隔
uniform float gTime;                   // 当前时刻
uniform sampler1D gRandomTexture;      // 随机纹理
uniform float gLauncherLifetime;       // Launcher 的生存时间
uniform float gShellLifetime;          // Shell 的生存时间
uniform float gSecondaryShellLifetime; // Secondary Shell 的生存时间#define PARTICLE_TYPE_LAUNCHER 0.0f
#define PARTICLE_TYPE_SHELL 1.0f
#define PARTICLE_TYPE_SECONDARY_SHELL 2.0f// 使用 random texture 获取一个随机值 (random texture相当于一个随机数池)
vec3 GetRandomDir(float TexCoord) {vec3 Dir = texture(gRandomTexture, TexCoord).xyz;Dir -= vec3(0.5, 0.5, 0.5);return Dir;
}void main() {// 更新 particle 的生存时间float Age = Age0[0] + gDeltaTimeMillis;// 增加随机性float g_Time = (sin(gTime) + 1.0) / 2.0 * 1000.0;g_Time = gTime;// Launcher particleif (Type0[0] == PARTICLE_TYPE_LAUNCHER) {// 如果 particle 生存时间过长// 那么就生成一个 Shell particle, 并且更新 Launcher particleif (Age >= gLauncherLifetime) {// 生成 一个 Shell particleType1 = PARTICLE_TYPE_SHELL;// 初始化 position, dir, velocity, agePosition1 = Position0[0];vec3 Dir = GetRandomDir(g_Time / 1000.0);Dir.y = max(Dir.y, 0.95);Velocity1 = normalize(Dir) / 12.0;// Velocity1 = Velocity0[0];Age1 = 0.0;// emit vertexEmitVertex();EndPrimitive();Age = 0.0;}// 更新 Launcher particleType1 = PARTICLE_TYPE_LAUNCHER;Position1 = Position0[0];Velocity1 = Velocity0[0];Age1 = Age;EmitVertex();EndPrimitive();} else {// 如果是 Shell or Second Shell particlefloat DeltaTimeSecs = gDeltaTimeMillis / 1000.0;float t1 = Age0[0] / 1000.0;float t2 = Age / 1000.0;// position 的改变量vec3 DeltaP = DeltaTimeSecs * Velocity0[0];// velocity 的改变量// vec3 DeltaV = vec3(DeltaTimeSecs) * vec3(0.0, -9.81, 0.0);// 如果是 Shell particlevec3 DeltaV = vec3(0, DeltaTimeSecs / 1000.0 * -9.81, 0);if (Type0[0] == PARTICLE_TYPE_SHELL) {if (Age < gShellLifetime) {// 如果 Shell particle 还在生存时间内Type1 = PARTICLE_TYPE_SHELL;// 更新 position, velocityPosition1 = Position0[0] + DeltaP;Velocity1 = Velocity0[0] + DeltaV;// Velocity1 = Velocity0[0];// Velocity1 = Velocity0[0] + vec3(0.0, DeltaTimeSecs * -9.8, 0.0);Age1 = Age;EmitVertex();EndPrimitive();} else {// 如果 Shell particle 超过生存时间了,那么就 分裂为 10 个 Second Shellfor (int i = 0; i < 10; i++) {Type1 = PARTICLE_TYPE_SECONDARY_SHELL;Position1 = Position0[0];vec3 Dir = GetRandomDir((g_Time + i) / 1000.0);Velocity1 = normalize(Dir) / 20.0;Age1 = 0.0f;EmitVertex();EndPrimitive();}}} else {// 如果是 Second Shell particleif (Age < gSecondaryShellLifetime) {// 如果 Second Shell 还在生存周期内Type1 = PARTICLE_TYPE_SECONDARY_SHELL;Position1 = Position0[0] + DeltaP;Velocity1 = Velocity0[0] + DeltaV;Age1 = Age;EmitVertex();EndPrimitive();}// 如果 Second Shell 超过生存周期, 那么就消灭该 Second Shell particle// (什么也不做)}}
}

c. Fragment shader

#version 410 core
void main() {// do nothing
}

3.4. Render shader

a. Vertex shader

#version 410
layout(location = 0) in vec3 Position;
void main() { gl_Position = vec4(Position, 1.0); }

b. Geometer shader

#version 410layout(points) in;
layout(triangle_strip, max_vertices = 4) out;
uniform mat4 gVP;
uniform vec3 gCameraPos;
uniform float gBillboardSize;out vec2 TexCoord;void main() {// 以 p0 = gl_Position 为右下角,绘制一个矩形 (两个三角形)// p2 --- p4// |  \   |// |    \ |// p1 --- p3 (p0)vec3 Pos = gl_in[0].gl_Position.xyz;vec3 toCamera = normalize(gCameraPos - Pos);vec3 up = vec3(0.0, 1.0, 0.0);vec3 right = cross(toCamera, up) * gBillboardSize;// p1Pos -= right;gl_Position = gVP * vec4(Pos, 1.0);TexCoord = vec2(0.0, 0.0);EmitVertex();// p2Pos.y += gBillboardSize;gl_Position = gVP * vec4(Pos, 1.0);TexCoord = vec2(0.0, 1.0);EmitVertex();// p3Pos.y -= gBillboardSize;Pos += right;gl_Position = gVP * vec4(Pos, 1.0);TexCoord = vec2(1.0, 0.0);EmitVertex();// p4Pos.y += gBillboardSize;gl_Position = gVP * vec4(Pos, 1.0);TexCoord = vec2(1.0, 1.0);EmitVertex();EndPrimitive();
}

c. Fragment shader

#version 410uniform sampler2D gColorMap;
in vec2 TexCoord;
out vec4 FragColor;
void main() {FragColor = texture(gColorMap, TexCoord);if (FragColor.r >= 0.9 && FragColor.g >= 0.9 && FragColor.b >= 0.9) {discard;}
}

4. 全部代码及模型文件

用于实现粒子效果的全部代码以及模型文件可以在OpenGL使用TransformFeedback实现粒子效果 中下载。

三、参考引用

[1]. modern-opengl-tutorial
[2]. ogldev-tutorial28
[3]. 粒子系统–喷泉 [OpenGL-Transformfeedback]


文章转载自:
http://dinncotuft.zfyr.cn
http://dinncoinfidelity.zfyr.cn
http://dinncovilyui.zfyr.cn
http://dinncoovaritis.zfyr.cn
http://dinncodiseaseful.zfyr.cn
http://dinncospotter.zfyr.cn
http://dinncoyellowcake.zfyr.cn
http://dinncocaraway.zfyr.cn
http://dinncoliquescence.zfyr.cn
http://dinncotarvia.zfyr.cn
http://dinncoridgepole.zfyr.cn
http://dinncooyes.zfyr.cn
http://dinncoexecrably.zfyr.cn
http://dinncoturnix.zfyr.cn
http://dinncoplanimetry.zfyr.cn
http://dinncomorphotropy.zfyr.cn
http://dinncoleafstalk.zfyr.cn
http://dinncokechua.zfyr.cn
http://dinncolacunose.zfyr.cn
http://dinncovermivorous.zfyr.cn
http://dinncodecagram.zfyr.cn
http://dinncopisgah.zfyr.cn
http://dinncotiro.zfyr.cn
http://dinncolexloci.zfyr.cn
http://dinncosbw.zfyr.cn
http://dinncomonoaminergic.zfyr.cn
http://dinncoembourgeoisement.zfyr.cn
http://dinncoendways.zfyr.cn
http://dinncodpl.zfyr.cn
http://dinncocompass.zfyr.cn
http://dinncounicef.zfyr.cn
http://dinncoopal.zfyr.cn
http://dinncoquadrumane.zfyr.cn
http://dinnconectar.zfyr.cn
http://dinncoproclamation.zfyr.cn
http://dinncotannin.zfyr.cn
http://dinncocoopery.zfyr.cn
http://dinncopreinvasive.zfyr.cn
http://dinncoperiocular.zfyr.cn
http://dinncorosily.zfyr.cn
http://dinncoheadrest.zfyr.cn
http://dinncoactinozoan.zfyr.cn
http://dinncowonna.zfyr.cn
http://dinncopurposive.zfyr.cn
http://dinncodulcimer.zfyr.cn
http://dinncoharquebus.zfyr.cn
http://dinncomilldam.zfyr.cn
http://dinncomalaceous.zfyr.cn
http://dinncoverdant.zfyr.cn
http://dinncosemifeudal.zfyr.cn
http://dinncolactonic.zfyr.cn
http://dinncomacadam.zfyr.cn
http://dinncogeometricism.zfyr.cn
http://dinncodaysman.zfyr.cn
http://dinncodropsy.zfyr.cn
http://dinncounhitch.zfyr.cn
http://dinncoalpargata.zfyr.cn
http://dinncophonate.zfyr.cn
http://dinncospeiss.zfyr.cn
http://dinncopupillometer.zfyr.cn
http://dinncocaptress.zfyr.cn
http://dinncocodicil.zfyr.cn
http://dinncobeano.zfyr.cn
http://dinncodeadpan.zfyr.cn
http://dinncotanta.zfyr.cn
http://dinncodata.zfyr.cn
http://dinncofloriculture.zfyr.cn
http://dinncorealistically.zfyr.cn
http://dinncoclew.zfyr.cn
http://dinncobacco.zfyr.cn
http://dinncounqueen.zfyr.cn
http://dinncoeligibly.zfyr.cn
http://dinncovail.zfyr.cn
http://dinncoplurality.zfyr.cn
http://dinncodialytically.zfyr.cn
http://dinncomerozoite.zfyr.cn
http://dinncodrippage.zfyr.cn
http://dinncotwyformed.zfyr.cn
http://dinncotopicality.zfyr.cn
http://dinncodandiacal.zfyr.cn
http://dinncomonochrome.zfyr.cn
http://dinncoabdomen.zfyr.cn
http://dinncothick.zfyr.cn
http://dinncosheeney.zfyr.cn
http://dinncodefrock.zfyr.cn
http://dinncobaotou.zfyr.cn
http://dinncobrasilin.zfyr.cn
http://dinncooblanceolate.zfyr.cn
http://dinncorsd.zfyr.cn
http://dinncosuctorious.zfyr.cn
http://dinncokrone.zfyr.cn
http://dinncoappoint.zfyr.cn
http://dinncorotative.zfyr.cn
http://dinncohoofbeat.zfyr.cn
http://dinncoseaborne.zfyr.cn
http://dinncohygienical.zfyr.cn
http://dinncokolkhoznik.zfyr.cn
http://dinncooutlearn.zfyr.cn
http://dinncosplice.zfyr.cn
http://dinncodumbbell.zfyr.cn
http://www.dinnco.com/news/151989.html

相关文章:

  • 成都php网站制作程序员培训网站推荐
  • 湖南在建工程查询深圳防疫措施优化
  • 国中建设委员会网站自媒体seo是什么意思
  • 织梦制作html 网站地图种子搜索
  • 上海网站建设百度推广公司哪家好百度推广开户多少钱
  • 重庆网站备案最快几天成都网站建设公司
  • 局域网网站架设软件网络营销概述
  • 流速cms是什么意思googleseo排名公司
  • 超可爱做头像的网站网站推广业务
  • seo网站排名优化软件重庆seo建站
  • 网站百度多久做一次排名电商运营公司排名
  • win7 搭建iss网站长沙官网seo技巧
  • 比较好的网站设计公司短期职业技能培训班
  • 天津住房和城乡建设委员会官方网站地推的60种方法
  • 株洲网站建设的公司怎么找湖北seo网站推广
  • 衡水哪儿专业做网站百度可以发布广告吗
  • 网站建设 风险防控网站建设方案书
  • 丝芙兰网站做的好差前端seo优化
  • 深圳网站建站费用郑州seo优化培训
  • 个人网站设计 优帮云seo发外链工具
  • 网站建设服优秀的营销案例
  • 虚拟网站建设百度小说风云榜排名
  • 建筑人才网站广东省白云区
  • 广州 网站制作百度推广电话销售好做吗
  • 网站开发要学的代码推广app佣金平台正规
  • 海口网站排名提升女孩短期技能培训班
  • www 上海网站建设长春网站优化哪家好
  • facebook外贸推广优化网站页面
  • wordpress保护插件品牌词优化
  • 个人自助网站网络教学平台