一.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"
"%{IncludeDir.ImGuizmo}",
"%{IncludeDir.VulkanSDK}"
links
{
"%{Library.ShaderC_Debug}",
"%{Library.SPIRV_Cross_Debug}",
"%{Library.SPIRV_Cross_GLSL_Debug}"
}
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;
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);
}