首页 文章

金属着色器中更宽,更平滑的线条

提问于
浏览
1

我正在为SceneKit应用程序将一些glsl / opengl代码转换为Metal,因此它可以由Metal渲染 . 我有一个用原始类型SCNGeometryPrimitiveTypePolygon创建的SCNNode(solidNode)和一个用原始类型SCNGeometryPrimitiveTypeLine创建的childNode(wireframeNode) . 子节点具有相同的顶点,即 . 是相同的型号,并稍微靠近相机/眼睛位置,以防止z战斗 .

在OpenGL中,我只是使用glLineWidth来绘制宽度超过1px的行,但是在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个值来指示下一个顶点是否具有相同的颜色(然后它应该以当前vert的颜色绘制边缘,否则边缘应该是黑色) .

这听起来像是一种可行的方法,使用顶点和片段着色器将线框直接绘制到实体节点上吗?如果是,我怎样才能确定片段是否在v1和v2之间的x像素宽的行上?

更具体地说,我想我需要在屏幕空间中定义当前顶点和下一个虚拟线之间的虚线,然后在屏幕空间中检查片段到该线的距离,以确定它是否是线的一部分,然后平滑它基于距离 .

编辑:我想这只适用于粗线基元 .

代码示例不一定是Metal,也可以是glsl . 而且我也可以使用不同的方法(除了为每个边创建一个三角形条/多边形) .

1 回答

  • 1

    考虑到我在屏幕上只有一些对象,包括一个可能非常大的对象,我决定使用SCNTechnique进行多通道渲染,而不是添加更多几何体来渲染线条 .

    基本上我将线框渲染为纹理,并在下一遍中读取它以确定片段是否靠近边缘 . 这只是水平模糊/辉光传递,以显示与默认1px线的差异 .

    enter image description here

    仍然需要一些工作,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()的值反转并使用屏幕高度 .

    之前和之后:
    before

    after

相关问题