Erlo

PixiJS+自定义Shader实现不规则图片扫光效果

时间:2021-05-13   阅读:90次   来源:开源中国
页面报错
点赞

本来打算用原生WebGL来实现,但是水平有限,关于WebGL能讲的干货并不多,而且繁琐的准备工作对没有了解过的小伙伴也过于枯燥乏味,于是干脆用已经封装好大部分底层细节的PixiJS来实现了。

相信很多前端在做一些活动页面的时候都碰到过扫光效果的需求,有很多dom+css的奇技淫巧可以做到,比如

①直接用一个光照图片从原图上飞过,用加了overflow:hidden 的元素限制显示区域。

②把一张已经渲染出光照的图片盖在原图上,通过css的clip方法来裁剪出光照区域。

③简单粗暴地使用 序列帧/gif/视频 来完成动画。

④使用css的filter滤镜来添加高光。

当然基于①、②、③的原理,在canvas2D上也能方便地实现个功能。

虽然,但是,精zuan益niu求jiao精jian的我肯定不能满足于这些小打小闹的实现方式,图像处理自然shader就可以排上用场了。

js相关逻辑很简单:

const stats = new Stats()
document.body.appendChild(stats.domElement)

let pageWidth = 0
let pageHeight = 0

const $canvas = document.querySelector('canvas')
const renderer = new PIXI.Renderer({
  view: $canvas,
  width: pageWidth,
  height: pageHeight,
  transparent: true,
  autoDensity: true,
  antialias: true
})

let uniforms = null
const stage = new PIXI.Container()
stage.name = 'stage'
const sprite = new PIXI.Sprite()
sprite.name = 'sprite'
sprite.anchor.set(0.5, 0.5)
sprite.position.set(0, 0)
stage.addChild(sprite)

let pauseAt = 0
const ticker = new PIXI.Ticker()
const loop = function () {
  stats.begin() // 性能监控
  // 移动光线
  if (uniforms) {
    if (uniforms.offsetX >= 2.3) {
      uniforms.offsetX = 0
      pauseAt = performance.now()
    }
    else if (!pauseAt || performance.now() - pauseAt > 1000) {
      uniforms.offsetX += 0.01
      pauseAt = 0
    }
  }
  renderer.render(stage)
  stats.end()
}

ticker.add(loop)

const img = 'pyro.png'
const loader = new PIXI.Loader()
loader.add([img])
loader.onComplete.add(async () => {
  // 获取材质
  sprite.texture = loader.resources[img].texture
  // 获取片元着色器代码
  const res = await fetch('./fragmentShader.frag')
  const fragStr = await res.text()
  // 添加 uniforms 变量
  uniforms = { offsetX: 0.0, size: [sprite.width, sprite.height] }
  // 使用默认顶点着色器来创建过滤器
  const filter = new PIXI.Filter(null, fragStr, uniforms)
  sprite.filters = [filter]
  // 开始动画循环
  ticker.start()
})
loader.load()

const onResize = (e) => {
  pageWidth = document.body.clientWidth
  pageHeight = document.body.clientHeight
  sprite.position.set(pageWidth * 0.5, pageHeight * 0.5)
  renderer.resize(pageWidth, pageHeight)
}

onResize()

window.onresize = onResize

需要提到的几点:

① pauseAt是为了让两次扫光的周期间隔一段时间,我这里是1秒

② 传入Filter的 uniforms.size 属性是为了获取争取的采样坐标。PixiJS在片元着色器中提供的内置varying变量vTextureCoord 使用的是 input coords,而不是 filter coords。即Filter的贴图尺寸是2的幂,比原贴图要大。 这里是默认顶点着色器的源码,可以看到它传递vTextureCoord前十如何计算出来的。

这是片元着色器的代码:

varying vec2 vTextureCoord;
uniform vec2 inputPixel;
uniform sampler2D uSampler;
uniform vec2 size;
uniform float offsetX; // 光束偏移距离(归一化)

void main(void)
{
  vec2 uv = vTextureCoord.xy * inputPixel.xy / size.xy;
  vec4 color = texture2D(uSampler, vTextureCoord);
  float y = uv.y;
  float x = uv.x - offsetX;
  if (color.a >= 1.0) {
    // 一粗一细两束光线
    if ((y < -x && y > -x - 0.1) || (y < -x - 0.2 && y > -x - 0.25)){
      color = mix(color, vec4(1.0), 0.5);
    }
  }
  gl_FragColor = color;
}

① vec2 normalizedCoords = vTextureCoord.xy * inputPixel.xy / size.xy; 用于把过滤器贴图缩放到原贴图的尺寸内;

② 这里画了一粗一细两束光线,分别是 y=-xy=-x-0.1 两个函数图围起来的区域,和 y=-x-0.3y=-x-0.25 两个函数图围起来的区域;

③ mix函数的原型是 genType mix (genType x, genType y, genType a),结果会返回线性混合的x和y,即 x*(1−a)+y*a,我这里是以0.5的比例混合

最终效果如下:

 完整代码戳这里

在线演示1 、在线演示2

相关推荐

提交留言

评论留言

还没有评论留言,赶紧来抢楼吧~~

吐槽小黑屋()

* 这里是“吐槽小黑屋”,所有人可看,只保留当天信息。

  • Erlo吐槽

    Erlo.vip2021-09-28 03:06:44Hello、欢迎使用吐槽小黑屋,这就是个吐槽的地方。
  • 返回顶部

    给这篇文章打个标签吧~

    棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认