Cocos Creator Shader 入门 ⑾ —— 光照跟随

一、光照跟随介绍

光照跟随是一种模拟动态光源聚焦的技术,其核心表现为:画面四周呈现深色暗角,将玩家视线自然引导至中心区域------通常是主角或关键目标。

随着角色在场景中移动,这个被照亮的中心区域也随之移动,如同聚光灯始终追随主角,营造出强烈的舞台感和叙事沉浸感。

假设我们在 Cocos Creator 中有这么一个 Demo:

即一个奔跑的外星人 Spine 动画对象可以在地图中随意移动,本文将介绍如何使用着色器来实现光照跟随该外星人的效果。

二、色调调整

和以往的案例一样,我们可以通过一个额外的摄像头捕获整体画面生成 Render Texture,从而在片元着色器中对整幅画面进行像素级处理:

js 复制代码
  vec4 frag () {
    vec4 color = texture(rawRT, uv);  // rawRT 为整体画面的渲染纹理

    // 对纹理采样后的像素进行加工
  }

应用光照跟随的场景一般处于弱光环境(例如夜间态),我们可以先修改整体画面色调,让其呈现出一种昏暗、被橙红色微光覆盖的视觉:

js 复制代码
  vec3 tintHandler(vec4 color) {
    // 傍晚色调参数 (橙红色调)
    vec3 eveningTint = vec3(1.0, 0.5, 0.3);
    
    // 原色与傍晚色调混合(20%)
    vec3 blended = mix(color.rgb, eveningTint, 0.2);
    
    // 整体降低 20% 亮度
    vec3 darkenColor = blended * 0.8;

    // 获取灰阶分量
    float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
    
    // 叠加灰阶来提升饱和度
    return mix(vec3(gray), darkenColor, 1.2);
  }

  vec4 frag () {
    vec4 color = texture(rawRT, uv);

    vec3 handledRGB = tintHandler(color);
    return vec4(handledRGB, color.a);
  }

tintHandler 函数对原画面的 RGB 分量进行了逐步调整处理:

其中第 12 行通过点积来获取灰阶的方式,在之前的《灰阶、反色等滤镜的实现 --------- 一、灰阶滤镜》中便已使用过了:

js 复制代码
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));

接着通过 mix 函数叠加上灰阶色值,可修改画面的饱和度:

js 复制代码
mix(vec3(gray), darkenColor, 1.2);  // 等价于 vec3(gray) * (1.0 - 1.2) + darkenColor * 1.2

下图是通过修改 mix 的第三个参数值,来实现不同饱和度的示例:

三、添加暗角

添加暗角的方法,与《灰阶、反色等滤镜的实现 --------- 5.1.2 暗角效果》的实现一致,我们同样新增一个 vignette 函数来处理暗角:

js 复制代码
  float vignette() {
    float vignetteIntensity = 1.0; // 暗角强度使用 1.0

    // 计算到中心的距离
    vec2 center = vec2(0.5, 0.5);
    float dist = distance(uv, center);
    
    // 通过 vignetteIntensity 来控制暗角扩散程度,使用 smoothstep 来平滑边缘暗角
    // `0.3` 是光照最亮区域半径,`0.8` 是最暗区域的扩散边缘
    return 1.0 - smoothstep(0.3, 0.8, dist * vignetteIntensity);
  }

  vec4 frag () {
    vec4 color = texture(rawRT, uv);

    if (showSpot == 0) {
      return color;
    }

    vec3 handledRGB = tintHandler(color);

    return vec4(handledRGB * vignette(), color.a);
  }

💡 之前的文章已介绍过暗角代码原理,故本文不再赘述。

执行效果如下:

此时光照区域偏大,我们可以通过调整 smoothstep 函数的两个阈值参数,来缩小聚光的覆盖范围:

js 复制代码
  float vignette() {
    float vignetteIntensity = 1.0;
    vec2 center = vec2(0.5, 0.5);
    float dist = distance(uv, center);
    
    // 缩小光照区间到 [0.05, 0.5]
    return 1.0 - smoothstep(0.05, 0.5, dist * vignetteIntensity);
  }

修改后的效果:

另外由于着色器使用的是正方形的 UV 坐标系空间,而最终渲染出来的界面尺寸为 1280x720(即宽高比约为 1.78),导致圆形的光照区域被水平拉伸为椭圆形。

我们可以在 vignette 中修正这个问题:

js 复制代码
  float vignette() {
    float vignetteIntensity = 1.0; // 暗角强度
    
    // 计算画布宽高比
    float aspectRatio = 1280.0 / 720.0;
    
    // 校正 UV 坐标,让聚光区域为正圆形
    vec2 aspectCorrectedUV = uv;
    aspectCorrectedUV.x = (uv.x - 0.5) * aspectRatio + 0.5;
    
    vec2 center = vec2(0.5, 0.5);
    float dist = distance(aspectCorrectedUV, center);  // 应用 aspectCorrectedUV
    
    return 1.0 - smoothstep(0.05, 0.5, dist * vignetteIntensity);
  }

其中第 9 行为修改的关键,它将原本 X 轴上的像素,从 1280 像素压缩到 720 像素等效范围:

js 复制代码
aspectCorrectedUV.x = (uv.x - 0.5) * aspectRatio + 0.5;

此行代码可分解为:

  1. 通过 uv.x - 0.5 将 UV 坐标的 X 轴范围修改为 [-0.5, 0.5],其中心点 X 坐标变为 0.0,且左边为负,右边为正。这意味此时乘以任何数值,水平方位的像素都将向画面正中央方向去做伸缩(即确保了画面拉伸的原点处于正中央);
  2. 乘以 aspectRatio,按照屏幕的宽高比对 X 轴做一个伸缩变换,将原本的矩形画面压缩为正方形;
  3. 把坐标平移回标准的 UV 坐标空间(即 [0.0, 1.0])。

执行效果:

四、光照跟随角色

目前光照位置都是固定在画面中心,无法跟随运动中的外星人角色:

对此我们可以通过外部组件脚本,实时往着色器传入角色的 UV 坐标。

4.1 着色器修改光照中心

首先在片元着色器中新增 vec2 类型的 uniform 参数 spotCenter,它是由外部脚本传入的角色 UV 坐标:

js 复制代码
  uniform UBO {
    vec2 spotCenter;  // 新增光照中心参数
    int showSpot;
  };

  float vignette() {
    float vignetteIntensity = 1.0;
    
    float aspectRatio = 1280.0 / 720.0;
    
    vec2 aspectCorrectedUV = uv;
    aspectCorrectedUV.x = (uv.x - 0.5) * aspectRatio + 0.5;

    // 对中心点也进行宽高比校正
    vec2 correctedCenter = spotCenter;
    correctedCenter.x = (spotCenter.x - 0.5) * aspectRatio + 0.5;
    
    // 应用校正后的中心 UV 坐标
    float dist = distance(aspectCorrectedUV, correctedCenter);
    
    return 1.0 - smoothstep(0.05, 0.5, dist * vignetteIntensity);
  }

留意第 16 行的处理:传入的 spotCenter 坐标也需要进行和 aspectCorrectedUV 相同的 X 轴宽高比校正,确保两者处于同一坐标体系下。

4.2 组件脚本更新材质参数

新增 SpotComp 组件脚本,在 update 生命周期中实时获取移动中的角色坐标,转化为 UV 坐标形式后传递给着色器:

js 复制代码
@ccclass('SpotComp')
export class SpotComp extends Component {
    spineNode: Node = null;
    spriteMaterial: Material = null;

    start() {
        this.spineNode = this.node.getChildByName('Spine');
        this.spriteMaterial = this.node.getChildByName('Sprite').getComponent(Sprite).material;
    }

    update() {
        // 1. 获取角色在屏幕空间的位置
        const worldPos = this.spineNode.worldPosition;
        
        // 2. 转换为归一化 UV 坐标 (0-1 范围)
        const uvX = worldPos.x / 1280;   // 1280 x 720 是画布的宽高
        const uvY = worldPos.y / 720;

        // 3. 传递给着色器
        this.spriteMaterial.setProperty('spotCenter', v2(uvX, uvY));
    }
}

此时光照中心便能跟随角色实时移动:

相关推荐
成长ing121382 天前
闪白效果
前端·cocos creator
冷水金枪鱼3 天前
Light2D光照系统(基于CocosCreater引擎3.x/2.x)
cocos creator
VaJoy5 天前
Cocos Creator Shader 入门 ⑽ —— 拖尾效果的实现
cocos creator
VaJoy15 天前
Cocos Creator Shader 入门 ⑼ —— 溶解动画
cocos creator
VaJoy1 个月前
Cocos Creator Shader 入门 ⑺ —— 图层混合样式的实现与 Render Texture
cocos creator
VaJoy1 个月前
Cocos Creator Shader 入门 ⑹ —— 灰阶、反色等滤镜的实现
cocos creator
VaJoy1 个月前
Cocos Creator Shader 入门 ⑸ —— 代码复用与绿幕抠图技术
cocos creator
VaJoy1 个月前
Cocos Creator Shader 入门 ⑷ —— 纹理采样与受击闪白的实现
cocos creator
VaJoy2 个月前
Cocos Creator Shader 入门 ⑶ —— 给节点设置透明度
cocos creator