OpenGL-第一个三角形与矩形(两个三角形)

目录

新建项目,进行配置

项目结构

项目属性配置

依赖项设置

三角形代码

部分函数解释

main.cpp

运行截图

矩形代码

main.cpp

运行截图

简单的三角形与矩形

白色三角形

白色矩形

全部代码下载


参考:LearnOpenGL

新建项目,进行配置

项目结构

项目结构图

项目属性配置

头文件及库的目录

注意,读者应该包含自己的,就是上篇文章OpenGL-VS2015配置GLFW库与GLAD库最后配置的目录,我嫌麻烦,将所有配置均放在了vs2015的VC下面,包含glfw、glad、freeglut、gltools等。

依赖项设置

依赖项设置图

三角形代码

glad.c是上篇文章在线配置glad库所得

三角形是画了三个点,使用点着色器以及片段着色器进行着色。你需要记住这些:

  • 顶点数组对象:Vertex Array Object,VAO
  • 顶点缓冲对象:Vertex Buffer Object,VBO
  • 索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO
流程

由图可知,VAO会从VBO中挖出有用的数据传给显卡,隔多少挖是根据VBO的数据来,第一个三角形中VBO中是3个一组,

所以,在用glAttributePointer去挖时是3个float挖一次,例如,glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

新手学到这里也不要轻易放弃,后面有国外大神写着色器类。就在教程的这篇文章中:

OpenGL-普通着色和Shader类(代码及使用方法)

如果第二天就看GLSL语言,着色器的编译链接等太烦了,还想看到效果。好多博主写到这里就没有了,我本来是想看别人博客跟着做的,一直没找到。所以,算是劝退警告,你可以看看简单的三角形(一些函数已经不用,不是3.x的),增强下自信心,简单了解一下VAO、VBO、VEO等,等学着色器类时回过头看一看。

核心库包含有 115 个函数+实用库包含有43个函数+辅助库包含有 31 个函数=189个函数

遇到哪些,我可能会写出来,建议大家下载一下OpenGL的所有函数,碰见不明白的看一看,当个手册。

部分函数解释

部分函数列表
函数名含义参数1含义参数2含义参数3含义参数4含义

void  glShaderSource

GLuint shader

GLsizei count

const GLchar * const *string

const GLint *length

着色器资源加载shader

   要被替换源代码的

着色器对象的句柄(ID)

count    指定字符串和长度数组中的元素数。string

    指定指向包含要加载到着色器的源代码的字符串的指针数组。

length

    指定字符串长度的数组。

GLuint glCreateShader

GLenum shaderType

创建着色器shaderType

    指定要创建的着色器

的类型。 只能是GL_VERTEX_SHADERGL_FRAGMENT_SHADER

      

void glCompileShader

(GLuint shader

编译着色器shader    指定要编译的着色器对象。      

void glGetShaderiv

GLuint shader,

GLenum pname,GLint *params

查询着色器的状态等shader指定要查询的着色器对象。pname

  指定着色器对象的参数。

可接受的符号名称为GL_SHADER_TYPE,

GL_DELETE_STATUS,

GL_COMPILE_STATUS,

GL_INFO_LOG_LENGTH,

GL_SHADER_SOURCE_LENGTH

params返回请求的参数结果值。  

void glLinkProgram

(GLuint program

链接着色器程序program指定要链接的program对象的句柄。      

main.cpp

//头文件-------------------------------------------------------------------------
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
//函数声明-----------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);

// 设置窗体宽高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
//着色器代码---------------------------------------------------------------------
//点着色器
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
//片段着色器
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

//主函数
int main()
{
	// glfw: 初始化和配置-------------------------------------------------
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //系统兼容
#endif

    //glfw 窗体生成--------------------------------------------------------------
	GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "firstTriangle", NULL, NULL);//设置标题、宽高等
	//判断窗体是否生成
	if (window == NULL)
	{
		std::cout << "创建GLFW窗口失败" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	// glad: 加载所有的OpenGL功能指针
	// ---------------------------------------
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "初始化GLAD失败" << std::endl;
		return -1;
	}

	// 建立并编译着色器--------------------------------------------------------------
	// 点着色器
	int vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);
	// 检查点着色器是否有错误
	int success;
	char infoLog[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		std::cout << "错误:顶点着色器编译失败\n" << infoLog << std::endl;
	}
	// 片段着色器
	int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);
	// 检查片段着色器是否有错误
	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
		std::cout << "错误:片段着色器编译失败\n" << infoLog << std::endl;
	}
	// 链接着色器 shaders
	int shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);
	// 检查着色器错误
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
	}
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	// 设置点数据 (还有缓冲) 配置点的属性(包含点坐标等)
	float vertices[] = {
		-0.5f, -0.5f, 0.0f, // left  
		0.5f, -0.5f, 0.0f, // right 
		0.0f,  0.5f, 0.0f  // top   
	};
	//顶点数组对象、顶点缓冲对象的绑定
	unsigned int VBO, VAO;
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &VBO);
	// 绑定顶点数组, 然后绑定并设置缓冲, 最后配置顶点属性.
	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

	//注意这是允许的,对glVertexAttribPointer的调用将VBO注册为顶点属性的绑定顶点缓冲区对象,所以之后我们可以安全地解除绑定
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	// 您可以在之后取消绑定VAO,以便其他VAO调用不会意外地修改此VAO,但这种情况很少发生。无论如何, 
	// 修改其他VAO需要调用glBindVertexArray,因此我们通常不会在不直接需要时解除VAO(VBO同样)的绑定。
	glBindVertexArray(0);

	// 取消注释此调用会绘制线框多边形。
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	
	while (!glfwWindowShouldClose(window))
	{
		// 输入
		processInput(window);

		// 清除屏幕
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		// 画第一个三角形
		glUseProgram(shaderProgram);
		glBindVertexArray(VAO); //可以知道我们只有一个三角形VAO,没必要每次都绑定它,但是我们这么做会让代码有一点组织性
		glDrawArrays(GL_TRIANGLES, 0, 3);
		// glBindVertexArray(0); //没必要每次都解绑 

		// glfw: 交换buffers和poll的IO事件 (按键按下/释放,鼠标移动等.)
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	// 一旦他们超出已有的资源,就取消所有资源的分配:
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);

	// 终止,清空之前所有的GLFW的预分配资源
	glfwTerminate();
	return 0;
}

// 查询GLFW相关按键是否被按下/释放,根据情况作出反应
void processInput(GLFWwindow *window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}

// glfw:无论窗口大小何时改变(由操作系统或用户自己)这个回调函数将会被执行
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	//确定viewport与新的窗口尺寸匹配; 请注意,宽度和高度将明显大于显示器上指定的宽度和高度。
	glViewport(0, 0, width, height);
}

运行截图

运行截图

矩形代码

矩形是画了4个点,以索引的方式,三个点组成一个三角形,用EBO可以节约资源。

main.cpp

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

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);

// 设置窗体宽高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
//点着色器
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
//片段着色器
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

//主函数
int main()
{
	// glfw: 初始化和配置
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif

														 // glfw 窗体生成
	GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "firstTriangle", NULL, NULL);//设置标题
																							  //判断窗体是否生成
	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	// glad: 加载所有的OpenGL功能指针
	// ---------------------------------------
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	// 建立并编译着色器
	// ------------------------------------
	// 点着色器
	int vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);
	// 检查点着色器是否有错误
	int success;
	char infoLog[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	// 片段着色器
	int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);
	// 检查片段着色器是否有错误
	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	// 链接着色器 shaders
	int shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);
	// 检查着色器错误
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
	}
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	// 设置点数据 (还有缓冲) 配置点的属性(包含点坐标等) 这里设置了4个,将以索引的方式选择点来画三角形
	float vertices[] = {
		0.5f,  0.5f, 0.0f,  // top right
		0.5f, -0.5f, 0.0f,  // bottom right
		-0.5f, -0.5f, 0.0f,  // bottom left
		-0.5f,  0.5f, 0.0f   // top left 
	};
	unsigned int indices[] = {  // note that we start from 0!
		0, 1, 3,  // 第一个三角形选择索引为 0 1 3的三个点
		1, 2, 3   // 第一个三角形选择索引为 1 2 3的三个点
	};
	unsigned int VBO, VAO, EBO;
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &VBO);
	glGenBuffers(1, &EBO);  //注意,这里使用EBO作为缓冲对象
	// 绑定顶点数组, 然后绑定并设置缓冲, 最后配置顶点属性.
	glBindVertexArray(VAO);

	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

	//注意这是允许的,对glVertexAttribPointer的调用将VBO注册为顶点属性的绑定顶点缓冲区对象,所以之后我们可以安全地解除绑定
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// 记住:当VAO处于活动状态时,不要取消绑定EBO,因为绑定元素缓冲对象IS存储在VAO中; 保持EBO的约束力。
	//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

	// 您可以在之后取消绑定VAO,以便其他VAO调用不会意外地修改此VAO,但这种情况很少发生。无论如何, 
	// 修改其他VAO需要调用glBindVertexArray,因此我们通常不会在不直接需要时解除VAO(VBO同样)的绑定。
	glBindVertexArray(0);


	// 取消注释此调用会绘制线框多边形。
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	// render loop
	// -----------
	while (!glfwWindowShouldClose(window))
	{
		// 输入
		// -----
		processInput(window);

		// render
		// ------
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		// 画第一个三角形
		glUseProgram(shaderProgram);
		glBindVertexArray(VAO); //可以知道我们只有一个三角形VAO,没必要每次都绑定它,但是我们这么做会让代码有一点组织性
		//glDrawArrays(GL_TRIANGLES, 0, 6);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		// glBindVertexArray(0); //没必要每次都解绑 

		// 交换buffers和poll的IO事件 (按键按下/释放,鼠标移动等.)
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	// optional: 一旦他们超出已有的资源,就取消所有资源的分配:
	// ------------------------------------------------------------------------
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);

	// glfw:终止,清空之前所有的GLFW的预分配资源
	// ------------------------------------------------------------------
	glfwTerminate();
	return 0;
}

// process all input:查询GLFW相关按键是否被按下/释放,根据情况作出反应
void processInput(GLFWwindow *window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}

// glfw:无论窗口大小何时改变(由操作系统或用户自己)这个回调函数将会被执行
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	//确定viewport与新的窗口尺寸匹配; 请注意,宽度和高度将明显大于显示器上指定的宽度和高度。
	glViewport(0, 0, width, height);
}

运行截图

矩形截图

 

简单的三角形与矩形

是的,没有错,你已经懵逼了,为什么画个三角形,画个矩形这么麻烦,就不能来个函数,直接传个顶点位置就直接画吗?没错,可以的。LearnOpenGL 的教程有点急了,对于小白来说就吓到了。着色器那些完全可以以后讲。

白色三角形

你只需要新建一个控制台项目,不需要任何配置,在main.cpp中有如下代码即可绘制白色三角形。

三角形方式:

GL_TRIANGLES: 每三个点一组画一个三角形,三角形之间是独立的

GL_TRIANGLE_STRIP:从第三个点开始,每个点与前面的两个顶点组合画一个三角形,绘制的是线性连续的一组三角形

GL_TRIANGLE_FAN:第三个点开始,每个点与前一个点和第一个点组合画一个三角形,即扇形连续三角形

main.cpp

#include <GL/glut.h>
//显示函数
void myDisplay(void)

{
	glClear(GL_COLOR_BUFFER_BIT);
	glBegin(GL_TRIANGLES);
	glVertex3f(-0.5f, -0.5f, 0.0f);
	glVertex3f(0.5f, -0.5f, 0.0f);
	glVertex3f(0.0f, 0.5f, 0.0f);
	glEnd();
	glFlush();
}
//主函数
int main(int argc, char *argv[])

{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(800, 600);
	glutCreateWindow("白色三角形");
	glutDisplayFunc(&myDisplay);
	glutMainLoop();
	return 0;
}

“点”是一切图形的基础
OpenGL提供了一系列函数。它们都以glVertex开头,后面跟一个数字和1~2个字母。例如:
glVertex2d
glVertex2f
glVertex3f
glVertex3fv
数字表示参数的个数,例如,2表示有两个参数。
字母表示参数的类型

                  s表示16位整数(OpenGL中将这个类型定义为GLshort),
                   i表示32位整数(OpenGL中将这个类型定义为GLint和GLsizei),
                   f表示32位浮点数(OpenGL中将这个类型定义为GLfloat和GLclampf),
                   d表示64位浮点数(OpenGL中将这个类型定义为GLdouble和GLclampd)。
                   v表示传递的几个参数将使用指针的方式,见下面的例子。
这些函数除了参数的类型和个数不同以外,功能是相同的。例如,以下五个代码段的功能是等效的:
(一)glVertex2i(1, 3);
(二)glVertex2f(1.0f, 3.0f);
(三)glVertex3f(1.0f, 3.0f, 0.0f);
(四)glVertex4f(1.0f, 3.0f, 0.0f, 1.0f);
(五)GLfloat VertexArr3[] = {1.0f, 3.0f, 0.0f};
            glVertex3fv(VertexArr3);

白色矩形

那是不是矩形就要画四个?五边形呢?六边形呢?那岂不是很麻烦,并不是,画矩形有glRectf()函数

main.cpp

#include <GL/glut.h>
//显示函数
void myDisplay(void)
{
	glClear(GL_COLOR_BUFFER_BIT);//清除屏幕
	glRectf(-0.5f, -0.5f, 0.5f, 0.5f);
	glFlush();//强制刷新缓冲
}
//主函数
int main(int argc, char *argv[])

{
	glutInit(&argc, argv);//初始化
	glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);//设置显示模式
	glutInitWindowPosition(100, 100);//设置位置和窗口大小
	glutInitWindowSize(400, 400);
	glutCreateWindow("白色矩形");
	glutDisplayFunc(&myDisplay);//传递函数
	glutMainLoop();
	return 0;
}

一些函数的解释:

1、glutInit,对GLUT进行初始化,这个函数必须在其它的GLUT使用之前调用一次。其格式比较死板,一般照抄这句glutInit(&argc, argv)就可以了。

2、 glutInitDisplayMode,设置显示方式,其中GLUT_RGB表示使用RGB颜色,与之对应的还有GLUT_INDEX表示使用索引颜色。GLUT_SINGLE表示使用单缓冲,与之对应的还有GLUT_DOUBLE使用双缓冲。

3、glutInitWindowPosition,这个简单,设置窗口在屏幕中的位置。

4、glutInitWindowSize,这个也简单,设置窗口的大小。

5、glutCreateWindow,根据前面设置的信息创建窗口。参数将被作为窗口的标题。注意:窗口被创建后,并不立即显示到屏幕上。需要调用glutMainLoop才能看到窗口。

6、glutDisplayFunc,设置一个函数,当需要进行画图时,这个函数就会被调用(这个说法不准确,初学者先这么认为)。

7、glutMainLoop,进行一个消息循环,就是一直显示窗口。

以gl开头的函数都是OpenGL的标准函数,下面对用到的函数进行介绍:

1、glClear,清除。GL_COLOR_BUFFER_BIT表示清除颜色,glClear函数还可以清除其它的东西。

2、glRectf,画一个矩形。四个参数分别表示了位于对角线上的两个点的横、纵坐标。

3、glFlush,强制刷新缓冲,保证前面的OpenGL命令立即执行(而不是让它们在缓冲区中等待)。其作用跟fflush(stdout)类似。

全部代码下载

我的网盘
提取码:waxk

更多OpenGL知识:现代OpenGL入门教程

有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。

 

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lady_killer9

感谢您的打赏,我会加倍努力!

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

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

打赏作者

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

抵扣说明:

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

余额充值