Hazel游戏引擎(044)加载Shader文件并编译

文中若有代码、术语等错误,欢迎指正

前言

  • 此节目的

    由于之前的着色器代码是写在Sandbox中的,属于硬编码,并且不好扩展和编写和美观等。

    想设计成从文件加载着色器代码,所以需要写相关代码读取文件内容,最终得到相应着色器代码

  • 如何实现

    • 使用面向对象处理,将Shader代码加载作为Shader类的行为。

    • 将shader代码放到assert文件夹下,成为资源,用Shader类的函数代码加载解析这个文件(使用C++的fstream类读取文件内容)

    • 得到着色器代码字符串后再编译、链接、删除,

关键代码

  • OpenGlShader的字符串读取放在string里,解析结果放在map的value中

    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());			// 初始化string的大小, in.tellg()返回位置
            in.seekg(0, std::ios::beg);			// in指回头部
            in.read(&result[0], result.size());	// in读入放在result指向的内存中
        }
        else {
            HZ_CORE_ERROR("不能打开文件:{0}", filepath);
        }
        return result;
    }
    std::unordered_map<GLenum, std::string> OpenGLShader::PreProcess(const std::string& source)
    {
        std::unordered_map<GLenum, std::string> shaderSources;
    
        std::string typeToken = "#type";
        size_t typeTokenLen = typeToken.size();
        size_t findCurPos = source.find(typeToken, 0);
        size_t findNextPos = findCurPos;
        while (findNextPos != std::string::npos) {
            size_t curlineEndPos = source.find_first_of("\r\n", findCurPos);///r/n写错为/r/n
            HZ_CORE_ASSERT(curlineEndPos != std::string::npos, "解析shader失败" );
            size_t begin = findCurPos + typeTokenLen + 1;
    
            std::string type = source.substr(begin, curlineEndPos - begin);// 获取到是vertex还是fragment
            HZ_CORE_ASSERT(ShaderTypeFromString(type), "无效的shader的类型	");
    
            size_t nextLinePos = source.find_first_not_of("\r\n", curlineEndPos);
            findNextPos = source.find(typeToken, nextLinePos);
            // 获取到具体的shader代码
            shaderSources[ShaderTypeFromString(type)] = source.substr(nextLinePos, findNextPos - (nextLinePos == std::string::npos ? source.size() - 1 : nextLinePos));
    
            findCurPos = findNextPos;
        }
        return shaderSources;
        /*
    			用find,而不是find_firtst_of,因为
    			find返回完全匹配的字符串的的位置;
    			find_first_of返回 被查 匹配字符串中某个字符的第一次出现位置。
    
    			std::string::npos是一个非常大的数
    			source.substr(0, source.size() + 10000)截取到从头到末尾,不会报错
    		*/
    }
    
  • 图解释解析代码的各个位置

    请添加图片描述

遇到的错误

  • 错误信息

    由于OpenGlShader子类有自己的Unpload函数,而父类Shader没有,所以需要动态指针强转指针类型为派生类指针指向派生类。

    在SandboxApp代码中

    std::dynamic_pointer_cast<Hazel::OpenGLShader>(m_SquareTexCoordShader);
    

    使用了动态指针强转,泛型类型是OpenGLShader,那么SandboxApp就需要引入OpenGLShader.h

    1. 而OpenGLShader头文件#include <glad/glad3.h>文件
    2. 当前的应用处于Sandbox项目中
    3. 而这个Sandbox项目并不包含#include <glad/glad3.h>这个文件,所以会报错


    请添加图片描述

    并且由于我在OpenGlShader.h文件中#include<GLFW/glfw3.h>头文件,也会报下面的错

    bug1_2

  • 报错分析

    看Premake.lua文件

    这三个项目都被GameEngineLightWeight项目引用中,并不向Sandbox项目提供

  • 处理错误:解决方法

    • 解决无法包括glfw/glfw3.h

      删除在OpenGLShader.h的glfw/glfw3.h,而是放到OpenGLShader.cpp文件中#include <glad/glad.h>

      请添加图片描述

      因为cpp文件被编译obj,在SandboxApp代码中并不包含OpenGLShader.cpp文件,也就不包含glad/glad.h。

    • 解决无法包括glad/glad.h

      OpenGlShader#include <glad/glad.h>是因为需要用到Glenum这个声明,所以在本文件定义GLenum并删除#include <glad/glad.h>即可

      请添加图片描述

      这样Sandbox就无法包含glad/glad.h文件了

  • 区分glm.hpp

    当OpenGLShader.h 引入<glm/glm.hpp>数学库文件,Sandbox不报错是因为

    • 在premake中设置了GameEngineLightWeight包含这个glm数学库文件。
    • 且Sandbox引用了GameEngineLightWeight项目,所以在Sandbox可以直接包含glm数学库文件

    而glad.h文件是属于GLAD项目中的,被GameEngineLightWeight所引用,所以能被GameEngineLightWeight包含,但是Sandbox项目没引用GLAD项目,所以Sandbox无法包含glad.h

    请添加图片描述

    请添加图片描述

代码修改

  • OpenGLShader.cpp

    #include "hzpch.h"
    #include "OpenGLShader.h"
    #include <fstream>
    #include <glad/glad.h>
    #include <glm/gtc/type_ptr.hpp>
    namespace Hazel {
    	static GLenum ShaderTypeFromString(const std::string& type) {
    		if (type == "vertex") {
    			return GL_VERTEX_SHADER;
    		}
    		if (type == "fragment" || type == "pixel") {
    			return GL_FRAGMENT_SHADER;
    		}
    		HZ_CORE_ASSERT(false, "不知道的shader类型");
    		return 0;
    	}
    	OpenGLShader::OpenGLShader(const std::string& filepath)
    	{
    		std::string source = ReadFile(filepath);
    		HZ_CORE_ASSERT(source.size(), "GLSL读取的字符串为空");
    		auto shaderSources = PreProcess(source);
    		Compile(shaderSources);
    	}
    	OpenGLShader::OpenGLShader(const std::string& vertexSrc, const std::string& fragmentSrc)
    	{
    		std::unordered_map<GLenum, std::string> shaderSources;
    		shaderSources[GL_VERTEX_SHADER] = vertexSrc;
    		shaderSources[GL_FRAGMENT_SHADER] = fragmentSrc;
    		Compile(shaderSources);
    	}
    	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());			// 初始化string的大小, in.tellg()返回位置
    			in.seekg(0, std::ios::beg);			// in指回头部
    			in.read(&result[0], result.size());	// in读入放在result指向的内存中
    		}
    		else {
    			HZ_CORE_ERROR("不能打开文件:{0}", filepath);
    		}
    		return result;
    	}
    	std::unordered_map<GLenum, std::string> OpenGLShader::PreProcess(const std::string& source)
    	{
    		std::unordered_map<GLenum, std::string> shaderSources;
    
    		std::string typeToken = "#type";
    		size_t typeTokenLen = typeToken.size();
    		size_t findCurPos = source.find(typeToken, 0);
    		size_t findNextPos = findCurPos;
    		while (findNextPos != std::string::npos) {
    			size_t curlineEndPos = source.find_first_of("\r\n", findCurPos);///r/n写错为/r/n
    			HZ_CORE_ASSERT(curlineEndPos != std::string::npos, "解析shader失败" );
    			size_t begin = findCurPos + typeTokenLen + 1;
    
    			std::string type = source.substr(begin, curlineEndPos - begin);// 获取到是vertex还是fragment
    			HZ_CORE_ASSERT(ShaderTypeFromString(type), "无效的shader的类型	");
    
    			size_t nextLinePos = source.find_first_not_of("\r\n", curlineEndPos);
    			findNextPos = source.find(typeToken, nextLinePos);
    			// 获取到具体的shader代码
    			shaderSources[ShaderTypeFromString(type)] = source.substr(nextLinePos, findNextPos - (nextLinePos == std::string::npos ? source.size() - 1 : nextLinePos));
    
    			findCurPos = findNextPos;
    		}
    		return shaderSources;
    		/*
    			用find,而不是find_firtst_of,因为
    			find返回完全匹配的字符串的的位置;
    			find_first_of返回被查匹配字符串中某个字符的第一次出现位置。
    
    			std::string::npos是一个非常大的数
    			source.substr(0, source.size() + 10000)截取到从头到末尾,不会报错
    		*/
    	}
    	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;
    			// Create an empty vertex shader handle
    			GLuint shader = glCreateShader(type);
    			// Send the vertex shader source code to GL
    			// Note that std::string's .c_str is NULL character terminated.
    			const GLchar* sourceCStr = source.c_str();
    			glShaderSource(shader, 1, &sourceCStr, 0);
    			// Compile the vertex shader
    			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);
    				// The maxLength includes the NULL character
    				std::vector<GLchar> infoLog(maxLength);
    				glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);
    				// We don't need the shader anymore.
    				glDeleteShader(shader);
    				// Use the infoLog as you see fit.
    				// In this simple program, we'll just leave
    				HZ_CORE_ERROR("{0} ", infoLog.data());
    				HZ_CORE_ASSERT(false, "shader 编译失败!");
    				break;
    			}			
    			// Attach our shaders to our program
    			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);
    			// Don't leak shaders either.
    			for (auto id : glShaderIDs) {
    				glDeleteShader(id);
    			}
    			// Use the infoLog as you see fit.
    			// In this simple program, we'll just leave
    			HZ_CORE_ERROR("{0} ", infoLog.data());
    			HZ_CORE_ASSERT(false, "shader link failure!");
    			return;
    		}
    		// Always detach shaders after a successful link.
    		for (auto id : glShaderIDs) {
    			glDetachShader(program, id);
    		}
    	}
    ......
    }
    
  • SandboxApp.cpp

    //m_SquareTexCoordShader.reset(Hazel::Shader::Create(squareTexCoordShaderVertexSrc, squareTexCoordShaderfragmentSrc));
    m_SquareTexCoordShader.reset(Hazel::Shader::Create("asserts/shaders/Texture.glsl"));
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

刘建杰

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值