一、为什么 Vulkan 很难,但又值得学习

如果你已经接触过 OpenGL、DirectX 11 或 Unity、Unreal 这类成熟引擎,那么第一次学习 Vulkan 时很容易产生一种感觉:
“为什么只是画一个三角形,竟然要写这么多代码?”

这是正常的。

因为 Vulkan 并不是一个“帮你自动管理渲染状态”的图形 API,而是一个“把 GPU 控制权尽可能交还给程序员”的现代底层图形与计算 API。它要求开发者显式管理:

  • GPU 设备与队列;

  • 显存分配;

  • Buffer 与 Image;

  • Pipeline 状态;

  • Shader 资源绑定;

  • Command Buffer 录制;

  • GPU 同步;

  • Swapchain 呈现;

  • 纹理采样;

  • 模型数据上传;

  • 多帧并行资源管理。

OpenGL 的典型风格是:

glBindTexture(...);
glUseProgram(...);
glDrawArrays(...);

很多状态是隐式存在于全局上下文中的。
而 Vulkan 的典型风格是:

vkCmdBindPipeline(...);
vkCmdBindDescriptorSets(...);
vkCmdBindVertexBuffers(...);
vkCmdDrawIndexed(...);

你必须明确告诉 GPU:
现在使用哪个 pipeline?
shader 能访问哪些资源?
顶点数据在哪里?
索引数据在哪里?
纹理在哪里?
渲染目标是什么?
什么时候开始?什么时候结束?
读写资源之间有没有同步关系?

因此,Vulkan 的学习重点不在于记住某个函数怎么调用,而在于建立一套正确的“GPU 工作流心智模型”。


二、Vulkan 的核心思想:显式、预编译、批量提交

可以把 Vulkan 理解为三个核心思想。

1. 显式控制

Vulkan 不喜欢“自动”。
它希望应用程序自己说明资源如何创建、如何使用、何时同步、何时销毁。

这带来的代价是代码量增加。
但收益是:

  • 减少驱动层猜测;

  • 降低运行时状态检查;

  • 提高多线程提交能力;

  • 更容易做现代渲染引擎的资源调度;

  • 更贴近 GPU 的真实执行模型。

2. 预创建状态对象

在 OpenGL 中,很多渲染状态可以随时修改。
例如当前绑定的 shader、混合状态、深度测试状态、光栅化状态等。

而 Vulkan 倾向于把这些状态提前组合成对象,例如:

  • VkPipeline

  • VkPipelineLayout

  • VkDescriptorSetLayout

  • VkRenderPass

  • VkFramebuffer

  • VkSampler

尤其是 graphics pipeline,创建成本较高,但使用时非常高效。
这符合现代引擎的思想:
运行前或加载阶段尽量准备好状态对象,运行时只做绑定和提交。

3. Command Buffer 批量录制

Vulkan 不直接要求你每次 draw call 都立即执行。
通常流程是:

开始录制 Command Buffer
    绑定 Pipeline
    绑定 Descriptor Set
    绑定 Vertex Buffer
    绑定 Index Buffer
    发出 Draw Call
结束录制 Command Buffer
提交到 Queue
GPU 执行

Command Buffer 是 Vulkan 的核心执行容器。
CPU 将命令录制进去,然后提交给 GPU 队列执行。

这使得 Vulkan 很适合多线程渲染:
不同线程可以并行录制不同 command buffer,然后由主线程统一提交。


三、Vulkan 程序的基本结构

一个最小的 Vulkan 渲染程序,大致包含以下模块:

Application
 ├── Instance
 ├── Surface
 ├── Physical Device
 ├── Logical Device
 ├── Queue
 ├── Swapchain
 ├── Image Views
 ├── Render Pass / Dynamic Rendering
 ├── Framebuffers
 ├── Descriptor Set Layout
 ├── Pipeline Layout
 ├── Graphics Pipeline
 ├── Command Pool
 ├── Command Buffers
 ├── Vertex Buffer / Index Buffer
 ├── Uniform Buffer
 ├── Texture Image / Image View / Sampler
 ├── Descriptor Pool
 ├── Descriptor Sets
 ├── Synchronization Objects
 └── Render Loop

初学者往往会被这些概念淹没。
但从渲染过程看,它们并不混乱。

你可以把它们分为五类。

第一类:上下文与设备

包括:

  • VkInstance

  • VkPhysicalDevice

  • VkDevice

  • VkQueue

它们回答的问题是:

我使用哪个 Vulkan 环境?
我选择哪块 GPU?
我创建哪个逻辑设备?
我往哪个队列提交任务?

第二类:窗口显示系统

包括:

  • VkSurfaceKHR

  • VkSwapchainKHR

  • swapchain images

  • image views

它们回答的问题是:

我要把最终图像显示到哪个窗口?
屏幕缓冲区有几张图像?
当前应该渲染到哪一张?

第三类:资源系统

包括:

  • VkBuffer

  • VkImage

  • VkDeviceMemory

  • VkImageView

  • VkSampler

它们回答的问题是:

顶点、索引、矩阵、材质、纹理、深度图、颜色图存在哪里?
这些数据如何被 GPU 访问?

第四类:Pipeline 与 Shader 绑定系统

包括:

  • VkShaderModule

  • VkPipeline

  • VkPipelineLayout

  • VkDescriptorSetLayout

  • VkDescriptorSet

  • push constants

它们回答的问题是:

GPU 应该如何处理顶点?
如何光栅化?
如何执行片元着色?
shader 能访问哪些资源?

第五类:命令与同步系统

包括:

  • VkCommandPool

  • VkCommandBuffer

  • VkSemaphore

  • VkFence

  • pipeline barrier

它们回答的问题是:

CPU 如何把命令发给 GPU?
GPU 任务之间如何排序?
当前帧何时可以写?
渲染结束后何时可以 present?

四、Instance、Physical Device 与 Logical Device

1. VkInstance:Vulkan 程序入口

VkInstance 是 Vulkan 应用的全局入口。
它不代表 GPU,而是代表应用程序与 Vulkan loader、validation layer、extension 系统之间的连接。

创建 instance 时通常需要指定:

  • 应用名称;

  • Vulkan API 版本;

  • 需要启用的 validation layers;

  • 需要启用的 instance extensions。

示意代码:

VkApplicationInfo appInfo{};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "Vulkan Renderer";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "Custom Engine";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_3;

VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;

vkCreateInstance(&createInfo, nullptr, &instance);

2. Physical Device:真实 GPU

VkPhysicalDevice 表示系统中的物理 GPU。
例如:

  • NVIDIA RTX 显卡;

  • AMD Radeon 显卡;

  • Intel 核显;

  • 移动端 Mali / Adreno GPU。

选择 physical device 时,需要检查:

  • 是否支持 graphics queue;

  • 是否支持 present queue;

  • 是否支持 swapchain extension;

  • 是否支持所需 feature;

  • 显存类型是否满足需求;

  • MSAA、anisotropy、sampler、descriptor 数量等硬件限制。

3. Logical Device:应用与 GPU 的连接

VkDevice 是逻辑设备。
应用程序并不是直接操作 physical device,而是基于 physical device 创建 logical device。

创建 logical device 时会指定:

  • 使用哪些 queue family;

  • 创建多少个 queue;

  • 启用哪些 device extensions;

  • 启用哪些 features。

例如要使用纹理各向异性过滤,就需要检查并启用:

VkPhysicalDeviceFeatures deviceFeatures{};
deviceFeatures.samplerAnisotropy = VK_TRUE;

五、Queue 与 Queue Family

GPU 内部可能支持多种任务队列:

  • graphics queue:执行图形渲染;

  • compute queue:执行计算任务;

  • transfer queue:执行数据拷贝;

  • present queue:负责把 swapchain image 显示到窗口。

Vulkan 使用 queue family 描述这些能力。
一个 queue family 可以支持一种或多种能力。

典型渲染程序至少需要:

Graphics Queue
Present Queue

有些 GPU 上 graphics queue 和 present queue 是同一个 queue family。
有些平台则可能分开。

这就是为什么 Vulkan 初始化阶段需要查找 queue family。


六、Swapchain:显示到屏幕的图像队列

1. 什么是 Swapchain

Swapchain 是一组可以被渲染并最终显示到屏幕的图像。

简单理解:

CPU / GPU 渲染一张图像
↓
图像进入 swapchain
↓
显示系统把图像 present 到窗口

通常 swapchain 包含多张 image:

  • 双缓冲:2 张;

  • 三缓冲:3 张。

多缓冲的目的是让 CPU、GPU、显示系统可以并行工作,减少等待。

2. Swapchain 需要确定什么

创建 swapchain 时,需要确定:

  • 图像格式,例如 VK_FORMAT_B8G8R8A8_SRGB

  • 色彩空间;

  • 分辨率 extent;

  • present mode;

  • image count;

  • image usage;

  • transform;

  • composite alpha。

3. Present Mode

常见 present mode:

VK_PRESENT_MODE_FIFO_KHR

类似垂直同步,基本所有平台都支持。

VK_PRESENT_MODE_MAILBOX_KHR

类似三缓冲低延迟模式,如果平台支持,常用于实时渲染。


七、Image View 与 Framebuffer

Swapchain 给你的是 VkImage
但是 shader、render pass 或 framebuffer 通常不能直接使用裸 VkImage,而是要通过 VkImageView 访问。

VkImageView 可以理解为:

对 VkImage 的一种视图解释方式

它决定:

  • 使用哪种格式解释 image;

  • 使用哪个 mip level;

  • 使用哪个 array layer;

  • 用作 color、depth 还是 stencil。

Framebuffer 则把具体的 image view 绑定到 render pass 的 attachment 上。

例如:

Render Pass 声明:我需要一个 color attachment 和一个 depth attachment
Framebuffer 提供:这一次渲染具体用 swapchain image view A 和 depth image view B

八、Render Pass 与 Dynamic Rendering

1. Render Pass 的传统模型

传统 Vulkan 中,VkRenderPass 描述一次渲染过程中的 attachment 使用方式。

它会说明:

  • 有哪些 color attachment;

  • 是否有 depth/stencil attachment;

  • attachment 初始 layout;

  • attachment 最终 layout;

  • load 操作;

  • store 操作;

  • subpass 结构;

  • subpass dependency。

例如:

开始时清空 color attachment
渲染过程中作为 color output 使用
结束后转换为 present layout

2. Dynamic Rendering 的现代趋势

较新的 Vulkan 版本和扩展引入了 dynamic rendering。
它减少了传统 render pass / framebuffer 的固定绑定关系,让应用可以在命令录制时直接指定渲染目标。

传统方式:

RenderPass + Framebuffer + Subpass

Dynamic Rendering 方式:

vkCmdBeginRendering
vkCmdEndRendering

对于现代引擎来说,dynamic rendering 更灵活,尤其适合:

  • 多 pass 渲染;

  • 后处理;

  • 延迟渲染;

  • 动态渲染目标切换;

  • frame graph 系统。

但初学者仍然建议先理解传统 render pass,因为它能帮助你掌握 attachment、layout、subpass、load/store 等核心概念。


九、Graphics Pipeline:Vulkan 最核心的状态对象

1. Pipeline 是什么

Graphics pipeline 是 Vulkan 中最重要的对象之一。
它描述 GPU 如何从顶点数据生成最终像素。

一个典型 graphics pipeline 包含:

Vertex Input
Input Assembly
Vertex Shader
Tessellation Shader 可选
Geometry Shader 可选
Viewport / Scissor
Rasterization
Multisampling
Depth / Stencil Test
Fragment Shader
Color Blend
Pipeline Layout
Render Pass / Rendering Info

可以把 pipeline 理解为:

GPU 渲染状态的完整快照

在 Vulkan 中,很多状态不是 draw call 时随便改,而是提前固定进 pipeline。
这使得驱动可以提前编译和优化。

2. Shader Stage

最常见的 graphics shader stage 是:

Vertex Shader
Fragment Shader

Vertex Shader 负责处理顶点。
它通常完成:

  • 模型空间到世界空间变换;

  • 世界空间到观察空间变换;

  • 观察空间到裁剪空间变换;

  • 输出纹理坐标;

  • 输出法线;

  • 输出颜色;

  • 输出 tangent basis。

Fragment Shader 负责处理像素片元。
它通常完成:

  • 纹理采样;

  • 光照计算;

  • PBR 材质计算;

  • alpha test;

  • normal mapping;

  • 输出最终颜色。

3. Vertex Input

Vertex Input 描述顶点缓冲区的内存结构。

例如一个顶点结构:

struct Vertex {
    glm::vec3 position;
    glm::vec3 normal;
    glm::vec2 texCoord;
};

在 Vulkan 中,需要告诉 pipeline:

  • 每个顶点多大;

  • position 在结构体中的 offset;

  • normal 在结构体中的 offset;

  • texCoord 在结构体中的 offset;

  • 每个 attribute 的格式是什么。

示意:

VkVertexInputBindingDescription binding{};
binding.binding = 0;
binding.stride = sizeof(Vertex);
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

std::array<VkVertexInputAttributeDescription, 3> attributes{};

attributes[0].binding = 0;
attributes[0].location = 0;
attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
attributes[0].offset = offsetof(Vertex, position);

attributes[1].binding = 0;
attributes[1].location = 1;
attributes[1].format = VK_FORMAT_R32G32B32_SFLOAT;
attributes[1].offset = offsetof(Vertex, normal);

attributes[2].binding = 0;
attributes[2].location = 2;
attributes[2].format = VK_FORMAT_R32G32_SFLOAT;
attributes[2].offset = offsetof(Vertex, texCoord);

这里的 location 必须与 shader 中的输入匹配:

layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec2 inTexCoord;

4. Input Assembly

Input Assembly 决定顶点如何组成图元。

常见类型:

VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP
VK_PRIMITIVE_TOPOLOGY_LINE_LIST
VK_PRIMITIVE_TOPOLOGY_POINT_LIST

最常见的是 triangle list。
例如每 3 个顶点组成一个三角形。

5. Viewport 与 Scissor

Viewport 决定 NDC 坐标如何映射到屏幕区域。
Scissor 决定实际允许写入的矩形区域。

Viewport 是坐标变换问题。
Scissor 是裁剪区域问题。

6. Rasterization

Rasterization 是把三角形转换为片元的过程。
相关状态包括:

  • polygon mode;

  • cull mode;

  • front face;

  • depth bias;

  • line width。

例如背面剔除:

rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;

7. Depth / Stencil

深度测试用于解决遮挡关系。
如果没有深度测试,后画的物体可能覆盖先画的物体,即使它在空间上更远。

常见配置:

depthStencil.depthTestEnable = VK_TRUE;
depthStencil.depthWriteEnable = VK_TRUE;
depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;

含义是:

如果新片元深度值更小,说明它离相机更近,允许通过测试并写入深度缓冲。

8. Color Blend

Color Blend 决定 fragment shader 输出颜色如何与 framebuffer 中已有颜色混合。

不透明物体通常关闭 blending:

blendAttachment.blendEnable = VK_FALSE;

透明物体通常开启 alpha blending:

blendAttachment.blendEnable = VK_TRUE;
blendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
blendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
blendAttachment.colorBlendOp = VK_BLEND_OP_ADD;

9. Pipeline Layout

Pipeline Layout 是 shader 资源接口的总描述。

它包含:

  • descriptor set layouts;

  • push constant ranges。

也就是说,pipeline layout 决定了 shader 可以访问哪些外部资源。

例如:

set = 0:全局相机 UBO
set = 1:材质纹理
push constants:当前物体的 model matrix 或 object id

十、Descriptor Set:Shader 访问资源的桥梁

1. Descriptor 是什么

Shader 不能凭空访问 CPU 侧对象。
它需要通过 descriptor 访问 GPU 资源。

Descriptor 可以描述:

  • uniform buffer;

  • storage buffer;

  • sampled image;

  • sampler;

  • combined image sampler;

  • storage image;

  • acceleration structure。

Descriptor Set 是一组 descriptor 的集合。
Descriptor Set Layout 则描述这一组 descriptor 的结构。

可以这样理解:

DescriptorSetLayout = 资源接口声明
DescriptorSet       = 具体绑定的资源实例

2. Descriptor Set 的三步使用

使用 descriptor set 通常分三步。

第一步:创建 descriptor set layout。

VkDescriptorSetLayoutBinding uboBinding{};
uboBinding.binding = 0;
uboBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uboBinding.descriptorCount = 1;
uboBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;

第二步:从 descriptor pool 分配 descriptor set。

vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet);

第三步:更新 descriptor set,让它指向具体 buffer 或 image。

VkDescriptorBufferInfo bufferInfo{};
bufferInfo.buffer = uniformBuffer;
bufferInfo.offset = 0;
bufferInfo.range = sizeof(UniformBufferObject);

VkWriteDescriptorSet descriptorWrite{};
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrite.dstSet = descriptorSet;
descriptorWrite.dstBinding = 0;
descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrite.descriptorCount = 1;
descriptorWrite.pBufferInfo = &bufferInfo;

vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);

渲染时绑定:

vkCmdBindDescriptorSets(
    commandBuffer,
    VK_PIPELINE_BIND_POINT_GRAPHICS,
    pipelineLayout,
    0,
    1,
    &descriptorSet,
    0,
    nullptr
);

3. Descriptor Set 的分组策略

实际引擎中,不应该把所有资源塞进一个 descriptor set。
更合理的做法是按更新频率分组。

例如:

set = 0:Frame 级资源
    Camera UBO
    Light UBO
    Shadow Map
    Environment Map

set = 1:Material 级资源
    BaseColor Texture
    Normal Texture
    MetallicRoughness Texture
    Material Parameters

set = 2:Object 级资源
    Object Transform
    Object ID
    Skinning Matrices

为什么按更新频率分组?

因为相机每帧更新,材质不一定每个物体都变,object transform 每个物体可能都变。
合理分组可以减少 descriptor 绑定次数,提高渲染效率。


十一、Uniform Buffer:传递矩阵与全局参数

Uniform Buffer 常用于存放小规模、只读、频繁访问的 shader 参数。

典型 UBO:

struct UniformBufferObject {
    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 proj;
};

Vertex Shader 中:

layout(set = 0, binding = 0) uniform UBO {
    mat4 model;
    mat4 view;
    mat4 proj;
} ubo;

layout(location = 0) in vec3 inPosition;

void main() {
    gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0);
}

这就是最经典的 MVP 变换:

Model Matrix      : 模型空间 → 世界空间
View Matrix       : 世界空间 → 相机空间
Projection Matrix : 相机空间 → 裁剪空间

需要注意,Vulkan 的裁剪空间和 OpenGL 有差异。
常见处理方式是:

ubo.proj[1][1] *= -1;

这是因为 Vulkan 的屏幕坐标约定与 OpenGL 不完全一致。


十二、Dynamic Uniform Buffer:多个物体共享一个大 UBO

1. 为什么需要 Dynamic Uniform Buffer

如果场景中有 1000 个物体,每个物体都有自己的 model matrix,最直接的做法是:

每个物体一个 uniform buffer
每个物体一个 descriptor set

这当然可行,但管理成本较高。

Dynamic Uniform Buffer 提供另一种方式:

创建一个大的 uniform buffer
里面连续存储多个物体的数据
渲染每个物体时通过 dynamic offset 指向不同位置

示意:

Dynamic UBO
 ├── Object 0 matrix
 ├── Object 1 matrix
 ├── Object 2 matrix
 ├── Object 3 matrix
 └── ...

绘制第 0 个物体:

dynamicOffset = 0;
vkCmdBindDescriptorSets(..., 1, &dynamicOffset);

绘制第 1 个物体:

dynamicOffset = alignedSize * 1;
vkCmdBindDescriptorSets(..., 1, &dynamicOffset);

绘制第 N 个物体:

dynamicOffset = alignedSize * N;
vkCmdBindDescriptorSets(..., 1, &dynamicOffset);

2. Dynamic UBO 的对齐问题

这是初学者最容易出错的地方。

Vulkan 对 dynamic uniform buffer offset 有对齐要求。
这个要求来自:

VkPhysicalDeviceLimits::minUniformBufferOffsetAlignment

因此不能简单使用:

sizeof(ObjectData)

作为步长。

应该使用对齐后的大小:

VkDeviceSize alignTo(VkDeviceSize value, VkDeviceSize alignment) {
    return (value + alignment - 1) & ~(alignment - 1);
}

VkDeviceSize objectSize = sizeof(ObjectData);
VkDeviceSize alignedSize = alignTo(objectSize, minUniformBufferOffsetAlignment);

否则 shader 读取数据可能错位,甚至 validation layer 报错。

3. Dynamic UBO 的适用场景

适合:

  • 每个物体一个 model matrix;

  • 每个物体少量参数;

  • 数据量不大;

  • 更新频率高;

  • draw call 中通过 dynamic offset 快速切换。

不适合:

  • 大规模实例数据;

  • 大数组;

  • GPU 写入数据;

  • 复杂材质表;

  • 海量骨骼矩阵。

这些情况更适合 storage buffer 或 structured buffer。


十三、Push Constants:小数据的高速传递方式

1. Push Constants 是什么

Push Constants 是 Vulkan 中用于向 shader 传递少量数据的机制。
它不需要创建 buffer,也不需要更新 descriptor set。

典型用途:

  • 当前物体 ID;

  • 当前材质 ID;

  • 一个小矩阵;

  • draw call 级别的开关;

  • 少量渲染参数。

示例结构:

struct PushConstants {
    glm::mat4 model;
    int materialIndex;
};

Pipeline Layout 中声明 push constant range:

VkPushConstantRange pushRange{};
pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
pushRange.offset = 0;
pushRange.size = sizeof(PushConstants);

录制 command buffer 时写入:

vkCmdPushConstants(
    commandBuffer,
    pipelineLayout,
    VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
    0,
    sizeof(PushConstants),
    &pushData
);

Shader 中读取:

layout(push_constant) uniform PushConstants {
    mat4 model;
    int materialIndex;
} pc;

2. Push Constants 与 UBO 的区别

机制 适合数据 是否需要 Descriptor 更新频率
Uniform Buffer 相机矩阵、光照参数、全局参数 需要 每帧 / 每对象
Dynamic Uniform Buffer 多对象小数据 需要 每 draw 切换 offset
Push Constants 极小且频繁变化的数据 不需要 每 draw 很方便
Storage Buffer 大规模结构化数据 需要 灵活

3. 使用建议

Push Constants 很方便,但不能滥用。
它的容量通常较小,跨平台保守使用时应存放几十到一百多字节级别的数据。

推荐策略:

Camera / Light        → Uniform Buffer
Object Transform      → Dynamic UBO 或 Push Constants
Material Parameters   → UBO / SSBO / Descriptor
Texture               → Combined Image Sampler
Large Object Data     → Storage Buffer

十四、Buffer:顶点、索引、Uniform 与 Storage

Vulkan 中 VkBuffer 是线性内存资源。
常见用途:

Vertex Buffer
Index Buffer
Uniform Buffer
Storage Buffer
Staging Buffer
Indirect Draw Buffer

1. Vertex Buffer

存储顶点数据:

struct Vertex {
    glm::vec3 position;
    glm::vec3 normal;
    glm::vec2 texCoord;
};

绘制时绑定:

VkBuffer vertexBuffers[] = { vertexBuffer };
VkDeviceSize offsets[] = { 0 };

vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);

2. Index Buffer

索引缓冲区用于复用顶点。

vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(commandBuffer, indexCount, 1, 0, 0, 0);

相比直接 vkCmdDraw,indexed drawing 能减少重复顶点数据。

3. Staging Buffer

很多 GPU 本地显存不能直接被 CPU 高效写入。
因此常见做法是:

CPU 写入 staging buffer
↓
vkCmdCopyBuffer
↓
拷贝到 device local vertex buffer

Staging buffer 通常使用:

VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT

最终 GPU 使用的 buffer 通常使用:

VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT

4. Storage Buffer

Storage Buffer 比 Uniform Buffer 更灵活。
它可以存储大量结构化数据,也可以被 compute shader 写入。

适合:

  • 粒子系统;

  • GPU culling;

  • 骨骼矩阵数组;

  • 材质数组;

  • 实例数据;

  • 大规模场景对象数据;

  • clustered lighting 数据。


十五、Vulkan 内存模型:Buffer 和 Memory 是分离的

这是 Vulkan 与很多高级 API 不同的地方。

创建一个 buffer:

vkCreateBuffer(device, &bufferInfo, nullptr, &buffer);

这一步只是创建 buffer 对象。
它还没有真正绑定显存。

接着需要查询内存需求:

vkGetBufferMemoryRequirements(device, buffer, &memRequirements);

然后分配 memory:

vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory);

最后绑定:

vkBindBufferMemory(device, buffer, bufferMemory, 0);

也就是说:

VkBuffer        = 资源对象
VkDeviceMemory  = 实际显存

实际工程中,不建议每个 buffer 都单独 vkAllocateMemory
更好的方式是使用 Vulkan Memory Allocator,也就是 VMA。
VMA 可以帮助你做显存分配、子分配、映射、释放和统计。


十六、Texture:Image、ImageView、Sampler 与 Layout

1. Texture 在 Vulkan 中由什么组成

Vulkan 中一张可采样纹理通常由三部分组成:

VkImage      : 图像存储本体
VkImageView  : 图像视图
VkSampler    : 采样器

VkImage 负责存储像素数据。
VkImageView 负责告诉 shader 如何解释这张 image。
VkSampler 负责描述采样方式。

例如:

线性过滤还是最近点过滤?
是否开启 mipmap?
UV 超出 0~1 后 repeat 还是 clamp?
是否开启各向异性过滤?

2. Texture 上传流程

纹理上传通常分为以下步骤:

读取图片文件
↓
创建 staging buffer
↓
CPU 把像素数据写入 staging buffer
↓
创建 VkImage
↓
转换 image layout:undefined → transfer dst
↓
vkCmdCopyBufferToImage
↓
生成 mipmap 可选
↓
转换 image layout:transfer dst → shader read only
↓
创建 image view
↓
创建 sampler
↓
写入 descriptor set

典型 image layout 转换:

VK_IMAGE_LAYOUT_UNDEFINED
    ↓
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
    ↓
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL

3. Combined Image Sampler

Fragment Shader 采样纹理时,常用 descriptor 类型是:

VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER

Descriptor 更新示意:

VkDescriptorImageInfo imageInfo{};
imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
imageInfo.imageView = textureImageView;
imageInfo.sampler = textureSampler;

VkWriteDescriptorSet descriptorWrite{};
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrite.dstSet = descriptorSet;
descriptorWrite.dstBinding = 1;
descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptorWrite.descriptorCount = 1;
descriptorWrite.pImageInfo = &imageInfo;

vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);

Shader 中:

layout(set = 1, binding = 0) uniform sampler2D baseColorTexture;

layout(location = 0) in vec2 fragTexCoord;
layout(location = 0) out vec4 outColor;

void main() {
    outColor = texture(baseColorTexture, fragTexCoord);
}

4. Mipmap

Mipmap 是一组逐级缩小的纹理。
它可以减少远处纹理闪烁,提高采样质量。

例如:

1024 x 1024
512 x 512
256 x 256
128 x 128
...
1 x 1

在 Vulkan 中生成 mipmap 通常需要:

  • image 支持 transfer src;

  • image 支持 transfer dst;

  • 格式支持 linear blit;

  • 多次 layout transition;

  • 多次 vkCmdBlitImage

5. sRGB 与 Linear

颜色纹理通常使用 sRGB 格式,例如:

VK_FORMAT_R8G8B8A8_SRGB

法线贴图、金属度粗糙度贴图、遮蔽贴图通常不应该使用 sRGB,而应该使用线性格式,例如:

VK_FORMAT_R8G8B8A8_UNORM

原因是:

BaseColor 是颜色,需要 gamma 处理。
Normal / Roughness / Metallic 是数据,不应该被 gamma 校正。

这是 PBR 渲染中非常重要的细节。


十七、同步:Vulkan 最容易出错的部分

Vulkan 的同步是显式的。
如果你没有正确同步,可能出现:

  • GPU 还没写完,下一步就开始读;

  • swapchain image 还没准备好就开始渲染;

  • 渲染还没结束就 present;

  • CPU 修改了 GPU 仍在使用的 uniform buffer;

  • image layout 不匹配;

  • 随机闪烁;

  • validation layer 报错;

  • 某些显卡正常,某些显卡崩溃。

1. Semaphore

Semaphore 用于 GPU 队列之间或队列操作之间的同步。

典型帧流程:

vkAcquireNextImageKHR
    signal imageAvailableSemaphore

vkQueueSubmit
    wait imageAvailableSemaphore
    signal renderFinishedSemaphore

vkQueuePresentKHR
    wait renderFinishedSemaphore

也就是说:

等 swapchain image 可用
↓
开始渲染
↓
渲染结束
↓
present 到屏幕

2. Fence

Fence 用于 CPU 等待 GPU。

常见用途:

CPU 不要覆盖 GPU 仍在使用的 per-frame 资源

每帧开始时:

vkWaitForFences(device, 1, &inFlightFence, VK_TRUE, UINT64_MAX);
vkResetFences(device, 1, &inFlightFence);

这表示:

等上一轮使用这套 frame resource 的 GPU 工作完成,再复用它。

3. Pipeline Barrier

Pipeline barrier 用于同步资源访问和 layout transition。

例如 texture 上传时:

Transfer Write
↓
Shader Read

需要 barrier 告诉 GPU:

前面的传输写入必须完成;
然后 fragment shader 才能读取;
同时 image layout 要转换到 shader read only。

同步是 Vulkan 中最体现底层性的部分。
初学者不要试图跳过它,因为真实项目中大量 bug 都来自同步错误。


十八、Command Pool 与 Command Buffer

1. Command Pool

Command Pool 用于分配 command buffer。
它通常和 queue family 绑定。

VkCommandPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.queueFamilyIndex = graphicsQueueFamily;
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;

2. Command Buffer

Command Buffer 是命令录制容器。

典型录制流程:

vkBeginCommandBuffer(commandBuffer, &beginInfo);

vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
                        pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);

vkCmdDrawIndexed(commandBuffer, indexCount, 1, 0, 0, 0);

vkCmdEndRenderPass(commandBuffer);

vkEndCommandBuffer(commandBuffer);

这段代码本质上描述了一帧中 GPU 应该做什么。


十九、渲染循环:一帧到底发生了什么

一个标准 Vulkan 渲染循环可以抽象为:

1. 等待当前 frame 的 fence
2. 从 swapchain 获取 image index
3. 重置 fence
4. 重置 command buffer
5. 录制 command buffer
6. 提交 graphics queue
7. present queue 显示
8. 进入下一帧

伪代码:

void drawFrame() {
    vkWaitForFences(device, 1, &inFlightFence[currentFrame], VK_TRUE, UINT64_MAX);

    uint32_t imageIndex;
    vkAcquireNextImageKHR(
        device,
        swapchain,
        UINT64_MAX,
        imageAvailableSemaphore[currentFrame],
        VK_NULL_HANDLE,
        &imageIndex
    );

    vkResetFences(device, 1, &inFlightFence[currentFrame]);
    vkResetCommandBuffer(commandBuffer[currentFrame], 0);

    recordCommandBuffer(commandBuffer[currentFrame], imageIndex);

    VkSubmitInfo submitInfo{};
    // wait imageAvailable
    // signal renderFinished
    vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence[currentFrame]);

    VkPresentInfoKHR presentInfo{};
    // wait renderFinished
    vkQueuePresentKHR(presentQueue, &presentInfo);

    currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}

现代 Vulkan 程序通常使用:

const int MAX_FRAMES_IN_FLIGHT = 2;

这样 CPU 可以准备下一帧,而 GPU 正在处理上一帧。


二十、glTF 模型加载:从文件到 Vulkan 渲染

1. glTF 是什么

glTF 可以理解为“3D 模型领域的 JPEG”。
它的目标不是作为建模软件的完整工程格式,而是作为运行时高效加载和传输的 3D 资产格式。

一个 glTF 模型通常包含:

  • scene;

  • node;

  • mesh;

  • primitive;

  • accessor;

  • buffer;

  • bufferView;

  • material;

  • texture;

  • image;

  • sampler;

  • animation;

  • skin。

对于 Vulkan 渲染器来说,重点通常是:

Mesh Geometry
Material
Texture
Node Transform
Animation
Skinning

2. glTF 的层级结构

glTF 的场景结构大致是:

Scene
 └── Node
      ├── Transform
      ├── Mesh
      │    └── Primitive
      │         ├── Attributes
      │         ├── Indices
      │         └── Material
      └── Children

一个 node 可以有变换,也可以引用 mesh。
一个 mesh 可以包含多个 primitive。
每个 primitive 通常对应一个材质。

3. glTF Primitive 到 Vulkan Buffer

glTF primitive 常见 attribute:

POSITION
NORMAL
TEXCOORD_0
TANGENT
COLOR_0
JOINTS_0
WEIGHTS_0

加载时需要把这些 attribute 解析成 Vulkan 顶点结构。

例如:

struct Vertex {
    glm::vec3 position;
    glm::vec3 normal;
    glm::vec4 tangent;
    glm::vec2 texCoord;
};

然后创建:

Vertex Buffer
Index Buffer

渲染时:

vkCmdBindVertexBuffers(...);
vkCmdBindIndexBuffer(...);
vkCmdDrawIndexed(...);

4. glTF 材质:PBR Metallic-Roughness

glTF 2.0 默认使用 PBR metallic-roughness 材质模型。

典型参数包括:

baseColorFactor
baseColorTexture
metallicFactor
roughnessFactor
metallicRoughnessTexture
normalTexture
occlusionTexture
emissiveTexture
emissiveFactor
alphaMode
alphaCutoff
doubleSided

在 Vulkan 中,这通常对应:

Material Uniform Buffer / Storage Buffer
+
多个 Combined Image Sampler

例如:

set = 1, binding = 0 : baseColorTexture
set = 1, binding = 1 : normalTexture
set = 1, binding = 2 : metallicRoughnessTexture
set = 1, binding = 3 : occlusionTexture
set = 1, binding = 4 : emissiveTexture
set = 1, binding = 5 : material parameter buffer

5. glTF 纹理通道约定

glTF 的 metallic-roughness texture 通常使用通道打包:

G 通道:roughness
B 通道:metallic

Occlusion texture 通常使用:

R 通道:occlusion

Normal texture 是切线空间法线。
如果模型没有 tangent,需要在加载阶段生成 tangent,常见算法是 MikkTSpace。

6. glTF 坐标与矩阵

glTF 使用右手坐标系。
Vulkan 的 clip space 与 OpenGL 不完全一致,因此模型加载后仍然要注意投影矩阵处理。

每个 node 的变换可能来自:

matrix

或者:

translation
rotation
scale

如果是 TRS,需要组合为:

localMatrix = T * R * S

再通过父子层级递归得到:

worldMatrix = parentWorldMatrix * localMatrix

渲染每个 primitive 时,把 worldMatrix 作为 object transform 传入 shader。

7. glTF 加载流程总结

完整流程如下:

读取 glTF / GLB 文件
↓
解析 scene / node / mesh / material / texture
↓
遍历 node 层级,计算 transform
↓
提取 primitive 顶点属性
↓
创建 vertex buffer
↓
创建 index buffer
↓
加载 image,创建 Vulkan texture
↓
创建 material descriptor set
↓
创建 object transform buffer 或 push constants
↓
录制 command buffer
↓
按 primitive 绑定 pipeline、descriptor、buffer 并 draw indexed

二十一、从“画三角形”到“加载模型”的完整架构

一个可扩展的 Vulkan renderer 可以这样组织:

Renderer
 ├── VulkanContext
 │    ├── Instance
 │    ├── PhysicalDevice
 │    ├── Device
 │    ├── Queues
 │    └── CommandPools
 │
 ├── SwapchainManager
 │    ├── Swapchain
 │    ├── ImageViews
 │    ├── DepthImage
 │    └── Framebuffers
 │
 ├── PipelineManager
 │    ├── ShaderModules
 │    ├── PipelineLayouts
 │    └── GraphicsPipelines
 │
 ├── DescriptorManager
 │    ├── DescriptorSetLayouts
 │    ├── DescriptorPools
 │    └── DescriptorSets
 │
 ├── ResourceManager
 │    ├── Buffers
 │    ├── Images
 │    ├── Samplers
 │    └── Memory Allocator
 │
 ├── ModelManager
 │    ├── glTF Loader
 │    ├── Meshes
 │    ├── Materials
 │    └── Textures
 │
 ├── FrameResources
 │    ├── CommandBuffer
 │    ├── UniformBuffers
 │    ├── Semaphores
 │    └── Fences
 │
 └── RenderLoop

初学 Vulkan 最忌讳把所有内容写在一个巨大 main.cpp 中。
更好的方法是从一开始就做模块化拆分。


二十二、Vulkan 中常见资源绑定方案

方案一:简单教学方案

适合画三角形、立方体、小模型。

set = 0:
    binding 0: Uniform Buffer, MVP matrix
    binding 1: Combined Image Sampler

优点:简单。
缺点:扩展性差。

方案二:按更新频率分组

适合中小型渲染器。

set = 0: Frame Data
    Camera
    Lights

set = 1: Material Data
    Textures
    Material parameters

set = 2: Object Data
    Transform

优点:结构清晰。
缺点:descriptor 管理稍复杂。

方案三:Bindless / Descriptor Indexing

适合大型引擎。

set = 0:
    Global UBO
    Big texture array
    Big material buffer
    Big object buffer

shader 通过 index 访问资源:

texture(textures[material.baseColorTextureIndex], uv);

优点:减少频繁 descriptor 绑定,适合海量材质和对象。
缺点:需要更复杂的 feature 检查和资源管理。


二十三、Vulkan Shader:GLSL、HLSL 与 SPIR-V

Vulkan 不直接消费 GLSL 或 HLSL 源码。
它使用中间表示:

SPIR-V

常见编译路径:

GLSL → glslangValidator → SPIR-V
HLSL → DXC → SPIR-V
Slang → Slang Compiler → SPIR-V

例如 GLSL 编译:

glslangValidator -V shader.vert -o vert.spv
glslangValidator -V shader.frag -o frag.spv

Vulkan 程序加载 SPIR-V 后创建 shader module:

VkShaderModuleCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = code.size();
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());

vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule);

然后在 pipeline creation 中指定 shader stage。


二十四、Validation Layer:Vulkan 开发必开

Vulkan 很底层,错误也很底层。
如果不使用 validation layer,很多 bug 只会表现为:

黑屏
闪烁
崩溃
某些显卡正常,某些显卡异常

开发阶段应该启用:

VK_LAYER_KHRONOS_validation

它可以检查:

  • descriptor 是否绑定正确;

  • image layout 是否匹配;

  • buffer usage 是否正确;

  • 同步是否存在明显问题;

  • pipeline state 是否兼容;

  • object 是否提前销毁;

  • command buffer 状态是否正确。

在 Vulkan 学习阶段,validation layer 的报错信息比教程更重要。
遇到错误不要绕过,应该逐条理解。


二十五、Vulkan 常见错误与排查思路

1. 黑屏

可能原因:

  • shader 没有正确编译;

  • pipeline 使用了错误 render pass;

  • viewport / scissor 设置错误;

  • vertex input layout 与 shader location 不匹配;

  • 没有绑定 descriptor set;

  • uniform buffer 数据错误;

  • depth test 把物体全部裁掉;

  • front face / cull mode 设置错误。

2. 纹理显示异常

可能原因:

  • image layout 不正确;

  • descriptor imageLayout 设置错误;

  • sampler 没有创建;

  • image view format 不正确;

  • sRGB / UNORM 使用错误;

  • staging buffer 拷贝大小错误;

  • mipmap layout transition 错误;

  • UV attribute 没有正确传入。

3. 模型变形

可能原因:

  • vertex attribute offset 错误;

  • glTF accessor stride 处理错误;

  • index type 错误;

  • node transform 乘法顺序错误;

  • 坐标系处理错误;

  • tangent 没有生成或方向错误;

  • skinning joint index / weight 解析错误。

4. 随机闪烁

可能原因:

  • uniform buffer 被 CPU 提前覆盖;

  • fence 使用错误;

  • 多帧资源没有分离;

  • pipeline barrier 不完整;

  • descriptor 指向已释放资源;

  • image layout transition 缺失。


二十六、Vulkan 学习路线

建议学习路线如下。

第一阶段:最小渲染闭环

目标:画出三角形。

需要掌握:

Instance
Device
Queue
Swapchain
Render Pass
Pipeline
Command Buffer
Semaphore
Fence

不要急着加载模型。
先把一帧渲染流程搞清楚。

第二阶段:Buffer 与矩阵

目标:画旋转立方体。

需要掌握:

Vertex Buffer
Index Buffer
Uniform Buffer
Descriptor Set
MVP Matrix
Depth Buffer

这一阶段是从 2D 三角形进入 3D 渲染的关键。

第三阶段:Texture

目标:画带纹理的模型。

需要掌握:

VkImage
VkImageView
VkSampler
Image Layout Transition
Staging Buffer
Combined Image Sampler
Mipmap
sRGB

这一阶段会真正理解 Vulkan 资源管理。

第四阶段:glTF 模型加载

目标:加载并显示 glTF 模型。

需要掌握:

glTF node hierarchy
mesh primitive
accessor / bufferView
material
texture
normal map
PBR

此时你的程序已经接近一个小型 renderer。

第五阶段:工程化

目标:构建可扩展渲染器。

需要掌握:

Frame Resources
Descriptor 分组
Pipeline Cache
VMA
Render Graph
Dynamic Rendering
Compute Shader
GPU Culling
Instancing
Bindless Texture

二十七、一个标准 Vulkan Renderer 的渲染顺序

假设我们要渲染一个 glTF 场景,典型顺序如下:

每帧开始
↓
等待当前帧 fence
↓
获取 swapchain image
↓
更新 camera uniform buffer
↓
更新 light uniform buffer
↓
更新 object transform dynamic buffer
↓
重置 command buffer
↓
开始录制 command buffer
↓
开始 render pass / dynamic rendering
↓
绑定 graphics pipeline
↓
绑定 frame descriptor set
↓
遍历 glTF scene nodes
    ↓
    计算 world matrix
    ↓
    遍历 mesh primitives
        ↓
        绑定 material descriptor set
        ↓
        设置 push constants 或 dynamic offset
        ↓
        绑定 vertex buffer
        ↓
        绑定 index buffer
        ↓
        vkCmdDrawIndexed
↓
结束 render pass
↓
结束 command buffer
↓
提交 queue
↓
present
↓
进入下一帧

这就是 Vulkan 渲染器的主干。


二十八、从教授视角理解 Vulkan:它不是 API,而是 GPU 合约

很多初学者把 Vulkan 当成“更复杂的 OpenGL”。
这是不准确的。

OpenGL 像是一个有大量隐式状态的图形接口。
你调用函数,驱动在背后推断你的意图。

Vulkan 更像是一份明确的 GPU 执行合约。
你必须告诉驱动:

资源是什么?
资源何时读?
资源何时写?
shader 需要哪些绑定?
pipeline 状态是什么?
命令如何组织?
队列如何同步?
图像 layout 如何变化?

这就是 Vulkan 的本质:

不是让 GPU 自动理解你的程序,
而是让你的程序明确描述 GPU 应该如何工作。

二十九、学习 Vulkan 时必须建立的五个核心心智模型

1. 资源不是变量,而是 GPU 对象

在 C++ 中,一个变量可以直接访问。
但在 Vulkan 中,shader 访问资源必须经过:

Buffer / Image
↓
Memory
↓
Descriptor
↓
Pipeline Layout
↓
Shader Binding

2. Pipeline 不是 shader,而是完整渲染状态

Shader 只是 pipeline 的一部分。
Pipeline 还包含:

Vertex Input
Rasterization
Depth Test
Blend
Render Target Format
Pipeline Layout

3. Descriptor Set 是 shader 的资源入口表

如果 shader 要访问 UBO 或 texture,就必须有 descriptor。
没有 descriptor,shader 不知道资源在哪里。

4. Command Buffer 是 CPU 写给 GPU 的任务清单

Vulkan 不鼓励立即模式。
你把命令录制到 command buffer,再提交给 GPU。

5. Synchronization 是正确性的基础

Vulkan 性能高的原因之一,是它不自动插入大量保守同步。
但这也意味着你必须自己保证资源访问顺序正确。


三十、结语:Vulkan 的复杂性来自真实的 GPU 世界

Vulkan 的学习曲线确实陡峭。
但它并不是人为复杂,而是把现代 GPU 渲染中的真实问题暴露给了开发者:

  • 显存如何分配;

  • 资源如何绑定;

  • shader 如何访问数据;

  • pipeline 如何固定状态;

  • command 如何提交;

  • GPU 如何同步;

  • 多帧如何并行;

  • 纹理如何转换 layout;

  • 模型如何转为 vertex/index/material;

  • 渲染结果如何 present 到屏幕。

一旦理解这些问题,Vulkan 就不再是一堆难记的结构体,而是一套非常清晰的 GPU 工作协议。

如果用一句话总结 Vulkan:

Vulkan 是一种显式描述 GPU 渲染与计算工作的现代图形 API。

如果再进一步总结它的工程价值:

Vulkan 让开发者以更高成本换取更高控制力、更低驱动开销、更强多线程能力和更可预测的渲染行为。

对于想深入图形学、游戏引擎、实时渲染、工业可视化、三维模型渲染、GPU 计算和现代渲染架构的人来说,Vulkan 是非常值得系统学习的一门技术。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐