Shader/README.md

201 lines
6.9 KiB
Markdown
Raw Normal View History

2022-07-25 03:11:37 +00:00
# Cosos Creator 2.2.X shader 教程
## 前言
自己之前要做shader的一些效果发现cocos相关文档很少后面在论坛看帖子慢慢摸索了几周也踩了几个坑耽误了一些功夫
现在把自己总结的一些笔记和大家分享讨论一下,有什么错误或者不足的欢迎提建议,自己也还是个刚入门的,希望可以帮到一些想着手的童鞋,也算是回馈社区
## 几个简单的效果
[查看demo代码](https://github.com/XuDaFei/CocosCreator-2.2.X-shader-demo)
![shader使用流程](/readme_pic/shader.png)
## 新建 shader 资源
### 编辑器中:
在 Creator 中新建所需的 matrial、effect, 并且在 material 中设置对应的 effct 资源
### 代码中:
你需要在creator.d.ts 中添加几个接口来防止 ts 报错(不加只是爆红,不影响使用)
```typescript
export class Material extends Asset {
effectAsset: Asset; //材质对应的effect资源
define(name: string, val: any): void; //设置宏定义
setProperty(name:string, val: any); //设置变量
static getBuiltinMaterial(materialUrl: string): Material //获取系统的材质
{
}
}
export class EffectAsset extends Asset
{
}
```
设置材质的接口
```typescript
/** !#en
Base class for components which supports rendering features.
!#zh
所有支持渲染的组件的基类 */
export class RenderComponent extends Component
{
/** !#en The materials used by this render component.
!#zh 渲染组件使用的材质。 */
sharedMaterials: Material[];
/**
!#en Get the material by index.
!#zh 根据指定索引获取材质
@param index index
*/
getMaterial(index: number): Material;
/**
!#en Set the material by index.
!#zh 根据指定索引设置材质
@param index index
@param material material
*/
setMaterial(index: number,material: Material): void;
}
```
你可以用上面的几个接口来加载对应 effect 和 material设置属性设置对应的材质到对应的组件上
只要是继承了 RenderComponent 的组件比如是Sprite, Label, Spine等都可以设置和获取材质
## Effect 资源和 Matrial 资源
EffectAsset 就是保存我们自己编写的 shader 程序, 在引擎中对应着 EffectAsset 资源, 引擎读取渲染组件中的 effect 配置,并设置
对应渲染数据后调用WebGL的API进行渲染。
Creator 2.2 版本已经更新了 effect 文件的格式,关于 Matrial 和 Effect 的可以参考 [Cocos Creator 3D文档](https://docs.cocos.com/creator3d/manual/zh/material-system/overview.html)
这里用一个最简单的栗子来介绍一下
```yaml
CCEffect %{
techniques:
- passes:
- vert: vs //指向vert shader
frag: fs //指向frag shader
blendState: //渲染参数
targets:
- blend: true
rasterizerState:
cullMode: none
properties: //变量,会显示在 material 面板 上
texture: { value: white }
u_time: { value: 1.0 }
}%
CCProgram vs %{ //顶点着色器GLSL 300 es格式
#include <cc-global> //引用头文件cc_matViewProj 变换矩阵就是在里面的变量
precision highp float; //定义精度
in vec3 a_position; //顶点位置
in vec2 a_uv0; //uv 坐标
out vec2 uv0; //插值输出到片元的uv 坐标
void main () {
gl_Position = cc_matViewProj * vec4(a_position, 1);
uv0 = a_uv0;
}
}%
CCProgram fs %{ //片元着色器
precision highp float; //定义精度
uniform sampler2D texture; //纹理
uniform ARGS { //除了系统的uniform ,其他uniform 变量都要定义在UBO(统一变量块)内
//时间 根据时间计算需要丢弃的像素颜色值范围,也就是溶解的范围
float u_time;
}
in vec2 uv0;
void main()
{
float time = u_time;
vec4 c = texture2D(texture,uv0); //用纹理和uv坐标采样到对应片元的颜色
float height = c.g;
if(height < time)
{
//丢弃像素,相当于溶解效果
discard;
}
if(height < time + 0.1) {
//这里可以对溶解边缘进行一些处理,比如透明度减少等
c.a = c.a-0.1;
}
//给片元(像素)赋值
gl_FragColor = c;
}
}%
```
Material 只需要在编辑器或者代码中设置对应的effect。在初始化和运行的时候设置对应的变量
## 运行
像溶解和流光等效果都需要在运行的代码中更新对应的时间参数,下面也举个小栗子
```typescript
const {ccclass,property} = cc._decorator;
@ccclass
export default class ShaderTime extends cc.Component
{
/**记录时间 */
private time: number;
/**精灵上的材质 */
private material: any;
private IsAdd: boolean;
/**时间参数 */
@property(cc.Float)
speed: number = 1.0;
start()
{
this.time = 0;
this.IsAdd = true;
this.material = this.node.getComponent(cc.Sprite).getMaterial(0); //获取材质
}
update(dt)
{
this.material.setProperty("u_time",this.time); //设置材质对应的属性
this.IsAdd ? this.time += dt * this.speed : this.time -= dt * this.speed;
if(this.time > 1.5)
{
this.IsAdd = false;
}
else if(this.time < -0.5)
{
this.IsAdd = true;
}
}
}
```
## 关于合图导致web和模拟器显示不一致的BUG
![合图错误](/readme_pic/hetucuowu.png)
原因: Cocos 会把小于512*512的碎图自动合图以减少DrawCall而effect中接收到的uv 坐标是整个合图的uv, 导致需要用到uv坐标的effect在自动合图下显示不正确。
解决办法:
- 关闭自动合图
ps: 在Cocos Creator 2.1.3和2.2.0 以上都支持单独取消某个纹理的合图
- 手动获取当前sprite 的纹理uv坐标传入到effect(无需取消自动合图)
```typescript
//获取UV位置到Effect
let frame = sprite.spriteFrame as any;
let l = 0,r = 0,b = 1,t = 1;
l = frame.uv[0];
t = frame.uv[5];
r = frame.uv[6];
b = frame.uv[3];
let u_UVoffset = new cc.Vec4(l,t,r,b);
let u_rotated = frame.isRotated() ? 1.0 : 0.0;
this._material.setProperty("u_UVoffset",u_UVoffset);
this._material.setProperty("u_rotated",u_rotated);
//在Effect 中接受u_UVoffset u_rotated 后重新设置UV
vec2 UVnormalize;
UVnormalize.x = (uv0.x-u_UVoffset.x)/(u_UVoffset.z-u_UVoffset.x);
UVnormalize.y = (uv0.y-u_UVoffset.y)/(u_UVoffset.w-u_UVoffset.y);
if(u_rotated > 0.5)
{
float temp = UVnormalize.x;
UVnormalize.x = UVnormalize.y;
UVnormalize.y = 1.0 - temp;
}
```
## 总结
有了材质和Effect以后cocos使用shader 更加直观了。
移植一个需要shader 效果其实也只是在effect文件中设置好变量修改一下shader 的代码片段语法,最后在代码或面板中设置参数即可
当然里面可能会有一些不兼容的属性或者一些细节的问题
## 学习Shader
- [OpenGL 教程](https://learnopengl-cn.github.io/intro/) 手把手教学,我自己之前也是看这个,良心教程!
- [WebGL 教程](https://webglfundamentals.org/webgl/lessons/zh_cn/)
- [GLSL语法](https://thebookofshaders.com/02/?lan=ch)