shader基础-形状-圆

210 阅读4分钟

,用方格纸来画正方形和长方形是很容易的。但是画圆就需要另一种方式了,尤其我们需要一个对“每个像素”的算法。一种解决办法是用step()函数将重新映射的空间坐标来画圆。

如何实现?让我们重新回顾一下数学课上的方格纸:我们把圆规展开到半径的长度,把一个针脚戳在圆圆心上,旋转着把圆的边界留下来。

将这个过程翻译给 shader 意味着纸上的每个方形格点都会隐含着问每个像素(线程)是否在圆的区域以内。我们通过计算像素到中心的距离来实现(这个判断)。

有几种方法来计算距离。最简单的是用distance()函数,这个函数其实内部调用 length()函数,计算不同两点的距离(在此例中是像素坐标和画布中心的距离)。length()函数内部只不过是用平方根sqrt()计算斜边的方程。

你可以使用distance(),length()或 sqrt()到计算屏幕的中心的距离。下面的代码包含着三个函数,毫无悬念的他们返回相同的结果。

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

void main(){
	vec2 st = gl_FragCoord.xy/u_resolution;
    float pct = 0.0;

    // a. The DISTANCE from the pixel to the center
    // pct = distance(st,vec2(0.5));

    // b. The LENGTH of the vector
    //    from the pixel to the center
    // vec2 toCenter = vec2(0.5)-st;
    // pct = length(toCenter);

    // c. The SQUARE ROOT of the vector
    //    from the pixel to the center
    vec2 tC = vec2(0.5)-st;
    pct = sqrt(tC.x*tC.x+tC.y*tC.y);

    vec3 color = vec3(pct);

	gl_FragColor = vec4( color, 1.0 );
}

1. 修改为圆

可以使用 step() 函数将大于 0.5 的像素点变成白色(1.0),小于等于 0.5 的像素点变成黑色(0.0)。以下是修改后的代码:


#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

void main(){
    vec2 st = gl_FragCoord.xy / u_resolution;
    float pct = 0.0;

    vec2 tC = vec2(0.5) - st;
    pct = sqrt(tC.x * tC.x + tC.y * tC.y);

    // 使用 step() 函数将大于 0.5 的像素点变成白色,小于等于 0.5 的像素点变成黑色
    vec3 color = step(0.5, pct) * vec3(1.0);

    gl_FragColor = vec4(color, 1.0);
}

image.png

2.反转前景色和背景色

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

void main(){
    vec2 st = gl_FragCoord.xy / u_resolution;
    float pct = 0.0;

    vec2 tC = vec2(0.5) - st;
    pct = sqrt(tC.x * tC.x + tC.y * tC.y);

    vec3 color = step(0.5, pct) * vec3(1.0);

    // 反转前景色和背景色
    color = 1.0 - color;

    gl_FragColor = vec4(color, 1.0);
}

image.png

3.使用smoothstep()函数,通过修改不同的值来试着做出一个边界顺滑的圆。

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

void main() {
    vec2 st = gl_FragCoord.xy / u_resolution;
    vec2 center = vec2(0.5); // 圆心的坐标
    float radius = 0.3; // 圆的半径

    // 计算当前片段到圆心的距离
    float distance = length(st - center);

    // 使用 smoothstep 函数创建边界顺滑的圆
    float smoothness = 0.1; // 控制边界的顺滑程度
    float circle = smoothstep(radius - smoothness, radius + smoothness, distance);

    vec3 color = vec3(circle); // 使用圆的值作为颜色

    gl_FragColor = vec4(color, 1.0);
}

image.png

4.使用dot增加运算速度

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

float circle(in vec2 _st, in float _radius){
    vec2 dist = _st - vec2(0.5);
    return smoothstep(_radius - (_radius * 0.01),
                      _radius + (_radius * 0.01),
                      dot(dist, dist) * 4.0);
}

void main(){
    vec2 st = gl_FragCoord.xy / u_resolution.xy;

    vec3 backgroundColor = vec3(0.0, 0.0, 0.0); // 设置背景颜色为黑色
    vec3 circleColor = vec3(0.0, 1.0, 0.0); // 设置圆的颜色为绿色

    float circleValue = circle(st, 0.9);
    vec3 color = mix(circleColor, backgroundColor, circleValue);

    gl_FragColor = vec4(color, 1.0);
}

这段代码使用 circle() 函数创建了一个边界顺滑的圆。

首先,我们定义了一个 circle() 函数,它接受一个归一化的坐标 _st 和一个半径 _radius 作为参数。在函数内部,我们计算了 _st 与圆心的距离的平方,并通过 dot() 函数将其与自身进行点乘运算。这样可以避免使用开平方函数,提高计算效率。

接下来,我们使用 smoothstep() 函数来创建一个边界顺滑的圆。通过调整阈值的范围,我们可以控制圆的边界的顺滑程度。这里的阈值范围为 _radius - (_radius * 0.01)_radius + (_radius * 0.01),使得边界有一个平滑的过渡。

mix() 函数是一个线性插值函数,用于在两个值之间进行插值。它接受三个参数:xya。函数的作用是根据插值因子 a,计算出介于 xy 之间的值。

具体而言,mix(x, y, a) 的计算过程如下:

  • a = 0.0 时,返回值为 x
  • a = 1.0 时,返回值为 y
  • a 在 0.0 和 1.0 之间时,返回值为 (1 - a) * x + a * y,即 xy 之间的线性插值。

在图形编程中,mix() 函数常用于颜色插值、位置插值和其他数值插值的计算。在上述代码中,我们使用 mix() 函数将圆的颜色和背景颜色进行插值,根据圆的值来控制插值因子,实现圆和背景的混合效果。 最后,我们将圆的值作为颜色,并将其赋值给输出片段的颜色属性 gl_FragColor

通过调整函数中的 _radius 参数,可以改变圆的大小。同时,可以根据需要调整阈值的范围,以控制边界的顺滑程度。

image.png