【OpenGL ES】Windows上OpenGL环境搭建

1 前言

        Windows 的图形 API 是 DirectX,对 OpenGL 的支持比较有限(系统自带的 opengl32.dll 仅支持 OpenGL 1.1 版本),因此在 Windows 上进行OpenGL 开发时,通常需要借助第三方库或工具来支持更高版本的 OpenGL 功能。

        目前主流的方案 GLFW / freeglut + Glad / GLEW。其中 GLFW 和 freeglut 都是用于管理 OpenGL 上下文、窗口、输入的开源库,它们可以相互替换;Glad 和 GLEW 都是用于管理 OpenGL 函数指针加载的库(即 OpenGL 加载库),它们也可以相互替换。因此有 4 种搭配方式:GLFW + Glad、GLFW + GLEW、freeglut + Glad、freeglut + GLEW。

GLFW 和 freeglut 的区别如下。

特性GLFWfreeglut
设计目标现代轻量级上下文管理GLUT 兼容,教学 / 原型开发
API 风格直接、灵活回调驱动,固定流程
OpenGL 工具提供几何绘制等工具函数
输入处理直接访问输入状态通过回调机制
Vulkan 支持
多线程支持友好有限
适用场景高性能应用、游戏、Vulkan 项目教学、旧项目维护、快速原型

        Glad 和 GLEW 的区别如下。

特性Glad GLEW
生成方式通过在线生成器或工具生成定制代码预编译的通用库(支持大多数扩展)
灵活性高度灵活(可选择 API 版本和扩展)相对固定(默认加载所有支持的扩展)
维护状态活跃维护(现代 OpenGL 首选)维护较少(旧项目中使用较多)
初始化需显式调用 gladLoadGL()需显式调用 glewInit()
头文件仅需 glad/glad.h(单头文件)需 GL/glew.h + 链接库
扩展控制生成时选择扩展,减少冗余默认加载所有扩展,可能冗余
兼容性支持 OpenGL ES、Vulkan 等主要专注 OpenGL

        由上述对比可知,GLFW  + Glad 是 4 种搭配方式中的最推荐的搭配方式。

        本文所有 Demo 的完整代码见 → Windows上 GLFW + Glad、GLFW + GLEW、freeglut + Glad、freeglut + GLEW 环境搭建

2 环境搭建

        本节将介绍 GLFW、freeglut、Glad、GLEW 的环境搭建,用户可以根据运行搭配情况,选择部分依赖库搭建到自己的项目中。

        为了让项目具有更好的兼容性和跨平台特性,本文基于 CMake 进行环境搭建。首先创建一个项目目录 glDemo,接着在 glDemo 中创建一个子目录 thirdParty,用于存放三方库文件,在 thirdParty 中创建 bin、include、lib、src 目录,分别用于存放二进制文件(.dll)、头文件(.h / .hpp)、库文件(.lib)、源文件(.c / .cpp),如下。

glDemo
  └─thirdParty
      ├─bin
      ├─include
      ├─lib
      └─src

 2.1 GLFW 环境搭建

        打开网站 https://www.glfw.org/download.html,点击以下按钮下载。

        glfw-3.4.bin.WIN64.zip 的内容如下。

        将 include 下面的所有文件复制到 thirdParty/include 中,将 lib-vc2022/glfw3.lib 复制到 thirdParty/lib/GLFW 中,复制后的目录结构如下。 

glDemo
  └─thirdParty
      ├─include
	  │    └─GLFW
      └─lib
           └─GLFW
                └─glfw3.lib

        如果用户想自己编译源码,可以去 https://github.com/glfw/glfw/releases 中下载源码,如 glfw-3.4.zip。

        解压后在根目录创建 build 目录,在 build 目录中创建 install 目录,用 CMake 配置如下。

        进入 build 目录中,双击 GLFW.sln 文件,会自动用 Visual Studio 打开项目,右键 ALL_BUILD,点击生成;再右键 INSTALL,点击生成。

        在 install 目录下面会生成以下文件,将 include 下面的所有文件复制到 thirdParty/include 中,将 lib/glfw3.lib 复制到 thirdParty/lib/GLFW 中。

2.2 freeglut 环境搭建

        打开网站 https://freeglut.sourceforge.net/index.php#download,点击以下按钮下载。用户也可以去 github 上下载(https://github.com/freeglut/freeglut/releases)。

        freeglut-3.6.0.tar.gz 的内容如下。

        解压后在根目录创建 build 目录,在 build 目录中创建 install 目录,用 CMake 配置如下。

        进入 build 目录中,双击 freeglut.sln 文件,会自动用 Visual Studio 打开项目,右键 ALL_BUILD,点击生成;再右键 INSTALL,点击生成。

        由于 freeglut 的 Debug 和 Release 版本生成的资源命名不一样,为了让项目能够在 Debug 和 Release 版本都能运行,需要将分别选择 Debug 和 Release 版本,都按照上述步骤生成一遍。

        在 install 目录下面会生成以下文件,带 d 后缀的文件(如:freeglutd.dll、freeglut_staticd.lib)是 Debug 版本的。

        将 include 下面的所有文件复制到 thirdParty/include 中,将 lib 下面的 freeglut.lib 和 freeglutd.lib 复制到 thirdParty/lib/freeglut 中,将 bin 下面的 freeglut.dll 和 freeglutd.dll 复制到 thirdParty/bin/freeglut 中,复制后的目录结构如下。 

glDemo
  └─thirdParty
      ├─bin
	  │    └─freeglut
      │         ├─freeglut.dll
      │         └─freeglutd.dll
      ├─include
	  │    └─GL
      └─lib
           └─freeglut
                ├─freeglut.lib
                └─freeglutd.lib

2.3 Glad 环境搭建

        打开网站 https://glad.dav1d.de/,根据需要进行配置,笔者的配置如下。

        配置完后点击底部的【GENERATE】按钮,下载 glad.zip。

        glad.zip 的内容如下。

        将 include 下面的所有文件复制到 thirdParty/include 中,将 src 下面的所有文件复制到 thirdParty/src/glad 中,复制后的目录结构如下。

glDemo
  └─thirdParty
      ├─include
	  │    ├─glad
	  │    └─KHR
      └─src
	  	   └─glad
                └─glad.c

2.4 GLEW 环境搭建

        打开网站 https://glew.sourceforge.net/,点击以下按钮下载。用户也可以去 github 上下载(https://github.com/nigels-com/glew/releases),但是需要自己编译源码。

        glew-2.1.0-win32.zip 的内容如下。

        将 include 下面的所有文件复制到 thirdParty/include 中,将 lib/Release/x64/glew32s.lib 复制到 thirdParty/lib/GLEW 中,复制后的目录结构如下。

glDemo
  └─thirdParty
      ├─include
	  │    └─GL
      └─lib
           └─GLEW
                └─glew32s.lib

3 运行搭配

3.1 GLFW + Glad 搭配

        项目目录结构如下。

        CMakeLists.txt

# 项目要求的最小CMake版本
cmake_minimum_required(VERSION 3.12)

# 声明项目名, 可以通过${PROJECT_NAME}访问, 在顶层CMakeLists.txt中, 也可以通过${CMAKE_PROJECT_NAME}访问
project("glDemo")

# 设置C++版本号
set(CMAKE_CXX_STANDARD 17)

message("BUILD TYPE: ${CMAKE_BUILD_TYPE}")

# 头文件目录列表 (便于以该路径为相对路径访问头文件)
include_directories(./thirdParty/include)

link_directories(./thirdParty/lib/GLFW)

# 源文件列表 (相对于当前CMakeLists.txt的路径)
file(GLOB_RECURSE SOURCES
    "thirdParty/src/**/*.c"
    "main.cpp")

message("source: ${SOURCES}")

# 添加可执行目标
add_executable(${CMAKE_PROJECT_NAME} ${SOURCES})

# 添加链接的三方库文件
target_link_libraries(${CMAKE_PROJECT_NAME}
    glfw3)

        main.cpp

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

using namespace std;

// 窗口尺寸变化回调函数
void resizeCallback(GLFWwindow* window, int width, int height)
{
	cout << "窗口尺寸变化, " << width << ", " << height << endl;
}

// 按键事件回调函数
void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
	cout << "键盘事件, " << key << ", " << scancode << ", " << action << ", " << mods << endl;
}

// 鼠标按键回调函数
void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
{
	cout << "鼠标按键事件, " << button << ", " << action << ", " << mods << endl;
}

// 光标进出窗口回调函数
void cursorEnterCallback(GLFWwindow* window, int entered)
{
	cout << "光标进出窗口事件, " << entered << endl;
}

// 光标移动回调函数
void cursorMoveCallback(GLFWwindow* window, double xpos, double ypos)
{
	cout << "光标移动事件, " << xpos << ", " << xpos << endl;
}

// 滚轮滑动回调函数
void scrollCallback(GLFWwindow* window, double xoffset, double yoffset)
{
	cout << "滚轮事件, " << xoffset << ", " << yoffset << endl;
}

// 关闭窗口回调
void closeCallback(GLFWwindow* window)
{
	cout << "关闭窗口回调" << endl;
}

// 注册监听器
void registerListeners(GLFWwindow* window)
{
	glfwSetFramebufferSizeCallback(window, resizeCallback);
	glfwSetKeyCallback(window, keyCallback);
	glfwSetMouseButtonCallback(window, mouseButtonCallback);
	glfwSetCursorEnterCallback(window, cursorEnterCallback);
	glfwSetCursorPosCallback(window, cursorMoveCallback);
	glfwSetScrollCallback(window, scrollCallback);
	glfwSetWindowCloseCallback(window, closeCallback);
}

int main()
{
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // 主版本号
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); // 次版本号
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 使用核心模式

	GLFWwindow* window = glfwCreateWindow(500, 500, "glDemo", NULL, NULL); // 窗体对象
	glfwMakeContextCurrent(window); // 设置当前窗体为OpenGL绘制舞台
	registerListeners(window); // 注册监听器

	// 加载 OpenGL API 指令
	if (!gladLoadGL())
	{
		cout << "Failed to initialize GLAD" << endl;
		glfwTerminate();
		return -1;
	}

	glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
	while (!glfwWindowShouldClose(window))
	{
		glfwPollEvents(); // 接收并分发窗口消息
		glClear(GL_COLOR_BUFFER_BIT);
		glfwSwapBuffers(window);
	}
	glfwDestroyWindow(window);
	glfwTerminate();
	return 0;
}

        运行效果如下。

3.2 GLFW + GLEW 搭配

        项目目录结构如下。

        CMakeLists.txt

# 项目要求的最小CMake版本
cmake_minimum_required(VERSION 3.12)

# 声明项目名, 可以通过${PROJECT_NAME}访问, 在顶层CMakeLists.txt中, 也可以通过${CMAKE_PROJECT_NAME}访问
project("glDemo")

# 设置C++版本号
set(CMAKE_CXX_STANDARD 17)

message("BUILD TYPE: ${CMAKE_BUILD_TYPE}")

# 头文件目录列表 (便于以该路径为相对路径访问头文件)
include_directories(./thirdParty/include)

link_directories(
    ./thirdParty/lib/GLFW
    ./thirdParty/lib/GLEW)

# 源文件列表 (相对于当前CMakeLists.txt的路径)
file(GLOB_RECURSE SOURCES "main.cpp")

message("source: ${SOURCES}")

# 添加可执行目标
add_executable(${CMAKE_PROJECT_NAME} ${SOURCES})

# 添加链接的三方库文件
target_link_libraries(${CMAKE_PROJECT_NAME}
    opengl32
    glew32s
    glfw3)

        main.cpp

#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>

using namespace std;

// 窗口尺寸变化回调函数
void resizeCallback(GLFWwindow* window, int width, int height)
{
	cout << "窗口尺寸变化, " << width << ", " << height << endl;
}

// 按键事件回调函数
void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
	cout << "键盘事件, " << key << ", " << scancode << ", " << action << ", " << mods << endl;
}

// 鼠标按键回调函数
void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
{
	cout << "鼠标按键事件, " << button << ", " << action << ", " << mods << endl;
}

// 光标进出窗口回调函数
void cursorEnterCallback(GLFWwindow* window, int entered)
{
	cout << "光标进出窗口事件, " << entered << endl;
}

// 光标移动回调函数
void cursorMoveCallback(GLFWwindow* window, double xpos, double ypos)
{
	cout << "光标移动事件, " << xpos << ", " << xpos << endl;
}

// 滚轮滑动回调函数
void scrollCallback(GLFWwindow* window, double xoffset, double yoffset)
{
	cout << "滚轮事件, " << xoffset << ", " << yoffset << endl;
}

// 注册监听器
void registerListeners(GLFWwindow* window)
{
	glfwSetFramebufferSizeCallback(window, resizeCallback);
	glfwSetKeyCallback(window, keyCallback);
	glfwSetMouseButtonCallback(window, mouseButtonCallback);
	glfwSetCursorEnterCallback(window, cursorEnterCallback);
	glfwSetCursorPosCallback(window, cursorMoveCallback);
	glfwSetScrollCallback(window, scrollCallback);
}

int main()
{
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // 主版本号
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); // 次版本号
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 使用核心模式

	GLFWwindow* window = glfwCreateWindow(500, 500, "glDemo", NULL, NULL); // 窗体对象
	glfwMakeContextCurrent(window); // 设置当前窗体为OpenGL绘制舞台
	registerListeners(window); // 注册监听器

	// 加载 OpenGL API 指令
	glewExperimental = GL_TRUE; // 确保GLEW能使用现代OpenGL方法
	if (glewInit() != GLEW_OK)
	{
		cout << "Failed to initialize GLEW" << endl;
		glfwTerminate();
		return -1;
	}

	glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
	while (!glfwWindowShouldClose(window))
	{
		glfwPollEvents(); // 接收并分发窗口消息
		glClear(GL_COLOR_BUFFER_BIT);
		glfwSwapBuffers(window);
	}
	glfwTerminate();
	return 0;
}

3.3 freeglut  + Glad 搭配

        项目目录结构如下。

        CMakeLists.txt

# 项目要求的最小CMake版本
cmake_minimum_required(VERSION 3.12)

# 声明项目名, 可以通过${PROJECT_NAME}访问, 在顶层CMakeLists.txt中, 也可以通过${CMAKE_PROJECT_NAME}访问
project("glDemo")

# 设置C++版本号
set(CMAKE_CXX_STANDARD 17)

message("BUILD TYPE: ${CMAKE_BUILD_TYPE}")

file(GLOB ASSETS "./thirdParty/bin/**/*.dll")

file(COPY ${ASSETS} DESTINATION ${CMAKE_BINARY_DIR})


# 头文件目录列表 (便于以该路径为相对路径访问头文件)
include_directories(./thirdParty/include)

link_directories(./thirdParty/lib/freeglut)

# 源文件列表 (相对于当前CMakeLists.txt的路径)
file(GLOB_RECURSE SOURCES
    "thirdParty/src/**/*.c"
    "main.cpp")

message("source: ${SOURCES}")

# 添加可执行目标
add_executable(${CMAKE_PROJECT_NAME} ${SOURCES})

# 添加链接的三方库文件
if (${CMAKE_BUILD_TYPE} STREQUAL "Debug")
    target_link_libraries(${CMAKE_PROJECT_NAME} freeglutd)
else ()
    target_link_libraries(${CMAKE_PROJECT_NAME} freeglut)
endif ()

        main.cpp

#include <glad/glad.h>
#include <GL/freeglut.h>
#include <iostream>

using namespace std;

// 窗口尺寸变化回调函数
void resizeCallback(int width, int height)
{
	cout << "窗口尺寸变化, " << width << ", " << height << endl;
}

// 按键事件回调函数
void keyCallback(unsigned char key, int xpos, int ypos)
{
	cout << "键盘事件, " << key << ", " << xpos << ", " << ypos << endl;
}

// 特殊按键事件回调函数 (功能键、方向键等)
void specialKeyCallback(int key, int xpos, int ypos)
{
	cout << "特殊键盘事件, " << key << ", " << xpos << ", " << ypos << endl;
}

// 鼠标按键回调函数
void mouseButtonCallback(int button, int state, int xpos, int ypos)
{
	cout << "鼠标按键事件, " << button << ", " << state << ", " << xpos << ", " << ypos << endl;
}

// 光标进出窗口回调函数
void cursorEnterCallback(int state)
{
	cout << "光标进出窗口事件, " << state << endl;
}

// 光标移动回调函数
void cursorMoveCallback(int xpos, int ypos)
{
	cout << "光标移动, " << xpos << ", " << xpos << endl;
}

// 光标拖拽回调函数
void cursorDragCallback(int xpos, int ypos)
{
	cout << "光标拖拽, " << xpos << ", " << ypos << endl;
}

// 滚轮滑动回调函数
void scrollCallback(int wheel, int direction, int xpos, int ypos)
{
	cout << "滚轮事件, " << wheel << ", " << direction << ", " << xpos << ", " << ypos << endl;
}

// 关闭窗口回调
void closeCallback()
{
	cout << "关闭窗口回调" << endl;
}

// 渲染回调
void drawCallback()
{
	glClear(GL_COLOR_BUFFER_BIT);
	glutSwapBuffers();
	glutPostRedisplay(); // 请求下一帧(创建循环)
}

// 注册监听器
void registerListeners()
{
	glutReshapeFunc(resizeCallback);
	glutKeyboardFunc(keyCallback);
	glutSpecialFunc(specialKeyCallback);
	glutMouseFunc(mouseButtonCallback);
	glutEntryFunc(cursorEnterCallback);
	glutPassiveMotionFunc(cursorMoveCallback);
	glutMotionFunc(cursorDragCallback);
	glutMouseWheelFunc(scrollCallback);
	glutCloseFunc(closeCallback);
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitContextVersion(3, 3); // 主版本号、次版本号
	glutInitContextProfile(GLUT_CORE_PROFILE);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
	glutInitWindowSize(500, 500);
	glutCreateWindow("glDemo");
	registerListeners(); // 注册监听器

	// 加载 OpenGL API 指令
	if (!gladLoadGL())
	{
		cout << "Failed to initialize GLAD" << endl;
		return -1;
	}

	glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
	glutDisplayFunc(drawCallback);
	glutMainLoop();
	return 0;
}

3.4 freeglut  + GLEW 搭配

        项目目录结构如下。

        CMakeLists.txt

# 项目要求的最小CMake版本
cmake_minimum_required(VERSION 3.12)

# 声明项目名, 可以通过${PROJECT_NAME}访问, 在顶层CMakeLists.txt中, 也可以通过${CMAKE_PROJECT_NAME}访问
project("glDemo")

# 设置C++版本号
set(CMAKE_CXX_STANDARD 17)

message("BUILD TYPE: ${CMAKE_BUILD_TYPE}")

file(GLOB ASSETS "./thirdParty/bin/freeglut/*")

file(COPY ${ASSETS} DESTINATION ${CMAKE_BINARY_DIR})

# 头文件目录列表 (便于以该路径为相对路径访问头文件)
include_directories(./thirdParty/include)

link_directories(
    ./thirdParty/lib/freeglut
    ./thirdParty/lib/GLEW)

# 源文件列表 (相对于当前CMakeLists.txt的路径)
file(GLOB_RECURSE SOURCES
    "main.cpp")

message("source: ${SOURCES}")

# 添加可执行目标
add_executable(${CMAKE_PROJECT_NAME} ${SOURCES})

# 添加链接的三方库文件
target_link_libraries(${CMAKE_PROJECT_NAME}
    opengl32
    glew32s)
if (${CMAKE_BUILD_TYPE} STREQUAL "Debug")
    target_link_libraries(${CMAKE_PROJECT_NAME} freeglutd)
else ()
    target_link_libraries(${CMAKE_PROJECT_NAME} freeglut)
endif ()

        main.cpp

#define GLEW_STATIC
#include <GL/glew.h>
#include <GL/freeglut.h>
#include <iostream>

using namespace std;

// 窗口尺寸变化回调函数
void resizeCallback(int width, int height)
{
	cout << "窗口尺寸变化, " << width << ", " << height << endl;
}

// 按键事件回调函数
void keyCallback(unsigned char key, int xpos, int ypos)
{
	cout << "键盘事件, " << key << ", " << xpos << ", " << ypos << endl;
}

// 特殊按键事件回调函数 (功能键、方向键等)
void specialKeyCallback(int key, int xpos, int ypos)
{
	cout << "特殊键盘事件, " << key << ", " << xpos << ", " << ypos << endl;
}

// 鼠标按键回调函数
void mouseButtonCallback(int button, int state, int xpos, int ypos)
{
	cout << "鼠标按键事件, " << button << ", " << state << ", " << xpos << ", " << ypos << endl;
}

// 光标进出窗口回调函数
void cursorEnterCallback(int state)
{
	cout << "光标进出窗口事件, " << state << endl;
}

// 光标移动回调函数
void cursorMoveCallback(int xpos, int ypos)
{
	cout << "光标移动, " << xpos << ", " << xpos << endl;
}

// 光标拖拽回调函数
void cursorDragCallback(int xpos, int ypos)
{
	cout << "光标拖拽, " << xpos << ", " << ypos << endl;
}

// 滚轮滑动回调函数
void scrollCallback(int wheel, int direction, int xpos, int ypos)
{
	cout << "滚轮事件, " << wheel << ", " << direction << ", " << xpos << ", " << ypos << endl;
}

// 关闭窗口回调
void closeCallback()
{
	cout << "关闭窗口回调" << endl;
}

// 渲染回调
void drawCallback()
{
	glClear(GL_COLOR_BUFFER_BIT);
	glutSwapBuffers();
	glutPostRedisplay(); // 请求下一帧(创建循环)
}

// 注册监听器
void registerListeners()
{
	glutReshapeFunc(resizeCallback);
	glutKeyboardFunc(keyCallback);
	glutSpecialFunc(specialKeyCallback);
	glutMouseFunc(mouseButtonCallback);
	glutEntryFunc(cursorEnterCallback);
	glutPassiveMotionFunc(cursorMoveCallback);
	glutMotionFunc(cursorDragCallback);
	glutMouseWheelFunc(scrollCallback);
	glutCloseFunc(closeCallback);
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitContextVersion(3, 3); // 主版本号、次版本号
	glutInitContextProfile(GLUT_CORE_PROFILE);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
	glutInitWindowSize(500, 500);
	glutCreateWindow("glDemo");
	registerListeners(); // 注册监听器

	// 加载 OpenGL API 指令
	glewExperimental = GL_TRUE; // 确保GLEW能使用现代OpenGL方法
	if (glewInit() != GLEW_OK)
	{
		cout << "Failed to initialize GLEW" << endl;
		return -1;
	}

	glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
	glutDisplayFunc(drawCallback);
	glutMainLoop();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

little_fat_sheep

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

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

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

打赏作者

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

抵扣说明:

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

余额充值