GLSL带有可变边框的圆角矩形

问题描述 投票:1回答:1

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

因此,我们将拥有:borderThicknessX0,borderThicknessX1,borderThicknessY0,borderThicknessY1

[我看过许多使用有符号距离字段(SDF)在着色器中创建圆角矩形的示例,但是还没有弄清楚如何使代码适应于允许可变的边界边缘厚度。

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

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

rounded rectangle with border

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

geometry glsl shader rectangles
1个回答
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 );

根据片段和片段到边界中心的距离(fHalfBorderThickness)计算fHalfBorderDist

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

    vec2 ellipse_ab    = u_fRadiusPx-v2edgeThickness;
    vec2 ellipse_isect = 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 > u_fRadiusPx && fragCoord.x < u_resolution.x - u_fRadiusPx)
    {
        fHalfBorderDist      = v2CenteredPos.y - v2HalfShapeSizePx.y;
        fHalfBorderThickness = v2edgeThickness.y / 2.0; 
    }
    else if (fragCoord.y > u_fRadiusPx && fragCoord.y < u_resolution.y - u_fRadiusPx)
    {
        fHalfBorderDist      = v2CenteredPos.x - v2HalfShapeSizePx.x;
        fHalfBorderThickness = v2edgeThickness.x / 2.0;
    }
    else
    {
        vec2 edgeVec = abs(vec2(
            uv.x > 0.5 ? iResolution.x-fragCoord.x : fragCoord.x,
            uv.y > 0.5 ? iResolution.y-fragCoord.y : fragCoord.y) - vec2(u_fRadiusPx));

        vec2 ellipse_ab    = u_fRadiusPx-v2edgeThickness;
        vec2 ellipse_isect = 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="50" value="20"/>
  </td> </tr>
  <tr> <td> bottom </td> 
      <td> <input type="range" id="bottom" min="1" max="50" value="30"/>
  </td> </tr>
  <tr> <td> left </td> 
      <td> <input type="range" id="left" min="1" max="50" value="25" />
  </td></tr>
  <tr> <td> right </td> 
      <td> <input type="range" id="right" min="1" max="50" value="35"/>
  </td> </tr>
  </table>
</form></div>

<canvas id="ogl-canvas" style="border: none"></canvas>
© www.soinside.com 2019 - 2024. All rights reserved.