美文网首页
Dragon Engine:材质系统

Dragon Engine:材质系统

作者: Dragon_boy | 来源:发表于2020-08-18 14:59 被阅读0次

这一节介绍材质系统,主要是进行着色器类的抽象:

namespace Dragon
{
    class Shader
    {
    public:
        virtual ~Shader() = default;

        virtual void Bind() const = 0;
        virtual void Unbind() const = 0;

        virtual const std::string& GetName() const = 0;

        static std::shared_ptr<Shader> Create(const std::string& filepath);
        static std::shared_ptr<Shader> Create(const std::string& name, const std::string& vertexSrc, const std::string& fragmentSrc);
    };

    class ShaderLibrary
    {
    public:
        void Add(const std::string& name, const std::shared_ptr<Shader>& shader);
        void Add(const std::shared_ptr<Shader> shader);
        std::shared_ptr<Shader> Load(const std::string& filepath);
        std::shared_ptr<Shader> Load(const std::string& name, const std::string& filepath);

        std::shared_ptr<Shader> Get(const std::string& name);

        bool Exists(const std::string& name) const;
    private:
        std::unordered_map<std::string, std::shared_ptr<Shader>> m_Shaders;
    };
}

这里设计了两个类,一个着色器类,一个着色器库类。着色器类的成员函数很简单,绑定,解绑,获取着色器名字,根据文件或者字符串源创建着色器。着色器库类可以管理着色器,主要数据结构是一个unordered_map,可以添加现有着色器,从文件中加载着色器,根据存储的着色器名字获取着色器对象等。

相关实现,着色器类:

namespace Dragon
{
    std::shared_ptr<Shader> Shader::Create(const std::string& filepath)
    {
        switch (Renderer::GetAPI())
        {
        case RendererAPI::API::None:    DG_CORE_ASSERT(false, "RendererAPI::None is currently not supported!"); return nullptr;
        case RendererAPI::API::OpenGL:  return std::make_shared<OpenGLShader>(filepath);
        }

        DG_CORE_ASSERT(false, "Unknown RendererAPI!");
        return nullptr;
    }

    std::shared_ptr<Shader> Shader::Create(const std::string& name, const std::string& vertexSrc, const std::string& fragmentSrc)
    {
        switch (Renderer::GetAPI())
        {
        case RendererAPI::API::None :
            DG_CORE_ASSERT(false, "RendererAPI::None is currently not supported!"); 
            return nullptr;
        case RendererAPI::API::OpenGL:
            return std::make_shared<OpenGLShader>(name,vertexSrc, fragmentSrc);
        }

        DG_CORE_ASSERT(false, "RendererAPI!");
        return nullptr;
    }

两个创建着色器的静态方法和之前的类似,根据API创建对应的着色器。

着色器类库实现方法:

    void ShaderLibrary::Add(const std::string& name, const std::shared_ptr<Shader>& shader)
    {
        DG_CORE_ASSERT(!Exists(name), "Shader already exists");
        m_Shaders[name] = shader;
    }
    void ShaderLibrary::Add(const std::shared_ptr<Shader> shader)
    {
        auto& name = shader->GetName();
        Add(name, shader);
    }
    std::shared_ptr<Shader> ShaderLibrary::Load(const std::string& filepath)
    {
        auto shader = Shader::Create(filepath);
        Add(shader);
        return shader;
    }
    std::shared_ptr<Shader> ShaderLibrary::Load(const std::string& name, const std::string& filepath)
    {
        auto shader = Shader::Create(filepath);
        Add(name, shader);
        return shader;
    }
    std::shared_ptr<Shader> ShaderLibrary::Get(const std::string& name)
    {
        DG_CORE_ASSERT(Exists(name), "Shader not found!");
        return m_Shaders[name];
    }
    bool ShaderLibrary::Exists(const std::string& name) const
    {
        return m_Shaders.find(name) != m_Shaders.end();
    }

Exits()函数使用unordered_mapfind函数来查找是否存在某一着色器名字,AddLoadGet函数很简单。

接下来看具体的OpenGL实现,类声明:

namespace Dragon
{
    class OpenGLShader : public Shader
    {
    public:
        OpenGLShader(const std::string& filepath);
        OpenGLShader(const std::string& name, const std::string& vertexSrc, const std::string& fragmentSrc);
        virtual ~OpenGLShader();

        virtual void Bind() const override;
        virtual void Unbind() const override;

        virtual const std::string& GetName() const override { return m_Name; }

        void UploadUniformInt(const std::string& name, int value);
        void UploadUniformBool(const std::string& name, bool value);
        void UploadUniformFloat(const std::string& name, float value);
        void UploadUniformFloat2(const std::string& name, const glm::vec2& value);
        void UploadUniformFloat3(const std::string& name, const glm::vec3& value);
        void UploadUniformFloat4(const std::string& name, const glm::vec4& value);

        void UploadUniformMat3(const std::string& name, const glm::mat3& matrix);
        void UploadUniformMat4(const std::string& name, const glm::mat4& matrix);

    private:
        std::string ReadFile(const std::string& filepath);
        std::unordered_map<unsigned int, std::string> PreProcess(const std::string& source);
        void Compile(const std::unordered_map<unsigned int, std::string>& shaderSources);
    private:
        uint32_t m_RendererID;
        std::string m_Name;
        bool m_FirstCreated = false;
    };
}

这里定义了多个Upload**函数,用于向着色器对象传递uniform变量。ReadFile可以读取文件,PreProcess可以处理着色器源代码,Compile函数可以编译着色器。

相关实现:

    static unsigned int ShaderTypeFromString(const std::string& type)
    {
        if (type == "vertex")
            return GL_VERTEX_SHADER;
        if (type == "fragment")
            return GL_FRAGMENT_SHADER;

        DG_CORE_ASSERT(false, "Unknown shader type!");
        return 0;
    }

首先定义了一个着色器类型转为OpenGL类型的函数,这里只定义了顶点和片元,大家还可以额外添加几何、细分曲面等。

    OpenGLShader::OpenGLShader(const std::string& filepath)
    {
        std::string source = ReadFile(filepath);
        auto shaderSources = PreProcess(source);
        Compile(shaderSources);

        //获取名字
        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);
    }

    OpenGLShader::OpenGLShader(const std::string& name, const std::string& vertexSrc, const std::string& fragmentSrc)
        :m_Name(name)
    {
        std::unordered_map<unsigned int, std::string> sources;
        sources[GL_VERTEX_SHADER] = vertexSrc;
        sources[GL_FRAGMENT_SHADER] = fragmentSrc;
        Compile(sources);
    }

读取文件的构造函数,首先ReadFile读取文件,接着PreProcess处理源代码字符串, ShaderParameterMapInit,SetShaderParameterName(source),CreateParameter()用于自动创建着色器参数,Compile用于编译着色器。下方的一串字符串分割代码用于找到文件的根目录。

第二个构造函数很简单,设置好map即可。

    OpenGLShader::~OpenGLShader()
    {
        glDeleteProgram(m_RendererID);
    }

析构函数删除着色器程序。

    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
        {
            DG_CORE_ERROR("Could not open file '{0}'", filepath);
        }
        return result;
    }

读取文件方法,std::ios::instd::ios::binary表示只读二进制读取文件。seekg函数第一个参数表示偏移位置,第二个参数表示流搜索开始的位置,相当于将流指针定位到文件尾部,tellg获取文件长度,并用resize改变字符串长度。接着使用seekg将文件流指针定位到起始位置,接着用read函数将文件从字符串起始位置读取并存储到字符串中。

    std::unordered_map<unsigned int, std::string> OpenGLShader::PreProcess(const std::string& source)
    {
        std::unordered_map<unsigned int, 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);
            DG_CORE_ASSERT(eol != std::string::npos, "Syntax error");
            size_t begin = pos + typeTokenLength + 1;
            std::string type = source.substr(begin, eol - begin);
            DG_CORE_ASSERT(ShaderTypeFromString(type), "Invalid shader type specified");

            size_t nextLinePos = source.find_first_not_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;
    }

该预处理函数与我们规定的着色器代码编写规则有关,我们使用#type ***标识着色器类型,比如一个兰伯特材质(无阴影):

#type vertex
#version 330 core

layout (location = 0) in vec3 a_Position;
layout (location = 1) in vec2 a_TexCoords;
layout (location = 2) in vec3 a_Normal;

out vec3 v_WorldPos;
out vec3 v_Normal;

uniform mat4 u_Model;
uniform mat4 u_View;
uniform mat4 u_Projection;

void main()
{
    v_Normal = vec3(transpose(inverse(u_Model)) * vec4(a_Normal, 0.0));
    v_WorldPos = vec3(u_Model * vec4(a_Position, 1.0));
    gl_Position = u_Projection * u_View * vec4(v_WorldPos, 1.0);
}

#type fragment
#version 330 core
layout (location = 0) out vec4 f_Color;

in vec3 v_WorldPos;
in vec3 v_Normal;

uniform vec3 u_Albedo;
uniform vec3 u_Ambient;

//平行光
struct DirLight
{
    vec3 direction;
    vec3 diffuse;
};

//点光源
struct PointLight
{
    vec3 position;
    vec3 diffuse;
    vec3 attenuation;
};

//光源
uniform DirLight u_DirLight;


uniform int u_PointLightNum;
#define MAX_POINT_LIGHT_NUM 10
uniform PointLight u_PointLights[MAX_POINT_LIGHT_NUM];


//函数定义
vec3 CalDirLight(DirLight dirLight);
vec3 CalPointLight(PointLight pointLight);

void main()
{
    //计算平行光
    vec3 result = CalDirLight(u_DirLight);

    //计算点光源
    for(int i = 0; i < u_PointLightNum; i++)
    {
        result += CalPointLight(u_PointLights[i]);
    }

    f_Color = vec4(result, 1.0);
}

vec3 CalDirLight(DirLight dirLight)
{
    vec3 normal = normalize(v_Normal);
    vec3 lightDir = normalize(-dirLight.direction);

    //环境光
    vec3 ambient = u_Ambient*0.01 * u_Albedo;
    
    //漫反射因数
    float diff = max(dot(normal, lightDir), 0.0);
    vec3 diffuse = diff * dirLight.diffuse * u_Albedo;

    vec3 result = ambient + diffuse;

    return result;
}

vec3 CalPointLight(PointLight pointLight)
{
    vec3 normal = normalize(v_Normal);
    vec3 lightDir = normalize(pointLight.position - v_WorldPos);

    //环境光
    vec3 ambient = u_Ambient*0.01 * u_Albedo;
    
    //漫反射因数
    float diff = max(dot(normal, lightDir), 0.0);
    vec3 diffuse = diff *  pointLight.diffuse * u_Albedo;

    //衰减
    float distance = length(pointLight.position - v_WorldPos);
    float attenuation = 1.0 / (pointLight.attenuation.x + pointLight.attenuation.y * distance + pointLight.attenuation.z * distance * distance);


    vec3 result = (ambient + diffuse) * attenuation;

    return result;
}

编译着色器的函数,非常的常规,基本来自于官网的示例:

    void OpenGLShader::Compile(const std::unordered_map<unsigned int, std::string>& shaderSources)
    {
        GLuint program = glCreateProgram();
        std::vector<unsigned int> glShaderIDs(shaderSources.size());

        for (auto& kv : shaderSources)
        {
            unsigned int 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);

                DG_CORE_ERROR("{0}", infoLog.data());
                DG_CORE_ASSERT(false, "Shader compilation failure!");
                break;
            }

            glAttachShader(program, shader);
            glShaderIDs.push_back(shader);
        }

        m_RendererID = program;

        glLinkProgram(program);

        GLint isLinked = 0;
        glGetProgramiv(program, GL_LINK_STATUS, (int*)&isLinked);
        if (isLinked == GL_FALSE)
        {
            GLint maxLength = 0;
            glGetShaderiv(program, GL_INFO_LOG_LENGTH, &maxLength);

            std::vector<GLchar> infoLog(maxLength);
            glGetShaderInfoLog(program, maxLength, &maxLength, &infoLog[0]);

            glDeleteProgram(program);
            
            for (auto id : glShaderIDs)
                glDeleteShader(id);

            DG_CORE_ERROR("{0}", infoLog.data());
            DG_CORE_ASSERT(false, "Shader program linking failure!");
        }
        for (auto id : glShaderIDs)
            glDetachShader(program, id);
    }

各种Upload**函数很简单, 以下面一个为例:

    void OpenGLShader::UploadUniformInt(const std::string& name, int value)
    {
        GLint location = glGetUniformLocation(m_RendererID, name.c_str());
        glUniform1i(location, value);
    }

绑定函数和解绑函数很简单:

    void OpenGLShader::Bind() const
    {
        glUseProgram(m_RendererID);
    }

    void OpenGLShader::Unbind() const
    {
        glUseProgram(0);
    }

至此,一个着色器类和一个着色器库类完成,在层类的构造方法中,可以这么用:

//加载着色器文件
m_ShaderLibrary.Load("shaderName","shaderFile");

// 获取着色器
auto shader = m_ShaderLibrary.Get("shaderName");

// 使用着色器程序
std::dynamic_pointer_cast<Dragon::OpenGLShader>(shader)->Bind();
//上传uniform参数
std::dynamic_pointer_cast<Dragon::OpenGLShader>(shader)->UploadUniform***();

Update()函数中:

auto shader = m_ShaderLibrary.Get("shaderName");

//设置背景颜色
Dragon::RenderCommand::SetColor({...});
//清除缓冲
Dragon::RenderCommand::Clear();
//开始场景
Dragon::Renderer::BeginScene();
// 使用着色器程序
std::dynamic_pointer_cast<Dragon::OpenGLShader>(shader)->Bind();
//上传uniform参数
std::dynamic_pointer_cast<Dragon::OpenGLShader>(shader)->UploadUniform***();
//提交渲染数据
Dragon::Renderer::Submit(...);
//结束场景
Dragon::Renderer::EndScene();

差不多就是这么一个结构。下一节介绍纹理类的实现。

项目github地址:https://github.com/Dragon-Baby/Dragon

相关文章

  • Dragon Engine:材质系统

    这一节介绍材质系统,主要是进行着色器类的抽象: 这里设计了两个类,一个着色器类,一个着色器库类。着色器类的成员函数...

  • Dragon Engine:事件系统

    本文同时发布在我的个人博客上:https://dragon_boy.gitee.io 事件系统的范围很广,这里我暂...

  • Dragon Engine:Debug

    本文同时发布在我的个人博客上:https://dragon_boy.gitee.io Debug,或者说日志系统,...

  • Dragon Engine:窗口

    本节介绍窗口的实现。这里使用开源库GLFW,之前也介绍过,只不过这里的主要任务就是将GLFW的API抽象出来,方便...

  • Dragon Engine:GUI

    这一节介绍GUI的实现。我们这里使用开源库ImGui。 首先我们将GUI分离为一个单独的层,创建ImGuiLaye...

  • Dragon Engine:层

    本文同时发布在我的个人博客上:https://dragon_boy.gitee.io/ 层的概念在许多软件中都存在...

  • Dragon Engine:编译项目

    编写大项目的时候,大部分文件可以复用,但构建项目的软件,使用的平台,可使用的外部资源因各种各不相同,所以大部分时候...

  • Dragon Engine:纹理模块

    这一节介绍纹理模块的实现。首先是纹理类的抽象: 纹理基类的构造不用多说,这里衍生出了两个纹理类,2D和立方体贴图。...

  • Dragon Engine:帧缓冲

    之前相关的图形学文章介绍过,帧缓冲非常有用,可以用来实现Post-Processing效果,以及阴影效果等,在进行...

  • Dragon Engine:基础渲染架构

    本节的目的是在已有的基础上构建出一个基础渲染架构。主要任务就是抽象。 一个简单的渲染架构包含很多模块,我之前发了许...

网友评论

      本文标题:Dragon Engine:材质系统

      本文链接:https://www.haomeiwen.com/subject/autkjktx.html