GLSL 圆角矩形角被拉伸
GLSL Rounded Rectangle Corners Are Stretched
我正在用 openGL 编写一个 GUI 库,并决定添加圆角,因为我觉得它可以使单元看起来更专业。
我已经实施了常见的
length(max(abs(p) - b, 0.0)) - radius
方法,它几乎完美地工作,除了角落看起来好像被拉伸的事实:
我的片段着色器:
in vec2 passTexCoords;
uniform vec4 color;
uniform int width;
uniform int height;
uniform int radius;
void main() {
fragment = color;
vec2 pos = (abs(passTexCoords - 0.5) + 0.5) * vec2(width, height);
float alpha = 1.0 - clamp(length(max(pos - (vec2(width, height) - radius), 0.0)) - radius, 0.0, 1.0);
fragment.a = alpha;
}
拉伸对我来说确实有意义,但是当我替换为
vec2 pos = (abs(passTexCoords - 0.5) + 0.5) * vec2(width, height) * vec2(scaleX, scaleY);
和
float alpha = 1.0 - clamp(length(max(pos - (vec2(width, height) * vec2(scaleX, scaleY) - radius), 0.0)) - radius, 0.0, 1.0);
(其中 scaleX 和 scaleY 是介于 0.0 和 1.0 之间的标量,表示矩形相对于屏幕的宽度和高度)矩形几乎完全消失:
我假设 passTexCoords
是 [0, 1] 范围内的纹理坐标。而那个width
和height
就是屏幕的大小。而scaleX
和scaleY
是绿色区域占屏幕大小的比例。
以像素为单位计算当前片段相对于绿色区域中心的绝对位置(pos
):
vec2 pos = (abs(passTexCoords - 0.5) + 0.5) * vec2(width*scaleX, height*scaleY);
计算圆弧中心点到当前片段的距离:
vec2 arc_cpt_vec = max(pos - vec2(width*scaleX, height*scaleY) + radius, 0.0);
如果向量的长度大于半径,则必须跳过片段:
float alpha = length(arc_cpt_vec) > radius ? 0.0 : 1.0;
问题是距离没有按比例缩放到屏幕 space 中,因此在最大的 window 轴上被拉伸。如果将归一化位置乘以屏幕的纵横比以及框的其他参数,则可以解决此问题。我在 Shadertoy 上写了一个例子:
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Input info
vec2 boxPos; // The position of the center of the box (in normalized coordinates)
vec2 boxBnd; // The half-bounds (radii) of the box (in normalzied coordinates)
float radius;// Radius
boxPos = vec2(0.5, 0.5); // center of the screen
boxBnd = vec2(0.25, 0.25); // half of the area
radius = 0.1;
// Normalize the pixel coordinates (this is "passTexCoords" in your case)
vec2 uv = fragCoord/iResolution.xy;
// (Note: iResolution.xy holds the x and y dimensions of the window in pixels)
vec2 aspectRatio = vec2(iResolution.x/iResolution.y, 1.0);
// In order to make sure visual distances are preserved, we multiply everything by aspectRatio
uv *= aspectRatio;
boxPos *= aspectRatio;
boxBnd *= aspectRatio;
// Time varying pixel color
vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
// Output to screen
float alpha = length(max(abs(uv - boxPos) - boxBnd, 0.0)) - radius;
// Shadertoy doesn't have an alpha in this case
if(alpha <= 0.0){
fragColor = vec4(col,1.0);
}else{
fragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
}
可能有一种计算成本较低的方法可以做到这一点,但这是我想出的一个简单的解决方案。
我正在用 openGL 编写一个 GUI 库,并决定添加圆角,因为我觉得它可以使单元看起来更专业。 我已经实施了常见的
length(max(abs(p) - b, 0.0)) - radius
方法,它几乎完美地工作,除了角落看起来好像被拉伸的事实:
我的片段着色器:
in vec2 passTexCoords;
uniform vec4 color;
uniform int width;
uniform int height;
uniform int radius;
void main() {
fragment = color;
vec2 pos = (abs(passTexCoords - 0.5) + 0.5) * vec2(width, height);
float alpha = 1.0 - clamp(length(max(pos - (vec2(width, height) - radius), 0.0)) - radius, 0.0, 1.0);
fragment.a = alpha;
}
拉伸对我来说确实有意义,但是当我替换为
vec2 pos = (abs(passTexCoords - 0.5) + 0.5) * vec2(width, height) * vec2(scaleX, scaleY);
和
float alpha = 1.0 - clamp(length(max(pos - (vec2(width, height) * vec2(scaleX, scaleY) - radius), 0.0)) - radius, 0.0, 1.0);
(其中 scaleX 和 scaleY 是介于 0.0 和 1.0 之间的标量,表示矩形相对于屏幕的宽度和高度)矩形几乎完全消失:
我假设 passTexCoords
是 [0, 1] 范围内的纹理坐标。而那个width
和height
就是屏幕的大小。而scaleX
和scaleY
是绿色区域占屏幕大小的比例。
以像素为单位计算当前片段相对于绿色区域中心的绝对位置(pos
):
vec2 pos = (abs(passTexCoords - 0.5) + 0.5) * vec2(width*scaleX, height*scaleY);
计算圆弧中心点到当前片段的距离:
vec2 arc_cpt_vec = max(pos - vec2(width*scaleX, height*scaleY) + radius, 0.0);
如果向量的长度大于半径,则必须跳过片段:
float alpha = length(arc_cpt_vec) > radius ? 0.0 : 1.0;
问题是距离没有按比例缩放到屏幕 space 中,因此在最大的 window 轴上被拉伸。如果将归一化位置乘以屏幕的纵横比以及框的其他参数,则可以解决此问题。我在 Shadertoy 上写了一个例子:
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Input info
vec2 boxPos; // The position of the center of the box (in normalized coordinates)
vec2 boxBnd; // The half-bounds (radii) of the box (in normalzied coordinates)
float radius;// Radius
boxPos = vec2(0.5, 0.5); // center of the screen
boxBnd = vec2(0.25, 0.25); // half of the area
radius = 0.1;
// Normalize the pixel coordinates (this is "passTexCoords" in your case)
vec2 uv = fragCoord/iResolution.xy;
// (Note: iResolution.xy holds the x and y dimensions of the window in pixels)
vec2 aspectRatio = vec2(iResolution.x/iResolution.y, 1.0);
// In order to make sure visual distances are preserved, we multiply everything by aspectRatio
uv *= aspectRatio;
boxPos *= aspectRatio;
boxBnd *= aspectRatio;
// Time varying pixel color
vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
// Output to screen
float alpha = length(max(abs(uv - boxPos) - boxBnd, 0.0)) - radius;
// Shadertoy doesn't have an alpha in this case
if(alpha <= 0.0){
fragColor = vec4(col,1.0);
}else{
fragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
}
可能有一种计算成本较低的方法可以做到这一点,但这是我想出的一个简单的解决方案。