在片段着色器中绘制线条粗细是否可行?

29

考虑到我使用GL_LINES绘制线条,在片段着色器中是否可能添加线条粗细?我看到的大多数示例似乎只访问基元内部的纹素,在片段着色器中需要编写一个线条粗细着色器来获取厚度,并且该着色器需要向线条基元外部的纹素写入数据。如果可能,提供一个非常小而基本的示例会很好。


尽管有一个被接受的答案,但更实际的答案是“你不想这样做”(在片段着色器中)- 这不是一种能够很好地扩展到许多行的技术。请参见itjak的答案 - ToolmakerSteve
5个回答

30

片段着色器有很多可能性。只需看看一些人正在做什么。我自己离那个水平还很远,但这段代码可以给你一个想法:

#define resolution vec2(500.0, 500.0)
#define Thickness 0.003

float drawLine(vec2 p1, vec2 p2) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;

  float a = abs(distance(p1, uv));
  float b = abs(distance(p2, uv));
  float c = abs(distance(p1, p2));

  if ( a >= c || b >=  c ) return 0.0;

  float p = (a + b + c) * 0.5;

  // median to (p1, p2) vector
  float h = 2 / c * sqrt( p * ( p - a) * ( p - b) * ( p - c));

  return mix(1.0, 0.0, smoothstep(0.5 * Thickness, 1.5 * Thickness, h));
}

void main()
{
  gl_FragColor = vec4(
      max(
        max(
          drawLine(vec2(0.1, 0.1), vec2(0.1, 0.9)),
          drawLine(vec2(0.1, 0.9), vec2(0.7, 0.5))),
        drawLine(vec2(0.1, 0.1), vec2(0.7, 0.5))));
}

另一种选择是使用texture2D检查附近像素的颜色 - 这样您可以使图像发光或变厚(例如,如果任何调整像素为白色 - 使当前像素为白色,如果靠近附近像素的下一个像素为白色 - 使当前像素为灰色)。


哇,那个网站真是太棒了! - Meda
16
尽管这种方法“可行”,但需要注意的是,这可能是世界上最低效的画三条线的方法。在混合过程中,被处理的99%的片段都会被抛弃。 - Damon
1
+Damon 确信这并不是最佳实践,而我的示例有些愚蠢;但只是为了演示着色器的原理和可能性 - 类似的方式可以做各种发光或粒子效果,或者从高度图中渲染整个地形,或者从距离场中生成复杂的几何分形,而不使用单个顶点。令人惊讶的是,着色器可以非常快速。 - defhlt
1
  • Damon,顺便说一句,比这更低效的方法是使用CPU计算像素,尽管这种方法被广泛使用。
- defhlt
1
我赞同Damon所说的。使用几何着色器可能比这种方法快1000倍以上,并且允许您在需要时使用普通片段着色器来应用设计到线条上。我同意这个答案完全解决了问题,但也许不符合精神要求。 - Elliot Woods
注意:这种方法不适用于大规模应用。如果使用CPU计算像素,效果会比这个好得多,尤其是在处理多行时。这不是任何人都应该复制的技术。另一方面,问题本身就存在根本性缺陷——这是如何做某件几乎肯定不想做的事情的有效演示。 - ToolmakerSteve

6
不,仅使用GL_LINES在片段着色器中不可能实现。这是因为GL限制您只能在提交给光栅化器的几何图形上绘制,因此您需要使用包含锯齿状原始线条和任何平滑顶点的几何图形。例如,您可以使用几何着色器将您的线扩展到理想线周围的四边形(或者实际上是两个三角形),这可以作为粗线。
通常,如果生成更大的几何图形(包括全屏幕四边形),则可以使用片段着色器绘制平滑线条。 这里有一个关于该主题的很好的讨论(带有代码示例)。

因此,在片段着色器中,您只能访问/修改绘制基元中的片段?那是有道理的。至于您提出的解决方案,感谢但在OpenGL ES中我无法使用几何着色器 :( - Meda
不是访问 - 你可以从任何源读取,但你只能写入一个位置,这个位置由光栅化器给出。如果你没有几何着色器,你可以在 CPU 上使用相同的方法。 - ltjax
5
除非你能证明它,否则不要说不。 - GottZ
1
@GottZ:好吧,我是根据问题中的条件进行回答的,即使用GL_LINES绘制线条。当然,如果您渲染全屏四边形,您可以在片段着色器中绘制任何内容。但这不是问题所要求的。 - ltjax
听起来有些傻,但是是的,我指的是一个全屏片段着色器,在其中你知道分辨率(在着色器内部)。为什么这么说?http://i.imgur.com/dw6y7Ko.png 因为我做到了。(是的,你在那里看到的只是两个多边形)。虽然这主要是一个原始的GLSL概念证明加上一些C++ WinAPI魔法。我需要像素大小信息才能获得完美的抗锯齿效果,而无需后期处理。 - GottZ
谢谢提供链接。 - Robinson

5
这是我的方法。设p1和p2为定义该线的两个点,point为希望测量到该线距离的点。Point很可能是gl_FragCoord.xy / resolution;
以下是该函数。
float distanceToLine(vec2 p1, vec2 p2, vec2 point) {
    float a = p1.y-p2.y;
    float b = p2.x-p1.x;
    return abs(a*point.x+b*point.y+p1.x*p2.y-p2.x*p1.y) / sqrt(a*a+b*b);
}

然后在您的混合和平滑步进函数中使用它。此外,请查看此答案:https://stackoverflow.com/a/9246451/911207

0
一个简单的技巧是在顶点着色器中添加抖动: gl_Position += vec4(delta, delta, delta, 0.0); 其中delta是像素大小,即1.0/viewsize。
使用零和delta作为抖动(作为统一变量传递)进行两次线绘制。

0
在片段着色器中绘制一条线,我们应该检查当前像素(UV)是否在线的位置上。(仅使用片段着色器代码效率不高!这只是用于glslsandbox测试) 一个可接受的UV点应满足以下两个条件:
1- (uv, pt1)之间的最大允许距离应小于(pt1, pt2)之间的距离。 通过此条件,我们创建了一个假设的圆,其圆心为pt2,半径= distance(pt2, pt1),并防止绘制超过distance(pt2, pt1)的线。
2- 对于每个UV,我们假设一个带有连接点的假想圆位于线(pt2,pt1)的ptc位置。 如果UV与PTC之间的距离小于线宽,则选择此UV作为线的点。
在我们的代码中: r = distance(uv,pt1) / distance(pt1,pt2) 给出0到1之间的值。 我们使用r的值在pt1和pt2之间插值出一个点(ptc)。
代码:
#ifdef GL_ES
precision mediump float;
#endif

uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;

float line(vec2 uv, vec2 pt1, vec2 pt2,vec2 resolution)
{
    
    float clrFactor = 0.0;
    float tickness = 3.0 / max(resolution.x, resolution.y);  //only used for tickness
    
    float r  = distance(uv, pt1) / distance(pt1, pt2);
    
    if(r <= 1.0) // if desired Hypothetical circle in range of vector(pt2,pt1)
    {
        vec2 ptc = mix(pt1, pt2, r); // ptc = connection point of Hypothetical circle and line calculated with interpolation
        float dist = distance(ptc, uv);  // distance betwenn current pixel (uv) and ptc
        if(dist < tickness / 2.0)
        {
            clrFactor = 1.0;
        }
    }
    return clrFactor;
}



void main()
{
    vec2 uv = gl_FragCoord.xy / resolution.xy; //current point
    //uv = current pixel
    //      0 < uv.x < 1 , 0 < uv.x < 1
    //      left-down= (0,0)
    //      right-top= (1,1)
    
    vec2 pt1 = vec2(0.1, 0.1);  //line point1 
    vec2 pt2 = vec2(0.8, 0.7);  //line point2 
       
    
    float lineFactor = line(uv, pt1, pt2, resolution.xy);
    vec3 color = vec3(.5, 0.7 , 1.0);
    
    gl_FragColor = vec4(color * lineFactor , 1.);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接