[C++] [OpenGL] GLFW工具类

提示

虽然是基于我的代码写的,但是部分还是能用的

SimpleGL.hpp

这是最重要的头文件,所有代码都包括了它

#ifndef __SIMPLEGL_MAIN_HPP__
#define __SIMPLEGL_MAIN_HPP__

#if defined(USE_GLEW)
#include <GL/glew.h>
#else
#include <glad/glad.c>
#endif

namespace SimpleGL
{
	bool initGL()
	{
		#if defined(__glad_h_)
			if(!gladLoadGL()) return false;
		#elif defined(__glew_h__)
			if(glewInit() != GLEW_OK) return false;
		#endif
		return true;
	}
}

#include "Program.hpp"
#include "Camera.hpp"
#include "Error.hpp"
#include "Sprite.hpp"
#include "Texture.hpp"
#include "Model.hpp"
#include "GLFWUtils.hpp"

#endif

介绍一下这句代码
如果定义了“USE_GLEW”,就会导入GLEW,否则就导入GLAD
用glad.c是因为直接用glad.h就显示未定义资源(就很奇怪)

#if defined(USE_GLEW)
#include <GL/glew.h>
#else
#include <glad/glad.c>
#endif

Camera

Camera类被我重写了

#ifndef __SIMPLEGL_CAMERA_HPP__
#define __SIMPLEGL_CAMERA_HPP__
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <ctime>

namespace SimpleGL
{
	class LookAt
	{
		public:
			const float PITCH_MAX = 89.9f;

			enum PitchDirection
			{
				POSITIVE_Y,
				ZERO,
				NEGATIVE_Y
			};
			enum YawDirection
			{
				POSITIVE_X,
				POSITIVE_Z,
				NEGATIVE_X,
				NEGATIVE_Z
			};
			enum MovementDirection
			{
				FORWARD,
				BACKWARD,
				LEFT,
				RIGHT,
				UP,
				DOWN
			};
			enum MovementSpeed
			{
				VERY_SLOW,
				SLOW,
				NORMAL,
				FAST,
				VERY_FAST,
				CUSTOM
			};

		private:
			glm::vec3 m_position;
			glm::vec3 m_front;
			glm::vec3 m_up;
			glm::vec3 m_right;
			float m_yaw;
			float m_pitch;
			glm::mat4 m_view_matrix;
			MovementSpeed m_movement_speed;
			float m_speed;
			float m_sensitivity;

		public:
			LookAt(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 front = glm::vec3(0.0f, 0.0f, -1.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f))
			{
				m_position = position;
				m_front = front;
				m_up = up;
				calcAngle();
				update();
				m_movement_speed = MovementSpeed::NORMAL;
				m_speed = 1.0f;
				m_view_matrix = glm::mat4(1.0f);
				m_sensitivity = 0.05f;
			}
			LookAt(glm::vec3 position, float yaw, float pitch, glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f))
			{
				m_position = position;
				m_yaw = yaw;
				m_pitch = pitch;
				m_up = up;
				update();
				m_movement_speed = MovementSpeed::NORMAL;
				m_speed = 1.0f;
				m_view_matrix = glm::mat4(1.0f);
				m_sensitivity = 0.05f;
			}

		public:
			LookAt& operator=(const LookAt& lookAt)
			{
				m_position = lookAt.m_position;
				m_front = lookAt.m_front;
				m_up = lookAt.m_up;
				m_right = lookAt.m_right;
				m_yaw = lookAt.m_yaw;
				m_pitch = lookAt.m_pitch;
				m_view_matrix = lookAt.m_view_matrix;
				m_movement_speed = lookAt.m_movement_speed;
				m_speed = lookAt.m_speed;
				m_sensitivity = lookAt.m_sensitivity;
				return *this;
			}

		public:
			LookAt& calcAngle()
			{
				m_front = glm::normalize(m_front);
				m_yaw = glm::degrees(glm::atan(m_front.z, m_front.x));
				m_pitch = glm::degrees(glm::asin(m_front.y));
				return *this;
			}

			LookAt& update()
			{
				if (m_pitch > PITCH_MAX)
					m_pitch =  PITCH_MAX;
				if (m_pitch < -PITCH_MAX)
					m_pitch = -PITCH_MAX;

				float h = glm::cos(glm::radians(m_pitch));
				float x = glm::cos(glm::radians(m_yaw)) * h;
				float y = glm::sin(glm::radians(m_pitch));
				float z = glm::sin(glm::radians(m_yaw)) * h;
				m_front = glm::normalize(glm::vec3(x, y, z));

				m_right = glm::normalize(glm::cross(m_front, m_up));
				m_view_matrix = glm::lookAt(m_position, m_position + m_front, m_up);
				return *this;
			}

		public:
			LookAt& setYawDirection(YawDirection direction)
			{
				if (direction == YawDirection::POSITIVE_X)
					m_yaw = 0.0f;
				else if (direction == YawDirection::POSITIVE_Z)
					m_yaw = 90.0f;
				else if (direction == YawDirection::NEGATIVE_X)
					m_yaw = 180.0f;
				else if (direction == YawDirection::NEGATIVE_Z)
					m_yaw = 270.0f;
				update();
				return *this;
			}
			LookAt& setYaw(float yaw)
			{
				m_yaw = yaw;
				update();
				return *this;
			}
			LookAt& yaw(float degree)
			{
				m_yaw += degree;
				update();
				return *this;
			}
			float getYaw()
			{
				return m_yaw;
			}

			LookAt& setPitchDirection(PitchDirection direction)
			{
				if (direction == PitchDirection::POSITIVE_Y)
					m_pitch = PITCH_MAX;
				else if (direction == PitchDirection::ZERO)
					m_pitch = 0.0f;
				else if (direction == PitchDirection::NEGATIVE_Y)
					m_pitch = -PITCH_MAX;
				update();
				return *this;
			}
			LookAt& setPitch(float pitch)
			{
				m_pitch = pitch;
				update();
				return *this;
			}
			LookAt& pitch(float degree)
			{
				m_pitch += degree;
				update();
				return *this;
			}
			float getPitch()
			{
				return m_pitch;
			}

			LookAt& setPosition(glm::vec3 position)
			{
				m_position = position;
				update();
				return *this;
			}
			LookAt& move(glm::vec3 distance, float deltaTime = 1.0f)
			{
				m_position += distance * deltaTime;
				update();
				return *this;
			}
			glm::vec3 getPosition()
			{
				return m_position;
			}

			LookAt& setFront(glm::vec3 front)
			{
				m_front = front;
				calcAngle();
				update();
				return *this;
			}
			glm::vec3 getFront()
			{
				return m_front;
			}

			LookAt& setUp(glm::vec3 up)
			{
				m_up = up;
				update();
				return *this;
			}
			glm::vec3 getUp()
			{
				return m_up;
			}

			LookAt& setMovementSpeed(MovementSpeed movement_speed, float speed = 1.0f)
			{
				m_movement_speed = movement_speed;
				if (m_movement_speed == MovementSpeed::VERY_SLOW)
					m_speed = 0.2f;
				else if (m_movement_speed == MovementSpeed::SLOW)
					m_speed = 0.5f;
				else if (m_movement_speed == MovementSpeed::NORMAL)
					m_speed = 1.0f;
				else if (m_movement_speed == MovementSpeed::FAST)
					m_speed = 2.0f;
				else if (m_movement_speed == MovementSpeed::VERY_FAST)
					m_speed = 5.0f;
				else if (m_movement_speed == MovementSpeed::CUSTOM)
					m_speed = speed;
				return *this;
			}
			float getMovementSpeed()
			{
				return m_speed;
			}

			LookAt& move(MovementDirection direction, float deltaTime = 1.0f)
			{
				if (direction == MovementDirection::FORWARD)
					m_position += m_front * m_speed * deltaTime;
				else if (direction == MovementDirection::BACKWARD)
					m_position += (-m_front) * m_speed * deltaTime;
				else if (direction == MovementDirection::LEFT)
					m_position += (-m_right) * m_speed * deltaTime;
				else if (direction == MovementDirection::RIGHT)
					m_position += m_right * m_speed * deltaTime;
				else if (direction == MovementDirection::UP)
					m_position += m_up * m_speed * deltaTime;
				else if (direction == MovementDirection::DOWN)
					m_position += (-m_up) * m_speed * deltaTime;
				update();
				return *this;
			}

			inline float clocksToSeconds(clock_t clocks)
			{
				return float(clocks) / float(CLOCKS_PER_SEC);
			}

			LookAt& rotate(float deltaX, float deltaY)
			{
				m_yaw += deltaX * m_sensitivity;
				m_pitch += deltaY * m_sensitivity;
				update();
				return *this;
			}

			LookAt& setRotateSpeed(float sensitivity)
			{
				m_sensitivity = sensitivity;
				return *this;
			}
			float getSensitivity()
			{
				return m_sensitivity;
			}

			LookAt& setViewMatrix(glm::mat4 matrix)
			{
				m_view_matrix = matrix;
				return *this;
			}
			glm::mat4 getViewMatrix()
			{
				return m_view_matrix;
			}
	};

	class Projection
	{
		public:
			const float FOVY_MIN = 0.1f;
			const float FOVY_MAX = 179.9f;

			enum ProjectionType
			{
				ORTHO,
				FRUSTUM,
				PERSPECTIVE
			};

		private:
			ProjectionType m_projection_type;
			float m_fovy;
			float m_aspect;
			float m_left;
			float m_right;
			float m_bottom;
			float m_top;
			float m_near;
			float m_far;
			glm::mat4 m_projection_matrix;

		public:
			Projection(float near_plane = 0.1f, float far_plane = 100.0f)
			{
				m_near = near_plane;
				m_far = far_plane;
				m_projection_type = Projection::PERSPECTIVE;
				m_projection_matrix = glm::mat4(1.0f);
			}
			Projection(float fovy, float aspect, float near_plane = 0.1f, float far_plane = 100.0f)
			{
				m_fovy = fovy;
				m_aspect = aspect;
				m_near = near_plane;
				m_far = far_plane;
				m_projection_type = Projection::PERSPECTIVE;
				m_projection_matrix = glm::mat4(1.0f);
			}
			Projection(float l, float r, float b, float t,  float n = 0.1f, float f = 100.0f)
			{
				m_left = l;
				m_right = r;
				m_bottom = b;
				m_top = t;
				m_near = n;
				m_far = f;
				m_projection_type = Projection::PERSPECTIVE;
				m_projection_matrix = glm::mat4(1.0f);
			}

		public:
			Projection& operator=(const Projection& projection)
			{
				m_projection_type = projection.m_projection_type;
				m_fovy = projection.m_fovy;
				m_aspect = projection.m_aspect;
				m_left = projection.m_left;
				m_right = projection.m_right;
				m_bottom = projection.m_bottom;
				m_top = projection.m_top;
				m_near = projection.m_near;
				m_far = projection.m_far;
				m_projection_matrix = projection.m_projection_matrix;
				return *this;
			}

		public:
			Projection& setProjectionType(ProjectionType type)
			{
				m_projection_type = type;
				return *this;
			}
			ProjectionType getProjectionType()
			{
				return m_projection_type;
			}

			Projection& setFovy(float fovy)
			{
				m_fovy = fovy;
				update();
				return *this;
			}
			Projection& zoom(float degree)
			{
				m_fovy += degree;
				update();
				return *this;
			}
			float getFovy()
			{
				return m_fovy;
			}

			Projection& setAspect(float aspect)
			{
				m_aspect = aspect;
				update();
				return *this;
			}
			float getAspect()
			{
				return m_aspect;
			}

			Projection& set(float l, float r, float b, float t, float n = 0.1f, float f = 100.0f)
			{
				m_left = l;
				m_right = r;
				m_bottom = b;
				m_top = t;
				m_near = n;
				m_far = f;
				update();
				return *this;
			}
			Projection& set(float fovy, float aspect, float near_plane = 0.1f, float far_plane = 100.0f)
			{
				m_fovy = fovy;
				m_aspect = aspect;
				m_near = near_plane;
				m_far = far_plane;
				update();
				return *this;
			}

			Projection& update()
			{
				if (m_fovy < FOVY_MIN)
					m_fovy = FOVY_MIN;
				else if (m_fovy > FOVY_MAX)
					m_fovy = FOVY_MAX;

				if (m_projection_type == ProjectionType::ORTHO)
					m_projection_matrix = glm::ortho(m_left, m_right, m_bottom, m_top, m_near, m_far);
				else if (m_projection_type == ProjectionType::FRUSTUM)
					m_projection_matrix = glm::frustum(m_left, m_right, m_bottom, m_top, m_near, m_far);
				else if (m_projection_type == ProjectionType::PERSPECTIVE)
					m_projection_matrix = glm::perspective(m_fovy, m_aspect, m_near, m_far);
				return *this;
			}

			Projection& setProjectionMatrix(glm::mat4 matrix)
			{
				m_projection_matrix = matrix;
				return *this;
			}
			glm::mat4 getProjectionMatrix()
			{
				return m_projection_matrix;
			}
	};

	class Camera : public LookAt, public Projection
	{
		public:
			Camera() : LookAt(), Projection() {}
			Camera(glm::vec3 position, glm::vec3 front, glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f)) : LookAt(position, front, up), Projection() {}
			Camera(glm::vec3 position, float yaw, float pitch, glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f)) : LookAt(position, yaw, pitch, up), Projection() {}
	};
}

#endif

GLFWUtils

最后才是今天的主角

#ifndef __SIMPLEGL_GLFWUTILS_HPP__
#define __SIMPLEGL_GLFWUTILS_HPP__
#include "SimpleGL.hpp"
#include <GLFW/glfw3.h>
#include <string>

namespace SimpleGL
{
	class GLFWUtils
	{
		public:
			static void processInput(GLFWwindow* window, Camera& camera, float deltaTime)
			{
				if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
					glfwSetWindowShouldClose(window, true);

				if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
					camera.move(SimpleGL::Camera::MovementDirection::FORWARD, deltaTime);
				if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
					camera.move(SimpleGL::Camera::MovementDirection::BACKWARD, deltaTime);
				if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
					camera.move(SimpleGL::Camera::MovementDirection::LEFT, deltaTime);
				if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
					camera.move(SimpleGL::Camera::MovementDirection::RIGHT, deltaTime);
				if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS)
					camera.move(SimpleGL::Camera::MovementDirection::UP, deltaTime);
				if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS)
					camera.move(SimpleGL::Camera::MovementDirection::DOWN, deltaTime);

				if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_PRESS)
				{
					float speed = camera.getMovementSpeed();
					speed -= 0.01f;
					speed = std::max(speed, 0.1f);
					camera.setMovementSpeed(SimpleGL::Camera::MovementSpeed::CUSTOM, speed);
				}
				else if (glfwGetKey(window, GLFW_KEY_F2) == GLFW_PRESS)
				{
					float speed = camera.getMovementSpeed();
					speed += 0.01f;
					speed = std::min(speed, 5.0f);
					camera.setMovementSpeed(SimpleGL::Camera::MovementSpeed::CUSTOM, speed);
				}
			}

			static void processCursor(Camera& camera, glm::vec2& lastPos, glm::vec2 currentPos, bool& isFirst)
			{
				if (isFirst)
				{
					lastPos = currentPos;
					isFirst = false;
				}

				glm::vec2 delta = currentPos - lastPos;
				lastPos = currentPos;
				camera.rotate(-delta.x, delta.y);
			}

			static void processResize(Camera& camera, int width, int height)
			{
				camera.setAspect(float(width) / float(height));
				glViewport(0, 0, width, height);
			}

			static void processScroll(Camera& camera, float offset)
			{
				camera.zoom(offset);
			}

			template<typename T>
			static T* getUserData(GLFWwindow* window)
			{
				return (T*)glfwGetWindowUserPointer(window);
			}

			static GLFWwindow* initWindow_FullScreen(std::string title, int major = 3, int minor = 3)
			{
				glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, major);
				glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, minor);
				glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

				GLFWmonitor* monitor = glfwGetPrimaryMonitor();
				const GLFWvidmode* videomode = glfwGetVideoMode(monitor);
				GLFWwindow* window = glfwCreateWindow(videomode->width, videomode->height, title.data(), monitor, NULL);
				glfwMakeContextCurrent(window);

				return window;
			}

			static void disableCursor(GLFWwindow* window)
			{
				if (window != nullptr)
					glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
			}

			static void calcDeltaTime(float& lastTime, float& deltaTime)
			{
				float currentTime = glfwGetTime();
				deltaTime = currentTime - lastTime;
				lastTime = currentTime;
			}
			
			template<typename T>
			static T* newUserData(GLFWwindow* window)
			{
				T* data = new T;
				glfwSetWindowUserPointer(window, data);
				return data;
			}
	};
}

#endif

简单的示例

最最最后是一个简单的示例

//#define USE_GLFW
#include "SimpleGL/SimpleGL.hpp"
#include <GLFW/glfw3.h>
#include <iostream>

class RenderableSprite : public SimpleGL::Sprite
{
	public:
		GLuint VAO;
		GLuint count;
		GLenum mode;

	public:
		RenderableSprite()
		{
			VAO = 0;
			count = 0;
			mode = GL_TRIANGLES;
		}

		~RenderableSprite()
		{
			glDeleteVertexArrays(1, &VAO);
		}

	public:
		RenderableSprite& render(SimpleGL::Program& program)
		{
			program.setUniform("u_model", getModelMatrix());
			glBindVertexArray(VAO);
			glDrawArrays(mode, 0, count);
			glBindVertexArray(0);
			return *this;
		}
};

struct Data
{
	SimpleGL::Camera camera;

	SimpleGL::Program program;
	
	GLuint FBO;
	GLuint texture;

	RenderableSprite axis;
	RenderableSprite model;

	bool isFirst;
	glm::vec2 lastPos;
	float lastTime;
	float deltaTime;
};

void processInput(GLFWwindow* window);
void cursor_pos(GLFWwindow* window, double x, double y);
void framebuffer_size(GLFWwindow* window, int width, int height);
void scroll(GLFWwindow* window,double x,double y);

int main()
{
	glfwInit();
	GLFWwindow* window = SimpleGL::GLFWUtils::initWindow_FullScreen("OpenGL 3.3");
	glfwSetCursorPosCallback(window, cursor_pos);
	glfwSetFramebufferSizeCallback(window, framebuffer_size);
	glfwSetScrollCallback(window,scroll);
	SimpleGL::GLFWUtils::disableCursor(window);

	if (!SimpleGL::initGL())
	{
		std::cout << "Failed to init GL." << std::endl;
		return -1;
	}

	glEnable(GL_DEPTH_TEST);

	Data* data = SimpleGL::GLFWUtils::newUserData<Data>(window);

	data->isFirst = true;
	data->lastTime = glfwGetTime();

	data->camera.setPosition(glm::vec3(1, 1, 1));
	data->camera.setFront(glm::vec3(-1, -1, -1));
	data->camera.set(60, 1);

	bool success = data->program.loadProgramFromFile("shader/vertex.vert", "shader/lighting.frag");
	if (!success)
	{
		std::cout << "Failed to load program." << std::endl;
		std::cout << data->program.getError() << std::endl;
		return -1;
	}

	GLfloat axis_ver[] =
	{
		-100.0f, 0.0f, 0.0f,
		    100.0f, 0.0f, 0.0f,
		    0.0f, -100.0f, 0.0f,
		    0.0f, 100.0f, 0.0f,
		    0.0f, 0.0f, -100.0f,
		    0.0f, 0.0f, 100.0f
	    };

	GLfloat axis_col[] =
	{
		0.0f, 0.0f, 0.0f,
		1.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f
	};

	GLuint axis_vao;
	GLuint axis_vbo[2];

	glGenVertexArrays(1, &axis_vao);
	glGenBuffers(2, axis_vbo);
	glBindVertexArray(axis_vao);
	glBindBuffer(GL_ARRAY_BUFFER, axis_vbo[0]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(axis_ver), axis_ver, GL_STATIC_DRAW);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), nullptr);
	glBindBuffer(GL_ARRAY_BUFFER, axis_vbo[1]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(axis_col), axis_col, GL_STATIC_DRAW);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), nullptr);
	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glDeleteBuffers(2, axis_vbo);

	RenderableSprite axis;
	axis.VAO = axis_vao;
	axis.count = sizeof(axis_ver) / sizeof(float) / 3;
	axis.mode = GL_LINES;

	data->axis = axis;

	while (!glfwWindowShouldClose(window))
	{
		SimpleGL::GLFWUtils::calcDeltaTime(data->lastTime,data->deltaTime);

		processInput(window);

		glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		data->program.useProgram();
		data->program.setUniform("u_projection", data->camera.getProjectionMatrix());
		data->program.setUniform("u_view", data->camera.getViewMatrix());
		data->program.setUniform("u_cameraposition", data->camera.getPosition());

		data->program.setUniform("u_enableUniformColor", false);
		data->program.setUniform("u_enableTexture", false);
		data->program.setUniform("u_enableLighting", false);
		data->axis.render(data->program);

		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	delete data;

	glfwTerminate();
	return 0;
}

void processInput(GLFWwindow* window)
{
	Data* data = SimpleGL::GLFWUtils::getUserData<Data>(window);
	SimpleGL::GLFWUtils::processInput(window,data->camera,data->deltaTime);
}

void cursor_pos(GLFWwindow* window, double x, double y)
{
	Data* data = SimpleGL::GLFWUtils::getUserData<Data>(window);
	glm::vec2 currentPos = glm::vec2(x, y);
	SimpleGL::GLFWUtils::processCursor(data->camera,data->lastPos,currentPos,data->isFirst);
}

void framebuffer_size(GLFWwindow* window, int width, int height)
{
	Data* data = SimpleGL::GLFWUtils::getUserData<Data>(window);
	SimpleGL::GLFWUtils::processResize(data->camera,width,height);
}

void scroll(GLFWwindow* window,double x,double y)
{
	Data* data = SimpleGL::GLFWUtils::getUserData<Data>(window);
	SimpleGL::GLFWUtils::processScroll(data->camera,y);
} 

运行结果

结果就算了,就一坐标轴

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值