Shader实现聚光灯效果(Follow-spot)

效果

聚光灯效果,主要用于突出游戏中的角色或特殊物体,并在黑暗环境中营造沉浸式的氛围,效果如下:

思路

效果的核心:

  • 控制光圈的形状和大小
  • 边缘虚化
  • 控制移动

前两点需要着色程序来控制,第三点通过外部脚本控制光圈的移动。

光圈实现

  • 圆形光圈:通过计算像素与圆心的距离,判断其是否在圆内。在圆内的像素保持原样,圆外的像素则设置透明度为0,从而实现一个圆形光圈。
  • 圆心坐标 :圆心默认在纹理的中间,坐标设置为 vec2(0.5,0.5)
  • 透明度控制 :使用 step 函数来控制透明度。如果像素距离圆心的距离大于半径,则透明度为0,否则为1

代码如下:

typescript 复制代码
void main () {
    vec4 color = vec4(1, 1, 1, 1);
    color *= texture(texture, v_uv0);
    color *= v_color;

    color.a = step(length(v_uv0 - vec2(0.5,0.5)), 0.5);
    gl_FragColor = color;
}

其中step为阶梯函数,它接受两个参数,一个边界值edge,一个输入变量value, 当value >= edge时,返回1,否则返回0.

typescript 复制代码
function step<T>(edge: T, value: T): T {
    return value >= edge ? 1.0 : 0.0;
}

通过step(length(v_uv0 - vec2(0.5,0.5)), 0.5);得出纹理坐标v_uv0到中心点vec2(0.5, 0.5)的距离小于半径0.5,透明度为1,可以展示,大于半径0.5, 透明度为0,不展示。

宽高比修正

仔细看后,发现光圈是椭圆的,不是圆的,因为在shader中,不管纹理的真实宽高是多少,纹理坐标 v_uv0 的 x 和 y 分量都是在 [0, 1] 范围内,而不考虑实际的宽高比,所以直接使用 v_uv0 来计算距离会导致椭圆形状的光圈,特别是在非正方形的纹理上。

为了确保圆形保持其形状,你需要调整 x 或 y 分量,使其在计算距离时考虑到纹理的实际宽高比。以下是相应的修改:

typescript 复制代码
vec4 color = vec4(1, 1, 1, 1);
color *= texture(texture, v_uv0);
color *= v_color;

float aspectRatio = ratio; // 纹理的宽高比(宽度除以高度),需要外部脚本计算好传进来
vec2 adjustedUV = vec2(v_uv0.x * aspectRatio, v_uv0.y);
float radius = 0.3; // 光圈半径,可以根据需要调整

color.a = step(length(adjustedUV - vec2(0.5 * aspectRatio, 0.5)), radius);
gl_FragColor = color;

在这段代码中,aspectRatio 是纹理的宽高比。通过将 v_uv0.x 乘以这个比例,我们调整了 x 坐标,使得圆形在宽高不同的纹理上依然保持圆形。同样,圆心坐标也进行了相应的调整。

注意,aspectRatio 的值需要从你的应用或游戏引擎中获取,通常是纹理的宽度除以高度。如果纹理是正方形的,那么这个值应该是 1。如果是宽度大于高度的长方形,这个值将大于 1;反之,如果高度大于宽度,这个值将小于 1。

外部计算背景图的宽高比,传入到shader

typescript 复制代码
this._material.setProperty('ratio', this.bg.width / this.bg.height);

通过这种方式,你可以确保在不同宽高比的纹理上都能绘制出一个标准的圆形光圈。并且这里将光圈的半径设置为0.3,减小光圈的大小。

边缘虚化

上面的图中光圈的边缘有明显的锯齿,为了使光圈的边缘更加平滑,要在着色器中实现边缘虚化处理,可以使用了 smoothstep 函数。这个函数可以在指定的范围内生成平滑的过渡,从而实现边缘虚化的效果。

typescript 复制代码
...
float blurWidth = blur; // 边缘虚化的宽度,通过外部传入

// 计算像素到圆心的距离
float dist = length(adjustedUV - vec2(0.5 * aspectRatio, 0.5));

// 使用 smoothstep 实现边缘虚化
color.a = 1.0 - smoothstep(radius - blurWidth, radius, dist);
...
  • blurWidth 是边缘虚化的宽度,这个值决定了虚化效果的强度和范围。
  • smoothstep 的前两个参数 radius - blurWidthradius 定义了虚化过渡的区间。dist 是当前处理像素与圆心的距离。当 dist 在这个区间内时,smoothstep 会返回一个在 0 到 1 之间平滑变化的值,从而实现虚化效果。

调整 blurWidth 的值可以控制边缘的虚化程度。较大的 blurWidth 值会产生更宽、更平滑的边缘,而较小的值则会产生较窄、较尖锐的边缘。

效果如下:

控制移动

片段着色器中定义一个 uniform 变量来代表圆心坐标,然后在外部脚本中更新这个坐标。以下是修改后的着色器代码,其中包含了一个 uniform vec2 center; 声明,以及如何在外部脚本中更新这个变量的说明:

typescript 复制代码
uniform vec2 center; // 外部脚本控制的圆心坐标
...
vec2 adjustedCenter = vec2(center.x * aspectRatio, center.y); 
// 计算像素到圆心的距离 
float dist = length(adjustedUV - adjustedCenter);
...

在外部脚本中控制圆心坐标:

typescript 复制代码
_onTouchMove(event: cc.Event.EventTouch) {
    this.center[0] += event.getDeltaX() / this.bg.width;
    this.center[1] -= event.getDeltaY() / this.bg.height;
    this._material.setProperty('center', this.center);
}

完整代码

typescript 复制代码
// FollowSpot.ts
const { ccclass, property } = cc._decorator;

@ccclass
export default class FollowSpot extends cc.Component {
  /** 背景 */
  @property(cc.Node)
  bg: cc.Node = null;

  /** 材质 */
  private _material: cc.Material = null;

  /** 光圈起点 */
  center: number[] = [0.1, 0.5];

  onLoad() {
    this._material = this.bg.getComponent(cc.Sprite).getMaterial(0);
    this._material.setProperty('ratio', this.bg.width / this.bg.height);
    this._material.setProperty('center', this.center);

    this.bg.on(cc.Node.EventType.TOUCH_MOVE, this._onTouchMove, this);
  }

  _onTouchMove(event: cc.Event.EventTouch) {
    this.center[0] += event.getDeltaX() / this.bg.width;
    this.center[1] -= event.getDeltaY() / this.bg.height;
    this._material.setProperty('center', this.center);
  }
}


// FollowSpot.effect
CCEffect %{
  techniques:
  - passes:
    - vert: vs
      frag: fs
      blendState:
        targets:
        - blend: true
      rasterizerState:
        cullMode: none
      properties:
        texture: { value: white }
        ratio: {
          value: 1.5,
          editor: {
            tooltip: "宽高比"
          }
        }
        blur: {
          value: 0.35,
          editor: {
            tooltip: "光圈模糊程度"
          }
        }
        radius: {
          value: 0.5,
          editor: {
            tooltip: "光圈半径"
          }
        }
        center: {
          value: [0.5, 0.5],
          editor: {
            tooltip: "光圈起点"
          }
        }
}%


CCProgram vs %{
  precision highp float;

  #include <cc-global>
  #include <cc-local>

  in vec3 a_position;
  in vec4 a_color;
  out vec4 v_color;

  #if USE_TEXTURE
  in vec2 a_uv0;
  out vec2 v_uv0;
  #endif

  void main () {
    vec4 pos = vec4(a_position, 1);

    #if CC_USE_MODEL
    pos = cc_matViewProj * cc_matWorld * pos;
    #else
    pos = cc_matViewProj * pos;
    #endif

    #if USE_TEXTURE
    v_uv0 = a_uv0;
    #endif

    v_color = a_color;

    gl_Position = pos;
  }
}%

CCProgram fs %{

  precision highp float;

  #include <alpha-test>

  in vec4 v_color;

  #if USE_TEXTURE
  in vec2 v_uv0;
  uniform sampler2D texture;
  #endif

  uniform ARGS{
    float radius;
    float blur;
    vec2 center;
    float ratio;
  };

  void main () {
    vec4 color = vec4(1, 1, 1, 1);
    color *= texture(texture, v_uv0);
    color *= v_color;

    float aspectRatio = ratio; // 纹理的宽高比(宽度除以高度)
    vec2 adjustedUV = vec2(v_uv0.x * aspectRatio, v_uv0.y);
    vec2 adjustedCenter = vec2(center.x * aspectRatio, center.y);
    float radius = 0.3; // 光圈半径,可以根据需要调整

    float blurWidth = blur; // 边缘虚化的宽度

    // 计算像素到圆心的距离
    float dist = length(adjustedUV - adjustedCenter);

    // 使用 smoothstep 实现边缘虚化
    color.a = 1.0 - smoothstep(radius - blurWidth, radius, dist);

    gl_FragColor = color;

  }
}%

到此就实现了开头的效果。

相关推荐
VaJoy17 天前
Cocos Creator Shader 入门 ⑺ —— 图层混合样式的实现与 Render Texture
cocos creator
VaJoy19 天前
Cocos Creator Shader 入门 ⑹ —— 灰阶、反色等滤镜的实现
cocos creator
VaJoy21 天前
Cocos Creator Shader 入门 ⑸ —— 代码复用与绿幕抠图技术
cocos creator
VaJoy22 天前
Cocos Creator Shader 入门 ⑷ —— 纹理采样与受击闪白的实现
cocos creator
VaJoy24 天前
Cocos Creator Shader 入门 ⑶ —— 给节点设置透明度
cocos creator
VaJoy1 个月前
Cocos Creator Shader 入门 (2) —— 给节点染色
cocos creator
VaJoy1 个月前
Cocos Creator Shader —— 附录
cocos creator
成长ing121381 个月前
多层背景视差滚动Parallax Scrolling
cocos creator
似水流年wxk1 个月前
cocos creator使用jenkins打包微信小游戏,自动上传资源到cdn,windows版运行jenkins
运维·jenkins·cocos creator
成长ing121383 个月前
点击音效系统
前端·cocos creator