问题
I’m converting some glsl / opengl code to Metal for a SceneKit app so it can be rendered by Metal. I have a SCNNode (solidNode) created with primitive type SCNGeometryPrimitiveTypePolygon, and a childNode (wireframeNode) created with primitive type SCNGeometryPrimitiveTypeLine. The child node has the same vertices, ie. is the same model, and is placed slightly closer to the camera/eye position to prevent z-fighting.
In OpenGL I simply used glLineWidth to draw lines more than 1px wide but it is not supported in Metal (and according to Apple won’t be in the future either as it’s not really a hardware function). Another non-supported feature that would solve my problem is a geometry shader.
Some additional info: The model (the node’s geometry) is modified in the app, both in the shader and by actually updating the vertex source and regenerating the SCNGeometry. So using a texture is not an option.
I currently build and draw the solid and the wireframe model as separate nodes. Obviously I would not mind drawing the wireframe in the shader on top of the faces of the solid node. However, I need to be able to specify one of three predefined colors for each edge individually. This complicates using one node because even though the vertices are not shared between faces, they can and often do still belong to edges of different colors (not if rendered with line primitives).
Vertex count is up to tens of thousands. Not having to generate the geometry for the wireframe node would save me some CPU time and memory I wouldn’t mind using on feeding the shader with enough info. Frame rate isn’t as major concern.
The polygons have an arbitrary number of edges ranging from 3 to possibly dozens. If they were all faces with 3 edges I think this method would work well: http://codeflow.org/entries/2012/aug/02/easy-wireframe-display-with-barycentric-coordinates/ and although maybe it can be extended to convex polygons, my polygons can be concave too.
I tried blurring the wireframe line primitives node with a CIFilter but it didnt play nice with Metal. Possibly using a separate SCNTechnique render pass and a custom blur shader for just the wireframe node could lead to the desired visual results but ideally I don’t have to use a separate node. Using SCNMaterial.fillMode as a basis for that is not applicable because it shows the edges of the the rendered triangles, not the polygon primitives.
The “next” vertex is known, i.e. I could pass a second list of verts in a semantic to have both the current vertex position and the position of the next vertex in the shader. I could use a float4/vec4 instead and use the 4th value to indicate if that next vertex has the same color (then it should draw the edge in the color of current vert, else the edge should be black).
Does that sound like a feasible approach, using a vertex and a fragment shader to draw the wireframe straight onto the solid node? If it does, how can I determine if a fragment is on a line of x pixels wide between v1 and v2?
More specifically, I think I need to define an imaginary line between the current vertex and the next, in screenspace, and then check the distance of a fragment towards that line, in screenspace, to determine if it’s part of the line, and then smooth it based on distance.
Edit: I guess that would work only with thick line primitives.
Code examples don’t have to be Metal, can be glsl as well. And I’m open for different approaches as well (except for creating a triangle strip/polygon for each edge).
回答1:
Considering I only have a few objects in screen, including one possibly very large, I decided to go with multipass rendering using SCNTechnique rather than adding more geometry to render lines.
Basically I render the wireframe to a texture, and read it in the next pass to determine if a fragment is near an edge. This is just the horizontal blur/glow pass to show the difference with the default 1px line.
Still needs some work, and 2224 is the currently hardcoded screenwidth, but it shows the basic principle.
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);
};
In the vertical pass the values of the float2() are reversed and the screen height is used.
Before and after:
来源:https://stackoverflow.com/questions/46912921/wider-and-smoother-lines-in-metal-shader