在CocosCreator中初探SDF(一)

时间:2023-9-23    作者:虣虣    分类: CocosCreator


什么是SDF(Signed Distance Field)

我们线看一下这几个单词在这个语境下怎么翻译

  • Signed: 正负号
  • Distance: 点到点的距离
  • Field: 区域

所以SDF是用来判断一个点是否在一个区域内, 负数表示在区域内, 整数表示区域外

使用SDF(Signed Distance Field)进行2D图形绘制

  • 创建Shader 新建一个标准的Shader文件, 删除多余的逻辑
CCEffect %{
  techniques:
  - passes:
    - vert: sprite-vs:vert
      frag: sprite-fs:frag
      depthStencilState:
        depthTest: false
        depthWrite: false
      blendState:
        targets:
        - blend: true
          blendSrc: src_alpha
          blendDst: one_minus_src_alpha
          blendDstAlpha: one_minus_src_alpha
      rasterizerState:
        cullMode: none
      properties:
        alphaThreshold: { value: 0.5 }
}%

CCProgram sprite-vs %{
  precision highp float;
  #include <builtin/uniforms/cc-global>

  in vec3 a_position;
  in vec2 a_texCoord;
  in vec4 a_color;

  out vec2 uv0;

  vec4 vert () {
    vec4 pos = vec4(a_position, 1);
    pos = cc_matViewProj * pos;

    uv0 = a_texCoord;

    return pos;
  }
}%

CCProgram sprite-fs %{
  precision highp float;
  #include <builtin/internal/embedded-alpha>
  #include <builtin/internal/alpha-test>

  in vec2 uv0;

  float circleSDK(vec2 uv, float radius) {
    float dis = length(uv - vec2(0.5, 0.5)) - radius;
    return dis;
  }

  vec4 frag () {
    float sdf = circleSDK(uv0, 0.2);
    return vec4(sdf, sdf, sdf, 1);
  }
}%
  • 绘制圆形,用SDF来判断是否在圆上很简单,就是判断一点是否在2D平面内的圆上
float circleSDK(vec2 uv, float radius) {
    // 因为使用的是UVz坐标,想要对其屏幕的中心,所以要偏移0.5个坐标
    float dis = length(uv - vec2(0.5, 0.5)) - radius;
    // 减去半径(radius), 大于0则在圆外,小于0则在圆内
    return dis;
  }
  • 创建相应的材质球, 并关联号相应的Shader。 创建一个2D的单色的Sprited对象,并关联刚刚创建好的材质球。在场景中可以看到如下图所示:

  • 看到上面的图有一点模糊不清的味道,使用step()函数将其二值话:
vec4 frag () {
    // step函数: 参数1 > 参数2 ? 0 : 1
    float sdf = step(0.001, circleSDK(uv0, 0.2)); 
    return vec4(sdf, sdf, sdf, 1);
}

  • 如上图所示我们绘制出来的圆是一个实心的圆, 那么使用mix进行线性插值运算来绘制空心的圆
vec4 frag () {
    float sdf = circleSDK(uv0, 0.2);
    float edge = step(0.001, abs(sdf));
    // mix函数: 同lerp函数一样效果 线性插值运算
    vec4 o = mix(vec4(0, 0, 0, 1), vec4(1, 1, 1, 1), edge); 

    return o;
}

  • 绘制等高线
vec4 frag () {
    //连续增长段
    float sdf = circleSDK(uv0, 0.2) * 30.0;
    // 离散化增长段
    float seg = floor(sdf);
    //离散段绘制
    vec4 o = mix(vec4(0, 0, 0, 1), vec4(1, 1, 1, 1), seg / 5.0); 
    return o;
}

  • 把两个极点的颜色暴露给编辑器,并给出一个初始的颜色值
properties:
    centerColor: {value: [0.7, 0.5, 0.3, 1]}
    edgeColor: {value: [0.4, 0.3, 0.3, 1]}

vec4 frag () {
    float sdf = circleSDK(uv0, 0.2) * 30.0;
    float seg = floor(sdf);
    vec4 o = mix(centerColor, edgeColor, seg / 5.0);
    return o;
}

  • 绘制等距离的描边线

  vec4 frag () {
     // 连续增长段
    float sdf = circleSDK(uv0, 0.2) * 30.0;
    // 离散化增长段
    float seg = floor(sdf);
    // 连续减离散区间,得到[0, 1]的取值范围
    seg = sdf - seg; 
    vec4 o = mix(centerColor, edgeColor, seg / 5.0);
    o = mix(vec4(0, 0, 0, 1), o, smoothstep(0.0, 0.1, 0.5 - abs(seg - 0.5)));
    //查找边
    float edge = step(0.1, abs(sdf)); 
    // 绘制圆的编译
    o = mix(vec4(1, 0, 0, 1), o,  edge);
    return o;

  }

  • 绘制矩形
float squareSDF(vec2 uv, float width) {
  uv = abs(uv - 0.3) - width;
  float inner = min( max(uv.x, uv.y), 0.0); //inner
  float outter = length(max(uv, vec2(0, 0))); // outerr
  return inner + outter;
}

  • 两个sdf图形求并集操作
// 求并集
float unionSDF(float a, float b)
{
    return min(a, b);
}

// 矩形和圆求并集运算
vec4 frag () {
  float squareSdf = step(0.001, squareSDF(uv0, 0.2));
  float circleSdf = step(0.001, circleSDK(uv0, 0.2));
  float sdf = unionSDF(squareSdf, circleSdf);
  return vec4(sdf, sdf, sdf, 1);
}

  • 两个sdf图形求交集操作

// 求交集
float subtractionSDF(float a, float b)
{
    return max(a, b);
}

// 矩形和圆交集运算
vec4 frag () {
  float squareSdf = step(0.001, squareSDF(uv0, 0.2));
  float circleSdf = step(0.001, circleSDK(uv0, 0.2));
  float sdf = subtractionSDF(squareSdf, circleSdf);
  return vec4(sdf, sdf, sdf, 1);
}

更多有效干活请关注 微信公共号【游戏讲坛】

CocosCreator SDF