OpenGL高级之 混合上

经验总计

 1.从const char转换到GLchar会报错,因此将GLchar类型转换为const GLchar
 2.当SOIL_load_image加载图片错误时,glTexImage2D以加载图片的数组为参数时会报出错误代码:1281。加载图片很容易出错,无论是文件地址写错一个字母,还是诸如在浏览器里下载的莫名格式图片,都很容易加载错误。这时就会报错1281,可以看看图片是否加载成功。

效果展示

 注意光是透明还不够,下图中透过草可以看到后面的物体,是因为我们丢弃掉了草透明度很高的一些片段,因此我们才可以透过草间缝隙看到后面。
在这里插入图片描述

源代码

顶点着色器

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoords;

out vec2 TexCoords;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * model * vec4(position, 1.0f);
    TexCoords = texCoords;
}

片段着色

#version 330 core
in vec2 TexCoords;

out vec4 color;

uniform sampler2D texture1;

void main()
{
    vec4 texColor = texture(texture1, TexCoords);
    if(texColor.a < 0.1)
        discard;
    color = texColor;
}

主程序

// Std. Includes
#include <string>

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>

// GLFW
#include <GLFW/glfw3.h>

// GL includes
#include "Shader.h"
#include "Camera.h"

// GLM Mathemtics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

// Other Libs
#include <SOIL.h>

// Properties
GLuint screenWidth = 800, screenHeight = 600;

// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void Do_Movement();
void printError();
GLenum glCheckError_(const char* file, int line)
{
    GLenum errorCode;
    while ((errorCode = glGetError()) != GL_NO_ERROR)
    {
        std::string error;
        switch (errorCode)
        {
        case GL_INVALID_ENUM:                  error = "INVALID_ENUM"; break;
        case GL_INVALID_VALUE:                 error = "INVALID_VALUE"; break;
        case GL_INVALID_OPERATION:             error = "INVALID_OPERATION"; break;
        case GL_STACK_OVERFLOW:                error = "STACK_OVERFLOW"; break;
        case GL_STACK_UNDERFLOW:               error = "STACK_UNDERFLOW"; break;
        case GL_OUT_OF_MEMORY:                 error = "OUT_OF_MEMORY"; break;
        case GL_INVALID_FRAMEBUFFER_OPERATION: error = "INVALID_FRAMEBUFFER_OPERATION"; break;
        }
        std::cout << error << " | " << file << " (" << line << ")" << std::endl;
    }
    return errorCode;
}
#define glCheckError() glCheckError_(__FILE__, __LINE__)
GLuint loadTexture(const GLchar* path, GLboolean alpha = false);

// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
bool keys[1024];
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;

GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;

// The MAIN function, from here we start our application and run our Game loop
int main()
{
    // Init GLFW
    glfwInit();
    glGetError();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr); // Windowed
    glfwMakeContextCurrent(window);

    // Set the required callback functions
    glfwSetKeyCallback(window, key_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // Options
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // Initialize GLEW to setup the OpenGL Function pointers
    glewExperimental = GL_TRUE;
    glewInit();

    // Define the viewport dimensions
    glViewport(0, 0, screenWidth, screenHeight);

    // Setup some OpenGL options
    glEnable(GL_DEPTH_TEST);

    // Setup and compile our shaders
    Shader shader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\fragmentShader.txt");
    printError();

    // pragma region预处理指令在Vis Stdio中使得代码可以折叠
#pragma region "object_initialization"
    // 立方体顶点数据
    GLfloat cubeVertices[] = {
        // Positions          // Texture Coords
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f,
         0.5f, -0.5f, -0.5f,  1.0f,  0.0f,
         0.5f,  0.5f, -0.5f,  1.0f,  1.0f,
         0.5f,  0.5f, -0.5f,  1.0f,  1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,
         0.5f, -0.5f,  0.5f,  1.0f,  0.0f,
         0.5f,  0.5f,  0.5f,  1.0f,  1.0f,
         0.5f,  0.5f,  0.5f,  1.0f,  1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,

        -0.5f,  0.5f,  0.5f,  1.0f,  0.0f,
        -0.5f,  0.5f, -0.5f,  1.0f,  1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,
        -0.5f,  0.5f,  0.5f,  1.0f,  0.0f,

         0.5f,  0.5f,  0.5f,  1.0f,  0.0f,
         0.5f,  0.5f, -0.5f,  1.0f,  1.0f,
         0.5f, -0.5f, -0.5f,  0.0f,  1.0f,
         0.5f, -0.5f, -0.5f,  0.0f,  1.0f,
         0.5f, -0.5f,  0.5f,  0.0f,  0.0f,
         0.5f,  0.5f,  0.5f,  1.0f,  0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f,  1.0f,
         0.5f, -0.5f, -0.5f,  1.0f,  1.0f,
         0.5f, -0.5f,  0.5f,  1.0f,  0.0f,
         0.5f, -0.5f,  0.5f,  1.0f,  0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,
         0.5f,  0.5f, -0.5f,  1.0f,  1.0f,
         0.5f,  0.5f,  0.5f,  1.0f,  0.0f,
         0.5f,  0.5f,  0.5f,  1.0f,  0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f
    };
    // 地面的顶点数据
    GLfloat planeVertices[] = {
        // Positions          // Texture Coords (note we set these higher than 1 that together with GL_REPEAT as texture wrapping mode will cause the floor texture to repeat)
         5.0f, -0.5f,  5.0f,  2.0f,  0.0f,
        -5.0f, -0.5f,  5.0f,  0.0f,  0.0f,
        -5.0f, -0.5f, -5.0f,  0.0f,  2.0f,

         5.0f, -0.5f,  5.0f,  2.0f,  0.0f,
        -5.0f, -0.5f, -5.0f,  0.0f,  2.0f,
         5.0f, -0.5f, -5.0f,  2.0f,  2.0f
    };
    // 草的顶点数据(两个三角形绘制出一个正方形)
    GLfloat transparentVertices[] = {
        // Positions         // Texture Coords (swapped y coordinates because texture is flipped upside down)
        0.0f,  0.5f,  0.0f,  0.0f,  0.0f,
        0.0f, -0.5f,  0.0f,  0.0f,  1.0f,
        1.0f, -0.5f,  0.0f,  1.0f,  1.0f,

        0.0f,  0.5f,  0.0f,  0.0f,  0.0f,
        1.0f, -0.5f,  0.0f,  1.0f,  1.0f,
        1.0f,  0.5f,  0.0f,  1.0f,  0.0f
    };
    // VAO流程:申请VAO、VBO,绑定VAO、VBO,将顶点数据传入VBO、设置数据解析方式,解绑VAO
    GLuint cubeVAO, cubeVBO;
    glGenVertexArrays(1, &cubeVAO);
    glGenBuffers(1, &cubeVBO);
    glBindVertexArray(cubeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glBindVertexArray(0);
    // Setup plane VAO
    GLuint planeVAO, planeVBO;
    glGenVertexArrays(1, &planeVAO);
    glGenBuffers(1, &planeVBO);
    glBindVertexArray(planeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), &planeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glBindVertexArray(0);
    // Setup transparent plane VAO
    GLuint transparentVAO, transparentVBO;
    glGenVertexArrays(1, &transparentVAO);
    glGenBuffers(1, &transparentVBO);
    glBindVertexArray(transparentVAO);
    glBindBuffer(GL_ARRAY_BUFFER, transparentVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(transparentVertices), transparentVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glBindVertexArray(0);
    // Load textures
    GLuint cubeTexture = loadTexture("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Resource\\container.jpg",0);
    GLuint floorTexture = loadTexture("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Resource\\hsys.jpg",0);
    GLuint transparentTexture = loadTexture("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Resource\\grass.png", 1);
    printError();

#pragma endregion

    // 草分布的位置
    std::vector<glm::vec3> vegetation;
    vegetation.push_back(glm::vec3(-1.5f, 0.0f, -0.48f));
    vegetation.push_back(glm::vec3(1.5f, 0.0f, 0.51f));
    vegetation.push_back(glm::vec3(0.0f, 0.0f, 0.7f));
    vegetation.push_back(glm::vec3(-0.3f, 0.0f, -2.3f));
    vegetation.push_back(glm::vec3(0.5f, 0.0f, -0.6f));

    // Game loop
    while (!glfwWindowShouldClose(window))
    {
        // Set frame time
        GLfloat currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // Check and call events
        glfwPollEvents();
        Do_Movement();

        // Clear the colorbuffer
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Draw objects
        shader.Use();
        glm::mat4 model =glm::mat4(1.0f);;
        glm::mat4 view = camera.GetViewMatrix();
        glm::mat4 projection = glm::perspective(camera.Zoom, (float)screenWidth / (float)screenHeight, 0.1f, 100.0f);
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
        // 渲染两个立方体(绑定VAO和纹理,定义模型矩阵,传入后渲染)
        glBindVertexArray(cubeVAO);
        glBindTexture(GL_TEXTURE_2D, cubeTexture);  // 由于纹理单元0默认使用,所有不需要激活
        model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        model = glm::mat4(1.0f);;
        model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        // 渲染地面
        glBindVertexArray(planeVAO);
        glBindTexture(GL_TEXTURE_2D, floorTexture);
        model = glm::mat4(1.0f);
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 6);
        // 渲染五个草
        glBindVertexArray(transparentVAO);
        glBindTexture(GL_TEXTURE_2D, transparentTexture);
        for (GLuint i = 0; i < vegetation.size(); i++)
        {
            model = glm::mat4(1.0f);;
            model = glm::translate(model, vegetation[i]);
            glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
            glDrawArrays(GL_TRIANGLES, 0, 6);
        }
        glBindVertexArray(0);

        // Swap the buffers
        glfwSwapBuffers(window);
        printError();
    }

    glfwTerminate();
    return 0;
}

// 从patj文件路径加载纹理,返回纹理的引用(alpha表示纹理是否为RGBA类型)
GLuint loadTexture(const GLchar* path, GLboolean alpha)
{
    std::cout << path << std::endl;
    //创建纹理ID并加载 
    GLuint textureID;
    glGenTextures(1, &textureID);
    int width, height;
    // 按照类型加载纹理
    unsigned char* image = SOIL_load_image(path, &width, &height, 0, alpha ? SOIL_LOAD_RGBA : SOIL_LOAD_RGB);
    // 将纹理绑定到对应类型上
    glBindTexture(GL_TEXTURE_2D, textureID);
    // 将图片数据传输到纹理中
    printError();
    glTexImage2D(GL_TEXTURE_2D, 0, alpha ? GL_RGBA : GL_RGB, width, height, 0, alpha ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, image);
    printError();
    // 生成多级渐近纹理
    glGenerateMipmap(GL_TEXTURE_2D);

    // 设置纹理环绕和过滤方式(透明的防止环绕上下和左右插值相关,使用GL_TO_EDGE边缘拉伸的环绕方式)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, alpha ? GL_CLAMP_TO_EDGE : GL_REPEAT);	
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, alpha ? GL_CLAMP_TO_EDGE : GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // 解绑释放图片内存
    glBindTexture(GL_TEXTURE_2D, 0);
    SOIL_free_image_data(image);
    return textureID;
}

// 打印错误信息
void printError()
{
    GLuint id = glGetError();
    if (id)
        std::cout << id << std::endl;
}

#pragma region "User input"

// Moves/alters the camera positions based on user input
void Do_Movement()
{
    // Camera controls
    if (keys[GLFW_KEY_W])
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if (keys[GLFW_KEY_S])
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if (keys[GLFW_KEY_A])
        camera.ProcessKeyboard(LEFT, deltaTime);
    if (keys[GLFW_KEY_D])
        camera.ProcessKeyboard(RIGHT, deltaTime);
}

// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
    if (key >= 0 && key < 1024)
    {
        if (action == GLFW_PRESS)
            keys[key] = true;
        else if (action == GLFW_RELEASE)
            keys[key] = false;
    }
}


void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    GLfloat xoffset = xpos - lastX;
    GLfloat yoffset = lastY - ypos;

    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    camera.ProcessMouseScroll(yoffset);
}

#pragma endregion
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: QML是一种用户界面定义语言,用于快速和轻松地创建跨平台的应用程序界面。与传统的图形对话框编程不同,QML通过使用声明性语法和元素组件来描述应用程序的图形界面。这使得界面的设计变得更加直观和易于理解。 OpenGL是一个用于开发图形应用程序的跨平台编程接口。它提供了一套功能强大的图形函数,可用于绘制2D和3D图形。OpenGL可以用于各种用途,如游戏开发、虚拟现实、数据可视化等。 在QML中使用OpenGL可以实现更高级的视觉效果和图形渲染。QML提供了一个OpenGL组件,可以轻松地将OpenGL内容嵌入到QML界面中。在QML中使用OpenGL,可以利用OpenGL的强大功能来实现复杂的图形效果和互动。 为了学习QML和OpenGL的结合使用,可以参考一些教程和文档。Qt官方网站提供了丰富的教程和示例,包括了使用QML和OpenGL的示例代码和文档。此外,还有一些第三方资源提供了有关QML和OpenGL的教程和示例代码。 学习QML和OpenGL的结合使用需要对QML和OpenGL的基础知识有一定的了解。首先,需要学习QML的语法和基本概念,如元素、属性和信号。然后,可以学习OpenGL的基本概念和函数,并了解如何在QML中使用OpenGL。 总之,QML和OpenGL的结合使用可以帮助我们创建更强大、更复杂的图形界面和视觉效果。通过学习相关教程和文档,我们可以掌握如何在QML中使用OpenGL,并利用其强大的功能来实现各种图形效果。 ### 回答2: QMl是一种用于创建用户界面的声明性语言,而OpenGL是一种用于实现3D图形渲染的开放图形库。结合起来,QML+OpenGL可以用于创建具有高效的3D图形渲染和动画效果的用户界面。 QML可以通过声明式的方式创建用户界面元素,并且具有良好的可视化能力和易于理解的语法。它支持丰富的UI控件和交互方式,同时还能轻松集成JavaScript代码来实现复杂的逻辑运算。在使用QML创建界面时,可以使用Qt Quick Controls提供的控件库或者自定义控件来满足特定需求。 而OpenGL是一种底层的图形库,用于实现高性能的3D图形渲染。通过OpenGL,可以直接进行硬件加速的图形操作,同时还可以利用图形硬件的强大计算能力,实现复杂的图形效果和计算。OpenGL提供了多种绘制图形的函数和接口,可以绘制点、线、多边形等不同类型的图形,并且可以进行纹理贴图、光照、阴影等高级渲染效果的实现。 QML+OpenGL的结合可以实现更为灵活且高效的用户界面设计。通过QML可以创建用户友好的界面元素,而OpenGL则可以在这些界面上实现更为复杂的3D图形渲染效果。在QML中,可以通过OpenGL的接口进行绘制和渲染操作,同时还可以将OpenGL渲染的结果嵌入到QML中,与其他界面元素进行交互。因此,通过学习QML+OpenGL教程,可以掌握如何使用QML和OpenGL结合起来创建出更为丰富和具有吸引力的用户界面。 ### 回答3: QML和OpenGL是两个不同的技术,但可以结合使用来创建各种交互式和图形密集型应用程序。下面是关于QML和OpenGL的简要介绍: QML是一种声明式的用户界面语言,用于创建功能丰富、跨平台的应用程序。它使用一种类似于JSON的语法来描述用户界面,可以通过编写更少的代码实现复杂的交互效果。QML具有良好的可扩展性和可重用性,并且易于理解和学习。它是Qt框架的一部分,因此可以与C++和其他Qt模块(如QtQuick)进行混合编程。 OpenGL是一种跨平台的图形库,用于渲染3D图形和执行其他图形操作。它提供了一套功能强大的API,可以绘制复杂的图形和执行高性能的计算。OpenGL可用于在各种设备上实现流畅的图形渲染,包括计算机、移动设备和嵌入式系统等。它支持多种图形效果,如阴影、反射、纹理映射和动画等。 QML和OpenGL可以结合使用来创建更具吸引力和交互性的用户界面。通过将QML和OpenGL结合起来,可以在QML界面中嵌入OpenGL渲染区域,并通过QML的声明式语法控制OpenGL渲染的效果和交互行为。这种混合编程的方法将QML的易用性和OpenGL的功能强大结合在一起,可以实现更高级的图形效果和用户交互。 总结而言,QML和OpenGL都是强大的技术,分别用于创建用户界面和图形渲染。结合使用它们可以实现各种复杂的应用程序,并为用户提供更好的视觉体验和交互性。如果您对QML和OpenGL有兴趣,可以参考官方文档和教程,以了解更多详细信息和开始使用它们。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仰望—星空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值