追光效果(shader)

效果演示

追光效果是在舞台全场黑暗的情况下用光柱突出角色或其他特殊物体,通过人为操控光源跟随人物移动,主要用来突出角色主体以及主体和环境的关系。在游戏中可以用来突出氛围以及聚焦玩家视线焦点,不仅可以用来营造沉浸式氛围,也可以用在解谜或者找物品等类别的游戏中。

实现思路

根据实际效果可以提炼出3个功能点:光圈的形状和大小可控,光圈的边缘虚化,光圈可操控移动。

光圈是一个圆,假设圆心在纹理的中间,它的坐标是vec2(0.5,0.5),我们只需让到圆心的距离大于半径的像素丢弃或者透明度为0,代码如下:

void main () {
  vec4 color = vec4(1, 1, 1, 1);
  color *= texture(texture, v_uv0);
  color *= v_color;

  color.a = step(length(v_uv0 - vec2(0.5,0.5)), 0.1);
  gl_FragColor = color;
}

其中step 是内置的规整函数 step(a, x) = x >= a? 1 : 0length是取模。上面的代码段应用在可以在正方形的纹理中可以得出一个正圆,但是如果纹理不是正方形,上面出来的效果会是一个椭圆,因为在shader无论纹理的真实宽高是多少,它的x,y变化范围都是0~1,是比例的变化。如果需要产生一个正圆,还是得通过获取纹理的真实宽高,来计算真实的宽高比例,异名选择的方式是在在组件初始化的时候,输入一个wh_ratio比例来获取,圆的真实半径通过勾股定理来计算,异名这里就没有开方了,直接通过半径平方的比较来舍去圆外的点。

void main () {
  vec4 o = vec4(1, 1, 1, 1);
  o *= texture(texture, v_uv0);
  o *= v_color;

  float circle = radius * radius;
  float rx = center.x * wh_ratio;
  float ry = center.y;
  float dis = (v_uv0.x * wh_ratio - rx) * (v_uv0.x * wh_ratio - rx) + (v_uv0.y  - ry) * (v_uv0.y - ry);

  o.a = step(dis, 0.1);
  gl_FragColor = o;
}
onload() {
  this.material.setProperty('wh_ratio', this.bg.width / this.bg.height);
}

这样子就能在一个不同宽高比的纹理中都能够画出一个正圆。

这样的圆的边缘是有锯齿的,而且追光需要光圈的边缘虚化,所以我们需要借助另外一个内置插值函数smoothstep(min, max, x),它能够返回一个在输入值之间平稳变化的插值,以此来达到边缘羽化的效果。

void main () {
  vec4 o = vec4(1, 1, 1, 1);
  o *= texture(texture, v_uv0);
  o *= v_color;

  float circle = radius * radius;
  float rx = center.x * wh_ratio;
  float ry = center.y;
  float dis = (v_uv0.x * wh_ratio - rx) * (v_uv0.x * wh_ratio - rx) + (v_uv0.y  - ry) * (v_uv0.y - ry);

  o.a = smoothstep(circle, circle - blur, dis);
  gl_FragColor = o;
}

接下来的让光圈随着动作的移动就很简单了,在touch的时候去更改光圈的圆心位置就行,因为我们的shader中是比例的变化,所以我们传进去的时候也要转化成比例,同时别忘了坐标的转化:

touchEvent(evt: cc.Event.EventTouch) {
  let pos = evt.getLocation();
  this.material.setProperty('center', [pos.x / this.bg.width, (this.bg.height - pos.y) / this.bg.height]);
}

这样子我们就把追光的功能实现了,剩下的就是根据业务的需要,生成追光的路径,这个就是把圆心的位置传进来即可。除了应用到舞台追光的那种场景中,异名觉得它的应用还可以有更多的想象空间,比如在黑暗的博物馆里,在手电筒的灯光照射下,蒙娜丽莎的微笑就更加神秘了…

mystical

效果预览

源码获取请点击查看原文,长按二维码查看效果:point_down:

ewm

我是异名,你的阅读是我的动力,其他文章链接:

源码地址https://github.com/ifengzp/cocos-awesome/

23赞

大佬这教程的速度,可以的~

大佬这个追光效果仅能应用在单一sprite上吗,如果往这个sprite里面添加其它元素,不会被遮罩起来呢。

弄一张黑色背景中间带透明圆圈的图遮在最上面是不是也可以实现这个效果啊。

嗯嗯,用一张足够大的图,中间挖空,然后移动它

shader是对单张纹理贴图的处理,你可以用相机把当前的画面投放到一张sprite上,然后对这张sprite应用材质

1赞

大佬,我想 弄一张黑色背景中间带透明圆圈的图遮在最上面,
float dis = (v_uv0.x * wh_ratio - rx) * (v_uv0.x * wh_ratio - rx) + (v_uv0.y - ry) * (v_uv0.y - ry);
将dis改成了负数,在编辑上看是可以的。但是实际上运行的又不行,请大佬指教!!!

我没听明白你要做什么效果。但是你后半段说的在编辑上看可以,实际运行不行,这里应该有两个可以排查的点,一个是你双击你的图片,看看资源面板上的package有没有去掉勾勾,要去掉勾勾,很多人都是忽略了这一点。还有一个就是可能你传入的坐标有问题,你下去排查一下就可以了

佩服,做的很用心!!!

大佬,做的效果大概就是楼上那个弄一张黑色背景中间带透明圆圈的图遮在最上面的效果。只不过那个透明圆圈就用shader来实现,所以才想将shader的控制的透明度反过实现

可以了大佬,我的是2.34版本,将资源Packable的选项去掉就可以了

这个必须mark

马克思!已起立!

我想请教一下大佬,我在屏幕上有一个不会移动的点光源照亮区域,然后有一个会移动的点光源照亮区域,怎么让移动的光源照亮区域进入不会移动的照亮区域时很好的融合一起呢?

准备用到这个效果,先标记一下 :kissing_heart:

用完,效果还好,但是不知道为啥我这边不能直接用,需要修改一下使用方式:


具体原因我也不知道为啥,实话我没吃透大佬的代码,我是一边调试一边凑的答案,并不是很明白为什么要这么改

最终效果:(录屏最低画质转的gif,有点糊)
录屏

1赞

先mark

先标记 mark

其实这种不能这么写,这么写的话图形什么的都是固定的,可应用的场景其实很少,你说的这种用distance field很简单就做出来了