Godot-StartUP

创建于:2018-07-28

创建人: Justus

44 信息 150 成员
讨论基于Godot以及Unity引擎的游戏开发经验,理论和最佳实践。共享一些通用思路以启发另一种生产工具中的实践。独立开发群QQ: 122017359

[Godot]Shader实现泛光效果

廿木 2018-09-06

Godot shader 泛光效果

        近日在画画的时候,想让一个光源有泛光效果,发现godot虽然有自带的泛光效果,在worldEnvironment节点上,但这个会作用到全屏幕,连你的ui都会有效果,所以尝试了下自己实现一个shader来产生泛光效果。


一、泛光效果的原理:

Image title

简单地说就是:

  1. 原图根据灰度过滤掉一部分颜色。
  2. 把过滤掉颜色的图片模糊化(blur)
  3. 把原图和模糊化之后地图片颜色相加(combine),得到泛光图。


二、项目构成:



Image title

  • 一张颜色分明的图,节点树里sprite类型的colors节点,我这张图是32*32大小的,图片背景透明。

  • Shader类型的材质Bloom.material(会在下面介绍),绑定在colors上。


        这里先说明一下,下面的内容是基于你对godot的shader有一定了解的情况下进行介绍的。



  • Bloom.material:

        我们的效果都只会用到片段着色器(fragment shader),所以只会用到godot Shader里的fragment()函数:

Image title


        然后我们需要一些参数来改变shader的效果,于是在fragment()函数外面定义:

Image title

        这几个变量目前来说就只是数值而已,后面用到的时候会再详细讲它们的作用。


        我们需要对每个fragment进行取样,并根据灰度过滤一部分颜色,我这里用了一个自定义函数:

Image title

上面这段计算灰度那里,百度一下rgb转灰度基本都能查到这个公式,另外更详细的介绍可以看维基

计算思路:

       首先我们要对图片进行取样,取样的函数是texture(sampler2D tex,vec2 uv),这个函数返回一个vec4变量,其实就是tex图片在uv上的颜色rgba值,范围在[0.0,1.0]。

        然后我们要向某个方向取样,就这么写:

Image title

就在UV那里添加一个2d的偏移量,这个radius就是我们之前定义的取样半径,但现在这个半径是一个绝对半径,我们不知道这个radius数值在屏幕上到底有多宽,因为我们不知道一个像素有多宽,所以还要把偏移量乘上像素大小,让radius变为基于像素的取样半径:

Image title


        然后把取样颜色存起来:

Image title

偏移8个方向取样,并把颜色叠加起来:

Image title

颜色叠加起来会很亮,所以叠加了多少次就除回去多少。


        现在我们知道怎么取样了,但我们现在要的不是单纯的取样结果,而是根据灰度过滤颜色,

于是,每一条取样都变成

Image title

用之前定义的limitGrayColor()函数过滤掉低于grayLimit的颜色。这里的grayLimit就是我们之前定义好的最低灰度。

        现在,我们已经完成了8个方向的取样,但是,我们只取了一次,模糊范围就只有radius*TEXTURE_PIXEL_SIZE 这么大,所以我们还要向外进行多次取样。于是代码就变成:


Image title这里的sampleCount就是我们之前定义的取样次数。


        然后我们获得了过滤灰度之后的模糊颜色

Image title


        最后把模糊色和原色叠加,获得泛光颜色并输出:

Image title

这里的brightness就是我们之前定义的模糊亮度,用来调节模糊色的亮度的,实际上只是使col的rgba线性增减。


最终效果:

Image title


[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]









(转发自:原日志地址

近期喜欢的会员

 

加入 indienova

  • 建立个人/工作室档案
  • 建立开发中的游戏档案
  • 关注个人/工作室动态
  • 寻找合作伙伴共同开发
  • 寻求线上发行
  • 更多服务……
登录/注册