引擎架构之应用封装
整体架构
MyApp 继承 CameraApp,CameraApp 继承 VulkanApp,这三者的作用各有不同:
- VulkanApp 封装了窗口以及 Vulkan 的公共资源,包括 device、queue、commond 和 swapchain 这些绘制中基本不变的基础设施,并将绘制的过程进行封装,具体定制部分,由外面传入的 Renderer 负责;
- CameraApp 在 Vulkan 的基础上提供了 Camera 的实现;
- MyApp 的作用在于,将需要的 Renderer 装入 VulkanApp 的绘制过程中,其中主要使用了 MultiRenderer(具体分析见引擎架构之 Vulkan Renderer 封装)
源码分析 - VulkanApp
struct RenderItem {
Renderer& renderer_;
bool enabled_ = true;
bool useDepth_ = true;
explicit RenderItem(Renderer& r, bool useDepth = true)
: renderer_(r)
, useDepth_(useDepth)
{}
};
struct VulkanRenderContext
{
VulkanInstance vk;
VulkanRenderDevice vkDev;
VulkanContextCreator ctxCreator;
VulkanResources resources;
VulkanRenderContext(void* window, uint32_t screenWidth, uint32_t screenHeight, const VulkanContextFeatures& ctxFeatures = VulkanContextFeatures()):
ctxCreator(vk, vkDev, window, screenWidth, screenHeight, ctxFeatures),
resources(vkDev),
depthTexture(resources.addDepthTexture(vkDev.framebufferWidth, vkDev.framebufferHeight, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)),
screenRenderPass(resources.addFullScreenPass()),
screenRenderPass_NoDepth(resources.addFullScreenPass(false)),
finalRenderPass(resources.addFullScreenPass(true, RenderPassCreateInfo { .clearColor_ = false, .clearDepth_ = false, .flags_ = eRenderPassBit_Last })),
clearRenderPass(resources.addFullScreenPass(true, RenderPassCreateInfo { .clearColor_ = true, .clearDepth_ = true, .flags_ = eRenderPassBit_First })),
swapchainFramebuffers(resources.addFramebuffers(screenRenderPass.handle, depthTexture.image.imageView)),
swapchainFramebuffers_NoDepth(resources.addFramebuffers(screenRenderPass_NoDepth.handle))
{
}
void updateBuffers(uint32_t imageIndex);
void composeFrame(VkCommandBuffer commandBuffer, uint32_t imageIndex);
// For Chapter 8 & 9
inline PipelineInfo pipelineParametersForOutputs(const std::vector<VulkanTexture>& outputs) const {
return PipelineInfo {
.width = outputs.empty() ? vkDev.framebufferWidth : outputs[0].width,
.height = outputs.empty() ? vkDev.framebufferHeight : outputs[0].height,
.useBlending = false
};
}
std::vector<RenderItem> onScreenRenderers_;
VulkanTexture depthTexture;
// Framebuffers and renderpass for on-screen rendering
RenderPass screenRenderPass;
RenderPass screenRenderPass_NoDepth;
RenderPass clearRenderPass;
RenderPass finalRenderPass;
std::vector<VkFramebuffer> swapchainFramebuffers;
std::vector<VkFramebuffer> swapchainFramebuffers_NoDepth;
void beginRenderPass(VkCommandBuffer cmdBuffer, VkRenderPass pass, size_t currentImage, const VkRect2D area,
VkFramebuffer fb = VK_NULL_HANDLE,
uint32_t clearValueCount = 0, const VkClearValue* clearValues = nullptr)
{
const VkRenderPassBeginInfo renderPassInfo = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = pass,
.framebuffer = (fb != VK_NULL_HANDLE) ? fb : swapchainFramebuffers[currentImage],
.renderArea = area,
.clearValueCount = clearValueCount,
.pClearValues = clearValues
};
vkCmdBeginRenderPass( cmdBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE );
}
};
struct VulkanApp
{
VulkanApp(int screenWidth, int screenHeight, const VulkanContextFeatures& ctxFeatures = VulkanContextFeatures())
: window_(initVulkanApp(screenWidth, screenHeight, &resolution_)),
ctx_(window_, resolution_.width, resolution_.height, ctxFeatures),
onScreenRenderers_(ctx_.onScreenRenderers_)
{
glfwSetWindowUserPointer(window_, this);
assignCallbacks();
}
~VulkanApp()
{
glslang_finalize_process();
glfwTerminate();
}
// 虚函数,在每帧更新的 updateBuffers 函数中调用
virtual void drawUI() {}
virtual void draw3D() = 0;
// 主循环
void mainLoop();
// Check if none of the ImGui widgets were touched so our app can process mouse events
inline bool shouldHandleMouse() const { return !ImGui::GetIO().WantCaptureMouse; }
// 鼠标与按键回调
virtual void handleKey(int key, bool pressed) = 0;
virtual void handleMouseClick(int button, bool pressed)
{
if (button == GLFW_MOUSE_BUTTON_LEFT)
mouseState_.pressedLeft = pressed;
}
virtual void handleMouseMove(float mx, float my)
{
mouseState_.pos.x = mx;
mouseState_.pos.y = my;
}
// 虚函数,每帧更新的时候调用,会传入每帧之间的间隔
virtual void update(float deltaSeconds) = 0;
inline float getFPS() const { return fpsCounter_.getFPS(); }
protected:
struct MouseState
{
glm::vec2 pos = glm::vec2(0.0f);
bool pressedLeft = false;
} mouseState_;
Resolution resolution_;
GLFWwindow* window_ = nullptr;
VulkanRenderContext ctx_;
std::vector<RenderItem>& onScreenRenderers_;
FramesPerSecondCounter fpsCounter_;
private:
void assignCallbacks();
void updateBuffers(uint32_t imageIndex);
};
VulkanApp 主要分为以下几部分:
initVulkanApp
初始化window
变量;VulkanRenderContext ctx_
初始化 Vulkan 上下文相关部分;mainloop
函数,进入渲染循环;
下面逐一分析
initVulkanApp
GLFWwindow* initVulkanApp(int width, int height, Resolution* resolution)
{
glslang_initialize_process();
volkInitialize();
// 1、初始化 GLFW
if (!glfwInit())
exit(EXIT_FAILURE);
if (!glfwVulkanSupported())
exit(EXIT_FAILURE);
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
// 2、设置分辨率
if (resolution)
{
*resolution = detectResolution(width, height);
width = resolution->width;
height = resolution->height;
}
// 3、 创建窗口
GLFWwindow* result = glfwCreateWindow(width, height, "VulkanApp", nullptr, nullptr);
if (!result)
{
glfwTerminate();
exit(EXIT_FAILURE);
}
return result;
}
initVulkanApp
函数用来初始化 GLFW 的窗口对象,主要做了以下几件事:
glslang_initialize_process();
初始化 glslang 工具;volkInitialize();
初始化 volk 库;glfwInit()
初始化 GLFW 库;- 检测分辨率;
- 创建窗口;
VulkanRenderContext
struct VulkanRenderContext
{
VulkanInstance vk;
VulkanRenderDevice vkDev;
VulkanContextCreator ctxCreator;
VulkanResources resources;
VulkanRenderContext(void* window, uint32_t screenWidth, uint32_t screenHeight, const VulkanContextFeatures& ctxFeatures = VulkanContextFeatures()):
ctxCreator(vk, vkDev, window, screenWidth, screenHeight, ctxFeatures),
resources(vkDev),
depthTexture(resources.addDepthTexture(vkDev.framebufferWidth, vkDev.framebufferHeight, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)),
screenRenderPass(resources.addFullScreenPass()),
screenRenderPass_NoDepth(resources.addFullScreenPass(false)),
finalRenderPass(resources.addFullScreenPass(true, RenderPassCreateInfo { .clearColor_ = false, .clearDepth_ = false, .flags_ = eRenderPassBit_Last })),
clearRenderPass(resources.addFullScreenPass(true, RenderPassCreateInfo { .clearColor_ = true, .clearDepth_ = true, .flags_ = eRenderPassBit_First })),
swapchainFramebuffers(resources.addFramebuffers(screenRenderPass.handle, depthTexture.image.imageView)),
swapchainFramebuffers_NoDepth(resources.addFramebuffers(screenRenderPass_NoDepth.handle))
{
}
void updateBuffers(uint32_t imageIndex);
void composeFrame(VkCommandBuffer commandBuffer, uint32_t imageIndex);
// For Chapter 8 & 9
inline PipelineInfo pipelineParametersForOutputs(const std::vector<VulkanTexture>& outputs) const {
return PipelineInfo {
.width = outputs.empty() ? vkDev.framebufferWidth : outputs[0].width,
.height = outputs.empty() ? vkDev.framebufferHeight : outputs[0].height,
.useBlending = false
};
}
std::vector<RenderItem> onScreenRenderers_;
VulkanTexture depthTexture;
// Framebuffers and renderpass for on-screen rendering
RenderPass screenRenderPass;
RenderPass screenRenderPass_NoDepth;
RenderPass clearRenderPass;
RenderPass finalRenderPass;
std::vector<VkFramebuffer> swapchainFramebuffers;
std::vector<VkFramebuffer> swapchainFramebuffers_NoDepth;
void beginRenderPass(VkCommandBuffer cmdBuffer, VkRenderPass pass, size_t currentImage, const VkRect2D area,
VkFramebuffer fb = VK_NULL_HANDLE,
uint32_t clearValueCount = 0, const VkClearValue* clearValues = nullptr)
{
const VkRenderPassBeginInfo renderPassInfo = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = pass,
.framebuffer = (fb != VK_NULL_HANDLE) ? fb : swapchainFramebuffers[currentImage],
.renderArea = area,
.clearValueCount = clearValueCount,
.pClearValues = clearValues
};
vkCmdBeginRenderPass( cmdBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE );
}
};
先看一下 VulkanRenderContext
的属性:
VulkanInstance vk;
类型定义如下,主要是存储 Vulkan 实例以及相关的扩展:struct VulkanInstance final { VkInstance instance; VkSurfaceKHR surface; VkDebugUtilsMessengerEXT messenger; VkDebugReportCallbackEXT reportCallback; };
包括 Vulkan 实例、Surface 表面对象、调试信息回调的对象等;
VulkanRenderDevice vkDev;
类型定义如下,主要存储整个 APP 都会用的 Vulkan 对象,主要是 device 设备、swapchain 交换链、queue 提交队列和 command 命令缓冲:struct VulkanRenderDevice final { // framebuffer 的宽高 uint32_t framebufferWidth; uint32_t framebufferHeight; VkDevice device; // Vulkan Device 接口 VkQueue graphicsQueue; // Vulkan 的图像队列 VkPhysicalDevice physicalDevice; // Vulkan PhysicalDevice 接口 uint32_t graphicsFamily; // Vulkan 的图像队列索引 VkSwapchainKHR swapchain; // 图像的交换链 VkSemaphore semaphore; // 信号量 VkSemaphore renderSemaphore; // 信号量 // 交换链中的图像对应的 Image 对象和 ImageView 对象 std::vector<VkImage> swapchainImages; std::vector<VkImageView> swapchainImageViews; // 命令缓冲和缓冲池 VkCommandPool commandPool; std::vector<VkCommandBuffer> commandBuffers; // For chapter5/6etc (compute shaders) // Were we initialized with compute capabilities bool useCompute = false; // 命令队列 // [may coincide with graphicsFamily] uint32_t computeFamily; VkQueue computeQueue; // 所有队列 // a list of all queues (for shared buffer allocation) std::vector<uint32_t> deviceQueueIndices; std::vector<VkQueue> deviceQueues; // 计算着色器专用的命令缓冲和缓冲池 VkCommandBuffer computeCommandBuffer; VkCommandPool computeCommandPool; };
VulkanContextCreator ctxCreator;
类型定义如下,主要用来初始化vk
和vkDev
:struct VulkanContextCreator { VulkanContextCreator() = default; VulkanContextCreator(VulkanInstance& vk, VulkanRenderDevice& dev, void* window, int screenWidth, int screenHeight, const VulkanContextFeatures& ctxFeatures = VulkanContextFeatures()); ~VulkanContextCreator(); VulkanInstance& instance; VulkanRenderDevice& vkDev; }; VulkanContextCreator::VulkanContextCreator(VulkanInstance& vk, VulkanRenderDevice& dev, void* window, int screenWidth, int screenHeight, const VulkanContextFeatures& ctxFeatures): instance(vk), vkDev(dev) { createInstance(&vk.instance); if (!setupDebugCallbacks(vk.instance, &vk.messenger, &vk.reportCallback)) exit(0); if (glfwCreateWindowSurface(vk.instance, (GLFWwindow *)window, nullptr, &vk.surface)) exit(0); if (!initVulkanRenderDevice3(vk, dev, screenWidth, screenHeight, ctxFeatures)) exit(0); }
初始化主要分为以下几个部分:
createInstance,创建 VulkanInstance 并传给 volk,创建信息主要包括 Vulkan 需要使用哪些扩展:
void createInstance(VkInstance* instance) { // https://vulkan.lunarg.com/doc/view/1.1.108.0/windows/validation_layers.html const std::vector<const char*> ValidationLayers = { "VK_LAYER_KHRONOS_validation" }; const std::vector<const char*> exts = { "VK_KHR_surface", #if defined (_WIN32) "VK_KHR_win32_surface" #endif #if defined (__APPLE__) "VK_MVK_macos_surface" #endif #if defined (__linux__) "VK_KHR_xcb_surface" #endif , VK_EXT_DEBUG_UTILS_EXTENSION_NAME , VK_EXT_DEBUG_REPORT_EXTENSION_NAME /* for indexed textures */ , VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME }; const VkApplicationInfo appinfo = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pNext = nullptr, .pApplicationName = "Vulkan", .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "No Engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = VK_API_VERSION_1_1 }; const VkInstanceCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pNext = nullptr, .flags = 0, .pApplicationInfo = &appinfo, .enabledLayerCount = static_cast<uint32_t>(ValidationLayers.size()), .ppEnabledLayerNames = ValidationLayers.data(), .enabledExtensionCount = static_cast<uint32_t>(exts.size()), .ppEnabledExtensionNames = exts.data() }; VK_CHECK(vkCreateInstance(&createInfo, nullptr, instance)); volkLoadInstance(*instance); }
setupDebugCallbacks,设置调试用回调函数:
bool setupDebugCallbacks(VkInstance instance, VkDebugUtilsMessengerEXT* messenger, VkDebugReportCallbackEXT* reportCallback) { { const VkDebugUtilsMessengerCreateInfoEXT ci = { .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, .pfnUserCallback = &VulkanDebugCallback, .pUserData = nullptr }; VK_CHECK(vkCreateDebugUtilsMessengerEXT(instance, &ci, nullptr, messenger)); } { const VkDebugReportCallbackCreateInfoEXT ci = { .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, .pNext = nullptr, .flags = VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_DEBUG_BIT_EXT, .pfnCallback = &VulkanDebugReportCallback, .pUserData = nullptr }; VK_CHECK(vkCreateDebugReportCallbackEXT(instance, &ci, nullptr, reportCallback)); } return true; }
glfwCreateWindowSurface
创建 Surface 表面;initVulkanRenderDevice3
初始化vkDev
,主要是设置设备需要支持的特性,具体创建参照initVulkanRenderDevice2WithCompute
:struct VulkanContextFeatures { bool supportScreenshots_ = false; bool geometryShader_ = true; bool tessellationShader_ = false; bool vertexPipelineStoresAndAtomics_ = false; bool fragmentStoresAndAtomics_ = false; }; bool initVulkanRenderDevice3(VulkanInstance& vk, VulkanRenderDevice& vkDev, uint32_t width, uint32_t height, const VulkanContextFeatures& ctxFeatures) { VkPhysicalDeviceDescriptorIndexingFeaturesEXT physicalDeviceDescriptorIndexingFeatures = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT, .shaderSampledImageArrayNonUniformIndexing = VK_TRUE, .descriptorBindingVariableDescriptorCount = VK_TRUE, .runtimeDescriptorArray = VK_TRUE, }; VkPhysicalDeviceFeatures deviceFeatures = { /* for wireframe outlines */ .geometryShader = (VkBool32)(ctxFeatures.geometryShader_ ? VK_TRUE : VK_FALSE), /* for tesselation experiments */ .tessellationShader = (VkBool32)(ctxFeatures.tessellationShader_ ? VK_TRUE : VK_FALSE), /* for indirect instanced rendering */ .multiDrawIndirect = VK_TRUE, .drawIndirectFirstInstance = VK_TRUE, /* for OIT and general atomic operations */ .vertexPipelineStoresAndAtomics = (VkBool32)(ctxFeatures.vertexPipelineStoresAndAtomics_ ? VK_TRUE : VK_FALSE), .fragmentStoresAndAtomics = (VkBool32)(ctxFeatures.fragmentStoresAndAtomics_ ? VK_TRUE : VK_FALSE), /* for arrays of textures */ .shaderSampledImageArrayDynamicIndexing = VK_TRUE, /* for GL <-> VK material shader compatibility */ .shaderInt64 = VK_TRUE, }; VkPhysicalDeviceFeatures2 deviceFeatures2 = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .pNext = &physicalDeviceDescriptorIndexingFeatures, .features = deviceFeatures /* */ }; return initVulkanRenderDevice2WithCompute(vk, vkDev, width, height, isDeviceSuitable, deviceFeatures2, ctxFeatures.supportScreenshots_); }
initVulkanRenderDevice2WithCompute
创建VulkanRenderDevice
变量:bool initVulkanRenderDevice2WithCompute(VulkanInstance& vk, VulkanRenderDevice& vkDev, uint32_t width, uint32_t height, std::function<bool(VkPhysicalDevice)> selector, VkPhysicalDeviceFeatures2 deviceFeatures2, bool supportScreenshots) { // 1. 设置 framebuffer 宽高 vkDev.framebufferWidth = width; vkDev.framebufferHeight = height; // 2. 查找合适的 physicalDevice,并创建创建 device,以及查找图形队列和计算队列的索引 VK_CHECK(findSuitablePhysicalDevice(vk.instance, selector, &vkDev.physicalDevice)); vkDev.graphicsFamily = findQueueFamilies(vkDev.physicalDevice, VK_QUEUE_GRAPHICS_BIT); vkDev.computeFamily = findQueueFamilies(vkDev.physicalDevice, VK_QUEUE_COMPUTE_BIT); VK_CHECK(createDevice2WithCompute(vkDev.physicalDevice, deviceFeatures2, vkDev.graphicsFamily, vkDev.computeFamily, &vkDev.device)); // 3. 创建图形队列和计算队列 vkGetDeviceQueue(vkDev.device, vkDev.graphicsFamily, 0, &vkDev.graphicsQueue); if (vkDev.graphicsQueue == nullptr) exit(EXIT_FAILURE); vkGetDeviceQueue(vkDev.device, vkDev.computeFamily, 0, &vkDev.computeQueue); if (vkDev.computeQueue == nullptr) exit(EXIT_FAILURE); // 4. 查询是否支持该 surface VkBool32 presentSupported = 0; vkGetPhysicalDeviceSurfaceSupportKHR(vkDev.physicalDevice, vkDev.graphicsFamily, vk.surface, &presentSupported); if (!presentSupported) exit(EXIT_FAILURE); // 5. 创建 swapchain 交换链,以及交换链中的图像对象 VK_CHECK(createSwapchain(vkDev.device, vkDev.physicalDevice, vk.surface, vkDev.graphicsFamily, width, height, &vkDev.swapchain, supportScreenshots)); const size_t imageCount = createSwapchainImages(vkDev.device, vkDev.swapchain, vkDev.swapchainImages, vkDev.swapchainImageViews); // 6. 更新图像命令缓冲的数量,交换链中的每个图像对应一个命令缓冲 vkDev.commandBuffers.resize(imageCount); // 7. 创建信号量 VK_CHECK(createSemaphore(vkDev.device, &vkDev.semaphore)); VK_CHECK(createSemaphore(vkDev.device, &vkDev.renderSemaphore)); // 8. 创建图像命令缓冲池,绑定图像提交队列,并创建命令缓冲 const VkCommandPoolCreateInfo cpi = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags = 0, .queueFamilyIndex = vkDev.graphicsFamily }; VK_CHECK(vkCreateCommandPool(vkDev.device, &cpi, nullptr, &vkDev.commandPool)); const VkCommandBufferAllocateInfo ai = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .pNext = nullptr, .commandPool = vkDev.commandPool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = static_cast<uint32_t>(vkDev.swapchainImages.size()), }; VK_CHECK(vkAllocateCommandBuffers(vkDev.device, &ai, &vkDev.commandBuffers[0])); // 9. 创建计算命令缓冲池,绑定计算提交队列,并创建命令缓冲 { // Create compute command pool const VkCommandPoolCreateInfo cpi1 = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .pNext = nullptr, .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, /* Allow command from this pool buffers to be reset*/ .queueFamilyIndex = vkDev.computeFamily }; VK_CHECK(vkCreateCommandPool(vkDev.device, &cpi1, nullptr, &vkDev.computeCommandPool)); const VkCommandBufferAllocateInfo ai1 = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .pNext = nullptr, .commandPool = vkDev.computeCommandPool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; VK_CHECK(vkAllocateCommandBuffers(vkDev.device, &ai1, &vkDev.computeCommandBuffer)); } vkDev.useCompute = true; return true; }
VulkanResources resources;
主要是用来构建各种 Vulkan 资源,对原生的 Vulkan API 进行封装,详细信息参照 引擎架构之资源管理 章节,以下是类型声明:struct VulkanResources { VulkanResources(VulkanRenderDevice& vkDev): vkDev(vkDev) {} ~VulkanResources(); VulkanTexture loadTexture2D(const char* filename); VulkanTexture loadCubeMap(const char* fileName, uint32_t mipLevels = 1); VulkanTexture loadKTX(const char* fileName); VulkanTexture createFontTexture(const char* fontFile); VulkanTexture addColorTexture(int texWidth = 0, int texHeight = 0, VkFormat colorFormat = VK_FORMAT_B8G8R8A8_UNORM, VkFilter minFilter = VK_FILTER_LINEAR, VkFilter maxFilter = VK_FILTER_LINEAR, VkSamplerAddressMode addressMode = VK_SAMPLER_ADDRESS_MODE_REPEAT); VulkanTexture addDepthTexture(int texWidth = 0, int texHeight = 0, VkImageLayout layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); VulkanTexture addSolidRGBATexture(uint32_t color = 0xFFFFFFFF); VulkanTexture addRGBATexture(int texWidth, int texHeight, void* data); VulkanBuffer addBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, bool createMapping = false); inline VulkanBuffer addUniformBuffer(VkDeviceSize bufferSize, bool createMapping = false) { return addBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, createMapping); } inline VulkanBuffer addIndirectBuffer(VkDeviceSize bufferSize, bool createMapping = false) { return addBuffer(bufferSize, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, // | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, createMapping); /* for debugging we make it host-visible */ } inline VulkanBuffer addStorageBuffer(VkDeviceSize bufferSize, bool createMapping = false) { return addBuffer(bufferSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, createMapping); /* for debugging we make it host-visible */ } inline VulkanBuffer addComputedIndirectBuffer(VkDeviceSize bufferSize, bool createMapping = false) { return addBuffer(bufferSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, createMapping); /* for debugging we make it host-visible */ } /* Allocate and upload vertex & index buffer pair */ VulkanBuffer addVertexBuffer(uint32_t indexBufferSize, const void* indexData, uint32_t vertexBufferSize, const void* vertexData); VkFramebuffer addFramebuffer(RenderPass renderPass, const std::vector<VulkanTexture>& images); RenderPass addFullScreenPass(bool useDepth = true, const RenderPassCreateInfo& ci = RenderPassCreateInfo()); RenderPass addRenderPass(const std::vector<VulkanTexture>& outputs, const RenderPassCreateInfo& ci = { .clearColor_ = true, .clearDepth_ = true, .flags_ = eRenderPassBit_Offscreen | eRenderPassBit_First }, bool useDepth = true); RenderPass addDepthRenderPass(const std::vector<VulkanTexture>& outputs, const RenderPassCreateInfo& ci = { .clearColor_ = false, .clearDepth_ = true, .flags_ = eRenderPassBit_Offscreen | eRenderPassBit_First }); VkPipelineLayout addPipelineLayout(VkDescriptorSetLayout dsLayout, uint32_t vtxConstSize = 0, uint32_t fragConstSize = 0); VkPipeline addPipeline(VkRenderPass renderPass, VkPipelineLayout pipelineLayout, const std::vector<const char*>& shaderFiles, const PipelineInfo& pipelineParams = PipelineInfo { .width = 0, .height = 0, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, .useDepth = true, .useBlending = false, .dynamicScissorState = false }); VkPipeline addComputePipeline(const char* shaderFile, VkPipelineLayout pipelineLayout); /* Calculate the descriptor pool size from the list of buffers and textures */ VkDescriptorPool addDescriptorPool(const DescriptorSetInfo& dsInfo, uint32_t dSetCount = 1); VkDescriptorSetLayout addDescriptorSetLayout(const DescriptorSetInfo& dsInfo); VkDescriptorSet addDescriptorSet(VkDescriptorPool descriptorPool, VkDescriptorSetLayout dsLayout); void updateDescriptorSet(VkDescriptorSet ds, const DescriptorSetInfo& dsInfo); const std::vector<VulkanTexture>& getTextures() const { return allTextures; } std::vector<VkFramebuffer> addFramebuffers(VkRenderPass renderPass, VkImageView depthView = VK_NULL_HANDLE); /** Helper functions for small Chapter 8/9 demos */ std::pair<BufferAttachment, BufferAttachment> makeMeshBuffers(const std::vector<float>& vertices, const std::vector<unsigned int>& indices); std::pair<BufferAttachment, BufferAttachment> loadMeshToBuffer(const char* filename, bool useTextureCoordinates, bool useNormals, std::vector<float>& vertices, std::vector<unsigned int>& indices); std::pair<BufferAttachment, BufferAttachment> createPlaneBuffer_XZ(float sx, float sz); std::pair<BufferAttachment, BufferAttachment> createPlaneBuffer_XY(float sx, float sy); private: VulkanRenderDevice& vkDev; std::vector<VulkanTexture> allTextures; std::vector<VulkanBuffer> allBuffers; std::vector<VkFramebuffer> allFramebuffers; std::vector<VkRenderPass> allRenderPasses; std::vector<VkPipelineLayout> allPipelineLayouts; std::vector<VkPipeline> allPipelines; std::vector<VkDescriptorSetLayout> allDSLayouts; std::vector<VkDescriptorPool> allDPools; std::vector<ShaderModule> shaderModules; std::map<std::string, int> shaderMap; bool createGraphicsPipeline( VulkanRenderDevice& vkDev, VkRenderPass renderPass, VkPipelineLayout pipelineLayout, const std::vector<const char*>& shaderFiles, VkPipeline* pipeline, VkPrimitiveTopology topology, bool useDepth, bool useBlending, bool dynamicScissorState, int32_t customWidth, int32_t customHeight, uint32_t numPatchControlPoints); };
除了上述最关键的四个初始属性外,其他属性用来保存 Vulkan 相关的渲染资源,其中关于 Renderer 的详细分析见 引擎架构之 FrameworkRenderer 封装:
struct RenderItem { Renderer& renderer_; bool enabled_ = true; bool useDepth_ = true; explicit RenderItem(Renderer& r, bool useDepth = true) : renderer_(r) , useDepth_(useDepth) {} }; // 1. 主要用来保存 Renderer,后面会具体分析 Renderer,这里的 Renderer 由子类初始化时传入 std::vector<RenderItem> onScreenRenderers_; // 2. 保存深度缓冲 VulkanTexture depthTexture; // 3. 用来屏幕绘制的 renderPass // Framebuffers and renderpass for on-screen rendering RenderPass screenRenderPass; RenderPass screenRenderPass_NoDepth; RenderPass clearRenderPass; // 清空颜色和深度缓存的 renderPass RenderPass finalRenderPass; // 调整图像格式,用来进行呈现 // 4. 保存不同 swapChain 中图像的 frameBuffer std::vector<VkFramebuffer> swapchainFramebuffers; std::vector<VkFramebuffer> swapchainFramebuffers_NoDepth;
分析完属性,我们看一下 VulkanRenderContext
中的函数:
先看构造函数
VulkanRenderContext(void* window, uint32_t screenWidth, uint32_t screenHeight, const VulkanContextFeatures& ctxFeatures = VulkanContextFeatures()): ctxCreator(vk, vkDev, window, screenWidth, screenHeight, ctxFeatures), resources(vkDev), depthTexture(resources.addDepthTexture(vkDev.framebufferWidth, vkDev.framebufferHeight, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)), screenRenderPass(resources.addFullScreenPass()), screenRenderPass_NoDepth(resources.addFullScreenPass(false)), finalRenderPass(resources.addFullScreenPass(true, RenderPassCreateInfo { .clearColor_ = false, .clearDepth_ = false, .flags_ = eRenderPassBit_Last })), clearRenderPass(resources.addFullScreenPass(true, RenderPassCreateInfo { .clearColor_ = true, .clearDepth_ = true, .flags_ = eRenderPassBit_First })), swapchainFramebuffers(resources.addFramebuffers(screenRenderPass.handle, depthTexture.image.imageView)), swapchainFramebuffers_NoDepth(resources.addFramebuffers(screenRenderPass_NoDepth.handle)) { }
这里主要是用来初始化 vk、vkDev、resources,以及各种资源
调用每个 renderer 的
updateBuffers
方法,更新数据:void VulkanRenderContext::updateBuffers(uint32_t imageIndex) { for (auto& r : onScreenRenderers_) if (r.enabled_) r.renderer_.updateBuffers(imageIndex); }
这里的
composeFrame
函数主要分为三部分,清空颜色与深度缓存、调用每个 renderer 的渲染指令、转换 swapChain 图像格式,以便于呈现void VulkanRenderContext::composeFrame(VkCommandBuffer commandBuffer, uint32_t imageIndex) { const VkRect2D defaultScreenRect { .offset = { 0, 0 }, .extent = {.width = vkDev.framebufferWidth, .height = vkDev.framebufferHeight } }; static const VkClearValue defaultClearValues[2] = { VkClearValue { .color = { 1.0f, 1.0f, 1.0f, 1.0f } }, VkClearValue { .depthStencil = { 1.0f, 0 } } }; beginRenderPass(commandBuffer, clearRenderPass.handle, imageIndex, defaultScreenRect, VK_NULL_HANDLE, 2u, defaultClearValues); vkCmdEndRenderPass( commandBuffer ); for (auto& r : onScreenRenderers_) if (r.enabled_) { RenderPass rp = r.useDepth_ ? screenRenderPass : screenRenderPass_NoDepth; VkFramebuffer fb = (r.useDepth_ ? swapchainFramebuffers : swapchainFramebuffers_NoDepth)[imageIndex]; if (r.renderer_.renderPass_.handle != VK_NULL_HANDLE) rp = r.renderer_.renderPass_; if (r.renderer_.framebuffer_ != VK_NULL_HANDLE) fb = r.renderer_.framebuffer_; r.renderer_.fillCommandBuffer(commandBuffer, imageIndex, fb, rp.handle); } beginRenderPass(commandBuffer, finalRenderPass.handle, imageIndex, defaultScreenRect); vkCmdEndRenderPass( commandBuffer ); }
这里的 beginRenderPass,主要用于在 command 中开始一次 renderPass,这里主要需要设置的属性包括:renderPass、frameBuffer、渲染区域,以及用来清空缓存的值
void beginRenderPass(VkCommandBuffer cmdBuffer, VkRenderPass pass, size_t currentImage, const VkRect2D area, VkFramebuffer fb = VK_NULL_HANDLE, uint32_t clearValueCount = 0, const VkClearValue* clearValues = nullptr) { const VkRenderPassBeginInfo renderPassInfo = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = pass, .framebuffer = (fb != VK_NULL_HANDLE) ? fb : swapchainFramebuffers[currentImage], .renderArea = area, .clearValueCount = clearValueCount, .pClearValues = clearValues }; vkCmdBeginRenderPass( cmdBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE ); }
mainLoop
mainLoop
函数的主要作用是开始渲染循环
void VulkanApp::mainLoop()
{
double timeStamp = glfwGetTime();
float deltaSeconds = 0.0f;
do
{
// 1、 update 是虚函数,由子类覆盖实现,用来根据时间间隔,更新变量
update(deltaSeconds);
const double newTimeStamp = glfwGetTime();
deltaSeconds = static_cast<float>(newTimeStamp - timeStamp);
timeStamp = newTimeStamp;
// 2、 计算 FPS
fpsCounter_.tick(deltaSeconds);
// 3、核心渲染函数,渲染一帧画面,下面详细分析
bool frameRendered = drawFrame(ctx_.vkDev,
[this](uint32_t img) { this->updateBuffers(img); },
[this](auto cmd, auto img) { ctx_.composeFrame(cmd, img); }
);
fpsCounter_.tick(deltaSeconds, frameRendered);
glfwPollEvents();
} while (!glfwWindowShouldClose(window_));
}
其中,核心是 drawFrame
函数,下面详细分析:
bool drawFrame(VulkanRenderDevice& vkDev, const std::function<void(uint32_t)>& updateBuffersFunc, const std::function<void(VkCommandBuffer, uint32_t)>& composeFrameFunc)
{
// 1、渲染准备工作,获取 swapChain 交换链中可用的图像的索引,并重置图像提交命令缓冲池,注意这里的 `vkDev.semaphore` 表示获取到图像后再触发
uint32_t imageIndex = 0;
VkResult result = vkAcquireNextImageKHR(vkDev.device, vkDev.swapchain, 0, vkDev.semaphore, VK_NULL_HANDLE, &imageIndex);
VK_CHECK(vkResetCommandPool(vkDev.device, vkDev.commandPool, 0));
if (result != VK_SUCCESS) return false;
// 2、触发每帧数据缓冲的更新调用,下面有详细分析
updateBuffersFunc(imageIndex);
// 3、获取对应的提交命令,准备开始录制命令
VkCommandBuffer commandBuffer = vkDev.commandBuffers[imageIndex];
const VkCommandBufferBeginInfo bi =
{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.pNext = nullptr,
.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
.pInheritanceInfo = nullptr
};
VK_CHECK(vkBeginCommandBuffer(commandBuffer, &bi));
// 4、此处调用的即是 `VulkanRenderContext::composeFrame`,用来依次调用 Renderer,录制绘制命令
composeFrameFunc(commandBuffer, imageIndex);
// 5、结束命令缓冲录制
VK_CHECK(vkEndCommandBuffer(commandBuffer));
// 6、提交命令,主要信息为三部分:要提交的命令,需要等待的获取图像的信号量 `vkDev.semaphore`,渲染完成后会触发的信号量 `vkDev.renderSemaphore`
const VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; // or even VERTEX_SHADER_STAGE
const VkSubmitInfo si =
{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pNext = nullptr,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &vkDev.semaphore,
.pWaitDstStageMask = waitStages,
.commandBufferCount = 1,
.pCommandBuffers = &vkDev.commandBuffers[imageIndex],
.signalSemaphoreCount = 1,
.pSignalSemaphores = &vkDev.renderSemaphore
};
VK_CHECK(vkQueueSubmit(vkDev.graphicsQueue, 1, &si, nullptr));
// 7、呈现渲染好的图像,主要信息包括:swapChain 交换链、本帧渲染的交换链中的图像的索引、等待的渲染完成的信号量 `vkDev.renderSemaphore`
const VkPresentInfoKHR pi =
{
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.pNext = nullptr,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &vkDev.renderSemaphore,
.swapchainCount = 1,
.pSwapchains = &vkDev.swapchain,
.pImageIndices = &imageIndex
};
VK_CHECK(vkQueuePresentKHR(vkDev.graphicsQueue, &pi));
// 8、简单起见,等待命令处理完成
VK_CHECK(vkDeviceWaitIdle(vkDev.device));
return true;
}
drawFrame
函数的主要作用是用来从 swapChain 中获取该帧渲染对应的图像对象的索引,然后准备好命令缓冲之后,录制好命令,在提交到队列中渲染,渲染完成后呈现到 swapChain 中,其中有两个自定义的函数,是从外部传入进来的,下面会详细分析:
updateBuffersFunc
void VulkanApp::updateBuffers(uint32_t imageIndex) { ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2((float)ctx_.vkDev.framebufferWidth, (float)ctx_.vkDev.framebufferHeight); ImGui::NewFrame(); drawUI(); ImGui::Render(); draw3D(); ctx_.updateBuffers(imageIndex); }
该函数用来在每帧渲染时调用,用来触发以下渲染数据更新:
drawUI()
虚函数,子类实现,更新 UI 的渲染数据;draw3D()
虚函数,子类实现,一般用来更新 Renderer 的 MVP 数据;ctx_.updateBuffers(imageIndex)
,调用VulkanRenderContext::updateBuffers
,这里会触发每个 Renderer 的updateBuffers
函数;
- composeFrameFunc
这里调用的是VulkanRenderContext::composeFrame
函数,用来真正录制绘制命令,详细解释参考上文;
构造函数
VulkanApp(int screenWidth, int screenHeight, const VulkanContextFeatures& ctxFeatures = VulkanContextFeatures()):
window_(initVulkanApp(screenWidth, screenHeight, &resolution_)),
ctx_(window_, resolution_.width, resolution_.height, ctxFeatures),
onScreenRenderers_(ctx_.onScreenRenderers_)
{
glfwSetWindowUserPointer(window_, this);
assignCallbacks();
}
void VulkanApp::assignCallbacks()
{
glfwSetCursorPosCallback(
window_,
[](GLFWwindow* window, double x, double y)
{
ImGui::GetIO().MousePos = ImVec2((float)x, (float)y);
int width, height;
glfwGetFramebufferSize(window, &width, &height);
void* ptr = glfwGetWindowUserPointer(window);
const float mx = static_cast<float>(x / width);
const float my = static_cast<float>(y / height);
reinterpret_cast<VulkanApp*>(ptr)->handleMouseMove(mx, my);
}
);
glfwSetMouseButtonCallback(
window_,
[](GLFWwindow* window, int button, int action, int mods)
{
auto& io = ImGui::GetIO();
const int idx = button == GLFW_MOUSE_BUTTON_LEFT ? 0 : button == GLFW_MOUSE_BUTTON_RIGHT ? 2 : 1;
io.MouseDown[idx] = action == GLFW_PRESS;
void* ptr = glfwGetWindowUserPointer(window);
reinterpret_cast<VulkanApp*>(ptr)->handleMouseClick(button, action == GLFW_PRESS);
}
);
glfwSetKeyCallback(
window_,
[](GLFWwindow* window, int key, int scancode, int action, int mods)
{
const bool pressed = action != GLFW_RELEASE;
if (key == GLFW_KEY_ESCAPE && pressed)
glfwSetWindowShouldClose(window, GLFW_TRUE);
void* ptr = glfwGetWindowUserPointer(window);
reinterpret_cast<VulkanApp*>(ptr)->handleKey(key, pressed);
}
);
}
构造函数中主要是用来设置 GLFW 窗口的回调函数,监听鼠标和键盘的事件
源码分析 - CameraApp
struct CameraApp: public VulkanApp
{
CameraApp(int screenWidth, int screenHeight, const VulkanContextFeatures& ctxFeatures = VulkanContextFeatures()):
VulkanApp(screenWidth, screenHeight, ctxFeatures),
positioner(glm::vec3(0.0f, 5.0f, 10.0f), vec3(0.0f, 0.0f, -1.0f), vec3(0.0f, -1.0f, 0.0f)),
camera(positioner)
{}
virtual void update(float deltaSeconds) override
{
positioner.update(deltaSeconds, mouseState_.pos, shouldHandleMouse() ? mouseState_.pressedLeft : false);
}
glm::mat4 getDefaultProjection() const {
const float ratio = ctx_.vkDev.framebufferWidth / (float)ctx_.vkDev.framebufferHeight;
return glm::perspective(45.0f, ratio, 0.1f, 1000.0f);
}
virtual void handleKey(int key, bool pressed) override;
protected:
CameraPositioner_FirstPerson positioner;
Camera camera;
};
struct VulkanContextFeatures
{
bool supportScreenshots_ = false;
bool geometryShader_ = true;
bool tessellationShader_ = false;
bool vertexPipelineStoresAndAtomics_ = false;
bool fragmentStoresAndAtomics_ = false;
};
void CameraApp::handleKey(int key, bool pressed)
{
if (key == GLFW_KEY_W)
positioner.movement_.forward_ = pressed;
if (key == GLFW_KEY_S)
positioner.movement_.backward_ = pressed;
if (key == GLFW_KEY_A)
positioner.movement_.left_ = pressed;
if (key == GLFW_KEY_D)
positioner.movement_.right_ = pressed;
if (key == GLFW_KEY_C)
positioner.movement_.up_ = pressed;
if (key == GLFW_KEY_E)
positioner.movement_.down_ = pressed;
}
这里的 CemaraApp 主要是增加了一个第一人称的摄像机组件,其中:
- 实现了虚函数
virtual void update(float deltaSeconds) override
,用来在每帧更新摄像机的位置; - 实现了虚函数
virtual void handleKey(int key, bool pressed) override
,用来监听按键的变化; - 实现了函数
glm::mat4 getDefaultProjection() const
,用来给子类提供 P 矩阵;
源码分析 - MyApp
struct MyApp: public CameraApp
{
MyApp()
: CameraApp(-95, -95)
, envMap(ctx_.resources.loadCubeMap("data/piazza_bologni_1k.hdr"))
, irrMap(ctx_.resources.loadCubeMap("data/piazza_bologni_1k_irradiance.hdr"))
, sceneData(ctx_, "data/meshes/test_graph.meshes", "data/meshes/test_graph.scene", "data/meshes/test_graph.materials", envMap, irrMap)
, plane(ctx_)
, multiRenderer(ctx_, sceneData)
, imgui(ctx_)
{
onScreenRenderers_.emplace_back(plane, false);
onScreenRenderers_.emplace_back(multiRenderer);
onScreenRenderers_.emplace_back(imgui, false);
sceneData.scene_.localTransform_[0] = glm::rotate(glm::mat4(1.f), (float)(M_PI / 2.f), glm::vec3(1.f, 0.f, 0.0f));
}
void drawUI() override {
ImGui::Begin("Information", nullptr);
ImGui::Text("FPS: %.2f", getFPS());
ImGui::End();
ImGui::Begin("Scene graph", nullptr);
int node = renderSceneTree(sceneData.scene_, 0);
if (node > -1)
selectedNode = node;
ImGui::End();
editNode(selectedNode);
}
void draw3D() override {
const mat4 p = getDefaultProjection();
const mat4 view =camera.getViewMatrix();
multiRenderer.setMatrices(p, view);
plane.setMatrices(p, view, mat4(1.f));
}
void update(float deltaSeconds) override
{
CameraApp::update(deltaSeconds);
// update/upload matrices for individual scene nodes
sceneData.recalculateAllTransforms();
sceneData.uploadGlobalTransforms();
}
private:
VulkanTexture envMap;
VulkanTexture irrMap;
VKSceneData sceneData;
InfinitePlaneRenderer plane;
MultiRenderer multiRenderer;
GuiRenderer imgui;
int selectedNode = -1;
void editNode(int node)
{
if (node < 0)
return;
mat4 cameraProjection = getDefaultProjection();
mat4 cameraView = camera.getViewMatrix();
ImGuizmo::SetOrthographic(false);
ImGuizmo::BeginFrame();
std::string name = getNodeName(sceneData.scene_, node);
std::string label = name.empty() ? (std::string("Node") + std::to_string(node)) : name;
label = "Node: " + label;
ImGui::Begin("Editor");
ImGui::Text("%s", label.c_str());
glm::mat4 globalTransform = sceneData.scene_.globalTransform_[node]; // fetch global transform
glm::mat4 srcTransform = globalTransform;
glm::mat4 localTransform = sceneData.scene_.localTransform_[node];
ImGui::Separator();
ImGuizmo::SetID(1);
editTransform(cameraView, cameraProjection, globalTransform);
glm::mat4 deltaTransform = glm::inverse(srcTransform) * globalTransform; // calculate delta for edited global transform
sceneData.scene_.localTransform_[node] = localTransform * deltaTransform; // modify local transform
markAsChanged(sceneData.scene_, node);
ImGui::Separator();
ImGui::Text("%s", "Material");
editMaterial(node);
ImGui::End();
}
void editTransform(const glm::mat4& view, const glm::mat4& projection, glm::mat4& matrix)
{
static ImGuizmo::OPERATION gizmoOperation(ImGuizmo::TRANSLATE);
if (ImGui::RadioButton("Translate", gizmoOperation == ImGuizmo::TRANSLATE))
gizmoOperation = ImGuizmo::TRANSLATE;
ImGui::SameLine();
if (ImGui::RadioButton("Rotate", gizmoOperation == ImGuizmo::ROTATE))
gizmoOperation = ImGuizmo::ROTATE;
ImGuiIO& io = ImGui::GetIO();
ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y);
ImGuizmo::Manipulate(glm::value_ptr(view), glm::value_ptr(projection), gizmoOperation, ImGuizmo::WORLD, glm::value_ptr(matrix));
}
void editMaterial(int node) {
if (!sceneData.scene_.materialForNode_.contains(node))
return;
auto matIdx = sceneData.scene_.materialForNode_.at(node);
MaterialDescription& material = sceneData.materials_[matIdx];
float emissiveColor[4];
memcpy(emissiveColor, &material.emissiveColor_, sizeof(gpuvec4));
gpuvec4 oldColor = material.emissiveColor_;
ImGui::ColorEdit3("Emissive color", emissiveColor);
if (memcmp(emissiveColor, &oldColor, sizeof(gpuvec4))) {
memcpy(&material.emissiveColor_, emissiveColor, sizeof(gpuvec4));
sceneData.updateMaterial(matIdx);
}
}
};
MyApp 的作用主要有三个:
- 初始化 SceneData,场景数据;
- 初始化 Renderer,并和 SceneData 关联;
- 实现简单的节点编辑器功能; 下面我们依次来分析