金属着色器中的线条更宽更平滑
Posted
技术标签:
【中文标题】金属着色器中的线条更宽更平滑【英文标题】:Wider and smoother lines in Metal shader 【发布时间】:2018-04-05 09:25:05 【问题描述】:我正在为 SceneKit 应用程序将一些 glsl / opengl 代码转换为 Metal,以便它可以由 Metal 渲染。我有一个使用原始类型 SCNGeometryPrimitiveTypePolygon 创建的 SCNNode (solidNode),以及使用原始类型 SCNGeometryPrimitiveTypeLine 创建的 childNode (wireframeNode)。子节点具有相同的顶点,即。是同一型号,并放置在稍微靠近相机/眼睛位置以防止 z-fighting。
在 OpenGL 中,我简单地使用 glLineWidth 来绘制超过 1 像素宽的线条,但 Metal 不支持它(根据 Apple 的说法,未来也不会支持,因为它不是真正的硬件功能)。另一个可以解决我的问题的不受支持的功能是几何着色器。
一些附加信息: 模型(节点的几何图形)在应用程序中进行修改,包括在着色器中以及通过实际更新顶点源并重新生成 SCNGeometry。所以使用纹理不是一种选择。
我目前将实体和线框模型构建和绘制为单独的节点。显然,我不介意在实体节点面顶部的着色器中绘制线框。但是,我需要能够为每个边缘单独指定三种预定义颜色中的一种。这使得使用一个节点变得复杂,因为即使顶点不在面之间共享,它们也可以并且通常仍然属于不同颜色的边(如果使用线图元渲染则不会)。
顶点数高达数万。不必为线框节点生成几何图形将为我节省一些 CPU 时间和内存,我不介意在为着色器提供足够信息时使用。帧速率不是主要问题。
多边形有任意数量的边,范围从 3 到可能几十个。如果它们都是具有 3 条边的面,我认为这种方法会很好用:http://codeflow.org/entries/2012/aug/02/easy-wireframe-display-with-barycentric-coordinates/,虽然它可能可以扩展到凸多边形,但我的多边形也可以是凹的。
我尝试使用 CIFilter 模糊线框线图元节点,但它与 Metal 配合得不好。可能只为线框节点使用单独的 SCNTechnique 渲染通道和自定义模糊着色器可能会产生所需的视觉效果,但理想情况下我不必使用单独的节点。使用 SCNMaterial.fillMode 作为基础是不适用的,因为它显示的是渲染三角形的边缘,而不是多边形图元。
“下一个”顶点是已知的,即我可以在语义中传递第二个顶点列表,以获得当前顶点位置和着色器中下一个顶点的位置。我可以改用 float4/vec4 并使用第 4 个值来指示下一个顶点是否具有相同的颜色(然后它应该以当前顶点的颜色绘制边缘,否则边缘应该是黑色)。
这听起来像是一种可行的方法吗,使用顶点和片段着色器将线框直接绘制到实体节点上?如果是这样,我如何确定片段是否在 v1 和 v2 之间的 x 像素宽的线上?
更具体地说,我认为我需要在屏幕空间中定义当前顶点和下一个顶点之间的假想线,然后在屏幕空间中检查片段与该线的距离,以确定它是否是该线的一部分,然后根据距离进行平滑处理。
编辑:我猜这仅适用于粗线基元。
代码示例不必是 Metal,也可以是 glsl。我也对不同的方法持开放态度(除了为每条边创建一个三角形带/多边形)。
【问题讨论】:
【参考方案1】:考虑到我在屏幕上只有几个对象,包括一个可能非常大的对象,我决定使用 SCNTechnique 进行多通道渲染,而不是添加更多几何体来渲染线条。
基本上,我将线框渲染为纹理,并在下一轮读取它以确定片段是否靠近边缘。这只是水平模糊/发光通道,以显示与默认 1px 线的差异。
还有一些工作要做,2224是当前硬编码的屏幕宽度,但它显示了基本原理。
fragment half4 blur_grid(out_vertex_t vert [[stage_in]],
texture2d<float, access::sample> gridNodeO [[texture(0)]])
float4 fragment_color_3 = gridNodeO.sample( s, vert.uv);
if (fragment_color_3.w < 0.01)
fragment_color_3 += gridNodeO.sample( s, ( vert.uv + float2(0.5, 0.0)/2224 ) );
fragment_color_3 += gridNodeO.sample( s, ( vert.uv - float2(0.5, 0.0)/2224 ) );
fragment_color_3 += gridNodeO.sample( s, ( vert.uv + float2(1.5, 0.0)/2224 ) ) /2;
fragment_color_3 += gridNodeO.sample( s, ( vert.uv - float2(1.5, 0.0)/2224 ) ) /2;
fragment_color_3 += gridNodeO.sample( s, ( vert.uv + float2(2.0, 0.0)/2224 ) ) /4;
fragment_color_3 += gridNodeO.sample( s, ( vert.uv - float2(2.0, 0.0)/2224 ) ) /4;
return half4(fragment_color_3);
;
在垂直传递中,float2() 的值被反转并使用屏幕高度。
之前和之后:
【讨论】:
以上是关于金属着色器中的线条更宽更平滑的主要内容,如果未能解决你的问题,请参考以下文章