【问题标题】:GLSL Rounded Rectangle with Variable Border具有可变边框的 GLSL 圆角矩形
【发布时间】:2019-12-05 14:49:13
【问题描述】:

我正在尝试编写一个 GLSL 着色器,它生成一个带边框的圆角矩形,类似于下面的示例,但每个边框边缘(顶部、底部、左侧、右侧)可以有不同的厚度。

因此,我们将有:而不是统一的边框厚度: 边界厚度X0,边界厚度X1,边界厚度Y0,边界厚度Y1

我查看了许多使用有符号距离场 (SDF) 在着色器中创建圆角矩形的示例,但还没有弄清楚如何调整代码以允许可变边框边缘厚度。

似乎很多方法都涉及片段坐标的 abs(),因此假设是均匀的。

任何人都可以就如何实现这一目标提供任何指导吗? 谢谢!

示例:rounded rect with constant border 例如:rounded rect with no border

【问题讨论】:

  • 您是否尝试过使矩形以 0,0 为中心并制作每个部分 (x,y 然后 -x,y 然后 -x,-y...) 不同?你可以轻松做到这一点
  • 感谢您的回复。您能否详细说明“制作每个部分(x,y 然后 -x,y 然后 -x,-y...)”,不确定我是否理解您所描述的内容。

标签: geometry glsl shader rectangles


【解决方案1】:

与 shadertoy 着色器 (rounded rect with constant border) 相比,您必须根据片段计算 u_fHalfBorderThickness

定义左侧、右侧、底部和顶部的厚度:

float u_ThicknessTop    = 20.0;
float u_ThicknessBottom = 30.0;
float u_ThicknessLeft   = 25.0;
float u_ThicknessRight  = 35.0;

根据截面计算边缘的厚度:

vec2 uv = fragCoord / iResolution;
vec2 edgeThickness = vec2(
    uv.x > 0.5 ? u_ThicknessRight : u_ThicknessLeft,
    uv.y > 0.5 ? u_ThicknessTop : u_ThicknessBottom );

计算依赖于fragment的fHalfBorderThickness以及fragment到边框中心的距离(fHalfBorderDist):

float fHalfBorderDist      = 0.0;
float fHalfBorderThickness = 0.0;
if (fragCoord.x > max(u_fRadiusPx, u_ThicknessLeft) && 
    fragCoord.x < u_resolution.x - max(u_fRadiusPx, u_ThicknessRight))
{
    fHalfBorderDist      = v2CenteredPos.y - v2HalfShapeSizePx.y;
    fHalfBorderThickness = v2edgeThickness.y / 2.0; 
}
else if (fragCoord.y > max(u_fRadiusPx, u_ThicknessBottom) && 
            fragCoord.y < u_resolution.y - max(u_fRadiusPx, u_ThicknessTop))
{
    fHalfBorderDist      = v2CenteredPos.x - v2HalfShapeSizePx.x;
    fHalfBorderThickness = v2edgeThickness.x / 2.0;
}
else
{
    vec2 edgeVec = max(vec2(0.0), u_fRadiusPx - vec2(
        uv.x > 0.5 ? iResolution.x-fragCoord.x : fragCoord.x,
        uv.y > 0.5 ? iResolution.y-fragCoord.y : fragCoord.y));

    vec2 ellipse_ab    = u_fRadiusPx-v2edgeThickness;
    vec2 ellipse_isect = (v2edgeThickness.x > u_fRadiusPx || v2edgeThickness.y > u_fRadiusPx) ? vec2(0.0) :
                            edgeVec.xy * ellipse_ab.x*ellipse_ab.y / length(ellipse_ab*edgeVec.yx); 

    fHalfBorderThickness = (u_fRadiusPx - length(ellipse_isect)) / 2.0;
    fHalfBorderDist      = length(edgeVec) - (u_fRadiusPx - fHalfBorderThickness);
}

注意,边框的内圆是椭圆。要验证一个点是否在边界上,您必须将圆角中心点到当前片段的线与椭圆相交。见Ellipse-Line Intersection

vec2 ellipse_ab    = u_fRadiusPx-v2edgeThickness;
vec2 ellipse_isect = edgeVec.xy * ellipse_ab.x*ellipse_ab.y / length(ellipse_ab*edgeVec.yx); 

看例子:

(function loadscene() {    

var canvas, gl, vp_size, prog, bufObj = {};

function initScene() {

    canvas = document.getElementById( "ogl-canvas");
    gl = canvas.getContext( "experimental-webgl" );
    if ( !gl )
      return;

    progDraw = gl.createProgram();
    for (let i = 0; i < 2; ++i) {
        let source = document.getElementById(i==0 ? "draw-shader-vs" : "draw-shader-fs").text;
        let shaderObj = gl.createShader(i==0 ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);
        gl.shaderSource(shaderObj, source);
        gl.compileShader(shaderObj);
        let status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
        if (!status) alert(gl.getShaderInfoLog(shaderObj));
        gl.attachShader(progDraw, shaderObj);
        gl.linkProgram(progDraw);
    }
    status = gl.getProgramParameter(progDraw, gl.LINK_STATUS);
    if ( !status ) alert(gl.getProgramInfoLog(progDraw));
    progDraw.inPos = gl.getAttribLocation(progDraw, "inPos");
    progDraw.u_resolution = gl.getUniformLocation(progDraw, "u_resolution");
    progDraw.u_ThicknessTop = gl.getUniformLocation(progDraw, "u_ThicknessTop");
    progDraw.u_ThicknessBottom = gl.getUniformLocation(progDraw, "u_ThicknessBottom");
    progDraw.u_ThicknessLeft = gl.getUniformLocation(progDraw, "u_ThicknessLeft");
    progDraw.u_ThicknessRight = gl.getUniformLocation(progDraw, "u_ThicknessRight");
    gl.useProgram(progDraw);

    var pos = [ -1, -1, 1, -1, 1, 1, -1, 1 ];
    var inx = [ 0, 1, 2, 0, 2, 3 ];
    bufObj.pos = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
    gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( pos ), gl.STATIC_DRAW );
    bufObj.inx = gl.createBuffer();
    bufObj.inx.len = inx.length;
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
    gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( inx ), gl.STATIC_DRAW );
    gl.enableVertexAttribArray( progDraw.inPos );
    gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 ); 
    
    gl.enable( gl.DEPTH_TEST );
    gl.clearColor( 0.0, 0.0, 0.0, 1.0 );

    window.onresize = resize;
    resize();
    requestAnimationFrame(render);
}

function resize() {
    //vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
    vp_size = [window.innerWidth, window.innerHeight];
    //vp_size = [256, 256]
    canvas.width = vp_size[0];
    canvas.height = vp_size[1];
}

function render(deltaMS) {

    var top = document.getElementById("top").value;
    var bottom = document.getElementById("bottom").value;
    var left = document.getElementById("left").value;
    var right = document.getElementById("right").value;

    gl.viewport( 0, 0, canvas.width, canvas.height );
    gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
   
    gl.uniform2f(progDraw.u_resolution, canvas.width, canvas.height);
    gl.uniform1f(progDraw.u_ThicknessTop, top);
    gl.uniform1f(progDraw.u_ThicknessBottom, bottom);
    gl.uniform1f(progDraw.u_ThicknessLeft, left);
    gl.uniform1f(progDraw.u_ThicknessRight, right);
    gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
    
    requestAnimationFrame(render);
}  

initScene();

})();
#gui { position : absolute; top : 0; left : 0; font-size : large; }
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;

attribute vec2 inPos;

void main()
{
    //ndcPos = inPos;
    gl_Position = vec4( inPos.xy, 0.0, 1.0 );
}
</script>

<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;

//varying vec2 ndcPos;  // normaliced device coordinates in range [-1.0, 1.0]
uniform float u_time;
uniform vec2 u_resolution;

uniform float u_ThicknessTop;
uniform float u_ThicknessBottom;
uniform float u_ThicknessLeft;
uniform float u_ThicknessRight;

const vec4 u_v4BorderColor = vec4(1.0, 1.0, 0.0, 1.0);
const vec4 u_v4FillColor   = vec4(1.0, 0.0, 0.0, 1.0);
const float u_fRadiusPx    = 50.0;

void main()
{
    vec2 iResolution = u_resolution;
    vec2 fragCoord   = gl_FragCoord.xy;
   
    vec2 uv = fragCoord / iResolution;
    vec2 v2edgeThickness = vec2(
        uv.x > 0.5 ? u_ThicknessRight : u_ThicknessLeft,
        uv.y > 0.5 ? u_ThicknessTop : u_ThicknessBottom );
    
    vec2 v2CenteredPos     = abs(fragCoord - iResolution.xy / 2.0);
    vec2 v2HalfShapeSizePx = iResolution/2.0 - v2edgeThickness/2.0;    

    float fHalfBorderDist      = 0.0;
    float fHalfBorderThickness = 0.0;
    if (fragCoord.x > max(u_fRadiusPx, u_ThicknessLeft) && 
        fragCoord.x < u_resolution.x - max(u_fRadiusPx, u_ThicknessRight))
    {
        fHalfBorderDist      = v2CenteredPos.y - v2HalfShapeSizePx.y;
        fHalfBorderThickness = v2edgeThickness.y / 2.0; 
    }
    else if (fragCoord.y > max(u_fRadiusPx, u_ThicknessBottom) && 
             fragCoord.y < u_resolution.y - max(u_fRadiusPx, u_ThicknessTop))
    {
        fHalfBorderDist      = v2CenteredPos.x - v2HalfShapeSizePx.x;
        fHalfBorderThickness = v2edgeThickness.x / 2.0;
    }
    else
    {
        vec2 edgeVec = max(vec2(0.0), u_fRadiusPx - vec2(
            uv.x > 0.5 ? iResolution.x-fragCoord.x : fragCoord.x,
            uv.y > 0.5 ? iResolution.y-fragCoord.y : fragCoord.y));
        
        vec2 ellipse_ab    = u_fRadiusPx-v2edgeThickness;
        vec2 ellipse_isect = (v2edgeThickness.x > u_fRadiusPx || v2edgeThickness.y > u_fRadiusPx) ? vec2(0.0) :
                                edgeVec.xy * ellipse_ab.x*ellipse_ab.y / length(ellipse_ab*edgeVec.yx); 
            
        fHalfBorderThickness = (u_fRadiusPx - length(ellipse_isect)) / 2.0;
        fHalfBorderDist      = length(edgeVec) - (u_fRadiusPx - fHalfBorderThickness);
    }

    vec4 v4FromColor = u_v4BorderColor; 
    vec4 v4ToColor   = vec4(0.0, 0.0, 1.0, 1.0);
    if (fHalfBorderDist < 0.0)
        v4ToColor = u_v4FillColor;
    
    gl_FragColor = mix(v4FromColor, v4ToColor, abs(fHalfBorderDist) - fHalfBorderThickness);
}
</script>

<div><form id="gui" name="inputs">
  <table>
  <tr> <td> top </td> 
      <td> <input type="range" id="top" min="1" max="100" value="20"/>
  </td> </tr>
  <tr> <td> bottom </td> 
      <td> <input type="range" id="bottom" min="1" max="100" value="30"/>
  </td> </tr>
  <tr> <td> left </td> 
      <td> <input type="range" id="left" min="1" max="100" value="25" />
  </td></tr>
  <tr> <td> right </td> 
      <td> <input type="range" id="right" min="1" max="100" value="35"/>
  </td> </tr>
  </table>
</form></div>

<canvas id="ogl-canvas" style="border: none"></canvas>

【讨论】:

  • 非常感谢您的详尽回答。着色器代码中似乎存在错误。见pasteboard.co/IK0lXhv.png 配置:u_resolution = vec2(100.0,100.0); u_fRadiusPx = 20.0; u_ThicknessTop = 12.0; u_ThicknessBottom = 18.0; u_ThicknessLeft = 6.0; u_ThicknessRight = 20.0;
  • 哦,我明白了。非常感谢,期待!
  • 感谢@Rabbid76,我认为当 u_fRadiusPx 小于任何组件边缘厚度时仍然存在问题。
  • 嗯。如果 u_fRadiusPx == 0 且边框 > 0,则矩形内会有一个矩形。 pasteboard.co/IK2Lysl.png
猜你喜欢
  • 1970-01-01
  • 2011-02-03
  • 1970-01-01
  • 2010-10-12
  • 2019-11-17
  • 2021-09-15
  • 1970-01-01
  • 2016-09-28
  • 2013-11-03
相关资源
最近更新 更多