[Godot]Shader实现泛光效果
Godot shader 泛光效果
近日在画画的时候,想让一个光源有泛光效果,发现godot虽然有自带的泛光效果,在worldEnvironment节点上,但这个会作用到全屏幕,连你的ui都会有效果,所以尝试了下自己实现一个shader来产生泛光效果。
一、泛光效果的原理:
简单地说就是:
- 原图根据灰度过滤掉一部分颜色。
- 把过滤掉颜色的图片模糊化(blur)
- 把原图和模糊化之后地图片颜色相加(combine),得到泛光图。
二、项目构成:
一张颜色分明的图,节点树里sprite类型的colors节点,我这张图是32*32大小的,图片背景透明。
Shader类型的材质Bloom.material(会在下面介绍),绑定在colors上。
这里先说明一下,下面的内容是基于你对godot的shader有一定了解的情况下进行介绍的。
Bloom.material:
我们的效果都只会用到片段着色器(fragment shader),所以只会用到godot Shader里的fragment()函数:
然后我们需要一些参数来改变shader的效果,于是在fragment()函数外面定义:
这几个变量目前来说就只是数值而已,后面用到的时候会再详细讲它们的作用。
我们需要对每个fragment进行取样,并根据灰度过滤一部分颜色,我这里用了一个自定义函数:
上面这段计算灰度那里,百度一下rgb转灰度基本都能查到这个公式,另外更详细的介绍可以看维基
计算思路:
首先我们要对图片进行取样,取样的函数是texture(sampler2D tex,vec2 uv),这个函数返回一个vec4变量,其实就是tex图片在uv上的颜色rgba值,范围在[0.0,1.0]。
然后我们要向某个方向取样,就这么写:
就在UV那里添加一个2d的偏移量,这个radius就是我们之前定义的取样半径,但现在这个半径是一个绝对半径,我们不知道这个radius数值在屏幕上到底有多宽,因为我们不知道一个像素有多宽,所以还要把偏移量乘上像素大小,让radius变为基于像素的取样半径:
然后把取样颜色存起来:
偏移8个方向取样,并把颜色叠加起来:
颜色叠加起来会很亮,所以叠加了多少次就除回去多少。
现在我们知道怎么取样了,但我们现在要的不是单纯的取样结果,而是根据灰度过滤颜色,
于是,每一条取样都变成
用之前定义的limitGrayColor()函数过滤掉低于grayLimit的颜色。这里的grayLimit就是我们之前定义好的最低灰度。
现在,我们已经完成了8个方向的取样,但是,我们只取了一次,模糊范围就只有radius*TEXTURE_PIXEL_SIZE 这么大,所以我们还要向外进行多次取样。于是代码就变成:
这里的sampleCount就是我们之前定义的取样次数。
然后我们获得了过滤灰度之后的模糊颜色
最后把模糊色和原色叠加,获得泛光颜色并输出:
这里的brightness就是我们之前定义的模糊亮度,用来调节模糊色的亮度的,实际上只是使col的rgba线性增减。
最终效果:
[spoilerblock text="【代码】"] shader_type canvas_item; uniform float radius:hint_range(0.0,2.0) = 0.5;//取样半径(基于像素) uniform int sampleCount:hint_range(1, 8) = 4;//取样次数 uniform float grayLimit :hint_range(0.0, 1.0) = 0.3;//取样最低灰度 uniform float brightness :hint_range(0.0, 1.0)= 0.5;//模糊亮度 vec4 limitGrayColor(vec4 color,float limit){ if(limit <=0.0)//没限制,输入什么就输出什么 return color; //计算灰度 float gray = dot(color.rgb,vec3(0.299, 0.587, 0.114)); if (gray<=limit){//灰度小于限制,就返回透明颜色 return vec4(0.0); }else //不然就保留该颜色 return color; }void fragment(){ vec2 ps = TEXTURE_PIXEL_SIZE;//像素大小 vec4 col = vec4(0.0);//最终输出的颜色 for(int i = 1;i<=sampleCount;i++){ //**当前为向8个方向取样,时间复杂度为O(n),模糊效果不是特别好,尤其是对角 //当然你也可以向外n*n个格子取样,不过时间复杂度是O(n^2),模糊效果较为精确 //网上那些n+n的取样方式是对一张图处理1次后把图提取出来再处理1次,要配合底层代码来写的。 col+=limitGrayColor( texture(TEXTURE,UV + vec2(0,-radius)*ps*float(i)),grayLimit); col+=limitGrayColor( texture(TEXTURE,UV + vec2(0,radius)*ps*float(i)),grayLimit); col+=limitGrayColor( texture(TEXTURE,UV + vec2(-radius,0)*ps*float(i)),grayLimit); col+=limitGrayColor( texture(TEXTURE,UV + vec2(radius,0)*ps*float(i)),grayLimit); col+=limitGrayColor( texture(TEXTURE,UV + vec2(radius,radius)*ps*float(i)),grayLimit); col+=limitGrayColor( texture(TEXTURE,UV + vec2(radius,-radius)*ps*float(i)),grayLimit); col+=limitGrayColor( texture(TEXTURE,UV + vec2(-radius,radius)*ps*float(i)),grayLimit); col+=limitGrayColor( texture(TEXTURE,UV + vec2(-radius,-radius)*ps*float(i)),grayLimit);} col/=(8.0*float(sampleCount)); COLOR = col*brightness+texture(TEXTURE,UV); } [/spoilerblock]