Cocos Creator 3.x 角色残影效果实现

1.前言

当我们玩游戏时,经常会注意到一种神奇的效果:游戏中的角色快速移动或者进行动作时,会留下一道闪烁的残影。这个效果非常酷,给游戏增加了动感和视觉吸引力。

那么,这个酷炫的效果是如何实现的呢?在本文中,我们将揭开人物残影效果的秘密,告诉你它是如何在游戏中实现的,以及为什么它对游戏的吸引力如此之大。

2.效果展示

3.原理

1.思路

说起残影,大家都应该能想象到他的产生原因。对应到游戏开发过程中就是将角色当前的图像与一定时间间隔之前的图像叠加起来,再进行对应透明度的变化来实现最终的效果;通常情况想要实现残影的效果有两种方案:

  • 1.通过去获取指定帧角色网格数据,生成一个新的网格模型,最终的效果是通过同时显示n个网格模型来实现

  • 2.通过屏幕后处理方式,获取指定帧数的角色图像效果,最终的效果是通过n张获取的角色效果叠加实现

2.实现

1.RenderTexture

「确定了我们需要添加残影的对象是角色,所以我们只需要拿到角色的渲染图像信息」

  • 项目设置中新增Layers,命名为Player

  • 将角色节点以及其所有子节点Layer 设置为Layer

  • 设置主相机Main Camera 的Visibility,将Player设置为不可见

  • 新增摄像机,命名Ghost Camera,设置其Visibility仅可见Player

  • 设置相机Ghost Camera Clear Flags为SOLID_COLOR

  • 设置相机Ghost Camera Clear Color的颜色为黑色,透明度为0

「通过以上步骤,我们已经创建了一个只渲染指定角色的摄像机,那么接下来我们将渲染的结果输出到屏幕上」

  • Canvas 想创建Sprite,命名Ghost Sprite,节点size 和设计分辨率保持一致

  • 创建RT,并且将rt渲染到Sprite上

private initRenderTexture(): void {
    this._renderTexture = new RenderTexture();
    this._renderTexture.reset({
        width: this._mainCamera.camera.width,
        height: this._mainCamera.camera.height,
    });
    this._ghostCamera.targetTexture = this._renderTexture;
    let spriteFrame: SpriteFrame = new SpriteFrame();
    spriteFrame.texture = this._renderTexture;
    this.ghostSprite.spriteFrame = spriteFrame;
    console.log("初始化rt", this._renderTexture);
}

「这个时候我们已经将角色通过rt的方式显示到了屏幕上」

2.相机

「想在场景中有两个摄像机,Main Camera和Ghost Camera;我们需要保持Ghost Camera节点的属性与Main Camera的保持一致」

private updateCameraTransform(): void {
    if (!this.ghostCameraNode || !this.mainCameraNode) return;
    this.ghostCameraNode.worldPosition = this.mainCameraNode.worldPosition;
    this.ghostCameraNode.worldScale = this.mainCameraNode.worldScale;
    this.ghostCameraNode.worldRotation = this.mainCameraNode.worldRotation;
}

3.残影

「接下来就是重点了...」

1.保存rt渲染图像

「上边已经提过了,咱们的实现方式是通过将n张保存的渲染图像再次进行叠加去实现残影的效果,那么首要就是去保存指定帧rt的数据;引擎是没有提供直接的接口的,通过查看源码和翻阅资料,可以通过以下代码去实现」

let textureBuffer: Uint8Array = this._renderTexture.readPixels();
//通过texture buffer 创建texture 
let img = new ImageAsset();
img.reset({
    _data: textureBuffer,
    width: this._renderTexture.width,
    height: this._renderTexture.height,
    format: Texture2D.PixelFormat.RGBA8888,
    _compressed: false
});
let texture: Texture2D = new Texture2D();
texture.image = img;
2.图像队列

「通过上边代码我们可以拿到指定帧rt的数据,并将其转化为Texture2D 格式,但是想要实现残影,仅仅只有一张是不行的,我们需要多张图像;」

  • 1.定义图像队列

private _ghostTexures: Array<Texture2D> = null;
    1. 指定数量

@property
_ghostNum: number = 5;
@property({ type: CCInteger })
public get ghostNum() {
    return this._ghostNum;
};
  • 3.指定获取图像间隔

@property
_ghostInterval: number = 0.1;
@property({ type: CCFloat })
public get ghostInterval() {
    return this._ghostInterval;
};
public set ghostInterval(val) {
    this._ghostInterval = val;
};
  • 4.获取图像队列

private updateGhostTexures(deltaTime: number): void {
  this._ghostIntervalDt += deltaTime;
  if (this._ghostIntervalDt >= this.ghostInterval) {
      this._ghostIntervalDt = 0;
  } else {
      return;
  }
  if (!this.ghostCameraNode || !this.mainCameraNode) return;
  if (this._ghostTexures.length >= this._ghostNum) {
      // 删除最后一个
      let texture: Texture2D = this._ghostTexures.splice(this._ghostNum - 1, 1)[0];
      texture.destroy();
  }

  let textureBuffer: Uint8Array = this._renderTexture.readPixels();
  //通过texture buffer 创建texture 
  let img = new ImageAsset();
  img.reset({
      _data: textureBuffer,
      width: this._renderTexture.width,
      height: this._renderTexture.height,
      format: Texture2D.PixelFormat.RGBA8888,
      _compressed: false
  });
  let texture: Texture2D = new Texture2D();
  texture.image = img;
  this._ghostTexures.unshift(texture);
}

「那么到此咱们已经成功的获取到了我们残影实现所需要的图像队列,接下来就需要通过自定义的材质将图像进行叠加」

4.shader
  • 1.复制引擎自带的builtin-sprite到项目中

  • 2.创建材质文件,指定effect为复制出来的builtin-sprite

  • 3.将材质文件绑定到Ghost Sprite 节点,Sprite组件的CustomMaterial;

「进行完上边的操作,接下来我们就可以通过编写shader 进行图像的叠加」

  • 定义属性

CCEffect %{
  techniques:
  - passes:
    - vert: sprite-vs:vert
      ....
      properties:
        alphaThreshold: { value: 0.5 }
        ghostTexure_0: {value: white }
        ghostTexure_1: {value: white }
        ghostTexure_2: {value: white }
        ghostTexure_3: {value: white }
        ghostTexure_4: {value: white }
}%
  • 片元着色器进行叠加

CCProgram sprite-fs %{
  ...
  #if USE_TEXTURE
    in vec2 uv0;
    #pragma builtin(local)
    layout(set = 2, binding = 12) uniform sampler2D cc_spriteTexture;
    uniform sampler2D ghostTexure_0;
    uniform sampler2D ghostTexure_1;
    uniform sampler2D ghostTexure_2;
    uniform sampler2D ghostTexure_3;
    uniform sampler2D ghostTexure_4;
  #endif
  vec4 frag () {
    vec4 o = vec4(1, 1, 1, 1);
    #if USE_TEXTURE
      //残影采样,并且计算颜色值以及透明度
      vec4 col0=CCSampleWithAlphaSeparated(ghostTexure_0, uv0);
      vec4 col1=CCSampleWithAlphaSeparated(ghostTexure_1, uv0);
      vec4 col2=CCSampleWithAlphaSeparated(ghostTexure_2, uv0);
      vec4 col3=CCSampleWithAlphaSeparated(ghostTexure_3, uv0);
      vec4 col4=CCSampleWithAlphaSeparated(ghostTexure_4, uv0);
      vec4 col= vec4(1, 1, 1, 1);
      col=(col0+col1+col2+col3+col4)/5.0;
      o=col;
    #endif
    o *= color;
    ALPHA_TEST(o);
    return o;
  }
}%

「此时一个简单的残影的效果就可以正常显示了」

「虽然此时的效果已经显示出来了,但是在显示上已经存在一些问题」

  • 1.上边的材质中只对传入的图像队列进行了叠加输出,但是没有包含原有的角色对象

  • 2.残影应该有透明度减淡的效果体现

  • 3.原角色显示区域不需要进行叠加处理

「基于以上两点,对片元着色器进行优化」

vec4 frag () {
  vec4 o = vec4(1, 1, 1, 1);

  #if USE_TEXTURE
     o *= CCSampleWithAlphaSeparated(cc_spriteTexture, uv0);
    //残影采样,并且计算颜色值以及透明度
    vec4 col0=CCSampleWithAlphaSeparated(ghostTexure_0, uv0);
    vec4 col1=CCSampleWithAlphaSeparated(ghostTexure_1, uv0);
    vec4 col2=CCSampleWithAlphaSeparated(ghostTexure_2, uv0);
    vec4 col3=CCSampleWithAlphaSeparated(ghostTexure_3, uv0);
    vec4 col4=CCSampleWithAlphaSeparated(ghostTexure_4, uv0);
    col0.a*=0.3;
    col1.a*=0.4;
    col2.a*=0.3;
    col3.a*=0.1;
    col4.a*=0.05;
    float a=max(col0.a,col1.a);
    a=max(col2.a,a);
    a=max(col3.a,a);
    a=max(col4.a,a);
    vec4 col= vec4(1, 1, 1, 1);
    col.rgb=(col0.rgb+col1.rgb+col2.rgb+col3.rgb+col4.rgb)/5.0;
    col.a=a;

    float r=step(o.a,a);
    o=mix(o,col,r);
  #endif
  o *= color;
  ALPHA_TEST(o);
  return o;
}

4.写在结尾

以上就是宗宝通过后处理 rt实现残影的大致流程,当然这种实现方式可能会在某些情况下无法满足需求,大家有更好的方式欢迎分享

  • 宗宝微信:「Carlos13207」

  • 源码示例获取方式
    • 「关注宗宝公众号:穿越的杨宗宝, 回复:"残影"」

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Cocos Creator模拟砸金蛋3d旋转效果 | 附代码egg.zip // Learn TypeScript: // - [Chinese] https://docs.cocos.com/creator/manual/zh/scripting/typescript.html // - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/typescript.html // Learn Attribute: // - [Chinese] https://docs.cocos.com/creator/manual/zh/scripting/reference/attributes.html // - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/reference/attributes.html // Learn life-cycle callbacks: // - [Chinese] https://docs.cocos.com/creator/manual/zh/scripting/life-cycle-callbacks.html // - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/life-cycle-callbacks.html const {ccclass, property} = cc._decorator; @ccclass export default class Game extends cc.Component { @property Count: number = 5; @property(cc.Prefab) prefab: cc.Prefab = null; @property(cc.Node) nodeParent: cc.Node = null; private mEggs: cc.Node[] = []; // LIFE-CYCLE CALLBACKS: // onLoad () {} start () { } // update (dt) {} onClick(event, data){ switch(data){ case 'add':{ this.addEggs(); break; } case 'move':{ this.moveEggs(); break; } case 'stop':{ this.stopMoveEggs(); break; } } } addEggs(){ if(this.Count <= 0){ return; } this.mEggs = []; const N = 360 / this.Count; for(let i = 0; i < this.Count; i++){ let egg = cc.instantiate(this.prefab); let js = egg.getComponent('Egg'); js.setRadian(i * N * Math.PI / 180); js.updatePos(); egg.parent = this.nodeParent; this.mEggs.push(egg); } } moveEggs(){ for(let i = 0; i < this.mEggs.length; i++){ this.mEggs[i].getComponent('Egg').setMove(true); } } stopMoveEggs(){ for(let i = 0; i < this.mEggs.length; i++){ this.mEggs[i].getComponent('Egg').setMove(false); } } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穿越的杨宗宝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值