我一直在学习Metal for iOS/OSX,我开始遵循Ray Wenderlich教程(https://www.raywenderlich.com/146414/metal-tutorial-swift-3-part-1-getting-started).本教程工作正常,但没有提及MTLVertexAttributeDescriptors.
现在我正在开发自己的应用程序,我遇到了奇怪的故障,我想知道我不使用MTLVertexAttributeDescriptors的事实是否与问题有关.
他们有什么不同?我已经能够制作各种具有不同顶点结构的着色器,我甚至都不知道这些东西.
我知道你用它们来描述在着色器中使用的顶点组件的布局.例如,着色器可能将此结构用于顶点,并且它将在下面函数的顶点描述符中设置.
typedef struct
{
    float3 position [[attribute(T_VertexAttributePosition)]];
    float2 texCoord [[attribute(T_VertexAttributeTexcoord)]];
} Vertex;
 class func buildMetalVertexDescriptor() -> MTLVertexDescriptor {
    let mtlVertexDescriptor = MTLVertexDescriptor()
    mtlVertexDescriptor.attributes[T_VertexAttribute.position.rawValue].format = MTLVertexFormat.float3
    mtlVertexDescriptor.attributes[T_VertexAttribute.position.rawValue].offset = 0
    mtlVertexDescriptor.attributes[T_VertexAttribute.position.rawValue].bufferIndex = T_BufferIndex.meshPositions.rawValue
    mtlVertexDescriptor.attributes[T_VertexAttribute.texcoord.rawValue].format = MTLVertexFormat.float2
    mtlVertexDescriptor.attributes[T_VertexAttribute.texcoord.rawValue].offset = 0
    mtlVertexDescriptor.attributes[T_VertexAttribute.texcoord.rawValue].bufferIndex = T_BufferIndex.meshGenerics.rawValue
    mtlVertexDescriptor.layouts[T_BufferIndex.meshPositions.rawValue].stride = 12
    mtlVertexDescriptor.layouts[T_BufferIndex.meshPositions.rawValue].stepRate = 1
    mtlVertexDescriptor.layouts[T_BufferIndex.meshPositions.rawValue].stepFunction = MTLVertexStepFunction.perVertex
    mtlVertexDescriptor.layouts[T_BufferIndex.meshGenerics.rawValue].stride = 8
    mtlVertexDescriptor.layouts[T_BufferIndex.meshGenerics.rawValue].stepRate = 1
    mtlVertexDescriptor.layouts[T_BufferIndex.meshGenerics.rawValue].stepFunction = MTLVertexStepFunction.perVertex
    return mtlVertexDescriptor
}
但即使没有MTLVertexDescriptor设置,着色器也可以访问顶点缓冲区和数组中顶点的位置/ texCoord组件.只需设置顶点缓冲区,着色器就可以访问所有组件.这个描述符有什么好处呢?
我在使用ATI驱动程序的OpenGL 3.3内核运行我的顶点着色器时遇到了极大的麻烦:
#version 150
uniform mat4 graph_matrix, view_matrix, proj_matrix;
uniform bool align_origin;
attribute vec2 graph_position;
attribute vec2 screen_position;
attribute vec2 texcoord0;
attribute vec4 color;
varying vec2 texcoord0_px;
varying vec4 color_px;
void main() {
    // Pick the position or the annotation position
    vec2 pos = graph_position;
    // Transform the coordinates
    pos = vec2(graph_matrix * vec4(pos, 0.0, 1.0));
    if( align_origin )
        pos = floor(pos + vec2(0.5, 0.5)) + vec2(0.5, 0.5);
    gl_Position = proj_matrix * view_matrix * vec4(pos + screen_position, 0.0, 1.0);
    texcoord0_px …我想知道是否有办法判断顶点着色器中是否启用了顶点属性?我知道如果禁用顶点属性,所有值都将被视为0.0,所以我可以进行如下测试:
if (attribute == 0)
{
    // Do something different to normal.
}
else
{
    // Use the attribute.
}
但是,对于启用该属性并且该值仅设置为0的情况,这具有明显的问题(它将被视为已禁用)!
另一种解决方案是使用一个统一变量来说明是否使用该属性,但我想知道GLSL中是否有任何内置可以做到这一点?
我有一些3D代码,我注意到它不会在严格的核心配置文件中呈现,但在"正常"(未明确请求为核心)配置文件上下文中很好.为了找出问题,我编写了最简单的最简单的OpenGL程序,只绘制了一个三角形和一个矩形:

我在这里发布了OpenGL程序作为Gist.
将useStrictCoreProfile变量设置为false,程序不会向控制台输出错误消息,并根据上面的屏幕截图绘制四边形和三角形,包括Intel HD OpenGL 3.3和带OpenGL 4.2的GeForce.
但是,将useStrictCoreProfile设置为true,它会清除背景颜色但不会绘制tri和quad,控制台输出是这样的:
GLCONN: OpenGL 3.2.0 @ NVIDIA Corporation GeForce GT 640M LE/PCIe/SSE2 (GLSL: 1.50 NVIDIA via Cg compiler)
LASTERR: OpenGL error at step 'render.VertexAttribPointer()': GL_INVALID_OPERATION
LASTERR: OpenGL error at step 'render.DrawArrays()': GL_INVALID_OPERATION
LASTERR: OpenGL error at step 'render.VertexAttribPointer()': GL_INVALID_OPERATION
LASTERR: OpenGL error at step 'render.DrawArrays()': GL_INVALID_OPERATION
LASTERR: OpenGL error at step '(post loop)': GL_INVALID_OPERATION
EXIT
...如果要求4.2严格的核心配置文件而不是3.2,则同样的问题.适用于3种不同的nvidia GPU,因此我假设我没有正确地符合严格的核心配置文件.我做错了什么,我该如何解决这个问题?
注意,你不会在上面的Gist中找到glEnableVertexAttribArray调用,因为它在我正在导入的glutil包中 - 但这确实被称为gist的compileShaders()函数中的最后一步.
我可以将颜色作为 4 个浮点发送到着色器 - 没问题。但是我想将其作为整数(或无符号整数,并不重要,重要的是 32 位)发送,并在着色器上的 vec4 中分解。
我使用 OpenTK 作为 OpenGL 的 C# 包装器(尽管它应该只是一个直接包装器)。
让我们考虑最简单的着色器之一,其顶点包含位置(xyz)和颜色(rgba)。
顶点着色器:
#version 150 core
in vec3 in_position;
in vec4 in_color;
out vec4 pass_color;
uniform mat4 u_WorldViewProj;
void main()
{
    gl_Position = vec4(in_position, 1.0f) * u_WorldViewProj;
    pass_color = in_color;
}
片段着色器:
#version 150 core
in vec4 pass_color;
out vec4 out_color;
void main()
{
    out_color = pass_color;
}
让我们创建顶点缓冲区:
public static int CreateVertexBufferColor(int attributeIndex, int[] rawData)
{
    var bufferIndex = GL.GenBuffer();
    GL.BindBuffer(BufferTarget.ArrayBuffer, bufferIndex);
    GL.BufferData(BufferTarget.ArrayBuffer, sizeof(int) …这似乎是一个 IHV 或另一个 IHV 一直以来都得到解答的问题,但最近我一直试图就顶点布局以及跨所有 IHV 和架构的现代渲染器的最佳实践达成共识。在有人说基准测试之前,我无法轻易做到这一点,因为我无法访问过去 5 年中每个 IHV 和每个架构的卡。因此,我正在寻找一些能够在所有平台上良好运行的最佳实践。
首先,显而易见的是:
Vertex类。现在来说说不太明显的事情。
许多人引用 NVIDIA 的话说,您应该始终交错,而且应该对齐到 32 或 64 字节。我还没有找到它的来源,但找到了 NVIDIA 的有关顶点着色器性能的文档,但它已经很旧了(2013 年),并且是关于移动而非桌面的 Tegra GPU。它特别说:
将顶点数据存储为交错的属性流(“结构数组”布局),这样属性的“过度获取”往往会预取可能对后续属性和顶点有用的数据。将属性存储为不同的、非交错的(“数组结构”)流可能会导致内存系统中的“页面抖动”,从而导致性能大幅下降。
快进 3 年到GDC 2016,EA 进行了一次演示,其中提到了应该对顶点缓冲区进行去交错的几个原因。然而,这个建议似乎与 AMD 架构相关,特别是 GCN。虽然他们提出了一个跨平台的案例来分离位置,但他们建议对所有内容进行去交错,并声明这将允许 GPU :
尽快清除缓存行
而且它对于 GCN (AMD) 架构来说是最佳的。
这似乎与我在其他地方听到的说法相冲突,即使用交错以充分利用缓存行。但同样,这与 AMD 无关。
拥有许多不同的 IHV,Intel、NVIDIA、AMD,现在还有 Apple 的 M1 GPU,而且每一个都有许多不同的架构,这让我完全不确定今天应该做什么(没有预算来测试数十个 GPU)。 GPU),以便最好地优化所有架构的性能,而不会导致
结果导致性能大幅下降
在某些架构上。特别是,去交错在 AMD 上仍然是最好的吗?它在 NVIDIA 上不再是问题,还是在桌面 NVIDIA GPU 上从来都不是问题?其他 IHV 又如何呢? …
我正在尝试计算各种游戏资产的顶点法线。我计算的法线用于“膨胀”模型(在真实模型后面绘制产生粗轮廓)。
我目前计算每张脸的法线并平均所有脸(堆栈溢出的其他几个问题建议这种方法)。但是,这不适用于像这样的尖角(相邻面的法线以橙色标记,我试图计算的法线以绿色标出)。

该物体看起来像一个小基座,我们正在查看左前角。有三个相邻的面(底面不可见;它的法线指向正下方)。
Blender 计算出一个很好的法线,它正好位于三个面的法线中间;似乎它以某种方式计算了一个法线,该法线对三个面法线中的每一个都具有最小旋转。当四边形以不同方式进行三角剖分时,Blender 的法线也不会改变。
平均面的法线给了我一个不同的法线,它在 Z 轴上略微向上(-0.45、-0.89、+0.08)。以这种方式给我的模型充气不会产生好的轮廓,因为轮廓的底面向上移动并且没有包围原始模型。
我试图查看 Blender 源代码,但找不到我要找的东西。如果有人可以指出 Blender 源代码中的算法,我也会接受。
我正在寻找一种很好的方法来渲染具有不同顶点布局的网格对象,而不需要花费很多精力(例如,为每个顶点布局定义渲染器类).您可以在下面看到一些不同顶点格式的示例.
enum EVertexFormat
{
    VERTEX_FORMAT_UNDEFINED = -1,
    VERTEX_FORMAT_P1 = 0,
    VERTEX_FORMAT_P1N1,
    VERTEX_FORMAT_P1N1UV,
    VERTEX_FORMAT_P1N1C1,
    VERTEX_FORMAT_P1N1UVC1,
};
// the simplest possible vertex -- position only 
struct SVertexP1
{
    math::Vector3D m_position;      // position of the vertex
};
struct SVertexP1N1
{
    math::Vector3D m_position;      // position of the vertex
    math::Vector3D m_normal;        // normal of the vertex
};
// a typical vertex format with position, vertex normal
// and one set of texture coordinates
struct SVertexP1N1UV
{
    math::Vector3D m_position;      // position of the vertex
    math::Vector3D m_normal; …我一直在寻找将属性与任意组的顶点相关联的方法,起初,实例化似乎是实现此目的的唯一方法,但是后来我迷迷糊糊地问了这个问题,这个答案指出:
但是,使用较新版本的OpenGL可能会设置某个顶点属性的缓冲区偏移增加的速率。有效地,这意味着给定顶点数组的数据在属性的缓冲区偏移量增加之前被复制到n个顶点。设置该除数的函数是glVertexBindingDivisor。
(强调我的)
在我看来,这似乎是答案在断言,我可以划分顶点数而不是实例数。但是,当我查看glVertexBindingDivisor的文档并将其与进行比较时,glVertexAttribDivisor它们似乎都指发生在实例而非顶点上的划分。例如,在glVertexBindingDivisor的文档中指出:
glVertexBindingDivisor和glVertexArrayBindingDivisor修改在单个draw命令中渲染图元的多个实例时通用顶点属性前进的速率。如果除数为零,则使用绑定到bindingindex的缓冲区的属性每个顶点前进一次。如果除数不为零,则属性在每个要渲染的顶点集的除数实例中前进一次。如果相应的除数值不为零,则将该属性称为实例。
(强调我的)
那么这两个功能之间的实际区别是什么?
对于每个属性,使用跨步顶点缓冲区与紧密打包缓冲区有什么优缺点?我的意思是例如:
步幅: xyzrgb xyzrgb xyzrgb
紧: xyzxyzxyz rgbrgbrgb
乍一看,您看起来很容易在使用跨步时更改大小,但是当您使用进行重新分配时,顶点缓冲区的内容将被删除glBufferData()。
对我来说,最好使用紧密模型,因为位置,颜色和texcoords可能来自本地内存中的不同数组,并且因为没有跨步缓冲区数据函数;您必须在上传之前将所有数组复制到交错缓冲区中,或者glBufferSubData()每个属性每个顶点使用一个(我猜这是一个糟糕的主意)。
似乎通常使用交错缓冲区(步幅)。这是为什么?我在这里想念什么吗?