【零基础】充分理解WebGL(二)

接上一篇:【零基础】充分理解WebGL(一)

在继续深入之前,我们先来解决上一篇中的遗留问题:

我们默认不设置顶点的时候,绘制的图形是整个 canvas 范围,但是 WebGL 并不支持四边形图元,那么我们原本的绘制范围是如何界定的呢?

因为三角形是基本图元,而 Canvas 画布本身是一个四边形,所以我们需要使用两个三角形的顶点进行绘制,这也是 gl-renderer 默认的顶点数据,它相当于:

renderer.setMeshData([
  {
    positions: [[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]],
    cells: [[0, 1, 3], [3, 1, 2]],
  },
]);

如下图所示,我们用两个三角形来完成四边形的绘制。a16c791a8515900fd71ad815b902ab6f.png实际上,任意二维简单多边形[1]都可以剖分成若干个三角形,然后进行绘制,这个过程在数学上叫做三角剖分d0df0d185f8199cc58bd60f0b98444e8.png

用 WebGL 绘制平面图形,三角剖分是一种基本的方法。不过这个问题我们可以留待后续的文章详细讲解。在这一讲我们先回到片段着色器部分,来谈谈利用着色器或者说利用 GPU 进行造型绘图的基本原理和方法。

与上一讲一样,我们通过动手代码实践来理解,先从简单的开始。

最简单的单色绘制,在上一讲已经说过了,比如下面的代码,将整个绘图区绘制为黑色:

const canvas = document.querySelector('canvas');
const renderer = new GlRenderer(canvas, {webgl2: true});

const fragment = `#version 300 es
precision highp float;
out vec4 FragColor;
void main() {
  FragColor = vec4(0, 0, 0, 1);
}
`;
const program = renderer.compileSync(fragment);
renderer.useProgram(program);
renderer.render();

下面我们稍微修改一下代码:

const canvas = document.querySelector('canvas');
const renderer = new GlRenderer(canvas, {webgl2: true});

const fragment = `#version 300 es
precision highp float;
out vec4 FragColor;
uniform vec2 resolution;
void main() {
  vec2 st = gl_FragCoord.xy / resolution;
  FragColor = vec4(0, 0, 0, 1);
  if(st.x > 0.5) {
    FragColor = vec4(1, 1, 1, 1);
  }
}
`;
const program = renderer.compileSync(fragment);
renderer.useProgram(program);
renderer.uniforms.resolution = [canvas.width, canvas.height];
renderer.render();

上面的代码很好理解,我们判断 x 坐标大于 0.5 时,输出颜色白色,否则为黑色。这样我们得到如下的效果:9ec5de615b399a0e509104765f25899e.png上面的代码,我们用if(st.x > 0.5)来判断黑白分界线,实际上我们有更简单的办法:

#version 300 es
precision highp float;
out vec4 FragColor;
uniform vec2 resolution;
void main() {
  vec2 st = gl_FragCoord.xy / resolution;
  FragColor.rgb = step(0.5, st.x) * vec3(1.0);
  FragColor.a = 1.0;
}

https://code.juejin.cn/pen/7100850170283163656

2ac8cbe47dde6451785653896a263794.png

这里我们用step函数来代替if语句,step(x, y)是阶梯函数,当 y 小于 x 时值为 0,y 大于等于 x 时值为 1。

在着色器中,step是一个非常好用的函数,可以使用它来绘制不同的图形。

比如下面这个例子通过step绘制一个圆形:

#version 300 es
precision highp float;
out vec4 FragColor;
uniform vec2 resolution;
void main() {
  vec2 st = gl_FragCoord.xy / resolution;
  vec2 center = vec2(0.5);
  FragColor.rgb = step(length(st - center), 0.2) * vec3(1.0);
  FragColor.a = 1.0;
}

https://code.juejin.cn/pen/7100852428756484103

993f59e67e58a7589fc3d1dba43159fd.png

消锯齿

直接用step绘制曲线,容易产生锯齿,我们可以通过smoothstep来消除锯齿:

#version 300 es
precision highp float;
out vec4 FragColor;
uniform vec2 resolution;
void main() {
  vec2 st = gl_FragCoord.xy / resolution;
  vec2 center = vec2(0.5);
  float d = length(st - center);
  FragColor.rgb = smoothstep(d - 0.015, d, 0.2) * vec3(1.0);
  FragColor.a = 1.0;
}

https://code.juejin.cn/pen/7100853943923638280

0ec63b1580b70172e5c1258d4eb1b764.png

smoothstep 对阶梯函数进行了平滑处理,它在范围的上下限之间进行插值。

通过两个 step 相减或者两个 smoothstep 相减的技巧,可以用来画线,例如我们修改一下上面的代码:

#version 300 es
precision highp float;
out vec4 FragColor;
uniform vec2 resolution;
void main() {
  vec2 st = gl_FragCoord.xy / resolution;
  vec2 center = vec2(0.5);
  float d = length(st - center);
  FragColor.rgb = (smoothstep(d - 0.015, d, 0.2) - smoothstep(d, d + 0.015, 0.18)) * vec3(1.0);
  FragColor.a = 1.0;
}

就可以绘制一个圆环:ac5bae1d939bdd0e462d0373ac741ccc.png我们可以将这个技巧封装成一个通用函数:

float stroke(float d, float d0, float w, float smth) {
  float th = 0.5 * w;
  smth = smth * w;
  float start = d0 - th;
  float end = d0 + th;
  return smoothstep(start, start + smth, d) - smoothstep(end - smth, end, d);
}

它的第一个参数接受一个距离量,第二个参数在指定距离的等距线附近绘制,第三个参数表示绘制宽度,第四个参数是平滑比率。

这样我们就可以用stroke来画线了,只要我们能把距离定义出来,比如下面的代码绘制了一条 x=0.5 的直线:

void main() {
  vec2 st = gl_FragCoord.xy / resolution;
  float d = stroke(st.x, 0.5, 0.02, 0.1);
  FragColor.rgb = d * vec3(1.0);
  FragColor.a = 1.0;
}

https://code.juejin.cn/pen/7100857056361447454

130d8cc7db48bf1afad384bcf15712f0.png

这种利用距离来构图的思路叫做距离场构图法。下面的代码绘制了y=x的直线和y=4*(x-0.5)**2的抛物线:

https://code.juejin.cn/pen/7100862005245902884

64d5318ebccdfe7680cdf0159fd2ba13.png

在这一讲的最后,留给大家一个作业,用距离场构图法来绘制一条正弦曲线,要求至少绘制 3 个周期,你知道如何绘制吗?如果你做出来了,可以把代码分享到原文链接中的评论区。

参考资料

[1]

简单多边形: https://www.baike.com/wiki/%E7%AE%80%E5%8D%95%E5%A4%9A%E8%BE%B9%E5%BD%A2/19923903?view_id=mywnwdgq41c00


- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

28cb17057a3de440559a7e821e0dc8a4.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值