creator shader:从零开始 第二篇,做个动态的光影

creator shader:从零开始 第二篇,做个动态的光影

从零开始,用shader画个彩虹

这一篇也要从创建一个新的effect开始,先改一下

先把CCProgram fs{}改成截图的样

17659860990610.5224138332503376

从纯白色的图开始,做下面的效果。

所以这个效果到底叫啥,为啥想不起来在哪见过的。蓝瘦。

17659860990640.2849917708745602

shader的随机函数

先上一个随机函数,用

o.xyz = vec3(hashOld12(uv));

,用uv生成一个随机值并填入gl_FragColor.rgb生成看一下,一个无规则的噪声图。用这个函数的功能就是从uv生成随机数 但是这是用数学计算出的伪随机数,效果是对不同uv出来的值是随机的,但是对固定uv随机,每次随机的结果也是固定的。

原理参考:[随机数生成](The Book of Shaders: Random)

 float hashOld12(vec2 p)
{
  return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
  }

17659860990750.30857330733691735

# 上正文

## 屏幕分格

 float  drawLayer(float scale,vec2 uv)
{
    // 注释设 scale为10的情况
    // 输出值
    float co = 0.;


    // 拷贝一份uv用,不修改原uv
    vec2 uv1 = uv;


    // uv值范围从 0-1变为 0-scale scale值为10就是0-10范围
    uv1 *= scale;


    // floor向下取整,计算出格子所在下标,
    // 10*10的格子,如果uv是 0.2345,0.2345,uv1就是2.345,2.345
    // 取整后 即为该uv所在格子下表,2,2
    // 0.2 <= uv < 0.3 该范围内所有uv坐标,处理后hv均为 2,2
    vec2 hv = floor(uv1);


    // fr是 fract对数字取小数部分, 0.2345,0.2345 -> uv1 2.345,2.345 -> 0.345,0.345
    // 如 0.2 <= uv < 0.3 处理后就是一个范围 0-1的范围
    vec2 fr = fract(uv1);


    // 用fr.x+fr.y作为输出看一下结果
    co += fr.x + fr.y;
    return co;
  }

17659860990790.08054376513807626

把处理后的坐标反映到颜色上可以直观的看到处理后的数值,图片已经成功分成了10*10的格子

下面,就可以用 像素点计算出来的 分格后的 下表和在格内的坐标来对每个格子画圆看一下。

## 每个格子内画圆

   vec2 hv = floor(uv1);
    vec2 fr = fract(uv1);


    // 画圆,用fr(即格内坐标)和 0.5,0.5的点的距离作为颜色值,circle范围0-0.5
    float circle = distance(fr,vec2(.5));

    // 上面的结果是距离值计算出来的,有明暗变化,用step把圆内都变成纯白色
    float radius = 0.4; // 半径
    circle = step(radius,circle);

    // 1. - circle 翻转色值,使距离圆心越近颜色越亮
    circle = 1. - circle;

    co += circle;
    co += drawGird(fr);
    return co;

## 圆太规律了,要加点变化

#### 随机函数终于用上了。

// 用hv引入随机的半径 因为一格内hv相同,随机值结果也就是半径值相等
    float radius = hashOld12(hv);

大小差异太大了。

调一下,

 // radius 0-1 映射到 0.1-0.4的范围
    radius = radius * 0.3 + 0.1;

## 每个格里都有,太规整了,去掉一半圆吧

// 半径*10取整,对2取余,舍弃一半的圆
    float f1 = mod(floor(radius * 10.),2.);
    radius *= f1;

## 还是太整齐了

#### 用hv.y做个随机,给该行的uv1.x做个差值吧

vec2 hvtemp = floor(uv1);

    float n = hashOld12(vec2(hvtemp.y));
    uv1.x += n;


    // floor向下取整,计算出格子所在下标,
    // 10*10的格子,如果uv是 0.2345,0.2345,uv1就是2.345,2.345
    // 取整后 即为该uv所在格子下表,2,2
    // 0.2 <= uv < 0.3 该范围内所有uv坐标,处理后hv均为 2,2
    vec2 hv = floor(uv1);

    // fr是 fract对数字取小数部分, 0.2345,0.2345 -> uv1 2.345,2.345 -> 0.345,0.345
    // 如 0.2 <= uv < 0.3 处理后就是一个范围 0-1的范围
    vec2 fr = fract(uv1);

## 差不多了,在多个随机亮度

......
    float radius = hashOld12(hv);
    // 亮度 用这个初始随机值做亮度用
    float strength = radius;
......
circle = 1. - circle;
circle *= strength;
......

# 从creator传入参数

### 传入几个颜色看一下

        colorLeft: {  value: [1.,1.,1.,1.],editor: { type: "color"}}
        colorRight: { value: [1.,1.,1.,1.],editor: { type: "color"}}
        color1: { value: [1.,1.,1.,1.],editor: { type: "color"}}
        color2: { value: [1.,1.,1.,1.],editor: { type: "color"}}
        color3: { value: [1.,1.,1.,1.],editor: { type: "color"}}

一定要注意缩进,这个格式的文件格式很严格

加入这两块地方,片元着色器就可以访问这一堆变量了,从材质面板也可以编辑这一堆变量的值了。

17659860991070.8355974273632558

## 加个背景色 在多家几层颜色

  void main () {
    vec2 uv = vec2(v_uv0.x,v_uv0.y);
    vec3 co = vec3(0.);
    // 加个背景色
    co += mix(colorLeft,colorRight,uv.y).xyz;

    vec4 carr[3];
    carr[0] = color1;
    carr[1] = color2;
    carr[2] = color3;

    for(int i = 0;i < 3;i++ ){
      float idx = float(i);
      // 用循环下表做一个递增的层半径
      float p1 = idx * 5. + 3.;


      // 给每一层做一个随机运动方向 也就是一个速度向量
      vec2 uvoff = vec2(hashOld12(vec2(p1)),hashOld12(vec2(p1 * 10.0)));
      // 速度*时间 = 偏移距离 让该层随时间运动 可以注释掉 *u_time 就不会运动了
      uvoff = uvoff *u_time * .1;

      vec2 p2 = vec2(uv.x,uv.y) + uvoff;


      // p1 半径, p2 供计算的uv值
      float layer = drawLayer(p1,p2);

      co += layer * carr[i].xyz;
    }

    gl_FragColor = vec4(co,1.);
  }

17659860991160.5401123825711822

## 每一层都很亮,要做出远暗进亮怎么办?

#### 让层亮度和格子大小成正比比例

// 让层亮度和格子大小成正比比例 (scale是uv的缩放数,越大 格子就越小)
    // * 9 是因为有些暗,变亮点,这个值可以随便调调
    strength *= 1. / scale * 9.;
    circle *= strength;

17659860991130.3204700502076654
现在,可以运行起来看一看了,

## 动态给shader传入参数 u_time

sp.getMaterial(0).effect.setProperty('u_time',this._utime);

this._utime自己用update累加一下哈 我的是每帧 += 0.01;

用js脚本实时修改shader的u_time的值,这时画面上的圆圈应该都能动起来了。

## 圆圈优化

    //circle = step(radius,circle); 这一行注释掉,用下一行。这时模糊圆圈边缘的函数,0.02*scale就是模糊的宽度,这个系数也可以自己调整到喜欢的数值,
    // 这个系数和strength乘的系数调整个不同的值,组合起来效果也大不一样。
circle = smoothstep(radius - .02 * scale,radius,circle);

17659860991190.1006881899816311

我的审美也就止步于此了。。
17659860991190.1006881899816311


公众号欢迎关注

17659861000750.2599713665486282

6赞

你这个系列时间跨度是不是有点长了

补了链接了

啥跨度,毕竟停更狗一直都存在,就是我哈哈哈 入门的东西不多,就那么多可写的,举一反三之后其实没啥可写的了

顶顶顶~ 这个 hashOld12 很实用

有点意思哈2

交换了紫薯布丁

老板,可以调整下,感觉看着像是高度近视看着的效果一样。有点晕啊

交作业


learnShader2.zip (3.7 MB)

// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.

CCEffect %{
techniques:
  - passes:
      - vert: vs
        frag: fs
        blendState:
          targets:
            - blend: true
            - blendSrc: src_alpha
            - blendDst: one_minus_src_alpha
        rasterizerState:
          cullMode: none
        properties:
          texture: { value: white }
          color1: { value: [1., 1., 1., 1.], editor: { type: "color" } }
          color2: { value: [1., 1., 1., 1.], editor: { type: "color" } }
          color3: { value: [1., 1., 1., 1.], editor: { type: "color" } }
          alphaThreshold: { value: 0.5 }
          u_time: { 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 <alpha-test>
#include <texture>

in vec4 v_color;

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

uniform Constants1 {
  vec4 color1;
  vec4 color2;
  vec4 color3;
  float u_time;
};

float drawGird(vec2 p) {
  if (p.x > 0.0 && p.x < 0.01) {
    return 1.0;
  }
  if (p.x > 0.99 && p.x < 1.0) {
    return 1.0;
  }
  if (p.y > 0.0 && p.y < 0.01) {
    return 1.0;
  }
  if (p.y > 0.99 && p.y < 1.0) {
    return 1.0;
  }
  return 0.0;
}

/**
* 简单的2D哈希函数
* @param p 输入2D向量
* @return 0.0~1.0范围内的哈希值
*/
float hashOld12(vec2 p)
{
  return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
}


/**
* 按指定缩放比例划分UV网格,并计算当前UV在网格中的视觉输出值
* @param scale 网格数量(如scale=10则将整个纹理划分为10x10的网格)
* @param uv 原始UV坐标(范围0.0~1.0)
* @return 基于网格位置的输出值(用于后续颜色计算)
*/
float drawLayer(float scale, vec2 uv)
{
  // 最终输出值,初始化为0.0
  float co = 0.0;
  
  // 复制原始UV坐标,避免修改外部传入的uv值
  vec2 uv1 = uv;
  
  // 将原始UV(0.0~1.0)拉伸到 0.0~scale 范围(如scale=10时,uv(1.0,1.0)对应uv1(10.0,10.0))
  // 目的是把整个纹理区域划分为 scale×scale 个均等的小格子
  uv1 *= scale;
  
  vec2 hvtemp = floor(uv1);
  
  float n = hashOld12(vec2(hvtemp.y));
  uv1.x += n;
  
  // floor向下取整,计算出格子所在下标,
  // 10*10的格子,如果uv是 0.2345,0.2345,uv1就是2.345,2.345
  // 取整后 即为该uv所在格子下表,2,2
  // 0.2 <= uv < 0.3 该范围内所有uv坐标,处理后hv均为 2,2
  vec2 hv = floor(uv1);
  
  // fr是 fract对数字取小数部分, 0.2345,0.2345 -> uv1 2.345,2.345 -> 0.345,0.345
  // 如 0.2 <= uv < 0.3 处理后就是一个范围 0-1的范围
  vec2 fr = fract(uv1);
  
  // 画圆,用fr(即格内坐标)和 0.5,0.5的点的距离作为颜色值,circle范围0-0.5
  float circle = distance(fr, vec2(0.5));
  
  // 上面的结果是距离值计算出来的,有明暗变化,用step把圆内都变成纯白色
  float radius = hashOld12(hv); // 半径
  
  // 亮度 用这个初始随机值做亮度用
  float strength = radius;
  
  // 让层亮度和格子大小成正比比例 (scale是uv的缩放数,越大 格子就越小)
  // * 9 是因为有些暗,变亮点,这个值可以随便调调
  strength *= 1.0 / scale * 16.0;
  
  // radius 0-1 映射到 0.1-0.4的范围
  radius = radius * 0.3 + 0.1;
  
  // 半径*10取整,对2取余,舍弃一半的圆
  float f1 = mod(floor(radius * 10.0), 2.0);
  radius *= f1;
  
  // circle = step(radius, circle); // 这一行注释掉,用下一行。这时模糊圆圈边缘的函数,0.02*scale就是模糊的宽度,这个系数也可以自己调整到喜欢的数值,
  // 这个系数和strength乘的系数调整个不同的值,组合起来效果也大不一样。
  circle = smoothstep(radius - 0.02 * scale, radius, circle);
  
  // 1. - circle 翻转色值,使距离圆心越近颜色越亮
  circle = 1.0 - circle;
  circle *= strength;
  
  co += circle;
  // co += drawGird(fr);
  
  return co;
}

void main() {
  // 分层绘制
  vec2 uv = vec2(v_uv0.x, v_uv0.y);
  vec3 co = vec3(0.6);
  
  // ❄️雪下降的速度
  float snowSpeed = -5.0;
  
  // 用于计算总Alpha值
  float totalAlpha = 0.0;
  
  vec4 carr[3];
  carr[0] = color1;
  carr[1] = color2;
  carr[2] = color3;
  for(int i = 0; i < 3; i ++ ) {
    float idx = float(i);
    // 用循环下标做一个递增的层半径
    float p1 = idx * 0.2 + 16.0;
    
    // 给一个向下的方向
    // 给每一层做一个随机运动方向 也就是一个速度向量
    vec2 uvoff = vec2(hashOld12(vec2(p1)), hashOld12(vec2(p1 * 10.0)) * snowSpeed);
    // 速度*时间 = 偏移距离 让该层随时间运动 可以注释掉 *u_time 就不会运动了
    uvoff = uvoff * u_time * 0.1;
    
    vec2 p2 = vec2(uv.x + idx * 0.12, uv.y + idx * 0.08) + uvoff;
    
    // p1 半径, p2 供计算的uv值
    float layer = drawLayer(p1, p2);
    
    // 累加颜色
    co += layer * carr[i].xyz;
    // 累加Alpha值(取各层的最大值,确保有图案的地方alpha不为0)
    totalAlpha = max(totalAlpha, layer);
  }
  
  // 设置最终颜色,alpha值等于图案的总亮度(背景区域为0,即透明)
  // 可以根据需要调整alpha的强度,比如 totalAlpha * 0.8 让图案半透明
  gl_FragColor = vec4(co, totalAlpha);
}
}%

ts脚本:

const { ccclass, property } = cc._decorator;

@ccclass
export class Dots extends cc.Component {

    @property({ type: cc.Sprite, tooltip: CC_DEV && '霓虹灯' })
    private neon: cc.Sprite = null;

    private _utime: number = 0;

    update() {
        this.neon.getMaterial(0).setProperty("u_time", this._utime);
        this._utime += 0.01;
    }

}