cocoscreator用shader实现移动道路

725 阅读1分钟

先来看最终效果:

tlvun-6ra87.gif

实现过程

本实例是基于cocoscreator2.4.8 创建一个场景roadScene.fire,在准备一张左右两边为透明像素的图片road.png, 图片的大小,要由项目情况而定,这里只是拿来做例子

road.png

由于packable 将图片设置为可动态合图,会使 Shader 中的 uv 是图集 uv,将导致计算错误),因此图片【属性检查器】中 packable 勾选状态为【取消】并应用。

将图片放到场景中:

2.png

这是正常默认shader显示的效果,显然不符合我们的要求。

这里需要自定义 Shader。因此我们右键创建一个 Shader,命名为 shaderRoad

为了使用此Shader,这里需要再创建一个材质并命名为matRoad

3.png

在 matRoad 的属性检查器中,将 Effect 属性设为 shaderRoad ,texture 属性设置为 iRoad 并应用。

4.png

将场景中道路节点的 Sprite 组件中的材质属性更改为 matRoad:

5.png

此时,我们成功使用了自定义的shader和材质来渲染Road精灵对象

编写shaderRoad.effect,代码如下:

CCEffect %{
  techniques:
  - passes:
    - vert: vs
      frag: fs
      blendState:
        targets:
        - blend: true
      rasterizerState:
        cullMode: none
      properties:
        texture: { value: white }
        alphaThreshold: { value: 0.5 }
        x0: { value: 0.48 }
        y0: { value: 0.8 }
        dy: { value: 0.0 }
}%
​
​
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 <cc-global>
  #include <alpha-test>
  #include <texture>
​
  in vec4 v_color;
  uniform NumasConstant {
    float x0;
    float y0;
    float dy;
  };
​
  #if USE_TEXTURE
  in vec2 v_uv0;
  uniform sampler2D texture;
  #endif
​
  void main () {
    vec4 o = vec4(1, 1, 1, 1);
​
    // 目标,重新计算 v_uv0 中的采样位置即可
    vec2 uv = vec2(v_uv0.x, v_uv0.y);
    
    if(uv.x <= 0.5) {
        // 左侧边界的直线方程为 y = y0- (y0/x0)*x; x0 取值范围为 (0, 0.5) y0 取值范围为 (0, 正无穷)
        // 计算当前渲染点对应左侧路边缘(直线)的值 s0
        float s0 = - x0/y0 *uv.y + x0;
        if(uv.x < s0) {
            uv.x = 0.0;
        }else{
            uv.x = 0.5 * (uv.x - s0)/(0.5 - s0);
        }
    }else{
        // 右侧边界的直线方程为 x = (1-x0) + x0*y/y0
        float s0 = 1.0 - x0 + uv.y * x0/y0;
        if(uv.x > s0) {
            uv.x = 1.0;
        }else{
            uv.x = 0.5 * (s0 - uv.x)/(s0 - 0.5);
        }
    }
    // 计算 v
    // 保持 v 采样空间在 [0, 1]
    uv.y = uv.y - dy;
    if(uv.y <= 0.0) {
        uv.y += 1.0;
    }
​
    #if USE_TEXTURE
      CCTexture(texture, uv, o);
    #endif
​
    o *= v_color;
​
    ALPHA_TEST(o);
​
    gl_FragColor = o;
  }
}%

创建Road组件,新建Road.ts

const {ccclass, property} = cc._decorator;
​
@ccclass
export default class Road extends cc.Component {
    private dy: number = 0;
    @property({type:cc.Sprite, visible:true}) private _sprite: cc.Sprite = null;
    start () {
        
    }
​
    update (dt) {
        this.dy += 0.01;
        if(this.dy > 1) {
            this.dy -= 1;
        }
        let _material = this._sprite.getMaterial(0);
        _material.setProperty("dy", this.dy);
    }
}

最后将Road.ts挂在场景中,设置_sprite,点击运行,就可以看到开头的效果了