• HazelEngine 学习记录 - Shader Asset Files


    Shader Asset Files

    我们之前的所有 Shader 都是在 cpp 文件中进行的,在 Shader 代码比较冗长的时候会让程序变得难以阅读,因此需要将 Shader 单独用文件保存起来,然后使用的时候进行读取和编译。

    之前 Shader 的创建是通过传入两个字符串,并对其进行编译之后进行绑定,现在新添加一种创建方式,就是通过文件名来进行读入:

    static Shader* Create(const std::string& filepath);
    //.cpp
    Shader* Shader::Create(const std::string& filepath)
    	{
    		switch (Renderer::GetAPI())
    		{
    			case RendererAPI::API::None:    HZ_CORE_ASSERT(false, "RendererAPI::None is currently not supported!"); return nullptr;
    			case RendererAPI::API::OpenGL:  return new OpenGLShader(filepath);
    		}
    
    		HZ_CORE_ASSERT(false, "Unknown RendererAPI!");
    		return nullptr;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    同样的,在引擎的 Shader 中我们只是进行 API 的选择,具体的实现放到对应 API 中进行:

    在 OpenGLShader 中,需要对传入的文件名进行读取,将文件中的所有字符串保存在一个string 变量当中,这里 Cherno 将 ver 和 frag 的 Shader 放在同一个文件当中,然后添加 type 对字符串进行处理,从而使整个 shader 看起来更加简洁:

    // Basic Texture Shader
    
    #type vertex
    #version 330 core
    
    layout(location = 0) in vec3 a_Position;
    layout(location = 1) in vec2 a_TexCoord;
    
    uniform mat4 u_ViewProjection;
    uniform mat4 u_Transform;
    
    out vec2 v_TexCoord;
    
    void main()
    {
    	v_TexCoord = a_TexCoord;
    	gl_Position = u_ViewProjection * u_Transform * vec4(a_Position, 1.0);
    }
    
    #type fragment
    #version 330 core
    
    layout(location = 0) out vec4 color;
    
    in vec2 v_TexCoord;
    
    uniform sampler2D u_Texture;
    
    void main()
    {
    	color = texture(u_Texture, v_TexCoord);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    在读取文件函数中进行如下处理:

    首先将所有的字符保存在 string 变量中并返回

    std::string OpenGLShader::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
    		{
    			HZ_CORE_ERROR("Could not open file '{0}'", filepath);
    		}
    
    		return result;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    然后对该字符串进行处理:

    std::unordered_map<GLenum, std::string> OpenGLShader::PreProcess(const std::string& source)
    	{
    		std::unordered_map<GLenum, std::string> shaderSources; //分别保存两个shader并记录,用哈希表方便我们使用的时候找到
    
    		const char* typeToken = "#type";  //先找到字符串 type 以此来确定是何种 shader
    		size_t typeTokenLength = strlen(typeToken);
    		size_t pos = source.find(typeToken, 0);  //从起始位置找 type
    		while (pos != std::string::npos)  //如果找到了,进入循环,没找到直接返回
    		{
    			size_t eol = source.find_first_of("\r\n", pos); //从 type 之后找当前行剩下的字符串(vertex/fragment)
    			HZ_CORE_ASSERT(eol != std::string::npos, "Syntax error");
    			size_t begin = pos + typeTokenLength + 1; 
    			std::string type = source.substr(begin, eol - begin);
    			HZ_CORE_ASSERT(ShaderTypeFromString(type), "Invalid shader type specified");
    
    			size_t nextLinePos = source.find_first_not_of("\r\n", eol); //确定类型之后,起始位置变为下一行的开始位置
    			pos = source.find(typeToken, nextLinePos); //然后找下一个 type token
    			shaderSources[ShaderTypeFromString(type)] = source.substr(nextLinePos, pos - (nextLinePos == std::string::npos ? source.size() - 1 : nextLinePos));//存储在哈希表中
    		}
    
    		return shaderSources;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    按照之前的方法进行编译,只不过这次我们把编译放在一个for 循环里面, 遍历哈希表进行编译:

    void OpenGLShader::Compile(const std::unordered_map<GLenum, std::string>& shaderSources)
    	{
    		GLuint program = glCreateProgram();
    		std::vector<GLenum> glShaderIDs(shaderSources.size());
    		for (auto& kv : shaderSources)
    		{
    			GLenum type = kv.first;
    			const std::string& source = kv.second;
    
    			GLuint shader = glCreateShader(type);
    
    			const GLchar* sourceCStr = source.c_str();
    			glShaderSource(shader, 1, &sourceCStr, 0);
    
    			glCompileShader(shader);
    
    			GLint isCompiled = 0;
    			glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
    			if (isCompiled == GL_FALSE)
    			{
    				GLint maxLength = 0;
    				glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
    
    				std::vector<GLchar> infoLog(maxLength);
    				glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);
    
    				glDeleteShader(shader);
    
    				HZ_CORE_ERROR("{0}", infoLog.data());
    				HZ_CORE_ASSERT(false, "Shader compilation failure!");
    				break;
    			}
    
    			glAttachShader(program, shader);
    			glShaderIDs.push_back(shader);
    		}
    
    		m_RendererID = program;
    
    		// Link our program
    		glLinkProgram(program);
    		// Note the different functions here: glGetProgram* instead of glGetShader*.
    		GLint isLinked = 0;
    		glGetProgramiv(program, GL_LINK_STATUS, (int*)&isLinked);
    		if (isLinked == GL_FALSE)
    		{
    			GLint maxLength = 0;
    			glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);
    			// The maxLength includes the NULL character
    			std::vector<GLchar> infoLog(maxLength);
    			glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);
    
    			// We don't need the program anymore.
    			glDeleteProgram(program);
    
    			for (auto id : glShaderIDs)
    				glDeleteShader(id);
    
    			HZ_CORE_ERROR("{0}", infoLog.data());
    			HZ_CORE_ASSERT(false, "Shader link failure!");
    			return;
    		}
    
    		for (auto id : glShaderIDs)
    			glDetachShader(program, id);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    构造函数:

    OpenGLShader::OpenGLShader(const std::string& filepath)
    	{
    		std::string source = ReadFile(filepath);
    		auto shaderSources = PreProcess(source);
    		Compile(shaderSources);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    之后我们添加一个简单的 shader

    // Basic Texture Shader
    
    #type vertex
    #version 330 core
    
    layout(location = 0) in vec3 a_Position;
    layout(location = 1) in vec2 a_TexCoord;
    
    uniform mat4 u_ViewProjection;
    uniform mat4 u_Transform;
    
    out vec2 v_TexCoord;
    
    void main()
    {
    	v_TexCoord = a_TexCoord;
    	gl_Position = u_ViewProjection * u_Transform * vec4(a_Position, 1.0);
    }
    
    #type fragment
    #version 330 core
    
    layout(location = 0) out vec4 color;
    
    in vec2 v_TexCoord;
    
    uniform sampler2D u_Texture;
    
    void main()
    {
    	color = texture(u_Texture, v_TexCoord);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    然后进行使用:

    m_TextureShader.reset(Hazel::Shader::Create("assets/shaders/Texture.glsl"));
    
    • 1

    这样我们的 Shader 就可以从外部文件导入了!

  • 相关阅读:
    docker的常用命令
    开关电源芯片好坏的判断标准是什么?如何判断电源芯片的好坏?
    STM32玩转物联网实战篇:5.ESP8266 WIFI模块MQTT通信示例详解
    超详细 | 差分进化算法原理及其实现(Matlab/Python)
    LeetCode第13题:罗马数字转整数
    Unity fbx动画压缩自动化
    关于项目采购管理,这些你需要知道
    什么是网络编程?
    算法刷题第九天:广度优先搜索 / 深度优先搜索--3
    企业实践开源的动机
  • 原文地址:https://blog.csdn.net/miyazono_/article/details/127548056