,用方格纸来画正方形和长方形是很容易的。但是画圆就需要另一种方式了,尤其我们需要一个对“每个像素”的算法。一种解决办法是用
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);
}
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);
}
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);
}
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() 函数是一个线性插值函数,用于在两个值之间进行插值。它接受三个参数:x,y 和 a。函数的作用是根据插值因子 a,计算出介于 x 和 y 之间的值。
具体而言,mix(x, y, a) 的计算过程如下:
- 当
a = 0.0时,返回值为x。 - 当
a = 1.0时,返回值为y。 - 当
a在 0.0 和 1.0 之间时,返回值为(1 - a) * x + a * y,即x和y之间的线性插值。
在图形编程中,mix() 函数常用于颜色插值、位置插值和其他数值插值的计算。在上述代码中,我们使用 mix() 函数将圆的颜色和背景颜色进行插值,根据圆的值来控制插值因子,实现圆和背景的混合效果。
最后,我们将圆的值作为颜色,并将其赋值给输出片段的颜色属性 gl_FragColor。
通过调整函数中的 _radius 参数,可以改变圆的大小。同时,可以根据需要调整阈值的范围,以控制边界的顺滑程度。