WebGL:使用着色器进行几何造型

webgl · 浏览次数 : 0

小编点评

## 着色器几何造型简介 着色器几何造型是一种在片元上进行几何图形绘制的技术。通过判断当前片元是否在三角形内部,以及根据不同距离的计算方式,我们可以为片元填充不同的颜色或绘制不同的图形。 **关键概念:** * **片元:** 每个像素在绘制时可以被多个片元覆盖。 * **距离:** 衡量点到某点的距离,用于判断点是否在三角形内部或其他图形中。 * **三角形距离:** 计算点到三个顶点的距离中最小值。 * **符号距离场渲染(SDF):** 利用空间中的距离分布来绘制图形。 **示例代码:** ```javascript void main() { vec4 color = texture2D(tMap, vUv); float d = distance(vUv, vec2(0.5, 0.5)); gl_FragColor.rgb = (1.0 - smoothstep(0.4, 0.4005, d)) * color.rgb; gl_FragColor.a = (1.0 - smoothstep(0.4, 0.4005, d)); } ``` **用途:** 着色器几何造型可以用于绘制各种图形,包括: * 三角形 * 圆形 * 正多边形 * 椭圆 * 其他复杂图形 **总结:** 着色器几何造型是一种强大的图形绘制技术,可以帮助我们实现更加复杂的图形效果。通过了解距离、三角形距离和符号距离场渲染等关键概念,我们可以轻松地为片元上色,绘制各种图形。

正文

前言

本文将介绍如何使用着色器来进行几何造型,说到几何图形大家一定都不陌生,比如说三角形、圆形,接触过WebGL基础使用的小伙伴一定都知道怎么去在画布上绘制一个三角形,只要传入三个顶点坐标,并选择绘图模式,我们就能在WebGL的画布上画出一个三角形。

但是除了这种形式之外,我们还可以直接使用片元着色器进行几何造型,那么具体要怎么做呢,下面就以三角形作为例子来进行演示。

绘制三角形

要实现三角形的绘制,我们需要先判断当前片元是否在三角形内部,也就是说在给片元上色之前,我们需要先计算出片元的色值。

假设我们已知三角形的三个顶点:

vec2(0.3),
vec2(0.5, 0.7),
vec2(0.7, 0.3)

那么我们就可以根据当前片元的纹理坐标和三角形三个顶点的坐标,计算片元与三角形的距离。下面我们会通过使用向量的叉乘和点乘来进行计算,对向量操作不太熟悉的小伙伴可以去找些资料复习一下,或者参考我前面的文章。

首先在片元着色器中定义一个函数,叫做line_distance,用于计算点到一条直线的距离,这里我们使用了向量的叉乘来计算,代码应该比较容易理解,就简单说一下,这里的参数st表示我们要判断的点,a和b分别表示直线上的两个点,因为这三个点都是平面上的点,叉积本身也是向量,所以直接取叉积Z轴的分量就是二维向量叉乘的结果。这个函数我们在后续会用于判断点是否在三角形内部。

// 点到直线的距离
float line_distance(in vec2 st, in vec2 a, in vec2 b) {
  vec3 ab = vec3(b - a, 0);
  vec3 p = vec3(st - a, 0);
  return cross(p, normalize(ab)).z;
}

接着继续定义一个函数,叫做seg_distance,用于计算点到一条线段的距离,点与线段的距离我们分为两类情况考虑,一类是点在线段的上方或下方,这个时候点到线段的距离,就等于点到这条线段所在直线的距离,第二类是点在线段的左右两侧,此时点到线段的距离就是点到线段两个端点的距离中的较小值。

// 点到线段的距离
float seg_distance(in vec2 st, in vec2 a, in vec2 b) {
  vec3 ab = vec3(b - a, 0);
  vec3 p = vec3(st - a, 0);
  float l = length(ab);
  float d = abs(
    cross(p, normalize(ab))
  ).z;
  float proj = dot(p, ab) / l;
  if (proj >= 0.0 && proj <=l) return d;
  return min(distance(st, a), distance(st, b));
}

最后定义一个函数,叫做triangle_distance,用于计算点到三角形的距离,这属于一个自定义的距离概念,从代码上看准确来说应该是点到三条边的距离中的最小值。在这段代码中,我们定义内部的距离取负数,外部的距离为正。

// 点与三角形的距离
float triangle_distance(in vec2 st, in vec2 a, in vec2 b, in vec2 c) {
  float d1 = line_distance(st, a, b);
  float d2 = line_distance(st, b, c);
  float d3 = line_distance(st, c, a);

  if (d1 >= 0.0 && d2 >= 0.0 && d3 >= 0.0
    || d1 <= 0.0 && d2 <= 0.0 && d3 <= 0.0) {
    return -min(abs(d1), min(abs(d2), abs(d3))); // 内部距离为负
  }

  return min(seg_distance(st, a, b), min(seg_distance(st, b, c), seg_distance(st, c, a))); // 外部距离为正
}

首先我们判断三组向量叉乘的结果,符号是否是一样的,如果是一样,说明当前点在三角形内部,那么点必然是在线段的上方,所以直接用三个叉积中的最小值。

如果点在三角形外部,就取点到三个线段距离中的最小值。

到这里我们就能得到片元与三角形的距离了。

float d = triangle_distance(
  vUv,
  vec2(0.3),
  vec2(0.5, 0.7),
  vec2(0.7, 0.3)
);

接着我们就利用这个距离来进行最简单的填充,将三角形的内部直接填充为白色。

gl_FragColor.rgb = (1.0 - smoothstep(0.0, 0.01, d)) * vec3(1.0);

当然除了填充之外,我们可以进行三角形的描边。

gl_FragColor.rgb = (smoothstep(-0.005, 0.0, d) - smoothstep(0.0, 0.005, d)) * vec3(1.0);

smoothstep(a, b, c)函数可能有些小伙伴不了解,这里简单说一下,这个函数接收3个参数。

  • 在a小于b的情况下
    • 如果c小于a,会返回0
    • 如果c大于b,就返回1
  • 而在a大于b的情况下
    • 如果c大于a,返回的是0
    • 如果c小于b,就返回1
  • 如果c在a和b之间,就返回一个过渡值

所以其实上面的计算也可以反过来写。

应用场景

那么看到这里,有些小伙伴可能就有疑问了,这感觉好像和直接用顶点画没什么区别,那么我们为什么要用着色器造型呢?答案其实很简单,就是它能帮助我们实现更多的图案效果。比如使用以下代码,可以实现三角环。

d = fract(20.0 * d);
gl_FragColor.rgb = (smoothstep(0.45, 0.5, d) - smoothstep(0.5, 0.55, d)) * vec3(1.0);

在这段代码中,将距离的值放大20倍,再取小数部分,就能实现重复的环。这种绘制方式叫做符号距离场渲染(SDF),这是图形渲染中的一个专有名词,本质上就是利用空间中的距离分布来着色,是着色器造型生成图案的基础方法。这种效果如果要用顶点来画就没这么简单了。

除了SDF,我们注意到代码中使用的是纹理坐标,所以着色器造型还可以配合纹理实现更多的效果,比如图片裁剪。下面我们来看一个简单的例子。

void main() {
  vec4 color = texture2D(tMap, vUv);
  float d = distance(vUv, vec2(0.5, 0.5));
  gl_FragColor.rgb = (1.0 - smoothstep(0.4, 0.4005, d)) * color.rgb;
  gl_FragColor.a = (1.0 - smoothstep(0.4, 0.4005, d));
}

这里我们定义的距离是片元到一个点的距离,根据这个距离我们能按照上面的方式绘制出一个圆心在0.5, 0.5的圆,同时我们现在还能将纹理按这个圆形裁剪出来,就还是蛮好用的效果。

总结

在刚开始着色器几何造型的学习时,我也有点奇怪WebGL已经有顶点可以画图形了,为什么还要有这个着色器造型,看到符号距离场SDF的时候,才感觉出来是有区别哦,好像还挺有用的。

绘制的步骤简单来说就两步,第一步,确定计算距离的方式,第二步,根据距离给片元上色。

绘制三角形和圆形都还算简单的,我相信很多小伙伴看下来应该都能理解了,可以自己动手尝试一下,有兴趣的小伙伴还可以去尝试更多的图形,比如正多边形、椭圆等等这些图形,甚至是更复杂的图形。

完整代码参考

与WebGL:使用着色器进行几何造型相似的内容:

WebGL:使用着色器进行几何造型

本文将介绍如何使用着色器来进行几何造型,说到几何图形大家一定都不陌生,比如说三角形、圆形,在WebGL中除了直接使用顶点绘制,我们还可以使用片元着色器进行造型,以下将介绍三角形的绘制,以及应用场景。

如何使用webgl(three.js)实现煤矿隧道、井下人员定位、掘进面、纵采面可视化解决方案——第十九课(一)

three.js、webgl、3D煤矿隧道、三维井下人员定位、掘进面三维可视化、纵采面可视化、采集面可视化展示、设备检测、数字孪生、物联网3D、3d建筑、3d库房,bim管理系统

WebGL实现简易的局部“马赛克”

在Canvas2D中通过调用`drawImage` API就能将图像绘制到画布上,在WebGL中我们也可以绘制图像,在绘制时我们需要用到WebGL中的纹理对象,在之前实现网格背景的视频中,我使用了一个叫做纹理坐标的配置,现在要完成纹理的加载我们也需要用到纹理坐标,并且我们可以通过对纹理坐标处理实现简...

three.js高性能渲染室外场景

大家好,本文在相关文章的基础上,使用three.js渲染了高性能的室外场景,在移动端也有较好的性能,并给出了代码,分析了关键点,感谢大家~ 关键词:three.js、Web3D、WebGL、室外场景、Instanced Draw、大场景、LOD、Frustum Cull、优化、开源 代码:Githu

three.js实现相机碰撞,相机不穿墙壁、物体

大家好,本文实现了相机碰撞检测,使相机不穿墙壁、物体,并给出了思路和代码,感谢大家~ 关键词:数字孪生、three.js、Web3D、WebGL、相机碰撞、游戏相机 我正在承接Web3D数字孪生项目,具体介绍可看承接各种Web3D业务 目录实现原理参考资料 实现前: 移动第三人称相机时,相机可能会穿

webgl centroid质心插值的一点理解

质心插值说的是什么 2023.10.04再次review这个细节点: https://www.opengl.org/pipeline/article/vol003_6/ https://github.com/WebGLSamples/WebGL2Samples/blob/master/samples

如何基于three.js(webgl)引擎架构,研发一套通过配置就能自动生成的3D机房系统

自动化3D机房、微模块、3D机房、3D数据中心、科技感数据中心、三维机房、3d建筑,3d消防,消防演习模拟,3d库房,3d档案室,3d密集架,webGL,threejs,3d机房,bim管理系统

第135篇:Three.js基础入门

好家伙,这东西太帅了,我要学会 先放张帅图(都是用three.js做出来的,这我学习动力直接拉满) 还有另外一个 Junni is... 帧数太高,录不了 开始学习 官方文档 1.Three.js是什么? Three.js是一款运行在浏览器中的 3D 引擎(基于WebGL的API的封装),你可以用它