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));
    }
}

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

相关推荐
LcGero1 个月前
TypeScript 快速上手:泛型与工具类型
typescript·cocos creator·游戏开发
LcGero1 个月前
Cocos Creator 3.x 高维护性打字机对话系统设计与实现
cocos creator·打字机
LcGero1 个月前
Cocos Creator 三端接入穿山甲 SDK
sdk·cocos creator·穿山甲
LcGero1 个月前
Cocos Creator平台适配层框架设计
cocos creator·平台·框架设计
LcGero1 个月前
Cocos Creator 业务与原生通信详解
android·ios·cocos creator·游戏开发·jsb
LcGero1 个月前
TypeScript 快速上手:前言
typescript·cocos creator·游戏开发
Setsuna_F_Seiei1 个月前
CocosCreator 游戏开发 - 多维度状态机架构设计与实现
前端·cocos creator·游戏开发
CodeCaptain4 个月前
cocoscreator 2.4.x 场景运行时的JS生命周期浅析
cocos creator·开发经验
CodeCaptain4 个月前
CocosCreator 3.8.x [.gitignore]文件内容,仅供参考
经验分享·cocos creator
VaJoy6 个月前
Cocos Creator Shader 入门 (21) —— 高斯模糊的高性能实现
前端·cocos creator