2

我正在使用 OpenGL ES 2.0(在 Android 上)来绘制简单的 2D 场景,图像很少。我有背景图像和其他一些具有 alpha 通道的图像。

我想仅使用着色器程序在纹理中的不透明像素周围绘制轮廓。经过一番广泛的搜索后,我找不到示例代码。看起来 GLES 2.0 还没有那么流行。

您能否提供一些示例代码或指出正确的方向,我可以在其中找到有关如何执行此操作的更多信息?

4

3 回答 3

3

There are a couple of ways of doing this depending on the a) Qaulity, and b) Speed you need. The common search terms are:

  • "glow outline"
  • "bloom"
  • "toon shader" or "toon shading"
  • "edge detection"
  • "silhouette extraction"
  • "mask"

1) The traditional approach is to use the stencil buffer and render to texture

  • Clear the stencil buffer (usually done once per frame)

    glClear( GL_COLOR_BUFFER_BIT | DEPTH_BUFFER_BIT | STENCIL_BUFFER_BIT )
    
  • Render to Texture

  • Disable Depth Writes

    glDepthMask( 1 );
    
  • Disable Color Buffer Writes

    glColorMask( 0, 0, 0, 0 );
    
  • Enable the Stencil buffer Set stencil to always pass and replace

    glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );
    glStencilFunc( GL_ALWAYS, 1, 1 );
    
  • Draw object into texture

  • Disable stencil
  • Enable Color Buffer Writes
  • Enable Depth Writes
  • Do a N-pass "tap", such as 5 or 7 pass tap where you blur the texture via rendering to itself in both the vertical and horizontal direction (another option is to scale drawing the texture image up)
  • Switch to orthographic projection
  • Draw & Blend the texture image back into the framebuffer
  • Restore perspective projection

2) Pass along extra vertex, namely which vertices are adjacent in the proper winding order, and dynamically generate extra outline triangles.

See: http://www.gamasutra.com/view/feature/1644/sponsored_feature_inking_the_.php?print=1

3) Use cheap edge detection. In the vertex shader check the dot product of the normal with the view. If it is between:

-epsilon < 0 < epsilon

Then you have an edge.

4) Use cheap-o-rama object scaling. It doesn't work for concave objects of course but depending on your quality needs may be "good enough"

  • Switch to a "flat" shader
  • Enable Alpha Testing
  • Draw the model scaled up slightly
  • Disable Alpha Testing
  • Draw the model but at the normal size

References:

Related SO questions:

于 2013-10-29T15:10:48.547 回答
2

To get the pixel shader drawing something, there needs to be geometry. As far as I understand, you want to draw a border around these images, but the outermost fragments generated would be image pixels in a basic implementation, so you'd overdraw them with any border.

If you want a 'line border', you cannot do anything else than drawing the image triangles/quads (GL_TRIANGLES,GL_QUADS), and in an additional call the outline (using GL_LINES), where you may share the vertices of a single quad. Consider, that lines can't be drawn efficiently by many GPU's)

Otherwise, see below solutions:

Solution 1:

  • Draw the rectangle as big as the image + border will be and adjust texture coords for the image, so that it will be placed within the rectangle appropriately. This way, no extra geometry or draw calls are required.
  • Set the texture border property (single 4 component color), there will be no need to do extra fragment shader calculations, the texture unit/sampler does all the work.

Texture properties:

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_BORDER)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_BORDER)
glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR,borderColor4f)

I've never used a color border for a single channel texture, so this approach needs to be verified.

Solution 2:

Similar to 1, but with calculations in the fragment shader to check, whether the texture coords are within the border area, instead of the texture border. Without modification, the scalars of a texture coord range from 0.0 to 1.0.

Texture properties may be:

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP)

The fragment color could be determined by any of these methods:

  • an additional border color attribute for the rectangle, where either the texel or that border color is selected then (could be a vertex attribute, but more likely an uniform or constant).

  • combination of the alpha texture with a second texture as background for the whole rectangle (like a picture frame) and here too, either texel is choosen.

  • some other math function

Of course, the color values could be mixed for image/border gradients.

EDIT:

As the number, length and position of such outline segments will vary and can even form concave shapes, you'd need to do this with a geometry shader, which is not available in ES 2.0 core. The best thing you can do is to precompute a line loop for each image on the CPU. Doing such tests in a shader is rather inefficient and even overkill, depending on image size, the hardware you actually run it on etc. If you'd draw a fixed amount of line segments and transform them using the vertex shader, you can not properly cover all cases, at least not without immense effort and GPU workload.

Should you intend to change the color values of corresponding texels, your fragment shader would need to fetch a massive and varying amount of texels for each neighbour pixel towards the texture edges as in all other implementations. Such brute force techniques are usually a replacement for recursive and iterative algos, for which the CPU is a better choice. So I suggest that you do it there by either modifying the texture or generate a second one for combination in the fragment shader.

Basically, you need to implement a path finding algo, which tries to 'get around' opaque pixels towards any edge.

于 2012-12-04T11:39:10.930 回答
0

您的 Alpha 通道可以看作是灰度图像。寻找任何边缘检测/绘图算法。例如 Canny 边缘检测器 (http://en.wikipedia.org/wiki/Canny_edge_detector)。或者,如果您的图像不是程序化的,则可能更好的主意是预先计算边缘。

如果您的目标是混合各种图像,然后根据混合结果应用轮廓,请尝试渲染到纹理,然后在屏幕上再次渲染该纹理并执行边缘检测算法。

于 2012-12-04T20:05:30.820 回答