Zhili's blog Zhili's blog
首页
关于
GitHub (opens new window)

Zhili

转型中的前端老兵
首页
关于
GitHub (opens new window)
  • webpack 区分环境使用CDN以及HtmlWebpackPlugin插件的编写
  • 使用 Mpvue 开发小程序总结
  • 前端知识汇总-持续更新
  • webGL知识汇总-持续更新
  • React思想要点
  • Mapbox 入门初试
  • 构建自己的 GLSL 绘图器 - 2d 版
  • webGL二维有向距离场(SDF)及布尔运算
    • 有向距离场
      • 移动
      • 圆形距离场
      • 矩形距离场
    • 距离场布尔运算
  • webGL入门:绘制一个三角形
  • 使用 GLSL 绘图尝试:绘制正弦曲线
  • 简单解析虚拟 DOM
  • 自己动手开发一个 markdown 转微信文章工具
  • Vue组件扩展及权限管理的实现技巧
  • 使用 CSS 绘制三角形
  • Nginx 前端配置和使用
  • webpack按需加载配置和babel编译
  • 面试总结39题
  • 使用node反向代理接口改造旧项目
  • 可视化表单搭建系统的开发与思考
  • 前端
zhili
2019-07-30

webGL二维有向距离场(SDF)及布尔运算

webGL中在着色器中的二维有向距离场(SDF)及布尔运算的一些理解和使用

# 有向距离场

在着色器中绘图,类似于在一个方格纸上涂色,每个方格就是一个像素点。想象一下如果我们要在方格纸上绘制一个圆该怎么做呢,我们只需要把圆形内部区域涂成一个颜色,圆形外部区域的涂成另外一种颜色(或者不涂色),这样我们便有了一个圆。在数学上我们怎么定义圆呢?及圆周上的点到圆心的距离等于半径,圆内部的点到圆心的距离小于半径,圆外部的点到圆心的距离大于半径。这样一圈一圈,我们可以在图上得到每一个点到圆心的距离,圆内部的点到圆心的距离减去半径为负值,圆外部点到圆心的距离减去半径为正值,因此我们称之为:有向距离场。

图片

# 移动

在计算距离场的时候一般将坐标中点设置为原点,方便我们计算。但是通常,图形不是绘制在原点的位置,这样我们便需要原点位置设置在图形的中点,移动到原点是一个几乎所有图形都会用到的操作。如果图形的中点在(0.5,0.5),那么将要将(0.5,0.5)设置为原点,我们只需要减去(0.5,0.5),那么在绘制过程中便是以(0.5,0.5)为原点的。

vec2 translate(in vec2 p,in vec2 c) {
	return p-c;
}
1
2
3

# 圆形距离场

圆形的距离计算最为简单,我们只需要计算坐标到圆心的距离即可,假设圆心在(0.5,0.5),由于着色器坐标归一化之后范围为[0,1],那么绘图区域中点到圆心的距离范围为[0,0.5],如下图所示:

图

计算代码可以写为这样:

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 uResolution;

void main(){
  vec2 st=gl_FragCoord.xy/uResolution;
  vec2 center=vec2(.5);
  float dis=distance(center,st.xy);
  gl_FragColor.rgb=dis*vec3(1.);
  gl_FragColor.a=1.;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 矩形距离场

矩形距离场稍微要复杂一些,假设矩形中点在正中心,长为w,宽为h,那么对于矩形内部的点来说,它们的x轴到中心的距离为[0,w/2],它们的y轴到中心的距离为[0,h/2];而矩形外面的的坐标,它们的x轴到中心的范围为[w/2,0],它们Y轴到中心点范围为[h/2,1],如下图所示:那么减去长宽的一半之后,内部点x的范围为[-w/2,0],外部点x轴范围为[-w/2+1,1],如下图所示:

矩形

那么绘制矩形就有一个简单的方式了,矩形内部,x值和y值都必须小于0;矩形外部,x值和y值必然有一个是大于0 的,那么只要x,y的最大值小于0 ,就是矩形内部,否则,就是矩形外部,代码可以这样写

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 uResolution;

float plot_Rect(vec2 st,vec2 c,vec2 halfwh){
	// 移动到中心
	st-=c;
	// 以绝对值减去长宽
	st=abs(st)-halfwh;
	// 在矩形内部的点 x 范围为:[-w/2,0],同时 y的范围为[-h/2,0],所以,只要最大值小于0就可以表示点在矩形的范围
	return max(st.x,st.y);
}

void main(){
	vec2 st=gl_FragCoord.xy/uResolution;
	float dis = plot_Rect(st,vec2(0.5),vec2(0.3,0.1));
	gl_FragColor.rgb=step(dis,0.0)*vec3(1.0);
	gl_FragColor.a = 1.0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

对于更多更复杂的图形,绘制方法也是一样的,判断一个点处于图形内部还是图形外部就可以绘制他们,越复杂的图形,其距离场函数可能就会越复杂,需要有很深的数学功底,这些有一些距离场函数可以供参考:

shadertroy创始人的博客 (opens new window) 里面有许多2D乃至3D的距离场函数,可以通过这个图粗略的了解一下:

2dsdf

以及 the book of shaders大佬的vscode-glsl-canvas (opens new window) 这个插件里面包含了一些可用的2d sdf函数片段,可以参考.

还有国外一位大佬写的着色器教程 (opens new window),很全面,很值得一看。

# 距离场布尔运算

通过距离场函数得到图形之后,可以通过布尔计算得到不同的组合,如下图所示,分别为圆于矩形进行交集(∩)、并集(∪)、差集(-)得到

布尔计算

代码如下图所示:

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 uResolution;

#include <lib/util.glsl>
#include <lib/shape.glsl>
#include <lib/color.glsl>

// 合并,取并集
float merge(float dis1,float dis2) {
	return min(dis1,dis2);
}

// 相交,取交集
float intersect(float dis1,float dis2){
	return max(dis1,dis2);
}

// 相减
float subtract(float base, float subtraction){
	return max(base,-subtraction);
}


void main(){
	vec2 st=gl_FragCoord.xy/uResolution;
	// 交集
	float c1 = sCircle(st,vec2(0.1,0.5),0.2);
	float r1 = sPoly(st,vec2(0.2,0.6),0.2,4);
	float dis1 = intersect(c1,r1);
	
	// 并
	float c2 = sCircle(st,vec2(0.5,0.5),0.1);
	float r2 = sPoly(st,vec2(0.5,0.6),0.1,4);
	float dis2 = merge(c2,r2);
	
	// 差
	float c3 = sCircle(st,vec2(0.8,0.5),0.1);
	float r3 = sPoly(st,vec2(0.8,0.6),0.1,4);
	float dis3 = subtract(c3,r3);
	
	float dis = merge(dis1,dis2);
	dis = merge(dis,dis3);

	gl_FragColor.rgb=fill(dis,AZUR);
	gl_FragColor.a=1.;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

我们可以通过这些做出更有意思的图形,图形学的世界其实就是数学的世界,正如大师所说的,使用着色器绘图就如同将一艘船放进瓶子里,过程是如此的复杂,但是结果却很美丽。共勉!

共勉

编辑 (opens new window)
#GLSL#webGL
上次更新: 2022/03/14, 06:54:51
构建自己的 GLSL 绘图器 - 2d 版
webGL入门:绘制一个三角形

← 构建自己的 GLSL 绘图器 - 2d 版 webGL入门:绘制一个三角形→

最近更新
01
可视化表单搭建系统的开发与思考
03-09
02
使用node反向代理接口改造旧项目
11-17
03
面试总结39题
10-11
更多文章>
Theme by Vdoing | Copyright © 2022-2023 zhili | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式