1.物理设备
- 需要在物理设备中找出支持graphic和present功能的队列簇
struct QueueFamilyIndices {
int graphicsFamily = -1;
int presentFamily = -1;
bool isComplete() {
return graphicsFamily >= 0 && presentFamily >= 0;
}
};
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
QueueFamilyIndices indices;
//; 第一次获取队列簇的数量
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
// 第二次获取队列簇的具体属性
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
int i = 0;
// 遍历队列簇,看该队列簇中的队列数量是否》0,并且支不支持图像
for (const auto& queueFamily : queueFamilies) {
if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
indices.graphicsFamily = i;
}
VkBool32 presentSupport = false;
// 检测该队列簇是否满足surface的present的功能
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
if (queueFamily.queueCount > 0 && presentSupport) {
indices.presentFamily = i;
}
if (indices.isComplete()) {
break;
}
i++;
}
// 返回满足图像和呈现功能的队列簇下表
return indices;
}
在队列簇中找到一个队列,可以用vkGetDeviceQueue,传入下标,可以拿到指定队列簇中的特定下标队列
vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue);
vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue);
- 需要检测物理设备是否支持swapchain的拓展
const std::vector<const char*> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
// 检查物理设备是否支持swapchain拓展
bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
for (const auto& extension : availableExtensions) {
requiredExtensions.erase(extension.extensionName);
}
return requiredExtensions.empty();
}
在检测完支持了swapchain拓展之后,还可以顺便获取一下创建swapchain需要的属性以及相关特性信息
,从surface中获取
// 具体swapchain信息
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
};
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
SwapChainSupportDetails details;
// 获取物理设备支持的surface的能力,包括最大,最小图像数量,图像尺寸
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
// 获取surface支持的图像格式的数量
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
// 获取surface支持的图像格式的值
if (formatCount != 0) {
details.formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
}
// 获取surface支持的呈现模式的数量
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
// 获取surface支持的呈现模式的具体值
if (presentModeCount != 0) {
details.presentModes.resize(presentModeCount);
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
}
return details;
}
bool isDeviceSuitable(VkPhysicalDevice device) {
// 找到物理设备中的队列簇下标
QueueFamilyIndices indices = findQueueFamilies(device);
// 确认物理设备是否支持swapchain的拓展
bool extensionsSupported = checkDeviceExtensionSupport(device);
// 获取构建swapchain的所有属性
bool swapChainAdequate = false;
if (extensionsSupported) {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
}
return indices.isComplete() && extensionsSupported && swapChainAdequate;
}
这样一个物理设备需要的东西我们全都拿到了,他已经没有价值了,被榨取干了
全部大概是这样的
// 用glfw库创surface
void createSurface() {
if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
throw std::runtime_error("failed to create window surface!");
}
}
// 选择物理设备
void pickPhysicalDevice() {
uint32_t deviceCount = 0;
// 第一次枚举,获取设备数量
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
if (deviceCount == 0) {
throw std::runtime_error("failed to find GPUs with Vulkan support!");
}
// 第二次枚举,获取具体数量的物理设备信息
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
// 遍历设备,获取最合适的设备
for (const auto& device : devices) {
if (isDeviceSuitable(device)) {
physicalDevice = device;
break;
}
}
if (physicalDevice == VK_NULL_HANDLE) {
throw std::runtime_error("failed to find a suitable GPU!");
}
}
2.instance
1.检测需要的拓展
这里使用了glfw的glfwGetRequiredInstanceExtensions函数,比较方便
auto extensions = getRequiredExtensions();
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();
std::vector<const char*> getRequiredExtensions() {
std::vector<const char*> extensions;
unsigned int glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
for (unsigned int i = 0; i < glfwExtensionCount; i++) {
extensions.push_back(glfwExtensions[i]);
}
if (enableValidationLayers) {
extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
}
return extensions;
}
2.检测需要启用的层(一般指验证层)
3.逻辑设备
创建逻辑设备需要填充一个结构体VkDeviceCreateInfo
typedef struct VkDeviceCreateInfo {
VkStructureType sType;
const void* pNext;
VkDeviceCreateFlags flags;
uint32_t queueCreateInfoCount;
const VkDeviceQueueCreateInfo* pQueueCreateInfos;
uint32_t enabledLayerCount;
const char* const* ppEnabledLayerNames;
uint32_t enabledExtensionCount;
const char* const* ppEnabledExtensionNames;
const VkPhysicalDeviceFeatures* pEnabledFeatures;
} VkDeviceCreateInfo;
1.queueCreateInfoCount 队列数量 前面我们找了一个队列簇,这里是1
2.pQueueCreateInfos 队列创建信息的结构体,有多少个队列,填充几个。
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<int> uniqueQueueFamilies = { indices.graphicsFamily, indices.presentFamily };
float queuePriority = 1.0f;
for (int queueFamily : uniqueQueueFamilies) {
VkDeviceQueueCreateInfo queueCreateInfo = {};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamily;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back(queueCreateInfo);
}
1)queueFamilyIndex 队列下标
2)queueCount 队列数量
3)pQueuePriorities 队列优先级
3.enabledLayerCount
ppEnabledLayerNames
ppEnabledExtensionNames
enabledExtensionCount
允许的拓展和layer,但是有点疑问,这个东西在创建instance的时候也用了,那跟这里的有什么区别呢。
4.pEnabledFeatures 允许的特性,可以用vkGetPhysicalDeviceFeatures获取
VkPhysicalDeviceFeatures deviceFeatures = {};
vkGetPhysicalDeviceFeatures(physicalDevice, &deviceFeatures);
createInfo.pEnabledFeatures = &deviceFeatures;
4 创建swapchain
typedef struct VkSwapchainCreateInfoKHR {
VkStructureType sType;
const void* pNext;
VkSwapchainCreateFlagsKHR flags;
VkSurfaceKHR surface;
uint32_t minImageCount;
VkFormat imageFormat;
VkColorSpaceKHR imageColorSpace;
VkExtent2D imageExtent;
uint32_t imageArrayLayers;
VkImageUsageFlags imageUsage;
VkSharingMode imageSharingMode;
uint32_t queueFamilyIndexCount;
const uint32_t* pQueueFamilyIndices;
VkSurfaceTransformFlagBitsKHR preTransform;
VkCompositeAlphaFlagBitsKHR compositeAlpha;
VkPresentModeKHR presentMode;
VkBool32 clipped;
VkSwapchainKHR oldSwapchain;
} VkSwapchainCreateInfoKHR;
presentMode 图像呈现选项
图像呈现选项
VK_PRESENT_MODE_IMMEDIATE_KHR: 应用程序提交的图像被立即传输到屏幕呈现,这种模式可能会造成撕裂效果。
VK_PRESENT_MODE_FIFO_KHR: 交换链被看作一个队列,当显示内容需要刷新的时候,显示设备从队列的前面获取图像,并且程序将渲染完成的图像插入队列的后面。如果队列是满的程序会等待。这种规模与视频游戏的垂直同步很类似。显示设备的刷新时刻被成为“垂直中断”。
VK_PRESENT_MODE_FIFO_RELAXED_KHR: 该模式与上一个模式略有不同的地方为,如果应用程序存在延迟,即接受最后一个垂直同步信号时队列空了,将不会等待下一个垂直同步信号,而是将图像直接传送。这样做可能导致可见的撕裂效果。
VK_PRESENT_MODE_MAILBOX_KHR: 这是第二种模式的变种。当交换链队列满的时候,选择新的替换旧的图像,从而替代阻塞应用程序的情形。这种模式通常用来实现三重缓冲区,与标准的垂直同步双缓冲相比,它可以有效避免延迟带来的撕裂效果。
imageSharingMode 多队列图像处理
多队列处理图像
VK_SHARING_MODE_EXCLUSIVE: 同一时间图像只能被一个队列簇占用,如果其他队列簇需要其所有权需要明确指定。这种方式提供了最好的性能。
VK_SHARING_MODE_CONCURRENT: 图像可以被多个队列簇访问,不需要明确所有权从属关系。
imageArrayLayers 指定每个图像组成的层数。除非我们开发3D应用程序,否则始终为1
imageUsage 位字段指定在交换链中对图像进行的具体操作
preTransform 如果交换链支持(supportedTransforms in capabilities),我们可以为交换链图像指定 某些转换逻辑,比如90度顺时针旋转或者水平反转。如果不需要任何transoform操作,可以简单的设置为currentTransoform
presentMode 混合Alpha字段指定alpha通道是否应用与与其他的窗体系统进行混合操作。如果忽略该功能,简单的填VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR。
clipped 如果clipped成员设置为VK_TRUE,意味着我们不关心被遮蔽的像素数据,比如由于其他的窗体置于前方时或者渲染的部分内容存在于可是区域之外,除非真的需要读取这些像素获数据进行处理,否则可以开启裁剪获得最佳性能。
oldswapchain 最后一个字段oldSwapChain。Vulkan运行时,交换链可能在某些条件下被替换,比如窗口调整大小或者交换链需要重新分配更大的图像队列。在这种情况下,交换链实际上需要重新分配创建,并且必须在此字段中指定对旧的引用,用以回收资源。这是一个比较复杂的话题.
获取image
创建完swapchain后,可以获取里面的图片,获取交换链中图像的数量,并根据数量设置合适的容器大小保存获取到的句柄集合。
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
swapchain imageview
使用任何的VkImage,包括在交换链或者渲染管线中的,我们都需要创建VkImageView对象。从字面上理解它就是一个针对图像的视图或容器,通过它具体的渲染管线才能够读写渲染数据,换句话说VkImage不能与渲染管线进行交互。除此之外,图像视图可以进一步定义具体Image的格式,比如定义为2D贴图,那么本质上就不需要任何级别的mipmapping。
void createImageViews() {
swapChainImageViews.resize(swapChainImages.size());
for (size_t i = 0; i < swapChainImages.size(); i++) {
VkImageViewCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
createInfo.image = swapChainImages[i];
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
createInfo.format = swapChainImageFormat;
createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
createInfo.subresourceRange.baseMipLevel = 0;
createInfo.subresourceRange.levelCount = 1;
createInfo.subresourceRange.baseArrayLayer = 0;
createInfo.subresourceRange.layerCount = 1;
if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create image views!");
}
}
}
viewType和format字段用于描述图像数据该被如何解释。
viewType 参数允许将图像定义为1D textures, 2D textures, 3D textures 和cube maps。
format 表明图像的格式信息
components 字段允许调整颜色通道的最终的映射逻辑。比如,我们可以将所有颜色通道映射为红色通道,以实现单色纹理。我们也可以将通道映射具体的常量数值0和1,默认是VK_COMPONENT_SWIZZLE_IDENTITY
subresourceRange 用于描述图像的使用目标是什么,以及可以被访问的有效区域。我们的图像将会作为color targets,没有任何mipmapping levels 或是多层 multiple layers。
图形管线
过程大概为:从顶点缓冲区拿到顶点数据->解释顶点信息是怎么用的,从哪到哪是顶点,到哪是颜色数据,并传递给shader->细分着色器,(把图形细分为更多的三角形)->光栅化(将图元整理成片源图形)->fragment shader应用于每个片元,确定每个帧缓冲区中写入的片元数据的颜色和深度值->不同片源颜色混合
shader
vulkan中普通的shader不能直接使用
C:/VulkanSDK/1.0.17.0/Bin32/glslangValidator.exe -V shader.vert
C:/VulkanSDK/1.0.17.0/Bin32/glslangValidator.exe -V shader.frag
然后把数据读入内存中使用
static std::vector<char> readFile(const std::string& filename) {
std::ifstream file(filename, std::ios::ate | std::ios::binary);
if (!file.is_open()) {
throw std::runtime_error("failed to open file!");
}
size_t fileSize = (size_t)file.tellg();
std::vector<char> buffer(fileSize);
file.seekg(0);
file.read(buffer.data(), fileSize);
file.close();
return buffer;
}
但是vulkan是不能直接使用的shader的,在将代码传递给渲染管线之前,我们必须将其封装到VkShaderModule对象中
VkShaderModule createShaderModule(const std::vector<char>& code) {
VkShaderModuleCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = code.size();
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
VkShaderModule shaderModule;
if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
throw std::runtime_error("failed to create shader module!");
}
return shaderModule;
}
可以看出,shadermoudle的创建比较简单,只需要二进制码缓冲区的指针和它的具体长度
有了shandermoudle还不行,因为他跟管线之间没有联系,管线不知道怎么联系,使用它
VkPipelineShaderStageCreateInfo vertShaderStageInfo = {};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = vertShaderModule;
vertShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo fragShaderStageInfo = {};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo };
stage 将在哪个流水线阶段使用着色器
moudle 对应的module
pName 入口,调用的主函数
Vertex input
VkPipelineVertexInputStateCreateInfo 结构体描述了顶点数据的格式
Bindings:根据数据的间隙,确定数据是每个顶点或者是每个instance(instancing)
Attribute 描述:描述将要进行绑定及加载属性的顶点着色器中的相关属性类型。
比如:下面的例子
const std::vector<Vertex> vertices = {
{ { -0.5f, -0.5f },{ 1.0f, 0.0f, 0.0f } },
{ { 0.5f, -0.5f },{ 0.0f, 1.0f, 0.0f } },
{ { 0.5f, 0.5f },{ 0.0f, 0.0f, 1.0f } },
{ { -0.5f, 0.5f },{ 1.0f, 1.0f, 1.0f } }
};
struct Vertex {
glm::vec2 pos;
glm::vec3 color;
static VkVertexInputBindingDescription getBindingDescription() {
VkVertexInputBindingDescription bindingDescription = {};
// 这里的binding随便写,但是不能超过最大顶点输入绑定。在我这里是32
// 只要这个数与后面
// vkCmdBindVertexBuffers(commandBuffers[i], 1, 1, vertexBuffers, offsets);
// 的第二个参数能对上就行
// 后面的VkVertexInputAttributeDescription与这个要一样
bindingDescription.binding = 1;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
return bindingDescription;
}
static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions() {
std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions = {};
// 见前面
attributeDescriptions[1].binding = 1;
// location 与 shader文件中的定义一致
attributeDescriptions[1].location = 0;
attributeDescriptions[1].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, pos);
attributeDescriptions[0].binding = 1;
attributeDescriptions[0].location = 1;
attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, color);
return attributeDescriptions;
}
};
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
auto bindingDescription = Vertex::getBindingDescription();
auto attributeDescriptions = Vertex::getAttributeDescriptions();
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
VkVertexInputBindingDescription 中:
inputRate 顶点扫描模式
stride 数据之间的步长(一组数据的长度)
binding 输入点,绑定点
这个属性与后面的vkCmdBindVertexBuffers(commandBuffers[i], 1, 1, vertexBuffers, offsets);有关
比如:上面定义了binding是1,,vkCmdBindVertexBuffers的第二个参数,绑定点也要是1.
VkVertexInputAttributeDescription中:
binding : 与上面一样
location : shader中的location属性一致,表明与shader中绑定
format : 数据格式
offset : 数据在这段顶点数据中的偏移量
这个会在创建图形管线中用到,让管线知道怎么解释顶点数据
光知道怎么解释没有用,还需要实际拿到数据,需要vkCmdBindVertexBuffers(commandBuffers[i], 1, 1, vertexBuffers, offsets); 进行数据绑定
Input assembly
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
topology : 顶点数据以什么类型的几何图元拓扑进行绘制
VK_PRIMITIVE_TOPOLOGY_POINT_LIST: 顶点到点
VK_PRIMITIVE_TOPOLOGY_LINE_LIST: 两点成线,顶点不共用
VK_PRIMITIVE_TOPOLOGY_LINE_STRIP: 两点成线,每个线段的结束顶点作为下一个线段的开始顶点
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST: 三点成面,顶点不共用
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP: 每个但教训的第二个、第三个顶点都作为下一个三角形的前两个顶点
primitiveRestartEnable : 是否启用顶点索重新开始图元
Viewports and scissors
viewport用于描述framebuffer作为渲染输出结果目标区域
VkViewport viewport = {};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float)swapChainExtent.width;
viewport.height = (float)swapChainExtent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
minDepth和maxDepth数值指定framebuffer中深度的范围。这些数值必须收敛在[0.0f, 1.0f]区间
viewports定义了image图像到framebuffer帧缓冲区的转换关系,裁剪矩形定义了哪些区域的像素被存储
VkRect2D scissor = {};
scissor.offset = { 0, 0 };
scissor.extent = swapChainExtent;
最后这些信息都被存放在VkPipelineViewportStateCreateInfo 中
VkPipelineViewportStateCreateInfo viewportState = {};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;
Rasterizer
栅化通过顶点着色器及具体的几何算法将顶点进行塑形,并将图形传递到片段着色器进行着色工作。它也会执行深度测试depth testing、面裁切face culling和裁剪测试,它可以对输出的片元进行配置,决定是否输出整个图元拓扑或者是边框(线框渲染)。所有的配置通过VkPipelineRasterizationStateCreateInfo结构体定义。
VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
//rasterizer.cullMode = VK_CULL_MODE_FRONT_BIT;
rasterizer.cullMode = VK_CULL_MODE_NONE;
//rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
depthClampEnable : 它的depthClampEnable设置为VK_TRUE,超过远近裁剪面的片元会进行收敛,而不是丢弃它们
rasterizerDiscardEnable : 是否允许光栅化
polygonMode : 光栅化产生图元的内容
VK_POLYGON_MODE_FILL: 多边形区域填充
VK_POLYGON_MODE_LINE: 多边形边缘线框绘制
VK_POLYGON_MODE_POINT: 多边形顶点作为描点绘制
lineWidth :线宽 lineWidth成员是直接填充的,根据片元的数量描述线的宽度。最大的线宽支持取决于硬件,任何大于1.0的线宽需要开启GPU的wideLines特性支持
cullMode :变量用于决定面裁剪的类型方式。可以禁止culling,裁剪front faces,cull back faces 或者全部。不想看正面,反面,还是全部,或者不管
frontFace :用于描述作为front-facing面的顶点的顺序,可以是顺时针也可以是逆时针,那个面试正面
depthBiasEnable :是否启用深度偏移
Multisampling
谓多重采样是抗锯齿anti-aliasing的一种实现。它通过组合多个多边形的片段着色器结果,光栅化到同一个像素。这主要发生在边缘,这也是最引人注目的锯齿出现的地方。如果只有一个多边形映射到像素是不需要多次运行片段着色器进行采样的,相比高分辨率来说,它会花费较低的开销。开启该功能需要GPU支持。
这个不了解
Depth and stencil testing
这个也不了解
Color blending
片段着色器输出具体的颜色,它需要与帧缓冲区framebuffer中已经存在的颜色进行混合。这个转换的过程成为混色,它有两种方式:
将old和new颜色进行混合产出一个最终的颜色
使用按位操作混合old和new颜色的值
第一种:
VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_FALSE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // 可选
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // 可选
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // 可选
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // 可选
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional
第二种
VkPipelineColorBlendStateCreateInfo colorBlending = {};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_TRUE;
colorBlending.logicOp = VK_LOGIC_OP_COPY; // 可选
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
colorBlending.blendConstants[0] = 0.0f; // 可选
colorBlending.blendConstants[1] = 0.0f; // 可选
colorBlending.blendConstants[2] = 0.0f; // 可选
colorBlending.blendConstants[3] = 0.0f; // 可选
如果你想使用第二种混合方式(位操作组合方式),那么你应用将 logicOpEnable 设置为 VK_TRUE,但是这样第一种就不起效。或者blendEnable 设为VK_FALSE,两个都不起效
Dynamic state
之前创建的一些结构体的状态可以在运行时动态修改,而不必重新创建。比如viewport的大小,line width和blend constants
这个没用过 放着
pipelienLayout
可以在着色器中使用uniform,它是类似与动态状态变量的全局变量,可以在绘画时修改,可以更改着色器的行为而无需重新创建它们。它们通常用于将变换矩阵传递到顶点着色器或者在片段着色器冲创建纹理采样器。
这些uniform数值需要在管线创建过程中,通过VkPipelineLayout对象指定
简而言之:描述符集,帮助uniformbuffer与shader建立联系,而描述符集布局,管理着所有的描述符集,而pipelineLayout中包含描述符布局,帮助pipelien知道怎么使用unform和与shader 联系