vulakn教程--Drawing a Triangle--Pipeline--Fixed function

原文地址 : vulkan-tutorial

(CSDN 这个markdown编辑器真不好 ,不能自动保存 , 按错一个快捷键, 之前编辑的内容都没了)

Fixed functions

这一章节的内容主要是Pipeline中不可编程部分的配置。

Vertex input

VkPipelineVertexInputStateCreateInfo 代表我们传递给Vertex Shader 顶点数据的格式,它涉及以下两个方面:

  1. 顶点数据的描述(Bindings) :数据间的间隔,以及判断数据是顶点数据(pre-vertex)还是实例数据(pre-instance)。

  2. 顶点属性的描述(Attribute Descriptions):传入到Vertex Shader 里的属性(attributes)类型,从哪个Binding加载以及offset。

因为我们把顶点硬编码到Shader Code中了,所以这个结构先赋空值,当我们有需要的时候再回过头来重新审视这个结构。

VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; 
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; 
vertexInputInfo.vertexBindingDescriptionCount = 0; 
vertexInputInfo.pVertexBindingDescriptions = nullptr; // Optional 
vertexInputInfo.vertexAttributeDescriptionCount = 0; 
vertexInputInfo.pVertexAttributeDescriptions = nullptr; // Optional

Input assembly

VkPipelineInputAssemblyStateCreateInfo 描述了两个内容:我们要画什么样的几何图形和图元顶点是否可以重用(primitive restart should be enabled)。

VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; 
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INF
O; 
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 与_STRIP有关,先置 FLASE。

Viewports and scissors

Viewport 其实就是输出结果被渲染到FrameBuffer的多大区域中。它总是从坐标(0,0)点开始,具有一定宽(width)和高(height)的矩形区域。这个区域我们用VkViewport表示:

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;

正如在创建swpaChain时所描述的那样,swapChain及其Image的尺寸可能和window的尺寸不同。我们用Swap Chain的 width和height 赋值Viewport,因为接下来Swap Chain的 images 将作为FrameBuffer使用。Min/maxDepth表示FrameBuffer的深度范围,深度取值在[0.0,1.0]范围内,注意,minDepth可能大于maxDepth,如果没有什么特殊需要,我们将按照标准的定义, 即:minDepth=0,maxDepth=1.0 。

Viewport 定义了image 到 FrameBuffer的变换,而Scissor 矩形框决定哪些区域的像素将会被存储,在Scissor矩形框外的像素将会在光栅化(像素化)阶段被丢弃。所以,比起变换,Scissor 更像是一个过滤器。如图:

这里我们想画整个FrameBuffer,所以我们这样定义Scissor :

VkRect2D scissor = {}; 
scissor.offset = {0,0}; 
scissor.extent = swapChainExtent;

这里我们用到了VkRect2D 结构:

typedef struct VkRect2D { 
    VkOffset2D offset; 
    VkExtent2D extent; 
} VkRect2D;

VkOffset2D 结构:

typedef struct VkOffset2D { 
    int32_t x; 
    int32_t y; 
} VkOffset2D;

VkExtent2D 结构:

typedef struct VkExtent2D { 
    uint32_t width; 
    uint32_t height; 
}VkExtent2D;

现在我们需要将Viewport和Scissor 结合起来,把他们运用到Pipeline的Viewport 状态(viewport state)中:

VkPipelineViewportStateCreateInfo viewportState = {};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;

注意,从VkPipelineViewportStateCreateInfo的结构上来看,在某些显卡上,我们可以使用多个Viewport和多个Scissor ,这涉及到显卡的支持,在我们创建Logicsl Device时,VkPhysicalDeviceFeatures 字段里有 VkBool32 multiViewport;的定义,你可以检查自己的显卡是否支持这个特性。

Rasterizer

光栅化(我个人更喜欢像素化这个称呼),它把来自Vertex Shader 操作后顶点组成的几何图形离散化成一个个片原(fragment),然后将片原传递到Fragment Shader 里进行着色。光栅化也执行depth testing、face culling 和 scissor test。你可以配置,选择是将整个多边形离散化成片原,还是只离散化边框(edges)(又叫 : wireframe rending),我们通过如下结构来进行设置:

VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE

depthClampEnable 如果设置成VK_TRUE,那些在视景体近平面(near)和远平面(far)之外的片原将会被拉紧/截取?(clamped 什么意思??),而不是丢弃,这在有些情况下很有用,但需要显卡特性支持。

rasterizerDiscardEnable 如果为VK_TRUE,几何数据(geometry)将无法通过Rasterization阶段,FrameBuffer 将得不到任何输出数据。

rasterizer.polygonMode = VK_POLYGON_MODE_FILL;

polygonMode 表示我们将从几何图形中产生生么样的片原(fragments),它的取值有以下几种:

VK_POLYGON_MODE_FILL: 填充整个多边形区域的片原
VK_POLYGON_MODE_LINE: 只有多边形边界(edges)的片原
VK_POLYGON_MODE_POINT: 只画多边形顶点

这里我们要画三角形区域,所以采用VK_POLYGON_MODE_FILL。

rasterizer.lineWidth = 1.0f; //线条粗细
/* 卷绕方式与背面裁剪 规定裁剪那个面:前面和背面,从摄像机的角度看,顶点按逆时针组成的图形 是正面,顺时背面。正反面的卷绕方式可以自定义。 */
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; 
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;

cullMode 表示裁剪方式,结构:
“`
typedef enum VkCullModeFlagBits {
VK_CULL_MODE_NONE = 0,
VK_CULL_MODE_FRONT_BIT = 0x00000001,
VK_CULL_MODE_BACK_BIT = 0x00000002,
VK_CULL_MODE_FRONT_AND_BACK = 0x00000003,
} VkCullModeFlagBits;

>frontFace 表示正面的卷绕方式,结构:
>```
>typedef enum VkFrontFace { 
    VK_FRONT_FACE_COUNTER_CLOCKWISE = 0,VK_FRONT_FACE_CLOCKWISE = 1,} VkFrontFace;
rasterizer.depthBiasEnable = VK_FALSE; 
rasterizer.depthBiasConstantFactor = 0.0f; // Optional 
rasterizer.depthBiasClamp = 0.0f; // Optional 
rasterizer.depthBiasSlopeFactor = 0.0f; // Optional

光栅化可以改变深度值,我们暂时不使用这些选项。

Multisampling

多重采样可以执行防锯齿的功能,在我们的应用中不需要使用,你可以查看文档去了解:

VkPipelineMultisampleStateCreateInfo multisampling = {}; 
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; 
multisampling.sampleShadingEnable = VK_FALSE; 
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; multisampling.minSampleShading = 1.0f; // Optional 
multisampling.pSampleMask = nullptr; /// Optional 
multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
multisampling.alphaToOneEnable = VK_FALSE; // Optional

Depth and stencil testing

如果我们使用depth和stencil buffer就要配置VkPipelineDepthStencilStateCreateInfo 参数,我们目前还没有使用它,当用到的时候我们在回过头来拜访它,现在值空即可。

Color blending

Fagment Shader 输出的颜色结果要和FrameBuffer中已有的颜色整合到一起,这里有两种方式:

  1. 混合(mix) 新颜色和旧颜色来产生最终的颜色。

  2. 将新颜色和旧颜色通过按位运算(bitwise)整合在一起。

为了完成颜色混合,我们需要填充两个结构体: VkPipelineColorBlendAttachmentState 对每一个FrameBuffer进行配置(per attached framebuffer)VkPipelineColorBlendStateCreateInfo对全局的颜色( global color)混合进行设置。在我们的应用中只有一个FrameBuffer。
先看一下 VkPipelineColorBlendAttachmentState :

typedef struct VkPipelineColorBlendAttachmentState {
    VkBool32 blendEnable;
    VkBlendFactor srcColorBlendFactor;
    VkBlendFactor dstColorBlendFactor;
    VkBlendOp colorBlendOp;
    VkBlendFactor srcAlphaBlendFactor;
    VkBlendFactor dstAlphaBlendFactor;
    VkBlendOp alphaBlendOp;
    VkColorComponentFlags colorWriteMask;
} VkPipelineColorBlendAttachmentState;

其中的 VkBlendFactor 结构:

typedef enum VkBlendFactor {
    VK_BLEND_FACTOR_ZERO = 0,VK_BLEND_FACTOR_ONE = 1,VK_BLEND_FACTOR_SRC_COLOR = 2,VK_BLEND_FACTOR_SRC_ALPHA = 6,...
} VkBlendFactor;

VkBlendOp 结构:

typedef enum VkBlendOp {
    VK_BLEND_OP_ADD = 0,VK_BLEND_OP_SUBTRACT = 1,VK_BLEND_OP_REVERSE_SUBTRACT = 2,VK_BLEND_OP_MIN = 3,VK_BLEND_OP_MAX = 4,...
} VkBlendOp;

配置 VkPipelineColorBlendAttachmentState :

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; 
//Optional 
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
//Optional 
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; 
// Optional
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; 
//Optional 
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; 
//Optional  
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; 
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional

下面的伪代码显示了处理过程:

if (blendEnable) {
    finalColor.rgb = (srcColorBlendFactor * newColor.rgb) <colorBlendOp>    (dstColorBlendFactor * oldColor.rgb);
    finalColor.a = (srcAlphaBlendFactor * newColor.a) <alphaBlendOp>    (dstAlphaBlendFactor * oldColor.a);
} else {
    finalColor = newColor;
}
finalColor = finalColor & colorWriteMask; //保留何种通道

我们更常用的可能是Alpha通道的混合:

colorBlendAttachment.blendEnable = VK_TRUE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;

伪代码:

finalColor.rgb = newAlpha * newColor + (1 - newAlpha) * oldColor; 
finalColor.a = newAlpha.a;

第二个配置结构 VkPipelineColorBlendStateCreateInfo :

typedef struct VkPipelineColorBlendStateCreateInfo {
    VkStructureType sType;
    const void* pNext;
    VkPipelineColorBlendStateCreateFlags flags;
    VkBool32 logicOpEnable;
    VkLogicOp logicOp;
    uint32_t attachmentCount;
    const VkPipelineColorBlendAttachmentState* pAttachments;
    float blendConstants[4];
} VkPipelineColorBlendStateCreateInfo;

VkLogicOp 结构:

typedef enum VkLogicOp {
    VK_LOGIC_OP_CLEAR = 0,VK_LOGIC_OP_AND = 1,VK_LOGIC_OP_AND_REVERSE = 2,VK_LOGIC_OP_COPY = 3,VK_LOGIC_OP_AND_INVERTED = 4,VK_LOGIC_OP_NO_OP = 5,VK_LOGIC_OP_XOR = 6,VK_LOGIC_OP_COPY_INVERTED = 12,VK_LOGIC_OP_OR_INVERTED = 13,...
} VkLogicOp;

填充 VkPipelineColorBlendStateCreateInfo :

VkPipelineColorBlendStateCreateInfo colorBlending = {};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
colorBlending.blendConstants[0] = 0.0f; // Optional 
colorBlending.blendConstants[1] = 0.0f; // Optional 
colorBlending.blendConstants[2] = 0.0f; // Optional 
colorBlending.blendConstants[3] = 0.0f; // Optional

如果我们要使用第二种混合方式,即按位混合(bitewise combination), logicOpEnable就要设置成VK_TRUE,然后新旧颜色就按照logicOp中的操作类型按位进行运算,但此时,我们在第一种结构中设置的混合方式将不起作用,如同blendEnable设置成VK_FALSE一样,但是colorWriteMask 任然会被第二种方式使用。当然我们可能对这两种混合方式都不感冒,正如我们上面说做的那样:全部设置为VK_FALSE,表示fragment 中的颜色将直接写入FrameBuffer中。

Dynamic state

当目前为止,以上我们为Pipeline 设置的属性,有一部分设置可以在不重建Pipeline的情况下被改变,比如:Viewport的大小,lineWidth 和 blend Constrants等。 通过VkDynamicsSate 我们可以知道可以改变那些东西:

typedef enum VkDynamicState {
    VK_DYNAMIC_STATE_VIEWPORT = 0,VK_DYNAMIC_STATE_SCISSOR = 1,VK_DYNAMIC_STATE_LINE_WIDTH = 2,VK_DYNAMIC_STATE_DEPTH_BIAS = 3,VK_DYNAMIC_STATE_BLEND_CONSTANTS = 4,VK_DYNAMIC_STATE_DEPTH_BOUNDS = 5,VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK = 6,VK_DYNAMIC_STATE_STENCIL_WRITE_MASK = 7,VK_DYNAMIC_STATE_STENCIL_REFERENCE = 8,} VkDynamicState

如果要在绘制阶段改变一些设置,我们需要填写如下结构:

VkDynamicState dynamicStates[] = { VK_DYNAMIC_STATE_VIEWPORT,VK_DYNAMIC_STATE_LINE_WIDTH };
VkPipelineDynamicStateCreateInfo dynamicState = {};
dynamicState.dynamicStateCount = 2;
dynamicState.pDynamicStates = dynamicStates;

如果我们确实这么做了,那之前对这些属性的设置都将无效,在绘画阶段就会要求我们给出这些属性的具体设置。

Pipeline layout

Uniform 全局变量如同我们之前介绍的Pipeline的可变部分一样,它们的改变不会导致Pipeline的重建。我们可以通过在绘画阶段改变这些uniform变量从而改变着色器(Shaders)的行为。最常用的情况就是传递给Vertex Shader 一个转换矩阵(Matrix),或者在Fragment Shader 中创建取样器(Samplers)。

我们需要使用VkPipelineLayout 结构来来声明Uniform 变量,这个过程必须在Pipeline创建阶段完成。 我们目前还不需要uniform 变量,这里写一空结构:

VDeleter<VkPipelineLayout> pipelineLayout {device,vkDestroyPipelineLayout}; // ÉùÃ÷
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 0; // Optional 
pipelineLayoutInfo.pSetLayouts = nullptr; // Optional 
pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional 
pipelineLayoutInfo.pPushConstantRanges = 0; // Optional
if (vkCreatePipelineLayout(device,&pipelineLayoutInfo,nullptr,&pipelineLayout) != VK_SUCCESS) {
    throw std::runtime_error("failed to create pipeline layout!");
}

原文源码 : Source code

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结