[我正在尝试编写一个GLSL着色器,该着色器生成一个带边框的圆角矩形,类似于下面的示例,但是每个边框的边缘(顶部,底部,左侧,右侧)可以具有不同的厚度。
因此,我们将拥有:borderThicknessX0,borderThicknessX1,borderThicknessY0,borderThicknessY1
[我看过许多使用有符号距离字段(SDF)在着色器中创建圆角矩形的示例,但是还没有弄清楚如何使代码适应于允许可变的边界边缘厚度。
[似乎很多方法都涉及片段坐标的abs(),因此假设是均匀的。
任何人都可以提供有关如何实现这一目标的指导吗?谢谢!
示例:rounded rect with constant border例如:rounded rect with no border
与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>