前言
这是我参与[第五届青训营]伴学笔记创作活动的第 16 天,相信的大家对图形构建或者说canvas比较的感兴趣,接下来我们就来讲讲WebGLweb图形库
初识 WebGL
Why WebGL / Why GPU?
WebGL是什么?GPU!==WebGL!==3D
WebGL为什么不像其他前端技术那么简单
WebGL是运用了GPU和3D的技术
Modern Graphics System
- 光栅 (
Raster): 几乎所有的现代图形系统都是基于光来绘制图形的,光栅就是指构成图像的像素阵列。 - 像素 (
Pixel) :一个像素对应图像上的一个点,它通常保存图像上的某个具体位置的颜色等信息。 - 帧缓存(
Frame Buffer) : 在绘图过程中,像素信息被存放于缓存中,帧缓存是一块内存地址。 - CPU (
Central Processing Unit) : 中央处理单元,负责逻辑计算。 - GPU (`Graphics Processing Unit) : 图形处理单元,负责图形计算
- 轮廓提取 /
meshing - 光栅化
- 帧缓存
- 渲染
The Pipeline
Data ==> Processor ==> Frame buffer ==> Pixels
通常情况下是将数据通过管道传输的形式传输数据,生成完数据,然后放到缓存区,然后批量的绘制出来,类似于活字印刷
CPU VS GPU
- GPU由大量的小运算单元构成
- 每个运算单元只负责处理很简单的计算
- 每个运算单元彼此独立
- 因此所有计算可以并行处理
对图形的计算不需要非常复杂的逻辑计算,比如像素点的信息,不需要很强大的运算单元和运算能力,但是我这给像素点是非常多的,即使是一张图片都是有48万个像素点,不可能开个48万个核去计算,那样非常的浪费,所以还是交给处理这方面比较强的
GPU去处理
WebGL & OpenGL
web.eecs.umich.edu/~sugih/cour…
- 理论上来去说
WebGL是利用GPU技术完成绘图的API,它实际上是OpenGL家族 的一个成员,这个成员有Javascript的接口,把这些API在javascript的runtime 里面去实现了。- OpenGL的
Javascript的接口底层和C++的OpenGL的借口底层是差不多的,因为他们是一个系列的
WebGL Startup
- 创建
WebGL上下文 - 创建
WebGL Program - 将数据存入缓冲区
- 将缓冲区数据读取到
GPU GPU执行WebGL程序,输出结果
Javascript创建WebGL上下文是通过canvas对象,我们在canvas对象上获得WebGL的上下文;- 接下来就创建
WebGL的程序,这里面有处理图形的代码,在GPU处理的部分,他不是运用javascript语言去编写的,而是GLSL编程语言去写的,把javascript代码给编译好,给构建出来;- 最后变成
WebGL的程序;然后我们去应用这个WebGL程序,然后我们创建数据,存入缓存区,最终我们将缓存区的数据读到GPU,在GPU里面去执行WebGL的程序,最后输出结果
Create WebGL Context
const canvas = document.querSelector('canvas')
const gl = canvas.getContext('webgl')
function create3DContext(canvas,options) {
const names = ['webgl','experimental-webgl','webkit-3d','maz-webgl'];
if (options.webgl2) names.unshift('webgl2');
let context = null;
for (let ii = 0; ii < names.length; ++ii ) {
try() {
context = canvas.getContext(names[ii],options);
} catch (context) {
break;
}
}
return context
}
The Shaders
- Vertex Shader
attribute vec2 position;
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(position,1.0,1.0);
}
- Fragment Shader
precision mediump float;
void main() {
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
- 着色器有两种:
- 顶点着色器,用来定义图形顶点的位置,从而可以绘制出不同的形状
- 片段着色器/片源着色器,顶点和图源定义好的区域类,用简单的概念来去理解的话,就是顶点图源确定了那个区域内的像素点;
- 比如说三角形,三个顶点的绘制确定就是交予顶点着色器去绘制,内容就交给片段着色器
Create Program
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertex);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragment);
gl.compileShader(fragmentShader);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
Data to Frame Buffer
Axes
Typed Array
const points = new Float32Array([
-1, -1,
0, 1,
1, -1,
])
const bufferId = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER,bufferId);
gl.bindBuffer(gl.ARRAY_BUFFER,points,gl.STATIC_DRAW)
canvas是左手坐标系,而webGL的话是右手坐标系,就是浏览器和canvas左上角的顶点坐标是(0,0),坐标x是向上的,坐标y是;webGL中心点是坐标圆点,左上角是(-1,1),右下角是(1,-1)
Frame Buffer to GPU
const vPosition = gl.getAttribLocation(program, 'position')//获取顶点着色器中的position变量的地址
gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);//给变量设置长度和类型
gl.enableVertexAttribArray(vPosition); //激活这个变量
attribute vec2 position;
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(position, 1.0, 1.0);
}
Output
l.clear(gl.COLOR_BUFFER_BTT);
gl.drawArray(gl.TRIANGLES, 0 ,points.length / 2)
WebGL
为什么 WebGL 那么难呢?
2D vs WebGL
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(250, 0);
ctx.lineTo(500, 500);
ctx.lineTo(0, 500);
ctx.fillStyle = 'red';
ctx.fill();
import {Renderer, Figure2D, Mesh2D} from '@mesh.js/core';
const canvas = document.querySelector('canvas');
const renderer = new Renderer(canvas);
const figure = new Figure2D();
figure.beginPath();
figure.moveTo(250, 0);
figure.lineTo(500, 500);
figure.lineTo(0, 500);
const mesh = new Mesh2D(figure, canvas);
mesh.setFill({
color: [1, 0, 0, 1],
});
renderer.drawMeshes([mesh]);
mesh.js 是月影大佬封装的
WebGL的底层,可以像2Dcanvas命令式去绘图;注意:在这个mesh.js或者说是WebGL底层是得分两个步骤1. 图形轮廓的处理,2. 把轮廓给网格化之后,然后才去完成渲染,这样就写出来的代码就和2Dcanvas代码就有点相似了
Polygons
Polvgons
Triangulations
多边形就是有一个简单的三角形去组装成一个复杂的多边形,刚刚我们已经搞定了一个有颜色的三角形,去掉颜色也只是一个
API的事情;我们需要对一个复杂的多边形进行三角剖分
Draw Polygon with 2D Triangulations
使用 Earcut 进行三角剖分
const vertices = [
[-0.7,0.51,
[-0.4,0.3],
[-0.25, 0.711,
[-0.1,0.56],
[-0.1, 0.13],
[0.4, 0.21],
[0,-0.6],
[-0.3,-0.3]
[-0.6,-0.3],
[-0.45,0.0],
]
const points = verctices.flat();
const triangles = earcut(points)
earcut的三角剖分API,这个API返回的是顶点的下标数组
3D Meshing
const {Scene} = spritejs;
const {Mesh3d, shaders} = spritejs.ext3d;
const container = document.getElementById('container');
const scene = new Scene({
container,
displayRatio: 2,
});
const layer = scene.layer3d('fglayer', {
camera: {
fov: 35,
},
directionalLight: [0.5, 1.0, -0.3],
directionalLightColor: [1, 1, 1, 0.15],
});
layer.camera.attributes.pos = [5, 3, 6];
layer.camera.lookAt([0, 0, 0]);
(async function () {
const texture = layer.createTexture('https://p2.ssl.qhimg.com/t01598a49e49aba1046.jpg');
const program = layer.createProgram({
...shaders.NORMAL_TEXTURE,
uniforms: {
tMap: {value: texture},
},
});
const model = await layer.loadModel('https://s3.ssl.qhres2.com/static/8613b585d1542274.json');
// For an accurate wireframe, triangle vertices need to be duplicated to make line pairs.
// Here we do so by generating indices. If your geometry is already indexed, this needs to be adjusted.
const index = new Uint16Array((model.position.length / 3 / 3) * 6);
for(let i = 0; i < model.position.length / 3; i += 3) {
// For every triangle, make three line pairs (start, end)
index.set([i, i + 1, i + 1, i + 2, i + 2, i], i * 2);
}
model.index = index;
// console.log(geometry);
const wireframeMesh = new Mesh3d(program, {
mode: 'LINES',
});
wireframeMesh.setGeometry(model);
layer.append(wireframeMesh);
wireframeMesh.animate([
{rotateY: 0},
{rotateY: 360},
], {
duration: 5000,
iterations: Infinity,
});
}());
3D 的更加的复杂,他也是分成非常多的三角网格,我们用实时的计算是非常的慢,所以在一般的情况下,
WebGL渲染3D的时候,不会去实时的分割这个三角网格,而是通过设计软件,把这个模型的三角网格去先分割好,然后把这些数据顶点传入到WebGL里面去渲染;所以一个复杂的图形是如此渲染的。
Transforms
-
平移
-
旋转
-
缩放
-
旋转+缩放是线性变换
-
从线性变换到齐次矩阵
2D和3D的transforms是差不多的;其中CSS3中的transform是23的矩阵,实际上是33 的矩阵,因为底下的(0,0,1),所以把这行给省略掉了;你可以理解为是一个是二维和三维矩阵的线性变换的transform
Apply Transforms
attribute vec2 position;
uniform mat3 modelMatrix;
void main() {
gl_PointSize = 1.0;
vec3 pos = modelMatrix * vec3(position, 1.0);
gl_Position = vec4(pos, 1.0);
}
let transform = gl.getUniformLocation(program, 'modelMatrix');
gl.uniformMatrix3fv(transform, false,
[0.5, 0, 0,
0, 0.5, 0,
0.1, -0.1, 1]);
固定的变量是可以通过
uniform传进来,得知道顶点是通过cos、sin、tan计算,3D就是3 * 3的矩阵线性变换,4D就是4 * 4的矩阵线性变换
3D Matrix
3D 标准模型的四个齐次矩阵 (mat4) 挑动高
-
- 投影矩阵
Projection Matrix
- 投影矩阵
-
- 模型矩阵
Model Matrix
- 模型矩阵
-
- 视图矩阵
View Matrix
- 视图矩阵
-
- 法向量矩阵
Normal Matrix
- 法向量矩阵
像
3D的标准模型是非常的复杂,一般的情况像threee.js或者像其他的一些渲染库,会有不同的齐次矩阵;
- 投影矩阵,投影矩阵是定义坐标系的,标准的坐标系是中间是坐标原点,两边是(-1,1)这样的坐标系,我们是可以缩放旋转这个坐标系的,缩放旋转坐标系就是用这个投影矩阵来做。
- 模型矩阵,对元素或者图形本身去进行这个,
transform的变换- 视图矩阵,因为在
3D模型里面有个摄像机,摄像机在某个空间位置,最终我们呈现出来的画面是摄像机在的那个位置拍摄出来的图像,所以我们可以对视图矩阵进行变换,相当于是移动这个摄像机- 法向量矩阵,定义了这个
3D模型每个面的法线的这个坐标,法线就是当前垂直这个面的所在的网格垂线;为什么要定义这个法线呢?是因为比如我们要计算光照,光线射到平面上的时候,它和平面上的这个夹角会影响到这个平面上的这个明暗。这时候我们就需要计算光射到平面上的夹角,这个角度和法向量的夹角;法向量矩阵就是来计算这个的。
推荐书籍文章
- 【The book of shaders】是适合图形学的入门,有中英版
- 【Mesh.js】基于
WebGL渲染的图形库,它提供的是相对底层的,与这些three.js相比更底层的这个绘图的API,它绘图的模式和canvas 2D是非常的相像- 【glsl-doodle】这个库在码上掘金平台有非常好的支持,开箱即用,它可以屏蔽掉那些
attribute创建Polygons细节,不用管这些,你直接写shader着色器就可以了- 【SpriteJS】是可视化渲染框架,库的功能还是挺强大的,它的底层就是
mesh.js实现的- 【thress.js】是一个现在比较流行
3D渲染库- 【ShaderToy】是写一些比较有意思有趣的
shader的平台,ShaderToy国内访问是比较慢的,想写的话,也可以在码上掘金上去写,码上掘金除了可以写glsl-doodle,也是兼容ShaderToy的 ,80%以上的例子都可在码上掘金上实现
WebGL项目实战
- 平台
- 码上掘金
- 运用的语法是
shaderToy
码上掘金的环境搭建
- 点击链接,进入码上掘金
- 点击
新建代码片段按钮 - 选择实验模板下的
GLSL - 因为码上掘金支持
GLSL的语法,并且实现了一个小demo,我们把右边javascript代码风格改成Custom(已经是Custom就省略此步骤) - 删除除以下信息的代码,以下代码采用的是
CDN引入库的模式,并设置了version版本,定义了类型
#!/jcode/lang/glsl https://xitu.github.io/jcode-languages/dist/lang-glsl.json
#version 300 es
precision highp float;
画个正方形
颜色的定义WebGL和CSS的区别
#!/jcode/lang/glsl https://xitu.github.io/jcode-languages/dist/lang-glsl.json
#version 300 es
precision highp float;
out vec4 fragColor;
void main() {
fragColor = vec4(1.0,0.0,0.0,1.0);
}
- 默认
shader着色器用的rgba的色彩,css类似,只不过css是运用的是整数,而且是0-255,来表示这颜色的阈值;webgl用的是浮点数,它是从0-1来表示,1是最大的,0是最小的;绿色的话就是一个(0,1,0,1)
画个黑白对半的正方形
#!/jcode/lang/glsl https://xitu.github.io/jcode-languages/dist/lang-glsl.json
#version 300 es
precision highp float;
out vec4 fragColor;
uniform vec2 dd_resolution;
void main() {
vec2 st = gl_FragCoord.xy / dd_resolution;
if (st.x > 0.5) {
fragColor = vec4(1.0);
} else {
fragColor.a = 1.0;
}
}
- 通过
if、else去判断来实现黑白对半
画个圆形
#!/jcode/lang/glsl https://xitu.github.io/jcode-languages/dist/lang-glsl.json
#version 300 es
precision highp float;
out vec4 fragColor;
uniform vec2 dd_resolution; // 传入画布分辨率
void main() {
vec2 st = gl_FragCoord.xy / dd_resolution;
fragColor.rgb = step(0.5,st.x) * vec3(1.0); // webgl 自带的阶梯函数
fragColor.a = 1.0;
}
- 通过
webgl自带的阶梯函数去实现黑白对半
void main() {
vec2 st = gl_FragCoord.xy / dd_resolution;
vec2 center = vec2(0.5);
float r = 0.2;
fragColor.rgb = step(length(st - center),r) * vec3(1.0);
fragColor.a = 1.0;
}
- 这个
r半径大于两个向量st和center之间的距离,就是黑色,反之小于就是白色 - 但是你会看到圆的边缘有锯齿,这是因为我们处理的是像素点,所以在默认的情况下,如果是曲线的话,是有锯齿的;如果你的显示设备比较好的话,看起来是比较的明显
- 对锯齿进行一个消除,在阶梯函数上做一个平滑,运用
smotthstep()函数,在0.01 - d之间是平滑的,因为当靠近边缘的时候会有一个渐变的效果,就会显示比较的平滑
void main() {
vec2 st = gl_FragCoord.xy / dd_resolution;
vec2 center = vec2(0.5);
float r = 0.2;
float d = length(st - center);
fragColor.rgb = smoothstep(d - 0.01, d, r) * vec3(1.0);
fragColor.a = 1.0;
}
画个圆环
#!/jcode/lang/glsl https://xitu.github.io/jcode-languages/dist/lang-glsl.json
#version 300 es
precision highp float;
out vec4 fragColor;
uniform vec2 dd_resolution; // 传入画布分辨率
void main() {
vec2 st = gl_FragCoord.xy / dd_resolution;
vec2 center = vec2(0.5);
float r = 0.2;
float d = length(st - center);
vec3 c1 = smoothstep(d - 0.01,d,r) * vec3(1.0);
vec3 c2 = smoothstep(d,d + 0.01,r - 0.03) * vec3(1.0);
fragColor.rgb = c1-c2;
fragColor.a = 1.0;
}
- 画两个圆,一个大一点,一个小一点,大圆 - 小圆 就可实现圆环,其中里面的小圆锯齿过渡就得另外一边的距离
+0.01
封装圆环绘画函数
- 对这个圆环进行封装,我们可以用这个封装方法去划线,
#!/jcode/lang/glsl https://xitu.github.io/jcode-languages/dist/lang-glsl.json
#version 300 es
precision highp float;
out vec4 fragColor;
uniform vec2 dd_resolution; // 传入画布分辨率
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);
};
void main() {
vec2 st = gl_FragCoord.xy / dd_resolution;
float d = stroke(st.x, 0.5, 0.02, 0.1);
fragColor.rgb = d * vec3(1.0);
fragColor.a = 1.0;
}
- 通过这个封装方法去画个圆环
out vec4 fragColor;
uniform vec2 dd_resolution; // 传入画布分辨率
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);
};
void main() {
vec2 st = gl_FragCoord.xy / dd_resolution;
vec2 center = vec2(0.5);
float r = 0.2;
float d = length(st - center);
float d2 = stroke(d, r, 0.02, 0.1);
fragColor.rgb = d2 * vec3(1.0);
fragColor.a = 1.0;
}
通过计算点到某个位置的距离来构图的思路,被称之为距离场构图法
画一条斜线和曲线和正弦曲线
out vec4 fragColor;
uniform vec2 dd_resolution; // 传入画布分辨率
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);
}
void main() {
vec2 st = gl_FragCoord.xy / dd_resolution;
vec2 center = vec2(0.5);
float r = 0.2;
float d = length(st - center);
// vec3 c1 = smoothstep(d - 0.01,d,r) * vec3(1.0);
// vec3 c2 = smoothstep(d,d + 0.01,r - 0.03) * vec3(1.0);
float d = stroke(d, r, 0.02, 0.1);
fragColor.rgb = d * vec3(1.0);
fragColor.a = 1.0;
}
- 绘制斜线和曲线和正弦曲线
void main() {
vec2 st = gl_FragCoord.xy / dd_resolution;
float d1 = stroke(st.y , st.x, 0.02, 0.1 );
float d2 = stroke(st.y, 4.0 * (st.x - 0.5) * (st.x - 0.5), 0.02, 0.1);
float d3 = stroke(st.y * 2.0, 1.0 - sin(30.0 * st.x),0.02,0.1);
fragColor.rgb = d1 * vec3(1.0,1.0,0.0)
+ d2 * vec3(0.0, 1.0, 1.0)
+ d3 * vec3(0.5, 1.0, 0.5);
fragColor.a = 1.0;
}
绘制任意的直线
float sdf_line(vec2 a, vec2 b, vec2 st) {
vec2 ap = st - a;
vec2 ab = b - a;
return ((ap.x * ab.y) - (ab.x * ap.y)) / length(ab);
}
那如何绘制线段呢?
- 线段和直线的区别: 直线是两个端点是无限长的,而线段是有限长度的,所以我们需要设置线段的两端的坐标
绘制一条线段
当
P的线段落到A上,d的距离就是PA;
当P的线段落到B上的话,就是PB
uniform vec2 dd_resolution;
out vec4 fragColor;
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);
}
float sdf_line(vec2 a, vec2 b, vec2 st) {
vec2 ap = st - a;
vec2 ab = b - a;
return ((ap.x * ab.y) - (ab.x * ap.y)) / length(ab);
}
float sdf_seg(vec2 a, vec2 b, vec2 st) {
vec2 ap = st - a;
vec2 ab = b - a;
vec2 bp = st - b;
float l = length(ab);
float proj = dot(ap, ab) / l;
if(proj >= 0.0 && proj <= l) {
return sdf_line(a, b, st);
}
return min(length(ap), length(bp));
}
float sdf_plot(vec2 a, vec2 b, vec2 c, vec2 st) {
float d1 = sdf_seg(a, b, st);
float d2 = sdf_seg(b, c, st);
return min(d1, d2);
}
void main() {
vec2 st = gl_FragCoord.xy / dd_resolution;
float d1 = sdf_seg(vec2(0.3), vec2(0.7, 0.5), st);
float d2 = sdf_line(vec2(1.0, 0.0), vec2(0.0, 1.0), st);
fragColor.rgb = stroke(d1, 0.0, 0.03, 0.2) * vec3(1.0, 1.0, 0.0)
+ stroke(d2, 0.0, 0.03, 0.2) * vec3(0.0, 1.0, 1.0);
fragColor.a = 1.0;
}
那我们如何计算P 落到AB的投影呢?
-
- 叉乘是平行四边形的面积,在物理学上来说就是力臂和力臂产生的力矩。
-
- 如果是点乘的话就是这两个向量,做的功,物理的话,就是力沿着这个位移的方向做的功,所以它等于的是
ap * ab * cosδ(Delta),余弦值就是ap在ab 上的投影那个向量 * ab再除以这个,就得到了ap投影的长度
- 如果是点乘的话就是这两个向量,做的功,物理的话,就是力沿着这个位移的方向做的功,所以它等于的是
-
- 这投影长度在
0 和 l之间
- 这投影长度在
绘制任意曲线
如何绘制椭圆的线段?
GSL的宏定义
#ifndef PLOT
#define PLOT(f, st, step) sdf_plot(vec2(st.x - step, f(st.x - step)), vec2(st.x, f(st.x)), vec2(st.x + step, f(st.x + step)), st)
#endif
- 完整代码
#!/jcode/lang/glsl https://xitu.github.io/jcode-languages/dist/lang-glsl.json
#version 300 es
precision highp float;
uniform vec2 dd_resolution;
out vec4 fragColor;
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);
}
float sdf_line(vec2 a, vec2 b, vec2 st) {
vec2 ap = st - a;
vec2 ab = b - a;
return ((ap.x * ab.y) - (ab.x * ap.y)) / length(ab);
}
float sdf_seg(vec2 a, vec2 b, vec2 st) {
vec2 ap = st - a;
vec2 ab = b - a;
vec2 bp = st - b;
float l = length(ab);
float proj = dot(ap, ab) / l;
if(proj >= 0.0 && proj <= l) {
return sdf_line(a, b, st);
}
return min(length(ap), length(bp));
}
float sdf_plot(vec2 a, vec2 b, vec2 c, vec2 st) {
float d1 = sdf_seg(a, b, st);
float d2 = sdf_seg(b, c, st);
return min(d1, d2);
}
#ifndef PLOT
#define PLOT(f, st, step) sdf_plot(vec2(st.x - step, f(st.x - step)), vec2(st.x, f(st.x)), vec2(st.x + step, f(st.x + step)), st)
#endif
float fx(in float x) {
return 0.0;
}
float fy(in float x) {
return 9999999.99 * x;
}
float f1(in float x) {
return floor(x);
}
float f2(in float x) {
return sin(2.0 * x) / x;
}
void main() {
vec2 st = gl_FragCoord.xy / dd_resolution;
st = mix(vec2(-10, -10), vec2(10, 10), st);
float stp = 0.1;
float px = PLOT(fx, st, stp);
float py = PLOT(fy, st, stp);
float c1 = stroke(px, 0.0, 0.2, 0.2);
float c2 = stroke(py, 0.0, 0.2, 0.2);
float p1 = PLOT(f1, st, stp);
float c3 = stroke(p1, 0.0, 0.4, 0.2);
float p2 = PLOT(f2, st, stp);
float c4 = stroke(p2, 0.0, 0.4, 0.2);
// float d1 = sdf_seg(vec2(0.3), vec2(0.7, 0.5), st);
// float d2 = sdf_line(vec2(1.0, 0.0), vec2(0.0, 1.0), st);
fragColor.rgb = c1 * vec3(1.0, 1.0, 1.0) +
c2 * vec3(1.0, 1.0, 1.0) +
c3 * vec3(1.0, 1.0, 0.0) +
c4 * vec3(0.0, 1.0, 1.0);
fragColor.a = 1.0;
}
如果是要封装椭圆的线段方法,会非常的复杂,我需要对曲线方程比较了解;在
GSL里面支持宏定义,我们可以定义一个宏,来对这个点进行一个采样,有了这个宏就可以定义任意的曲线了
绘制三角形
/**
三角形的 SDF
*/
float sdf_triangle(vec2 st, vec2 a, vec2 b, vec2 c) {
vec2 va = a - b;
vec2 va = b - c;
vec2 va = c - a;
float d1 = sdf_line(a, b, st);
float d2 = sdf_line(b, c, st);
float d3 = sdf_line(c, a, st);
// 三角形内切圆半径
float l = abs(va.x * vb.y - va.y * vb.x) / (length(va) + length(vb) + length(vc));
// 点在三角形内部,定义距离为正
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), min(abs(d3))) / l;
}
// 点在三角形内部,定义距离为正
d1 = sdf_seg(a, b, st);
d2 = sdf_seg(b, c, st);
d3 = sdf_seg(c, a, st);
return -min(abs(d1), min(abs(d2),abs(d3))) / l;
}
void main() {
vec2 st = gl_FragCoord.xy / dd_resolution;
// st = mix(vec(-10, -10), vec2(10, 10), st);
float stp = 0.1;
float px = PLOT(fx, st, stp);
float py = PLOT(fy, st, stp);
float c1 = stroke(px, 0.0, 0.2, 0.2);
float c2 = stroke(py, 0.0, 0.2, 0.2);
float p1 = PLOT(f1, st, stp);
float c3 = stroke(p1, 0.0, 0.4, 0.2);
float p2 = PLOT(f2, st, stp);
float c4 = stroke(p2, 0.0, 0.4, 0.2);
float d = sdf_triangle(st, vec2(0.25), vec2(0.75), vec2(0.25, 0.75));
float c5 = stoke(d, 0.0, 0.2, 0.3);
// float d1 = sdf_seg(vec2(0.3), vec2(0.7, 0.5), st);
// float d2 = sdf_line(vec2(1.0, 0.0), vec2(0.0, 1.0), st);
fragColor.rgb = c5 * vec3(0.5, 0.5, 1.0);
fragColor.a = 1.0;
}
思想都是运用距离场构图法来去绘制的
void main() {
vec2 st = gl_FragCoord.xy / dd_resolution;
// st = mix(vec(-10, -10), vec2(10, 10), st);
float stp = 0.1;
float px = PLOT(fx, st, stp);
float py = PLOT(fy, st, stp);
float c1 = stroke(px, 0.0, 0.2, 0.2);
float c2 = stroke(py, 0.0, 0.2, 0.2);
float p1 = PLOT(f1, st, stp);
float c3 = stroke(p1, 0.0, 0.4, 0.2);
float p2 = PLOT(f2, st, stp);
float c4 = stroke(p2, 0.0, 0.4, 0.2);
float d = sdf_triangle(st, vec2(0.25), vec2(0.75), vec2(0.25, 0.75));
float c5 = stoke(d, 0.0, 0.2, 0.3);
// float d1 = sdf_seg(vec2(0.3), vec2(0.7, 0.5), st);
// float d2 = sdf_line(vec2(1.0, 0.0), vec2(0.0, 1.0), st);
fragColor.rgb = d * vec3(0.5, 0.5, 1.0);
fragColor.a = 1.0;
}
绘制复杂图形
中国古代钱币
钱币代码链接
花代码链接
直角坐标系转换成极坐标,还能通过随机和噪声,可以绘制比较多好玩的