OpenGL笔记十之Shader类的封装

OpenGL笔记十之Shader类的封装

------ 2024-07-10 晚上

总结自bilibili赵新政老师的教程

code review!

文章目录

1.运行

2.目录结构

复制代码
.
└── 11_OpenGL_ShaderClass
    ├── application
    │   ├── Application.cpp
    │   ├── Application.h
    │   └── CMakeLists.txt
    ├── assets
    │   └── shaders
    │       ├── fragment.glsl
    │       └── vertex.glsl
    ├── CMakeLists.txt
    ├── glad.c
    ├── glframework
    │   ├── CMakeLists.txt
    │   ├── core.h
    │   ├── shader.cpp
    │   └── shader.h
    ├── main.cpp
    ├── thirdParty
    │   └── include
    │       ├── glad
    │       │   └── glad.h
    │       └── KHR
    │           └── khrplatform.h
    └── wrapper
        ├── checkError.cpp
        ├── checkError.h
        └── CMakeLists.txt

10 directories, 17 files

3.main.cpp

cpp 复制代码
#include <iostream>

#include "glframework/core.h"
#include "glframework/shader.h"
#include <string>
#include <assert.h>//断言
#include "wrapper/checkError.h"
#include "application/Application.h"

/*
*┌────────────────────────────────────────────────┐
*│ 目	   标: Shader类编写
*│ 讲    师: 赵新政(Carma Zhao)
*│ 拆分目标:
*│			1 实现Shader文件的读取
*│			2 实现Shader内容的编译链接 
*│			3 实现Shader查错的函数
*└────────────────────────────────────────────────┘
*/

GLuint vao;
Shader* shader = nullptr;

void OnResize(int width, int height) {
	GL_CALL(glViewport(0, 0, width, height));
	std::cout << "OnResize" << std::endl;
}

void OnKey(int key, int action, int mods) {
	std::cout << key << std::endl;
}



void prepareVAO() {
	//1 准备positions colors
	float positions[] = {
		-0.5f, -0.5f, 0.0f,
		0.5f, -0.5f, 0.0f,
		0.0f,  0.5f, 0.0f,
	};

	float colors[] = {
		1.0f, 0.0f,0.0f,
		0.0f, 1.0f,0.0f,
		0.0f, 0.0f,1.0f
	};

	unsigned int indices[] = {
		0, 1, 2
	};

	//2 VBO创建
	GLuint posVbo, colorVbo;
	glGenBuffers(1, &posVbo);
	glBindBuffer(GL_ARRAY_BUFFER, posVbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);

	glGenBuffers(1, &colorVbo);
	glBindBuffer(GL_ARRAY_BUFFER, colorVbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);

	//3 EBO创建
	GLuint ebo;
	glGenBuffers(1, &ebo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	//4 VAO创建
	glGenVertexArrays(1, &vao);
	glBindVertexArray(vao);

	//5 绑定vbo ebo 加入属性描述信息
	//5.1 加入位置属性描述信息
	glBindBuffer(GL_ARRAY_BUFFER, posVbo);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, (void*)0);

	//5.2 加入颜色属性描述数据
	glBindBuffer(GL_ARRAY_BUFFER, colorVbo);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, (void*)0);

	//5.3 加入ebo到当前的vao
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);

	glBindVertexArray(0);
}

void prepareShader() {
	shader = new Shader("assets/shaders/vertex.glsl","assets/shaders/fragment.glsl");
}

void render() {
	//执行opengl画布清理操作
	GL_CALL(glClear(GL_COLOR_BUFFER_BIT));

	//1 绑定当前的program
	shader->begin();
	
	//2 绑定当前的vao
	GL_CALL(glBindVertexArray(vao));
	//3 发出绘制指令
	glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
	glBindVertexArray(0);
	shader->end();
}


int main() {
	if (!app->init(800, 600)) {
		return -1;
	}

	app->setResizeCallback(OnResize);
	app->setKeyBoardCallback(OnKey);

	//设置opengl视口以及清理颜色
	GL_CALL(glViewport(0, 0, 800, 600));
	GL_CALL(glClearColor(0.2f, 0.3f, 0.3f, 1.0f));

	prepareShader();
	prepareVAO();
	while (app->update()) {
		render();
	}

	app->destroy();

	return 0;
}

4.application

4.1.CMakeLists.txt

cmake 复制代码
#递归将本文件夹下所有cpp放到FUNCS中
file(GLOB_RECURSE APP ./  *.cpp)

#将FUNCS中所有cpp编译为funcs这个lib库
add_library(app ${APP} )

target_include_directories(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(app PRIVATE glfw)

4.2.Application.h

cpp 复制代码
#pragma once 

/*
*┌────────────────────────────────────────────────┐
*│ 目	   标: 封装Application(表示了当前应用程序本身)
*│ 讲    师: 赵新政(Carma Zhao)
*│ 拆分目标:
*│
*│ 		1	单例类(全局唯一实例)
*│ 		2	成员变量 + 成员函数
*				2.1 成员函数-init(初始化)
*				2.2 成员函数-update(每一帧执行)
*				2.3 成员函数-destroy(结尾执行)
*│ 		3	响应回调函数(Resize)
*				3.1 声明一个函数指针ResizeCallback
*				3.2 声明一个ResizeCallback类型的成员变量
*				3.3 声明一个SetResizeCallback的函数 ,设置窗体变化响应回调函数
*				3.4 声明一个static的静态函数,用于响应glfw窗体变化
*				3.5 将静态函数设置到glfw的监听Resize监听当中
*				3.6 * 学会使用glfw的UserPointer
*│ 		4	响应键盘消息函数(KeyBoard)
*				3.1 声明一个static的静态函数,用于响应glfw的键盘事件
*				3.2 将静态函数设置到glfw的监听KeyCallback监听当中
*				3.3 声明一个函数指针KeyBoardCallback
*				3.4 声明一个KeyBoardCallback类型的成员变量
*				3.5 声明一个SetKeyBoardCallback的函数 ,设置键盘响应回调函数
*				3.6 * 学会使用glfw的UserPointer
*└────────────────────────────────────────────────┘
*/
#include <iostream>


#define app Application::getInstance()

class GLFWwindow;

using ResizeCallback = void(*)(int width, int height);
using KeyBoardCallback = void(*)(int key, int action, int mods);

class Application {
public:
	~Application();
	
	//用于访问实例的静态函数
	static Application* getInstance();

	bool init(const int& width = 800, const int& height = 600);

	bool update();

	void destroy();


	uint32_t getWidth()const { return mWidth; }
	uint32_t getHeight()const { return mHeight; }

	void setResizeCallback(ResizeCallback callback) { mResizeCallback = callback; }
	void setKeyBoardCallback(KeyBoardCallback callback) { mKeyBoardCallback = callback; }

private:
	//C++类内函数指针
	static void frameBufferSizeCallback(GLFWwindow* window, int width, int height);
	static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);

private:
	//全局唯一的静态变量实例
	static Application* mInstance;

	uint32_t mWidth{ 0 };
	uint32_t mHeight{ 0 };
	GLFWwindow* mWindow{ nullptr };

	ResizeCallback mResizeCallback{ nullptr };
	KeyBoardCallback mKeyBoardCallback{ nullptr };

	Application();
};

4.3.Application.cpp

cpp 复制代码
#include "Application.h"
#include<glad/glad.h>
#include<GLFW/glfw3.h>


//初始化Application的静态变量
Application* Application::mInstance = nullptr;
Application* Application::getInstance() {
	//如果mInstance已经实例化了(new出来了),就直接返回
	//否则需要先new出来,再返回
	if (mInstance == nullptr) {
		mInstance = new Application();
	}

	return mInstance;
}

Application::Application() {

}

Application::~Application() {

}


bool Application::init(const int& width, const int& height) {
	mWidth = width;
	mHeight = height;

	//1 初始化GLFW基本环境
	glfwInit();
	//1.1 设置OpenGL主版本号、次版本号
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	//1.2 设置OpenGL启用核心模式(非立即渲染模式)
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	//2 创建窗体对象
	mWindow = glfwCreateWindow(mWidth, mHeight, "OpenGLStudy", NULL, NULL);
	if (mWindow == NULL) {
		return false;
	}

	//**设置当前窗体对象为OpenGL的绘制舞台
	glfwMakeContextCurrent(mWindow);

	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
		std::cout << "Failed to initialize GLAD" << std::endl;
		return false;
	}
	
	glfwSetFramebufferSizeCallback(mWindow, frameBufferSizeCallback);

	//this就是当前全局唯一的Application对象
	glfwSetWindowUserPointer(mWindow, this);

	//键盘响应
	glfwSetKeyCallback(mWindow, keyCallback);

	return true;
}

bool Application::update() {
	if (glfwWindowShouldClose(mWindow)) {
		return false;
	}

	//接收并分发窗体消息
	//检查消息队列是否有需要处理的鼠标、键盘等消息
	//如果有的话就将消息批量处理,清空队列
	glfwPollEvents();

	//切换双缓存
	glfwSwapBuffers(mWindow);

	return true;
}

void Application::destroy() {
	//退出程序前做相关清理
	glfwTerminate();
}


void Application::frameBufferSizeCallback(GLFWwindow* window, int width, int height) {
	std::cout << "Resize" << std::endl;

	Application* self = (Application*)glfwGetWindowUserPointer(window);
	if (self->mResizeCallback != nullptr) {
		self->mResizeCallback(width, height);
	}

	//if (Application::getInstance()->mResizeCallback != nullptr) {
	//	Application::getInstance()->mResizeCallback(width, height);
	//}
}

void Application::keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
	Application* self = (Application*)glfwGetWindowUserPointer(window);
	if (self->mKeyBoardCallback != nullptr) {
		self->mKeyBoardCallback(key, action, mods);
	}
}

5.assets

5.1.shaders:vertex.glsl

glsl 复制代码
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 color;
void main()
{
   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
   color = aColor;
}

5.2.shaders:fragment.glsl

glsl 复制代码
#version 330 core
out vec4 FragColor;
in vec3 color;
void main()
{
   FragColor = vec4(color, 1.0f);
}

6.glframework

6.1.CMakeLists.txt

cmake 复制代码
file(GLOB_RECURSE FW ./  *.cpp)

add_library(fw ${FW} )

6.2.core.h

cpp 复制代码
#pragma once

//注意:glad头文件必须在glfw引用之前引用
#include <glad/glad.h>
#include <GLFW/glfw3.h>

6.3.shader.h

cpp 复制代码
#pragma once

#include "core.h"
#include<string>

class Shader {
public:
	Shader(const char* vertexPath, const char* fragmentPath);
	~Shader();
	
	void begin();//开始使用当前Shader

	void end();//结束使用当前Shader

private:
	//shader program
	//type:COMPILE LINK
	void checkShaderErrors(GLuint target,std::string type);

private:
	GLuint mProgram{ 0 };
};

6.4.shader.cpp

cpp 复制代码
#include"shader.h"
#include"../wrapper/checkError.h"

#include<string>
#include<fstream>
#include<sstream>
#include<iostream>

Shader::Shader(const char* vertexPath, const char* fragmentPath) {
	//声明装入shader代码字符串的两个string
	std::string vertexCode;
	std::string fragmentCode;

	//声明用于读取vs跟fs文件的inFileStream
	std::ifstream vShaderFile;
	std::ifstream fShaderFile;

	//保证ifstream遇到问题的时候可以抛出异常
	vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	try {
		//1 打开文件
		vShaderFile.open(vertexPath);
		fShaderFile.open(fragmentPath);
		
		//2 将文件输入流当中的字符串输入到stringStream里面
		std::stringstream vShaderStream, fShaderStream;
		vShaderStream << vShaderFile.rdbuf();
		fShaderStream << fShaderFile.rdbuf();

		//3 关闭文件
		vShaderFile.close();
		fShaderFile.close();

		//4 将字符串从stringStream当中读取出来,转化到code String当中
		vertexCode = vShaderStream.str();
		fragmentCode = fShaderStream.str();
	}
	catch (std::ifstream::failure& e) {
		std::cout << "ERROR: Shader File Error: " << e.what() << std::endl;
	}

	const char* vertexShaderSource = vertexCode.c_str();
	const char* fragmentShaderSource = fragmentCode.c_str();
	//1 创建Shader程序(vs、fs)
	GLuint vertex, fragment;
	vertex = glCreateShader(GL_VERTEX_SHADER);
	fragment = glCreateShader(GL_FRAGMENT_SHADER);

	//2 为shader程序输入shader代码
	glShaderSource(vertex, 1, &vertexShaderSource, NULL);
	glShaderSource(fragment, 1, &fragmentShaderSource, NULL);

	//3 执行shader代码编译 
	glCompileShader(vertex);
	//检查vertex编译结果
	checkShaderErrors(vertex, "COMPILE");
	
	glCompileShader(fragment);
	//检查fragment编译结果
	checkShaderErrors(fragment, "COMPILE");
	
	//4 创建一个Program壳子
	mProgram = glCreateProgram();

	//6 将vs与fs编译好的结果放到program这个壳子里
	glAttachShader(mProgram, vertex);
	glAttachShader(mProgram, fragment);

	//7 执行program的链接操作,形成最终可执行shader程序
	glLinkProgram(mProgram);

	//检查链接错误
	checkShaderErrors(mProgram, "LINK");

	//清理
	glDeleteShader(vertex);
	glDeleteShader(fragment);
}
Shader::~Shader() {

}

void Shader::begin() {
	GL_CALL(glUseProgram(mProgram));
}

void Shader::end() {
	GL_CALL(glUseProgram(0));
}

void Shader::checkShaderErrors(GLuint target, std::string type) {
	int success = 0;
	char infoLog[1024];

	if (type == "COMPILE") {
		glGetShaderiv(target, GL_COMPILE_STATUS, &success);
		if (!success) {
			glGetShaderInfoLog(target, 1024, NULL, infoLog);
			std::cout << "Error: SHADER COMPILE ERROR" << "\n" << infoLog << std::endl;
		}
	}
	else if (type == "LINK") {
		glGetProgramiv(target, GL_LINK_STATUS, &success);
		if (!success) {
			glGetProgramInfoLog(target, 1024, NULL, infoLog);
			std::cout << "Error: SHADER LINK ERROR " << "\n" << infoLog << std::endl;
		}
	}
	else {
		std::cout << "Error: Check shader errors Type is wrong" << std::endl;
	}
}

7.wrapper

7.1.CMakeLists.txt

cmake 复制代码
#递归将本文件夹下所有cpp放到FUNCS中
file(GLOB_RECURSE WRAPPER ./  *.cpp)

#将FUNCS中所有cpp编译为funcs这个lib库
add_library(wrapper ${WRAPPER} )

target_include_directories(wrapper PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(wrapper PRIVATE glfw)

7.2.checkError.h

cpp 复制代码
#pragma once 

//预编译宏
#ifdef DEBUG
#define GL_CALL(func)  func;checkError();
#else
#define GL_CALL(func)  func;
#endif 


void checkError();

7.3.checkError.cpp

cpp 复制代码
#include "checkError.h"
#include <glad/glad.h>
#include <string>
#include <iostream>
#include <assert.h>

void checkError() {
	GLenum errorCode = glGetError();
	std::string error = "";
	if (errorCode != GL_NO_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_OUT_OF_MEMORY: error = "OUT OF MEMORY"; break;
		default:
			error = "UNKNOWN";
			break;
		}
		std::cout << error << std::endl;

		//assert会根据传入的bool值,来决定程序是否停止
		//true:程序顺利运行
		//false:程序会断死
		assert(false);
	}
}

8.glad.c:略

9.主CMakeLists.txt

cmake 复制代码
# 指定 CMake 最低版本
cmake_minimum_required(VERSION 3.12)
add_definitions (-DDEBUG)

# 项目名称
project(OpenGL_Lecture)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)

file(GLOB ASSETS "./assets" )

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

# 包含头文件目录
include_directories(
    ${CMAKE_CURRENT_SOURCE_DIR}/thirdParty/include
    /usr/include
)

# 包含库文件目录
link_directories(
    ${CMAKE_CURRENT_SOURCE_DIR}/thirdParty/lib
    /usr/lib/x86_64-linux-gnu
)

# 设置 CMAKE_PREFIX_PATH 以找到 GLFW
set(CMAKE_PREFIX_PATH "/usr/local/lib/cmake/glfw3")
set(GLFW_DIR "/usr/local/lib/cmake/glfw3")

# 查找 GLFW3 库
find_package(glfw3 REQUIRED CONFIG)

add_subdirectory(wrapper)
add_subdirectory(application)
add_subdirectory(glframework)

# 添加可执行文件
add_executable(openglStudy "main.cpp" "glad.c")

# 链接库
target_link_libraries(openglStudy glfw wrapper app fw)

10.thirdParty

10.1.glad.h:略

10.2.khrplatform.h:略

11.CMake中的关键使用:拷贝assets文件夹到CMAKE_BINARY_DIR

11.1.代码

cmake 复制代码
file(GLOB ASSETS "./assets" )
file(COPY ${ASSETS} DESTINATION ${CMAKE_BINARY_DIR})

11.2.build中可以找到assets

相关推荐
Digitally3 分钟前
如何顺利地将应用程序从 Android 转移到Android
android
吃汉堡吃到饱9 分钟前
【Android】屏幕适配小合集
android
摆烂..1 小时前
博客摘录「 unity 协程 多线程 进程 纤程 区别和介绍」2025年5月22日
笔记
Ao0000001 小时前
数据库5——审计及触发器
android·数据库
看到我,请让我去学习4 小时前
LInux—shell编程
android·linux·chrome
z人间防沉迷k4 小时前
高效查询:位图、B+树
开发语言·数据结构·笔记·python·算法
尬尬_4 小时前
【计算机网络】TCP如何保障传输可靠性_笔记
服务器·笔记·tcp/ip·计算机网络·考研
孤寂大仙v4 小时前
【Linux笔记】——网络基础
linux·网络·笔记
scdifsn6 小时前
动手学深度学习12.6. 多GPU的简洁实现-笔记&练习(PyTorch)
pytorch·笔记·深度学习
沐雨风栉7 小时前
Ubuntu+Docker+内网穿透:保姆级教程实现安卓开发环境远程部署
android·ubuntu·docker