查看原文
其他

三行Shader实现Cocos Creator 3D边缘光

麒麟子TM 麒麟子随笔 2022-06-10

零、先看一些图 


图1:边缘光因子检查


图2:黄色,一般用于霸体效果


图3:红色,一般用于特殊技能特效


图4:白色,一般用于受击效果


图5:绿色,一般用于人物,NPC选中时高亮


看着群里的小伙伴们都很热衷于Shader的编写,麒麟子刚好做到了这个需求:焦点对象高亮、受击变色、霸体、特殊技能角色周身氛围增强,所以在此分享一下Cocos Creator 3D版本下,Shader的编写。

在此也提醒一下各位小萌新,Shader的编写于图形引擎而言,就好比SQL的编写于数据库一样。

如果不去理解数据库存储原理,快照方式,查询机制。是写不出好SQL的。

Shader编写也是如此,我们应该对图形渲染管线、图形API接口特性、常见的光照算法、向量运算可利用的特点等加以掌握,才能写出符合需求且不损效率的好Shader.

推荐大家学习《WebGL编程指南》《3D游戏中的数学基础》这两本书,想白嫖的可以私信我

阅读本文你将GET到以下知识点

1、添加一个可以在Inspector面板操作的shader变量

2、添加一个vs到fs传递的变量

3、法线转换

4、点乘的妙用

5、让fs根据自己的意愿进行颜色输出

5、完整的边缘光实现

坐稳了,开始给油了。

一、材质的使用问题

群里有不少朋友在问,为什么自己的材质没办法改颜色,改贴图。这个问题虽然比较简单,但对于刚接触3D的朋友来说,不容易找到方法,在这里就科普一下。

  1. 每一个材质文件对应一个渲染效果,如果有十个物体都使用同一个材质文件,那我改了材质文件,就会影响这十个物体。

  2. 我们创建的对象如Cube之类的,会赋值一个默认材质文件,由于系统内其它地方也要使用这个材质文件,所以是不允许修改的。如果你要修改,那么就新建一个材质文件,并赋值给你的物体。

  3. 从FBX导入的模型材质也是锁了属性的,因为这个属性是从FBX里读出来的,不允许修改。如果需要修改,也只有新建一个材质来处理。

二、Cocos Creator 3D Effects简单介绍

我们常说的Shader其实不仅包含了GLSL代码,还包含了一些渲染状态,和参数管理。因此引擎一般不会直接叫Shader,比如Cocos和Unity3D把它叫Effect,也有一些引擎叫FX。

要制作一个复杂点的Effect,我们一般不去新建effect,而是直接复制builtin-standard来修改。先简单介绍一下Cocos Creator 3D的Effect文件。 《更详细的介绍在这里》

每一个Effect由多个techniques组成,每一个technique由多个passes组成。每个effect同一时间只会激活一个technique。一个technique里面会有多个pass,通常情况下只会有一个。但我们有时候也会做一些特殊处理,这个时候就会用到多个pass了。

properties:&props用于定义Inspector面板上可操作的参数。我们可以定义向量、颜色、贴图、整数。在我们平时写Shader的时候,想添加什么样的类型,复制一份来改就行了。

三、边缘光(RemLight)的实现 

边缘光和雾化效果一样,是性价比超级高的表现手法。本文开头的图,大家应该已经看到了。它可以用于游戏中各类情景。有某些场合,我们还能够利用边缘光增加物体与场景融入感。

边缘光的原理非常简单,模型上的三角面,与我们摄像机正对着的,我们认为它的边缘光因子0,与我们摄像机垂直的,我们认为它的边缘光因子为1。

而在Shader中,我们要判断一个面是否与我们的摄像机正对着,最简单的就是使用顶点法线。 我们利用视线反方向与法线方向的点乘值来判断。

因为点乘值 = cos(向量夹角)。 我们根据cos值的值域可以发现, 当夹角为0的时候,法线和摄像机正对,此时cos值为1,反之,当夹角为90度的时候,法线和摄相机垂直,此时cos值为0。刚好与我们要的结果相反。最终我们的公式如下。

float fRem = 1.0 - dot(-cameraDir,normal);

最后,将这个因子用于物体颜色和边缘光颜色的插值,即可得到我们想要的结果。

边缘光有两个属性:1、颜色  2:强度。

正好我们可以用一个vec4来作为边缘光参数。rgb作为颜色,a作为强度。

接下来我们说一下如何添加边缘光。

1、properties:&props新增一个边缘光属性,使我们能够通过Inspector面板和代码控制边缘光参数

remLightColor: { value: [1.0, 1.0, 1.0, 1.0], target: remColor, editor: { displayName: Rem Color, type: color } }

remLightColor:是属性定义,名称叫啥并无实际意义,但麒麟子做的时候,发现不能和remColor相同,否则属性面板上出不来。

target:remColor:表示这个值要和下面的remColor uniform绑定。

editor: { displayName: Rem Color, type: color }:表示在面板上显示为Rem Color,类型是颜色类型

2、在下方CCProgram shared-ubos 中的 uniform Constants 域添加一个 vec4 remColor 常量。

到此,我们要添加的外部变量就设置好。我们既可以通过Inspector面板调节remColor参数,也可以在代码中使用setProperties来设置参数。

如果添加成功,它应该在下图所示这个位置

 

3、在vs中转换法线到摄相机空间,并传递给fs

shader编写的时候,我们最重要的就是要考虑清楚,在哪一个空间计算是最方便的。在这个例子中,麒麟子决定在摄像机空间进行计算,在摄像机空间进行计算,可以以 引擎的默认方向(0,0,-1)作为摄像机方向。(因为所有物体已经被转换到这个空间中后,摄像机被视为无旋转、无缩放、无平移的)

我们找到out vec3 v_normal,并在下方添加一个 out vec3 v_view_normal;

将法线计算部分改为如下语句:

vec4 normal = vec4(In.normal,0.0);

v_normal = normalize((matWorldIT * normal).xyz);

v_view_normal = normalize(((cc_matView * matWorldIT) * normal).xyz);

v_view_normal就是我们要的摄像机空间的法线方向。

 

4、在fs中计算边缘光因子

standard-fs部分,in vec3 v_normal;下面添加in vec3 v_view_normal;

return CCFragOutput(color);之前 #endif 之后添加下面两条语句

float fRem = (1.0 - dot(normalize(v_view_normal),vec3(0,0,1.0))) * remColor.w;

color.rgb = mix(color.rgb,remColor.rgb,fRem);

到此,就边缘光添加完毕。

如果要实现本文图1的边缘光因子检查。只需要改为color.rgb = mix(vec3(0.0,0.0,0.0),remColor.rgb,fRem);即可


四、总结

麒麟子不忽悠人,真正有意义的就三行Shader代码

1、v_view_normal = normalize(((cc_matView * matWorldIT) * normal).xyz);

2、float fRem = (1.0 - dot(normalize(v_view_normal),vec3(0,0,1.0))) * remColor.w;

3、color.rgb = mix(color.rgb,remColor.rgb,fRem);


边缘光虽然是一种很简单的实现,但它巧妙地利用了视线与法线的关系。从而达到了我们意想不到的效果。这就是科学的力量。

当然,我们也能明显感受到,边缘光对立方体等表面不平滑的物体效果并不好,用的时候要注意场合。

如果觉得光看文章不够清楚的朋友,麒麟子还准备了本项目完整源码。欢迎查看。

老规矩 公众号菜单界面回复暗号 20200818 可获得源码仓库地址。

你可以不用点‘在看’,也可以不转发,更可以不关注本公众号

!!!!!!!!!只要你的良心不会痛!!!!!!!!

点都不意外

3D 才是麒麟子的主场

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存