欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 会展 > OpenGL编译用户着色器shader

OpenGL编译用户着色器shader

2024/12/22 1:21:16 来源:https://blog.csdn.net/qq_45651072/article/details/144310315  浏览:    关键词:OpenGL编译用户着色器shader

        shader相信很多朋友们都听说过,shader就是运行再GPU上的程序。虽然是这么说,但是我们发现,很多IDE开发工具比如说visual studio 没有办法直接去运行shader代码。这是因为,许多编译器不会自动将shader文件编译成可执行的代码然后发送给GPUshader代码的编译需要开发人员手动动用GPUshader编译接口,将对应的代码编译成可执行的程序。在这里,笔者就为大家介绍,如何使用OpenGL提供的API编译用户自己的shader程序。

Shader

OpenGL渲染管线

        这里先为大家介绍一下OpenGL渲染管线如下图所示

事实上在一众着色器当中,只有顶点着色器和片元着色器是必须的,其他都是可选的。具体想要了解更多,可以去看一些资料或者书籍,比如笔者手上的这本《OpenGL编程指南》

OpenGL shader

        OpenGL 支持的 shader 语法是 OpenGL shader language 简称是glsl。这个shader语法和C++基本上是大同小异,很快就可以轻松上手。

话说到这里,让我们回顾一下上一篇文章OpenGL渲染结果移至ImGui窗口上,有细心的本有不难发现,我并没有去编译用户着色器,而且必要的顶点着色器和片元着色器都没有进行编写,但是我们仍然得到我们想要的渲染结果。其原因是,OpenGL自带默认的着色器,所以对于初学者来说,可以尽可能使用少的代码去实现自己想要的结果,这就是为什么图形接口的学习一般都是从OpenGL开始学起。

OpenGL着色器编译

Shader 类

        这里笔者写了一个Shader类,整体的代码如下

Shader.h

#pragma once#include<unordered_map>
typedef unsigned int GLenum;class Shader {
public:Shader(const std::string& filePath);~Shader();void Bind();void UBind();void UploadUniformFloat4(const std::string& name, float* value);private:std::string ReadFile(const std::string& filePath);std::unordered_map<GLenum, std::string> PreProcess(const std::string& source);void Compile(const std::unordered_map<GLenum, std::string>& shaderSources);
private:uint32_t m_ShaderID;std::string m_Name;
};

Shader.cpp

#include<glad/glad.h>
#include<string>
#include<array>
#include<fstream>
#include<iostream>#include"Shader.h"static GLenum ShaderTypeFromString(const std::string& type) {if (type == "vertex")return GL_VERTEX_SHADER;else if (type == "fragment" || type == "pixel")return GL_FRAGMENT_SHADER;std::cout << "Unknown shader type" << std::endl;return 0;
}Shader::Shader(const std::string& filePath) :m_ShaderID(0) {std::string source = ReadFile(filePath);auto shaderSource = PreProcess(source);Compile(shaderSource);auto lastSlash = filePath.find_last_of("/\\");lastSlash = lastSlash == std::string::npos ? 0 : lastSlash + 1;auto lastDot = filePath.rfind('.');auto count = lastDot == std::string::npos ? filePath.size() - lastSlash : lastDot - lastSlash;m_Name = filePath.substr(lastSlash, count);
}Shader::~Shader() {glDeleteProgram(m_ShaderID);
}void Shader::Bind(){glUseProgram(m_ShaderID);
}void Shader::UBind(){glUseProgram(0);
}void Shader::UploadUniformFloat4(const std::string& name, float* value) {int location = glGetUniformLocation(m_ShaderID, name.c_str());glUniform4f(location, value[0], value[1], value[2], value[3]);
}std::string Shader::ReadFile(const std::string& filePath) {std::string result;std::ifstream in(filePath, std::ios::in | std::ios::binary);if (in) {in.seekg(0, std::ios::end);result.resize(in.tellg());in.seekg(0, std::ios::beg);in.read(&result[0], result.size());in.close();}else {std::cout << "着色器文件没有正常打开" << std::endl;__debugbreak();}return result;
}std::unordered_map<GLenum, std::string> Shader::PreProcess(const std::string& source) {std::unordered_map<GLenum, std::string> shaderSources;const char* typeToken = "#type";size_t typeTokenLength = strlen(typeToken);size_t pos = source.find(typeToken,0);while (pos != std::string::npos) {size_t eol = source.find_first_of("\r\n", pos);if (eol == std::string::npos) {std::cout << "着色器语法出错" << std::endl;__debugbreak();}size_t begin = pos + typeTokenLength + 1;std::string type = source.substr(begin, eol - begin);if (!ShaderTypeFromString(type)) {std::cout << "这是一个不合法的着色器类型" << std::endl;__debugbreak();}size_t nextLinePos = source.find_first_of("\r\n", eol);pos = source.find(typeToken, nextLinePos);shaderSources[ShaderTypeFromString(type)] = source.substr(nextLinePos, pos - (nextLinePos == std::string::npos ? source.size() - 1 : nextLinePos));}return shaderSources;
}void Shader::Compile(const std::unordered_map<GLenum, std::string>& shaderSources) {unsigned int program = glCreateProgram();//一次性至多编译两种着色器if (shaderSources.size() < 2) {std::cout << "一次性至多编译两种着色器" << std::endl;__debugbreak();}std::array<GLenum, 2> glShaderIDs;int glShaderIDIndex = 0;for (auto& kv : shaderSources) {GLenum type = kv.first;const std::string& source = kv.second;unsigned int shader = glCreateShader(type);const char* sourceCStr = source.c_str();glShaderSource(shader, 1, &sourceCStr, 0);glCompileShader(shader);int isCompiled = 0;glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);if (isCompiled == GL_FALSE) {int maxLength = 0;glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);std::vector<char> infoLog(maxLength);glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);glDeleteShader(shader);std::cout << "着色器编译出错:" << infoLog.data() << std::endl;__debugbreak();break;}glAttachShader(program, shader);glShaderIDs[glShaderIDIndex++] = shader;}m_ShaderID = program;// Link our programglLinkProgram(program);// Note the different functions here: glGetProgram* instead of glGetShader*.int isLinked = 0;glGetProgramiv(program, GL_LINK_STATUS, (int*)&isLinked);if (isLinked == GL_FALSE) {int maxLength = 0;glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);// The maxLength includes the NULL characterstd::vector<char> infoLog(maxLength);glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);// We don't need the program anymore.glDeleteProgram(program);for (auto id : glShaderIDs)glDeleteShader(id);std::cout << "用户着色器链接失败:" << infoLog.data() << std::endl;__debugbreak();return;}for (auto id : glShaderIDs)glDetachShader(program, id);
}

着色器代码

TextureShader.glsl

#type vertex
#version 450 core
//标记为0的内存位置输入一个有两个分量的向量,这是顶点的位置
layout(location = 0) in vec2 v_Position;void main(){//顶点位置的数据进行赋值,需要转换为齐次向量gl_Position = vec4(v_Position,0.0f,1.0f);
}#type fragment
#version 450 core
//标记为0的内存位置输出一个有四个分量的向量,这是像素的颜色
layout(location = 0) out vec4 o_Color;void main(){o_Color = vec4(0.8f,0.2f,0.3f,1.0f);
}

着色器介绍

        上面虽然是一个着色器文件,其实这里面写了两个着色器,一个是顶点着色器,一个是片元着色器。#type vertex 下面的是顶点着色器,#type fragment 下面的是片元着色器。为什么这两个要一起写了?前面也介绍了,这两个着色器是必需要有的,所以笔者推荐这个着色器最好就是一起写。顶点着色器必须要有输入数据,片元着色器必须要有输出数据,不然屏幕上就看不到任何东西。

着色器编译

笔者将其分成了3个步骤进行

1、读取对应的文件内容

std::string Shader::ReadFile(const std::string& filePath) {std::string result;std::ifstream in(filePath, std::ios::in | std::ios::binary);if (in) {in.seekg(0, std::ios::end);result.resize(in.tellg());in.seekg(0, std::ios::beg);in.read(&result[0], result.size());in.close();}else {std::cout << "着色器文件没有正常打开" << std::endl;__debugbreak();}return result;
}

TextureShader.glsl当中的文本信息全部转换成一个string类型当中进行存储。

2、确定着色器的类型,以及每个着色器的代码

std::unordered_map<GLenum, std::string> Shader::PreProcess(const std::string& source) {std::unordered_map<GLenum, std::string> shaderSources;const char* typeToken = "#type";size_t typeTokenLength = strlen(typeToken);size_t pos = source.find(typeToken,0);while (pos != std::string::npos) {size_t eol = source.find_first_of("\r\n", pos);if (eol == std::string::npos) {std::cout << "着色器语法出错" << std::endl;__debugbreak();}size_t begin = pos + typeTokenLength + 1;std::string type = source.substr(begin, eol - begin);if (!ShaderTypeFromString(type)) {std::cout << "这是一个不合法的着色器类型" << std::endl;__debugbreak();}size_t nextLinePos = source.find_first_of("\r\n", eol);pos = source.find(typeToken, nextLinePos);shaderSources[ShaderTypeFromString(type)] = source.substr(nextLinePos, pos - (nextLinePos == std::string::npos ? source.size() - 1 : nextLinePos));}return shaderSources;
}

对于OpenGL来说我们不光要告诉它需要编译的代码,还要告诉它编译的着色器代码是什么类型的着色器代码。在前面可以看到TextureShader.glsl当中有 #type vertex 这样的语句,这个并不是glsl语法,我们在进行文本处理的时候需要省略掉才行,不然的话编译会失败,这个只是用来告诉程序下面着色器代码是什么类型着色器的,所以这里选择返回了一个字典,用来存储着色器的类型和需要编译的程序。能够编译的是下面两段

#version 450 core
//标记为0的内存位置输入一个有两个分量的向量,这是顶点的位置
layout(location = 0) in vec2 v_Position;void main(){//顶点位置的数据进行赋值,需要转换为齐次向量gl_Position = vec4(v_Position,0.0f,1.0f);
}
#version 450 core
//标记为0的内存位置输出一个有四个分量的向量,这是像素的颜色
layout(location = 0) out vec4 o_Color;void main(){o_Color = vec4(0.8f,0.2f,0.3f,1.0f);
}

他们已经被分开存储了。 

3、编译链接着色器

void Shader::Compile(const std::unordered_map<GLenum, std::string>& shaderSources) {//注册使用下面两个着色器的程序号unsigned int program = glCreateProgram();//一次性至多编译两种着色器if (shaderSources.size() < 2) {std::cout << "一次性至多编译两种着色器" << std::endl;__debugbreak();}std::array<GLenum, 2> glShaderIDs;int glShaderIDIndex = 0;for (auto& kv : shaderSources) {GLenum type = kv.first;const std::string& source = kv.second;//注册对饮类型的着色器unsigned int shader = glCreateShader(type);const char* sourceCStr = source.c_str();glShaderSource(shader, 1, &sourceCStr, 0);//编译着色器源码glCompileShader(shader);int isCompiled = 0;glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);//检查着色器是否编译失败if (isCompiled == GL_FALSE) {int maxLength = 0;glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);std::vector<char> infoLog(maxLength);glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);glDeleteShader(shader);std::cout << "着色器编译出错:" << infoLog.data() << std::endl;__debugbreak();break;}//将着色器加入到这个程序当中glAttachShader(program, shader);glShaderIDs[glShaderIDIndex++] = shader;}m_ShaderID = program;// Link our programglLinkProgram(program);// Note the different functions here: glGetProgram* instead of glGetShader*.int isLinked = 0;glGetProgramiv(program, GL_LINK_STATUS, (int*)&isLinked);//检查程序是否能够链接成功if (isLinked == GL_FALSE) {int maxLength = 0;glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);// The maxLength includes the NULL characterstd::vector<char> infoLog(maxLength);glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);// We don't need the program anymore.glDeleteProgram(program);for (auto id : glShaderIDs)glDeleteShader(id);std::cout << "用户着色器链接失败:" << infoLog.data() << std::endl;__debugbreak();return;}for (auto id : glShaderIDs)glDetachShader(program, id);
}

上面大致流程就是,注册程序的编号,创建对应类型的着色器,根据下面的代码

static GLenum ShaderTypeFromString(const std::string& type) {if (type == "vertex")return GL_VERTEX_SHADER;else if (type == "fragment" || type == "pixel")return GL_FRAGMENT_SHADER;std::cout << "Unknown shader type" << std::endl;return 0;
}

可以知道 #type vertex 对应的着色器类型就是GL_VERTEX_SHADER#type fragment 对应的着色器类型就是GL_FRAGMENT_SHADER。创建了对应的着色器类型过后就是对源码进行编译,放入到程序当中,检查这个程序能否顺利接入管线当中,隔离开然后等待被调用。

使用用户自定义着色器

着色器使用,主函数代码如下

#include<glad/glad.h>
#include<GLFW/glfw3.h>#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"#include<iostream>#include"FrameBuffer.h"
#include"Shader.h"int main() {glfwInit();GLFWwindow* window = glfwCreateWindow(640, 480, "Triangles", NULL, NULL);glfwMakeContextCurrent(window);glfwSwapInterval(1); // Enable vsync// Setup Dear ImGui contextIMGUI_CHECKVERSION();ImGui::CreateContext();ImGuiIO& io = ImGui::GetIO(); (void)io;io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controlsio.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controlsio.ConfigFlags |= ImGuiConfigFlags_DockingEnable;         // Enable Dockingio.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;       // Enable Multi-Viewport / Platform Windows//io.ConfigViewportsNoAutoMerge = true;//io.ConfigViewportsNoTaskBarIcon = true;// Setup Dear ImGui styleImGui::StyleColorsDark();//ImGui::StyleColorsLight();// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.ImGuiStyle& style = ImGui::GetStyle();if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable){style.WindowRounding = 0.0f;style.Colors[ImGuiCol_WindowBg].w = 1.0f;}// Setup Platform/Renderer backendsImGui_ImplGlfw_InitForOpenGL(window, true);ImGui_ImplOpenGL3_Init("#version 130");//需要初始化GLADif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {std::cout << "Failed to initialize GLAD" << std::endl;return -1;}float positions[6] = {-0.5f, -0.5,0.0f, 0.5f,0.5f, -0.5f};GLuint buffer = 0;glGenBuffers(1, &buffer);glBindBuffer(GL_ARRAY_BUFFER, buffer);glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), NULL);glEnableVertexAttribArray(0);bool show_demo_window = true;ImVec2 viewPortSize(640,480);float colorEditor[4] = {1.0f, 1.0f, 1.0f, 1.0f};FrameBuffer *pFrameBuffer = new FrameBuffer(640, 480);Shader* pShader = new Shader("assets/shaders/TextureShader.glsl");pShader->UBind();while (!glfwWindowShouldClose(window)) {pFrameBuffer->Bind();pShader->Bind();glClear(GL_COLOR_BUFFER_BIT);glDrawArrays(GL_TRIANGLES, 0, 3);pFrameBuffer->UBind();// Start the Dear ImGui frameImGui_ImplOpenGL3_NewFrame();ImGui_ImplGlfw_NewFrame();ImGui::NewFrame();ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());ImGui::Begin("ViewPort");viewPortSize = ImGui::GetContentRegionAvail();if (viewPortSize.x * viewPortSize.y > 0 && (viewPortSize.x != pFrameBuffer->GetWidth() || viewPortSize.y != pFrameBuffer->GetHeight())) {pFrameBuffer->Resize(viewPortSize.x, viewPortSize.y);glViewport(0, 0, viewPortSize.x, viewPortSize.y);}uint32_t textureID = pFrameBuffer->GetColorAttachment();ImGui::Image(reinterpret_cast<void*>(textureID), viewPortSize, { 0,1 }, { 1,0 });ImGui::End();ImGui::Begin("ColorEditor");ImGui::ColorEdit4("##colorEditor", colorEditor);ImGui::End();/*if(show_demo_window)ImGui::ShowDemoWindow(&show_demo_window);*/// RenderingImGui::Render();ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable){GLFWwindow* backup_current_context = glfwGetCurrentContext();ImGui::UpdatePlatformWindows();ImGui::RenderPlatformWindowsDefault();glfwMakeContextCurrent(backup_current_context);}glfwSwapBuffers(window);glfwPollEvents();}// CleanupImGui_ImplOpenGL3_Shutdown();ImGui_ImplGlfw_Shutdown();ImGui::DestroyContext();delete pFrameBuffer;delete pShader;glfwDestroyWindow(window);glfwTerminate();
}

得到的结果是

三角形被顺利染成了红色,有人可能会说这也有点费了这么大的劲,就把颜色改成了红色,实在是有点无聊,那让我们来做一些比较Cool的事。

我们修改一下着色器

#type vertex
#version 450 corelayout(location = 0) in vec2 v_Position;void main(){gl_Position = vec4(v_Position,0.0f,1.0f);
}#type fragment
#version 450 corelayout(location = 0) out vec4 o_Color;
//增加的片段
uniform vec4 u_Color;void main(){o_Color = u_Color;
}

主函数也修改一下

pShader->UBind();while (!glfwWindowShouldClose(window)) {pFrameBuffer->Bind();pShader->Bind();//新增片段pShader->UploadUniformFloat4("u_Color", colorEditor);glClear(GL_COLOR_BUFFER_BIT);

展示一下结果

我们现在可以通过ImGui上面的控件对三角形的颜色进行实时修改了,不用去改动程序,是不是很棒了。下面还是把整个主函数放出来,如果对里面的FrameBuffer类不了解的可以看笔者的OpenGL渲染结果移至ImGui窗口上这篇文章,同样有源代码,希望对大家能有帮助。

#include<glad/glad.h>
#include<GLFW/glfw3.h>#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"#include<iostream>#include"FrameBuffer.h"
#include"Shader.h"int main() {glfwInit();GLFWwindow* window = glfwCreateWindow(640, 480, "Triangles", NULL, NULL);glfwMakeContextCurrent(window);glfwSwapInterval(1); // Enable vsync// Setup Dear ImGui contextIMGUI_CHECKVERSION();ImGui::CreateContext();ImGuiIO& io = ImGui::GetIO(); (void)io;io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controlsio.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controlsio.ConfigFlags |= ImGuiConfigFlags_DockingEnable;         // Enable Dockingio.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;       // Enable Multi-Viewport / Platform Windows//io.ConfigViewportsNoAutoMerge = true;//io.ConfigViewportsNoTaskBarIcon = true;// Setup Dear ImGui styleImGui::StyleColorsDark();//ImGui::StyleColorsLight();// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.ImGuiStyle& style = ImGui::GetStyle();if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable){style.WindowRounding = 0.0f;style.Colors[ImGuiCol_WindowBg].w = 1.0f;}// Setup Platform/Renderer backendsImGui_ImplGlfw_InitForOpenGL(window, true);ImGui_ImplOpenGL3_Init("#version 130");//需要初始化GLADif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {std::cout << "Failed to initialize GLAD" << std::endl;return -1;}float positions[6] = {-0.5f, -0.5,0.0f, 0.5f,0.5f, -0.5f};GLuint buffer = 0;glGenBuffers(1, &buffer);glBindBuffer(GL_ARRAY_BUFFER, buffer);glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), NULL);glEnableVertexAttribArray(0);bool show_demo_window = true;ImVec2 viewPortSize(640,480);float colorEditor[4] = {1.0f, 1.0f, 1.0f, 1.0f};FrameBuffer *pFrameBuffer = new FrameBuffer(640, 480);Shader* pShader = new Shader("assets/shaders/TextureShader.glsl");pShader->UBind();while (!glfwWindowShouldClose(window)) {pFrameBuffer->Bind();pShader->Bind();pShader->UploadUniformFloat4("u_Color", colorEditor);glClear(GL_COLOR_BUFFER_BIT);glDrawArrays(GL_TRIANGLES, 0, 3);pFrameBuffer->UBind();// Start the Dear ImGui frameImGui_ImplOpenGL3_NewFrame();ImGui_ImplGlfw_NewFrame();ImGui::NewFrame();ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());ImGui::Begin("ViewPort");viewPortSize = ImGui::GetContentRegionAvail();if (viewPortSize.x * viewPortSize.y > 0 && (viewPortSize.x != pFrameBuffer->GetWidth() || viewPortSize.y != pFrameBuffer->GetHeight())) {pFrameBuffer->Resize(viewPortSize.x, viewPortSize.y);glViewport(0, 0, viewPortSize.x, viewPortSize.y);}uint32_t textureID = pFrameBuffer->GetColorAttachment();ImGui::Image(reinterpret_cast<void*>(textureID), viewPortSize, { 0,1 }, { 1,0 });ImGui::End();ImGui::Begin("ColorEditor");ImGui::ColorEdit4("##colorEditor", colorEditor);ImGui::End();/*if(show_demo_window)ImGui::ShowDemoWindow(&show_demo_window);*/// RenderingImGui::Render();ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable){GLFWwindow* backup_current_context = glfwGetCurrentContext();ImGui::UpdatePlatformWindows();ImGui::RenderPlatformWindowsDefault();glfwMakeContextCurrent(backup_current_context);}glfwSwapBuffers(window);glfwPollEvents();}// CleanupImGui_ImplOpenGL3_Shutdown();ImGui_ImplGlfw_Shutdown();ImGui::DestroyContext();delete pFrameBuffer;delete pShader;glfwDestroyWindow(window);glfwTerminate();
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com