浅尝一下Godot Shader

前言

                这几天下来总算是整理到Shader了,先说结论,Godot的Shader非常非常好用。理由:

1.集成好;2.易编写;3.见效快(均是相对于Unity)。

基础探索


        一定要夸的是,Godot引擎真的非常轻便,甚至是Shader这种东西都能很轻易的实现集成,而且还集成得非常好。

        不像那个逆天UnityShader,找个智能提示都费劲!Godot里直接就集成了,虽然不算很智能。但是写起来确实方便,不用打开其他编辑器的感觉确实爽,我开始有学习GDScript的冲动了,因为我已经懒到连VScode都懒得打开。

        在提一嘴,如果用Godot的蓝色主题用得不爽的话,可以在编辑器设置里调整,我现在就是跟着Windows系统的配色方案进行调整的,非常美观,感觉像是换成了一个很高级的引擎,真是“人靠衣著”啊。

        Godot已经将所有所有关于Shader的东西放在手册里了,什么内置函数和变量,注意事项等等,所以真的不懂就去看手册绝对够用了,当然可能是因为我已经有事先学习过Vulkan和UnityShader的经验了,所以才觉得简单。

        倘若是完全新手的话,应该先从最基础的学起,Godot的手册只负责告诉我们有什么,而非怎么做。

        这里说几个我目前比较常用的点。

常用技巧(也许)


shader类型

        着色器类型决定了你可以使用的内置函数,变量,渲染模式等,每个模式有染但不完全相同。所以写shader前要先确定好这玩意是用来干什么的,比如像我这种2D人用的是canva_item类型的shader,那么我翻阅手册的时候就要按照它来查东西。

        不同的着色器阶段(shader state/stage)也有自己的内置变量等等,所以Godot把整个着色器开发封装得很好。

uniform 统一

        这个也是元老级的关键字了,应该也是从GLSL继承而来,还记得在Vulkan中就是通过它们传递CPU数据的,换句话说,在Godot里也一样,这是为Shader建立与内存数据沟通的桥梁。它跟另一个类型varying不同,varying用于着色器内部的顶点到片元阶段之间的数据传递,而uniform类型适用于整个shader甚至shader之外。

        对于Godot编辑器而言,它等价于shader们的“Export”标签,通过它可以快速实现效果测试与修改。

        对于uniform还应当注意到它的hints,用来指示该成员的具体用法,有些hint呢用于方便inspector调整,还有些呢很关键,比如filter,repeat,screen_texture,depth_texture等等那些hint,可以影响到成员的实际表达。

        比如想要循环噪声,应当设置为repeat enable,这样在进行采样的时候才可以实现无限循环。

render_mode 渲染模式

        这些个渲染模式都是Godot渲染内置的,虽然不一定要指定,因为GodotShader编辑器为我们自动配置好了很多东西。但是有一些非常好玩,所以我想记录一下:

unshaded 无阴影

        这个翻译不好,因为无阴影同时也意味着“无光照”,所以可以理解为“无光影”可能好点。开了这个模式可以忽略光照的影响,将albedo完全反馈到我们眼前(albedo指反射率,其实就是漫反射的颜色)。

light_only 仅光照

        这个是我觉得最好玩的一个,也是潜力最大的一个。这个模式下只有在接收到光照的条件下才开始绘制物体,所以想想就能实现很多效果,感觉真的潜力无限。

TIME 内置时间变量

        这是专门用于写时间相关的shader的,单位是秒,默认3600秒一次循环,可以在项目设置里面调整。通过这个可以编写很多好玩的shader哈,比如通过波函数实现水体等等都是人尽皆知的了。

        TIME属于全局内置,所以可以在顶点和片元等等不同阶段使用,每个阶段都有属于自己的内置,比如VERTEX,COLOR那些常见的,最好自己按需看表,因地制宜。

优点展现

        相对于Unity,Godot的Shader简直可以说是简单了,UnityShader不仅结构复杂,写起来也复杂,不过因为更加贴近于GLSL原生,所以“可能”上限会更高。不过既然都选择了Godot,肯定是相信自己的手艺能突破引擎限制吧(doge)。

        总之GodotShader跟UnityShader之间的差异简直就是它们两者本体之间的差异的缩影。

实践环节


简单描边

以下shader实现了一个简单的描边outline效果(2D):

        首先是开头指定一下shader类型,这是个用于2D的shader,所以canvas_item。

        然后uniform两个变量,一个作描边颜色,一个作描边大小。

        然后在片元着色器中对源纹理(TEXTURE)进行上下左右的偏移采样。什么意思呢,拿color_up为例,UV + vec2(0, size * TEXTURE_PIXEL_SIZE.y)得到的是偏移之后的UV值,也就是原本在UV位置的点取值取到UV + vec2(0, size * TEXTURE_PIXEL_SIZE.y)去了,所以相当颜色向上偏移,最终造成图像整体上移。

        注意一些事实,Godot中的UV呢是左上角为原点,右下角为终点的(就和它的2D世界坐标一样)。所以UV增加,取值向下偏移,原本在该点的颜色取到了在该点之下的颜色,导致图像整体上移。

        还有就是时刻提醒自己,COLOR跟UV都是各个片元自己的,这种基本常识还是要有,片元着色器是对每个片元的遍历操作。片元可以理解为具有渲染信息的像素点,它是光栅化过程的中间产物,并最终参与形成最终像素点(只有颜色的那个)。

        这里的TEXTURE_PIXEL_SIZE是内置变量,表示图像尺寸的倒数。所以size * TEXTURE_PIXEL_SIZE,就相当于希望偏移 size 个像素点。或者你将其理解为对UV值的标准化也可以,因为UV的取值是0到1。比如:图像尺寸32×32,某点UV为(0.5,0.5),采样就采到(16,16)。当UV偏移了1/32,即UV(0.53125,0.53125),采样得0.53125 × 32 = 17,而且是正好等于17,所以其实还是偏移了一个像素。

        最后最后还要注意,这个图像尺寸是整个图像的,换句话说,图集图像(Altas)中某个位置的图像用到的UV也是整个图集的,你可以试着去移动图集图像中某个小图的UV,然后你会发现可以看到其他图集中的图像。事实上很多基于UV动画就是这样做的。

        所以这整个shader其实就是把整个图像上下左右偏移了size个像素,然后我们再基于原本的图像对四个偏移出来的图像进行修改,毕竟需求是描边,所以不能覆盖原本的图像,这里我使用的是一个判断语句,意思是如果原本图像的那个位置的颜色本来其实是透明的,那么它就可以是描边颜色,反之就是不变。这里用绝对值判断小于某个0.0001是因为不这样干Godot会警告你可能会丢精度。

        最后其实可以用注释掉的那个语句替换,如果你不想用判断语句的话,因为在shader中用判断语句太多不太好。mix其实就是shader的Lerp,懂了吧,想要干的事和判断语句是一样的,不过直接用计算取代了判断。最终效果也是一模一样的,放心。

shader_type canvas_item;

uniform vec4 color : source_color = vec4(0,0,0,1);
uniform float size = 1.0f;

void fragment()
{	
	vec4 color_origin = texture(TEXTURE,UV);
	
	vec4 color_up = 
	texture(TEXTURE, UV + vec2(0,size * TEXTURE_PIXEL_SIZE.y));
	vec4 color_down = 
	texture(TEXTURE, UV + vec2(0,size * -TEXTURE_PIXEL_SIZE.y));
	vec4 color_left = 
	texture(TEXTURE, UV + vec2(size * -TEXTURE_PIXEL_SIZE.x,0));
	vec4 color_right = 
	texture(TEXTURE, UV + vec2(size * TEXTURE_PIXEL_SIZE.x,0));
	
	vec4 outline = color_up + color_down + color_left + color_right;
	outline.rgb = color.rgb;
	
	COLOR = abs(color_origin.a) < 0.0001 ? outline : COLOR;
    // COLOR = mix(outline, color_origin, color_origin.a);
}

 

 简单用途

        懒得演示了,反正可以用来丰富游戏画面,做一些简单的选择高亮效果还是很容易的。

注意事项


运行时设置shader参数

(Material as ShaderMaterial).SetShaderParameter(ShaderParamName, Value);

        就一句话的事,但是这里的类型转换是必须的(对于C#)。

资源唯一性

        和Unity一样,shader作为一个“程序”,实际参与游戏渲染需要媒介,也就是所谓的“材质”(Material),就是一堆用来渲染的数据的集合。

        那么就要注意了,假设有两个角色,你想实现鼠标移入就高亮的效果,你很开心地给两位都加上了带着高亮shader的材质,结果发现当移入一个角色时,两个角色同时高亮了。

        这就是资源唯一性的问题,因为两者本质上在内存中共享同一个材质,当你设置这个材质的参数时,理所当然它们都会变化。

        在Godot中,资源类型呢都有一个叫"Local to Scene"的功能,也就是可以为每个用到该资源的场景本地实例化一个资源。以此可以解决上述问题的发生,就在inspector中勾选一下就可以了。

        所以在开发中要搞清楚什么资源是唯一的,什么是共享的,比如Material可以每个实例一个,但是这些Material用到的shader却还是同一个。

        还有一点是,Godot的资源是可以虽然保存为tres这种文件,意味着每个运行时的资源实例都是由该文件加载读取生成的,不论有多少个引用,都只会基于一个tres文件生成一个实例。除非像上述一样主动实例化其它的实例。

        .tres译为“Text Resource”文本资源,和tscn同门。

结语                

        写得有点多了,本来不想说这么多的。本来不想碰shader的,但是还是抵挡不住美术的诱惑,后来才发现以前被Unity坑了,觉得写Shader是种折磨。GodotShader真的很好写。

        2DShader几乎用不到顶点着色器,因为顶点实在太少了,大多数就一张图四个点。所以围绕着片元着色器是2D永恒的主题,还真的要感谢Vulkan,折磨了我这么久,终于让我在写Shader的时候高兴一会了。

        后来我才发现,我一直花很多时间学习怎么去做游戏才能做的好,却没花过多少时间做游戏。写Shader时候侥幸的愉悦方才让我回想起来刚开始做游戏的时候是因为什么,而它们绝非是为了写框架,写策划。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Moweiii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值