Fork me on GitHub

tag-2021-09-22-tag

开篇

我在Shadertoy或者WegGL中编写着色器程序时,经常需要用到许多绘制2D或者3D图形学公式。和数学公式一样,这些公式大多数时候是需要记忆的。为了后续方便记忆和查阅,本博文总结了一些我在平时开发绘制图形(尤其是3D图形)时常用的计算公式。这其中大多数都是前人的思想结晶,我会在每一份说明文字中注明算法的来源,他们有些来自书籍,有些来自视频,更多的是来自互联网上博客文字(虽然也都不是他们原创,但可以知道这些技巧被使用时的上下文)。本博文只作为记录和总结功能,并不提供创新的想法,并且后续会持续地更新。

Shapes(图形)

Remap(重分)

不知道用“重分”一词是否准确,这个公式用在2D绘制的场景中,在需要对固定的区域内进行再次归一化处理,以便处理图形。此算法是在youtube频道上看到的。

  vec2 Remap(vec2 p, float t, float r, float b, float l) {
    return vec2((p.x - r)/ (l -r), (p.y - b) / (t - b));
  }

Plot(线条)

虽然在HTML中画一条线很容易,但在shader中画一条线却没那么简单。需要用到smoothstep函数来处理。在The Book of Shaders的基础知识中看到。

  float plot_x(vec2 p, float start, float end, float blur) {
    return smoothstep(start, start + blur, p.x)
        - smoothstep(end, end + blur, p.x)
  }

  float plot_y(vec2 p, float start, float end, float blur) {
    return smoothstep(start, start + blur, p.y)
        - smoothstep(end, end + blur, p.y)
  }

Grid(表格)

这是基础分块技术,旨在把画布分成若干等分,是很多酷炫效果的基础。在The Book of Shaders的基础知识中学习到,

  vec2 uv = fract(uv * 10.0);

Signed field distance (基础2D和3D的图像算法)

符号距离场,这个名字翻译实在有些别扭,但是确实非常基础和实用的创造基础图形的方法。这些方法由著名Inigo Quilez原创,他是Shadertoy的创始人,专注图形领域二十多年,是一位真正的大神级别人物。该系列公式在他的博客中有总结,总共分2D图形和3D图像两篇。此处只做一些常用的基础的图形:

Sphere(球体)

  float sdSphere( vec3 p, float s )
  {
    return length(p)-s;
  }

Cube (立方体)

  float sdBox( vec3 p, vec3 b )
  {
    vec3 q = abs(p) - b;
    return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
  }

Cylinder (圆柱体)

  float sdRoundedCylinder( vec3 p, float ra, float rb, float h )
  {
    vec2 d = vec2( length(p.xz)-2.0*ra+rb, abs(p.y) - h );
    return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rb;
  }

颜色

RGB和HSV颜色互转

  
vec3 rgb2hsv(vec3 c){
  vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
  vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
  vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
  float d = q.x - min(q.w, q.y);
  float e = 1.0e-10;
  return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}

vec3 hsv2rgb(vec3 c){
  vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0), 6.0)-3.0)-1.0, 0.0, 1.0);
  rgb = rgb * rgb * (3.0 - 2.0 * rgb);
  return c.z * mix(vec3(1.0), rgb, c.y);
}

View(视角)

RayMarch(光线步进)

在Webgl或者OpenGL,可以直接用顶点着色器构建三维空间中的物体,而在只能使用片元着色器(fragment shader)的环境中,RayMarch算法是进入3D世界的基础算法公式。它的核心原理是利用一束光线逐步去探索一个物体的边缘的每个点,使用这个方法才能在2D绘制3D物体。它的计算非常“昂贵”,在GPU能力较差的机器上,帧数会降低。该方法在 Nathan Vaughn's Shaders Language Tutorial 有详细说明。

#define MAX_STEP  255
#define PRECISION 0.01
float RayMarch(vec2 ro, vec2 rd, float start, float end) {
  float depth = start;
  for(int i=0; i<MAX_STEP; i++) {
    vec2 p = ro + rd * depth;
    float d = (length(p) - 1.0);
    depth += d;

    if(depth < PRECISION  || depth > end) break;
  }
  return depth;
}

Camera Matrix(相机矩阵)

相机矩阵也是在无法使用顶点着色器(vertex shader)的环境中使用,它规定了一个相机的方向,视线方向从而固定了一个观察者的位置。该方法也是在Nathan Vaughn's Shaders Language Tutorial 博文中有说明。

  mat3 camera(vec3 ro, vec3 lp) {
    vec3 camera_direction = normalize(lp - ro);
    vec3 camera_right = normalize(cross(vec3(0.0, 1.0, 0.0), camera_direction));
    vec3 camera_up = normalize(cross(camera_xx, camera_direction));
    return mat3(
        -camera_direction,
        camera_right,
        -camera_up 
    );
  }

光线碰撞

抗阴影锯齿(anti aslising)

WebGL在绘制阴影的过程中,阴影会在边缘显示不平滑的效果。抗阴影锯齿使用平均取值过渡算法,让阴影变得丝滑。最初在Learn OpenGl 教程中学习到:

  float shadows = 0.0;
  float opacity= .4;// 阴影alpha值, 值越小暗度越深
  float texelSize= 1.0 / 2028.0;// 阴影像素尺寸,值越小阴影越逼真
  vec4 rgbaDepth;
  //  消除阴影边缘的锯齿 去平局差值四周围的
  for(float y=-2.0; y <= 2.0; y++){
      for(float x=-2.0; x <=2.0; x++){
          rgbaDepth = texture(u_texture, depth.xy + vec2(x, y) * texelSize);
          shadows += (depth.z - bias > rgbaDepth.r) ? 1.0 : 0.0;
      }
  }
  shadows /= 9.0;// 4*4的样本
  float visibility = min(opacity + (1.0 - shadows), 1.0); // 抗锯齿阴影

Noise(噪声)

噪声属于图形技术中较为高级的图像算法。是一种有效的模拟现实世界随机现象的方法:

Random(随机算法)

Fractal(分形)

Matrix(矩阵)

rotate(旋转)

3D的旋转矩阵虽然经常使用却对于人脑来说有些记忆成本,不过为了使的记忆简化,记住基础的2D也是一种非常方便的手段。例如处理一个3D物体在一个方向上进行旋转,就可以只用2D矩阵来处理,该方法最初是在The Book Oif Shaders中学习到,但真正的用法是在youtube中。

  mat2 rotate2D(float angle) {
    return mat2(cos(angle), -sin(angle),
                sin(angle), cos(angle));
  }

缩放与旋转是异曲同工,不再赘述:

scale(缩放)

  mat2 scale2D(vec2 r) {
    return mat2(r.x, 0.0,
                0.0, r.y
    );
  }

Materials & Light(材质和光照)

3D场景的基础光照模型---冯式光照模型也称为ADS光照模型,是让虚拟世界中的无图看起来跟家真实的有效光照模型。最初是在《自顶而下的图像学设计》中看到。

Ambient(环境光)

  gl_FragColor = ambientColor * a_color;

diffuse(漫反射)

  float fDot = dot(a_normal, u_lightDirection);
  gl_FragColor = diffuseColor * fDot * a_color;

specular(镜面反射)

  vec2 ref = reflect(a_normal, u_lightDirection);
  float fDot = clamp(dot(ref, -u_viewDirection), 0.0, 1.0);
  float n = pow(fDot, shiness);
  gl_FragColor = specularColor * n * a_color;

法线

法线其实就是一个表面的倾斜率。在webgl中,我们可以直接指定buffer中的数据每个顶点的法线。而在缺少顶点着色器的片元着色器中,我们需要通过取两个点之间连线的倾斜率来得到一个表面的倾斜率:

  vec3 calcNormal(vec3 p, float d) {
  vec2 e = vec2(1.0, -1.0) * 0.0005; // epsilon
  float r = 1.; // radius of sphere
  return normalize(
    e.xyy * d +
    e.yyx * d +
    e.yxy * d +
    e.xxx * d);
  }

Position(定位)

Canvas(坐标转换)

  vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / u_resolution.y;

极坐标

  vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / u_resolution.y
  vec2 polarCoordinate = (atan(uv.x, uv.y), length(uv));
posted on 2021-11-01 10:07  chen·yan  阅读(162)  评论(0编辑  收藏  举报