跳转至

一.Vulkan资源

下载Vulkan

先下载Vulkan,在vulkan文件夹下的lib,确保有如下文件

设置环境变量

这些库我们就不拷贝到项目里了,我们通过引入环境变量来获得相关的静态库文件。 使用者只需要单独下载Vulkan并设置好环境变量就能使用Vulkan的功能。

vulkan我下载的相当早,这个环境变量已经有了,有点记不清是不是自己设置的了,总之没有的话就设置一下

Premake

首先我们先把Dependencies单独分个文件出来管理

Dependencies

vulkan_sdk我们通过环境变量读到了 可以看到我们为了引入Vulkan,加入了想当多的依赖。 vulkan_SDK我们也要确保这些文件都存在。 vulkanSDK_Debug就是我们自己把他们丢进去的文件了。 比如下面这些,都丢到引擎应用程序目录下的vender/VulkanSDK/Lib中

shaderc_sharedd.lib"
spirv-cross-cored.lib
spirv-cross-glsld.lib
SPIRV-Toolsd.lib
这里其实我相当的费解,不知道为啥要自建个目录去存放

-- CryDust Dependencies
VULKAN_SDK = os.getenv("VULKAN_SDK")

IncludeDir = {}
IncludeDir["stb_image"] = "%{wks.location}/Hazel/vendor/stb_image"
IncludeDir["yaml_cpp"] = "%{wks.location}/Hazel/vendor/yaml-cpp/include"
IncludeDir["GLFW"] = "%{wks.location}/Hazel/vendor/GLFW/include"
IncludeDir["Glad"] = "%{wks.location}/Hazel/vendor/Glad/include"
IncludeDir["ImGui"] = "%{wks.location}/Hazel/vendor/ImGui"
IncludeDir["ImGuizmo"] = "%{wks.location}/Hazel/vendor/ImGuizmo"
IncludeDir["glm"] = "%{wks.location}/Hazel/vendor/glm"
IncludeDir["entt"] = "%{wks.location}/Hazel/vendor/entt/include"
IncludeDir["shaderc"] = "%{wks.location}/Hazel/vendor/shaderc/include"
IncludeDir["SPIRV_Cross"] = "%{wks.location}/Hazel/vendor/SPIRV-Cross"
IncludeDir["VulkanSDK"] = "%{VULKAN_SDK}/Include"

LibraryDir = {}
LibraryDir["VulkanSDK"] = "%{VULKAN_SDK}/Lib"
LibraryDir["VulkanSDK_Debug"] = "%{wks.location}/CryDust/vendor/VulkanSDK/Lib"

Library = {}

Library["Vulkan"] = "%{LibraryDir.VulkanSDK}/vulkan-1.lib"
Library["VulkanUtils"] = "%{LibraryDir.VulkanSDK}/VkLayer_utils.lib"

Library["ShaderC_Debug"] = "%{LibraryDir.VulkanSDK_Debug}/shaderc_sharedd.lib"
Library["SPIRV_Cross_Debug"] = "%{LibraryDir.VulkanSDK_Debug}/spirv-cross-cored.lib"
Library["SPIRV_Cross_GLSL_Debug"] = "%{LibraryDir.VulkanSDK_Debug}/spirv-cross-glsld.lib"
Library["SPIRV_Tools_Debug"] = "%{LibraryDir.VulkanSDK_Debug}/SPIRV-Toolsd.lib"

Library["ShaderC_Release"] = "%{LibraryDir.VulkanSDK}/shaderc_shared.lib"
Library["SPIRV_Cross_Release"] = "%{LibraryDir.VulkanSDK}/spirv-cross-core.lib"
Library["SPIRV_Cross_GLSL_Release"] = "%{LibraryDir.VulkanSDK}/spirv-cross-glsl.lib"

之前的Premake

删去依赖后

include "Dependencies.lua"

Editor

静态运行库(MDd)改为静态库(MTd),

    staticruntime "off"

CryDust

静态库

staticruntime "off"
include
        "%{IncludeDir.ImGuizmo}",
        "%{IncludeDir.VulkanSDK}"
links: Debug的时候用自己设置的路径
        links

        {

            "%{Library.ShaderC_Debug}",

            "%{Library.SPIRV_Cross_Debug}",

            "%{Library.SPIRV_Cross_GLSL_Debug}"

        }
Release和Dist的时候用环境变量拿路径
        links

        {

            "%{Library.ShaderC_Release}",

            "%{Library.SPIRV_Cross_Release}",

            "%{Library.SPIRV_Cross_GLSL_Release}"

        }

三、Timer.h

提供通用的计时方法

#pragma once
#include <chrono>
namespace CryDust {
    class Timer
    {
    public:
        Timer()
        {
            Reset();
        }
        void Timer::Reset()
        {
            m_Start = std::chrono::high_resolution_clock::now();
        }
        float Timer::Elapsed()
        {
            return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - m_Start).count() * 0.001f * 0.001f * 0.001f;
        }
        float Timer::ElapsedMillis()
        {
            return Elapsed() * 1000.0f;
        }
    private:
        std::chrono::time_point<std::chrono::high_resolution_clock> m_Start;
    };
}

四. 命令行参数功能

Application

新建一个结构体

    struct ApplicationCommandLineArgs
    {
        int Count = 0;
        char** Args = nullptr;
        const char* operator[](int index) const
        {
            CORE_DEBUG_ASSERT(index < Count);
            return Args[index];
        }
    }

修改构造函数,新增相关的变量和函数

class Application
{


Application(const std::string& name = "CryDust App", ApplicationCommandLineArgs args = ApplicationCommandLineArgs());

        ApplicationCommandLineArgs GetCommandLineArgs() const { return m_CommandLineArgs; }
    private:
        ApplicationCommandLineArgs m_CommandLineArgs;

}


...


Application* CreateApplication(ApplicationCommandLineArgs args);

EntryPoint

#include "CryDust/Core/Application.h"
extern CryDust::Application* CryDust::CreateApplication(ApplicationCommandLineArgs args);

    auto app = CryDust::CreateApplication({argc,argv});

创建场景

只需要 .exe 场景名。就可以启动了

五.常量缓冲区功能

UniformBuffer

#pragma once
#include "CryDust/Core/Base.h"
namespace CryDust {
    class UniformBuffer
    {
    public:
        virtual ~UniformBuffer() {}
        virtual void SetData(const void* data, uint32_t size, uint32_t offset = 0) = 0;

        static Ref<UniformBuffer> Create(uint32_t size, uint32_t binding);
    };
}

对应的OpenGL实现

h

#pragma once
#include "CryDust/Renderer/UniformBuffer.h"
namespace CryDust {
    class OpenGLUniformBuffer : public UniformBuffer
    {
    public:
        OpenGLUniformBuffer(uint32_t size, uint32_t binding);
        virtual ~OpenGLUniformBuffer();
        virtual void SetData(const void* data, uint32_t size, uint32_t offset = 0) override;
    private:
        uint32_t m_RendererID = 0;
    };
}

cpp

#include "cdpch.h"
#include "OpenGLUniformBuffer.h"
#include <glad/glad.h>
namespace CryDust {
    OpenGLUniformBuffer::OpenGLUniformBuffer(uint32_t size, uint32_t binding)
    {
        glCreateBuffers(1, &m_RendererID);
        glNamedBufferData(m_RendererID, size, nullptr, GL_DYNAMIC_DRAW); 
        glBindBufferBase(GL_UNIFORM_BUFFER, binding, m_RendererID);
    }
    OpenGLUniformBuffer::~OpenGLUniformBuffer()
    {
        glDeleteBuffers(1, &m_RendererID);
    }
    void OpenGLUniformBuffer::SetData(const void* data, uint32_t size, uint32_t offset)
    {
        glNamedBufferSubData(m_RendererID, offset, size, data);
    }
}

Uniform cpp

#include "cdpch.h"
#include "UniformBuffer.h"
#include "CryDust/Renderer/Renderer.h"
#include "Platform/OpenGL/OpenGLUniformBuffer.h"
namespace CryDust {
    Ref<UniformBuffer> UniformBuffer::Create(uint32_t size, uint32_t binding)
    {
        switch (Renderer::GetAPI())
        {
        case RendererAPI::API::None:    CORE_DEBUG_ASSERT(false, "RendererAPI::None is currently not supported!"); return nullptr;
        case RendererAPI::API::OpenGL:  return CreateRef<OpenGLUniformBuffer>(size, binding);
        }
        CORE_DEBUG_ASSERT(false, "Unknown RendererAPI!");
        return nullptr;
    }
}

六、SPIR-V支持

Texture.glsl

改为SPIR-V的格式了

#version 450 
->
#version 450 core

缓冲区设置

uniform mat4 u_ViewProjection;
->
layout(std140, binding = 0) uniform Camera
{
    mat4 u_ViewProjection;
};
uniform sampler2D u_Textures[32];
->
layout (binding = 0) uniform sampler2D u_Textures[32];

输出变量(这不就是unity里的形式吗)

out vec4 v_Color;
out vec2 v_TexCoord;
out flat float v_TexIndex;
out float v_TilingFactor;
out flat int v_EntityID;

->
struct VertexOutput
{
    vec4 Color;
    vec2 TexCoord;
    float TexIndex;
    float TilingFactor;
};
layout(location = 0) out VertexOutput Output;
layout(location = 4) out flat int v_EntityID;

赋值

    v_Color = a_Color;
    v_TexCoord = a_TexCoord;
    v_TexIndex = a_TexIndex;
    v_TilingFactor = a_TilingFactor;

    ->

    Output.Color = a_Color;
    Output.TexCoord = a_TexCoord;
    Output.TexIndex = a_TexIndex;
    Output.TilingFactor = a_TilingFactor;

SPIR-V C++代码

OpenGLShader

删去Compile代码,加上:

        void CompileOrGetVulkanBinaries(const std::unordered_map<GLenum, std::string>& shaderSources);
        void CompileOrGetOpenGLBinaries();
        void CreateProgram();
        void Reflect(GLenum stage, const std::vector<uint32_t>& shaderData);

private:
        std::string m_FilePath;
        std::unordered_map<GLenum, std::vector<uint32_t>> m_VulkanSPIRV;
        std::unordered_map<GLenum, std::vector<uint32_t>> m_OpenGLSPIRV;

        std::unordered_map<GLenum, std::string> m_OpenGLSourceCode;

CPP实现

引入库

#include <shaderc/shaderc.hpp>

#include <spirv_cross/spirv_cross.hpp>
#include <spirv_cross/spirv_glsl.hpp>

#include "CryDust/Core/Timer.h"

工具类

工具函数: - 判断shader种类 - shader种类生成对应的ShaderC - shader种类对应的字符串 - shader缓存目录 - 创建缓存目录 - 根据种类读取缓存的OpenGL的shader - 根据种类读取缓存的Vulkan的shader

    namespace Utils {

        static GLenum ShaderTypeFromString(const std::string& type)
        {
            if (type == "vertex")
                return GL_VERTEX_SHADER;
            if (type == "fragment" || type == "pixel")
                return GL_FRAGMENT_SHADER;
            CORE_DEBUG_ASSERT(false, "Unknown shader type!");
            return 0;
        }

        static shaderc_shader_kind GLShaderStageToShaderC(GLenum stage)
        {
            switch (stage)
            {
            case GL_VERTEX_SHADER:   return shaderc_glsl_vertex_shader;
            case GL_FRAGMENT_SHADER: return shaderc_glsl_fragment_shader;
            }
            CORE_DEBUG_ASSERT(false);
            return (shaderc_shader_kind)0;
        }
        static const char* GLShaderStageToString(GLenum stage)
        {
            switch (stage)
            {
            case GL_VERTEX_SHADER:   return "GL_VERTEX_SHADER";
            case GL_FRAGMENT_SHADER: return "GL_FRAGMENT_SHADER";
            }
            CORE_DEBUG_ASSERT(false);
            return nullptr;
        }
        static const char* GetCacheDirectory()
        {
            // TODO: make sure the assets directory is valid
            return "assets/cache/shader/opengl";
        }
        static void CreateCacheDirectoryIfNeeded()
        {
            std::string cacheDirectory = GetCacheDirectory();
            if (!std::filesystem::exists(cacheDirectory))
                std::filesystem::create_directories(cacheDirectory);
        }
        static const char* GLShaderStageCachedOpenGLFileExtension(uint32_t stage)
        {
            switch (stage)
            {
            case GL_VERTEX_SHADER:    return ".cached_opengl.vert";
            case GL_FRAGMENT_SHADER:  return ".cached_opengl.frag";
            }
            CORE_DEBUG_ASSERT(false);
            return "";
        }
        static const char* GLShaderStageCachedVulkanFileExtension(uint32_t stage)
        {
            switch (stage)
            {
            case GL_VERTEX_SHADER:    return ".cached_vulkan.vert";
            case GL_FRAGMENT_SHADER:  return ".cached_vulkan.frag";
            }
            CORE_DEBUG_ASSERT(false);
            return "";
        }
    }

文件路径构造

        Utils::CreateCacheDirectoryIfNeeded();

        {
            Timer timer;
            CompileOrGetVulkanBinaries(shaderSources);
            CompileOrGetOpenGLBinaries();
            CreateProgram();
            CORE_DEBUG_WARN("Shader creation took {0} ms", timer.ElapsedMillis());
        }

shader名字,vertex,frag路径分开构造

    OpenGLShader::OpenGLShader(const std::string& name, const std::string& vertexSrc, const std::string& fragmentSrc)
        : m_Name(name)
    {
        CD_PROFILE_FUNCTION();
        std::unordered_map<GLenum, std::string> sources;
        sources[GL_VERTEX_SHADER] = vertexSrc;
        sources[GL_FRAGMENT_SHADER] = fragmentSrc;
        CompileOrGetVulkanBinaries(sources);
        CompileOrGetOpenGLBinaries();
        CreateProgram();
    }

Vulkan

主要步骤是: - 设置编译选项:vulkan - 开启优化 - 读文件 - 拿sahder数据并清理掉 - 遍历shader资源 - 如果已经缓冲,读 - 如果未缓存:先将glsl编译的SPIR-V,再检查编译结果,再存入缓存 - 最后执行反射逻辑

    void OpenGLShader::CompileOrGetVulkanBinaries(const std::unordered_map<GLenum, std::string>& shaderSources)
    {
        GLuint program = glCreateProgram();

        shaderc::Compiler compiler;
        shaderc::CompileOptions options;
        options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2);
        const bool optimize = true;
        if (optimize)
            options.SetOptimizationLevel(shaderc_optimization_level_performance);

        std::filesystem::path cacheDirectory = Utils::GetCacheDirectory();

        auto& shaderData = m_VulkanSPIRV;
        shaderData.clear();
        for (auto&& [stage, source] : shaderSources)
        {
            std::filesystem::path shaderFilePath = m_FilePath;
            std::filesystem::path cachedPath = cacheDirectory / (shaderFilePath.filename().string() + Utils::GLShaderStageCachedVulkanFileExtension(stage));

            std::ifstream in(cachedPath, std::ios::in | std::ios::binary);
            if (in.is_open())
            {
                in.seekg(0, std::ios::end);
                auto size = in.tellg();
                in.seekg(0, std::ios::beg);

                auto& data = shaderData[stage];
                data.resize(size / sizeof(uint32_t));
                in.read((char*)data.data(), size);
            }
            else
            {
                shaderc::SpvCompilationResult module = compiler.CompileGlslToSpv(source, Utils::GLShaderStageToShaderC(stage), m_FilePath.c_str(), options);
                if (module.GetCompilationStatus() != shaderc_compilation_status_success)
                {
                    CORE_DEBUG_ERROR(module.GetErrorMessage());
                    CORE_DEBUG_ASSERT(false);
                }

                shaderData[stage] = std::vector<uint32_t>(module.cbegin(), module.cend());

                std::ofstream out(cachedPath, std::ios::out | std::ios::binary);
                if (out.is_open())
                {
                    auto& data = shaderData[stage];
                    out.write((char*)data.data(), data.size() * sizeof(uint32_t));
                    out.flush();
                    out.close();
                }
            }
        }

        for (auto&& [stage, data] : shaderData)
            Reflect(stage, data);
    }

Opengl也是类似

主要就是编译选项不太一样

void OpenGLShader::CompileOrGetOpenGLBinaries()
    {
        auto& shaderData = m_OpenGLSPIRV;

        shaderc::Compiler compiler;
        shaderc::CompileOptions options;
        options.SetTargetEnvironment(shaderc_target_env_opengl, shaderc_env_version_opengl_4_5);
        const bool optimize = false;
        if (optimize)
            options.SetOptimizationLevel(shaderc_optimization_level_performance);

        std::filesystem::path cacheDirectory = Utils::GetCacheDirectory();

        shaderData.clear();
        m_OpenGLSourceCode.clear();
        for (auto&& [stage, spirv] : m_VulkanSPIRV)
        {
            std::filesystem::path shaderFilePath = m_FilePath;
            std::filesystem::path cachedPath = cacheDirectory / (shaderFilePath.filename().string() + Utils::GLShaderStageCachedOpenGLFileExtension(stage));

            std::ifstream in(cachedPath, std::ios::in | std::ios::binary);
            if (in.is_open())
            {
                in.seekg(0, std::ios::end);
                auto size = in.tellg();
                in.seekg(0, std::ios::beg);

                auto& data = shaderData[stage];
                data.resize(size / sizeof(uint32_t));
                in.read((char*)data.data(), size);
            }
            else
            {
                spirv_cross::CompilerGLSL glslCompiler(spirv);
                m_OpenGLSourceCode[stage] = glslCompiler.compile();
                auto& source = m_OpenGLSourceCode[stage];

                shaderc::SpvCompilationResult module = compiler.CompileGlslToSpv(source, Utils::GLShaderStageToShaderC(stage), m_FilePath.c_str());
                if (module.GetCompilationStatus() != shaderc_compilation_status_success)
                {
                    CORE_DEBUG_ERROR(module.GetErrorMessage());
                    CORE_DEBUG_ASSERT(false);
                }

                shaderData[stage] = std::vector<uint32_t>(module.cbegin(), module.cend());

                std::ofstream out(cachedPath, std::ios::out | std::ios::binary);
                if (out.is_open())
                {
                    auto& data = shaderData[stage];
                    out.write((char*)data.data(), data.size() * sizeof(uint32_t));
                    out.flush();
                    out.close();
                }
            }
        }
    }

着色器程序创建CreateProgram

    void OpenGLShader::CreateProgram()
    {
        GLuint program = glCreateProgram();

        std::vector<GLuint> shaderIDs;
        //遍历一遍opengl的SPIRV

        for (auto&& [stage, spirv] : m_OpenGLSPIRV)
        {
            //先根据SPIRV创建一下shader
            GLuint shaderID = shaderIDs.emplace_back(glCreateShader(stage));
            //绑定shader喽
            glShaderBinary(1, &shaderID, GL_SHADER_BINARY_FORMAT_SPIR_V, spirv.data(), spirv.size() * sizeof(uint32_t));
            glSpecializeShader(shaderID, "main", 0, nullptr, nullptr);
            glAttachShader(program, shaderID);
        }
        //连接程序
        glLinkProgram(program);

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

            std::vector<GLchar> infoLog(maxLength);
            glGetProgramInfoLog(program, maxLength, &maxLength, infoLog.data());
            CORE_DEBUG_ERROR("Shader linking failed ({0}):\n{1}", m_FilePath, infoLog.data());

            glDeleteProgram(program);
            //错误了就删
            for (auto id : shaderIDs)
                glDeleteShader(id);
        }

        for (auto id : shaderIDs)
        {
            //删除程序和id
            glDetachShader(program, id);
            glDeleteShader(id);
        }

        m_RendererID = program;
    }

反射

目前就是输出一下,编译的时候开启反射

    void OpenGLShader::Reflect(GLenum stage, const std::vector<uint32_t>& shaderData)
    {
        //先拿到编译器和shader资源
        spirv_cross::Compiler compiler(shaderData);
        spirv_cross::ShaderResources resources = compiler.get_shader_resources();

        //路径,常量缓冲和图片采样
        CORE_DEBUG_TRACE("OpenGLShader::Reflect - {0} {1}", Utils::GLShaderStageToString(stage), m_FilePath);
        CORE_DEBUG_TRACE("    {0} uniform buffers", resources.uniform_buffers.size());
        CORE_DEBUG_TRACE("    {0} resources", resources.sampled_images.size());

        //常量缓冲
        CORE_DEBUG_TRACE("Uniform buffers:");
        for (const auto& resource : resources.uniform_buffers)
        {
            //拿到类型信息、大小、绑定点和成员数量并输出
            const auto& bufferType = compiler.get_type(resource.base_type_id);
            uint32_t bufferSize = compiler.get_declared_struct_size(bufferType);
            uint32_t binding = compiler.get_decoration(resource.id, spv::DecorationBinding);
            int memberCount = bufferType.member_types.size();

            CORE_DEBUG_TRACE("  {0}", resource.name);
            CORE_DEBUG_TRACE("    Size = {0}", bufferSize);
            CORE_DEBUG_TRACE("    Binding = {0}", binding);
            CORE_DEBUG_TRACE("    Members = {0}", memberCount);
        }
    }```

# 七.渲染器
添加摄像机矩阵和常亮缓冲区
```c++
        struct CameraData
        {
            glm::mat4 ViewProjection;
        };
        CameraData CameraBuffer;
        Ref<UniformBuffer> CameraUniformBuffer;
然后删去之前的shader绑定和纹理设置 之前创建帧缓冲
        s_Data.CameraUniformBuffer = UniformBuffer::Create(sizeof(Renderer2DData::CameraData), 0);

BeginScene,场景相机可以直接拿到view和Projection

通过开始场景,在这里计算透视与观察矩阵并设置缓冲

    void Renderer2D::BeginScene(const Camera& camera, const glm::mat4& transform)
    {
        CD_PROFILE_FUNCTION();
        s_Data.CameraBuffer.ViewProjection = camera.GetProjection() * glm::inverse(transform);
        s_Data.CameraUniformBuffer->SetData(&s_Data.CameraBuffer, sizeof(Renderer2DData::CameraData));
        StartBatch();
    }

Flush:绑定shader

        s_Data.TextureShader->Bind();

八,编辑器

EditorLayer 命令行参数支持

命令行参数大于1那么就返学劣化对应的路径的场景
        auto commandLineArgs = Application::Get().GetCommandLineArgs();
        if (commandLineArgs.Count > 1)
        {
            auto sceneFilePath = commandLineArgs[1];
            SceneSerializer serializer(m_ActiveScene);
            serializer.Deserialize(sceneFilePath);
        }