具体操作步骤
接下来以一个制造旋转效果的 shader 为例子,提供了这些参数的设置:
- 旋转速度 float
- 旋转中心位置 vec2
- 逆时针/顺时针 bool
- 扭曲度 float
并在使用的贴图一致的前提下并且参数不同的值都能够合批。
最终项目可以从 GITHUB 获取。
CCC版本:3.8.0
深入了解可以阅读后续的 参考资料 及 源码阅读。
第一步、shader(.effect)
1. 将 builtin-sprite.effect 复制一份出来,重命名为 rotate-sprite.effect.
- builtin-sprite.effect 是 Sprite 组件默认使用的 shader(.effect)。
在 Assets面板 中搜索 builtin-sprite 即可找到 builtin-sprite.effect。
复制一份到项目的 assets 中。
重命名为 rotate-sprite.effect。
2. 打开 rotate-sprite.effect,在 顶点着色器 sprite-vs 上定义顶点参数,并传递给 片元着色器 sprite-fs。
在编辑前 sprite-vs 如下,其中如 in vec3 a_position
这类以 a_
就是使用的顶点参数。
CCProgram sprite-vs %{
...
in vec3 a_position;
in vec2 a_texCoord;
in vec4 a_color;
out vec4 color;
out vec2 uv0;
vec4 vert () {
...
uv0 = a_texCoord;
#if SAMPLE_FROM_RT
CC_HANDLE_RT_SAMPLE_FLIP(uv0);
#endif
color = a_color;
return pos;
}
}%
将我们要用到的顶点参数添加后。
CCProgram sprite-vs %{
...
in vec3 a_position;
in vec2 a_texCoord;
in vec4 a_color;
// 旋转速度
in float a_rotateSpeed;
// 旋转中心
in vec2 a_rotateCenter;
// 是否顺时针旋转
in float a_clockwise;
// 扭曲度
in float a_distort;
...
}%
因为旋转效果要在 片元着色器 sprite-fs 中实现,因此我们把这些 顶点参数的值 传递给 片元着色器 sprite-fs。
在 顶点着色器 sprite-vs 中定义对应的 out
输出变量。
CCProgram sprite-vs %{
...
out vec4 color;
out vec2 uv0;
// 旋转速度
out float rotateSpeed;
// 旋转中心
out vec2 rotateCenter;
// 是否顺时针旋转
out float clockwise;
// 扭曲度
out float distort;
...
}%
在 顶点着色器 sprite-vs 的函数中完成对 out
输出变量 的赋值。
CCProgram sprite-vs %{
...
vec4 vert () {
...
uv0 = a_texCoord;
#if SAMPLE_FROM_RT
CC_HANDLE_RT_SAMPLE_FLIP(uv0);
#endif
color = a_color;
rotateSpeed = a_rotateSpeed;
rotateCenter = a_rotateCenter;
clockwise = a_clockwise;
distort = a_distort;
return pos;
}
}%
最终 shader(.effect) 的 顶点着色器 sprite-vs 会是这样。
CCProgram sprite-vs %{
precision highp float;
#include <builtin/uniforms/cc-global>
#if USE_LOCAL
#include <builtin/uniforms/cc-local>
#endif
#if SAMPLE_FROM_RT
#include <common/common-define>
#endif
in vec3 a_position;
in vec2 a_texCoord;
in vec4 a_color;
// 旋转速度
in float a_rotateSpeed;
// 旋转中心
in vec2 a_rotateCenter;
// 是否顺时针旋转
in float a_clockwise;
// 扭曲度
in float a_distort;
out vec4 color;
out vec2 uv0;
// 旋转速度
out float rotateSpeed;
// 旋转中心
out vec2 rotateCenter;
// 是否顺时针旋转
out float clockwise;
// 扭曲度
out float distort;
vec4 vert () {
vec4 pos = vec4(a_position, 1);
#if USE_LOCAL
pos = cc_matWorld * pos;
#endif
#if USE_PIXEL_ALIGNMENT
pos = cc_matView * pos;
pos.xyz = floor(pos.xyz);
pos = cc_matProj * pos;
#else
pos = cc_matViewProj * pos;
#endif
uv0 = a_texCoord;
#if SAMPLE_FROM_RT
CC_HANDLE_RT_SAMPLE_FLIP(uv0);
#endif
color = a_color;
rotateSpeed = a_rotateSpeed;
rotateCenter = a_rotateCenter;
clockwise = a_clockwise;
distort = a_distort;
return pos;
}
}%
3. 在 片元着色器 sprite-fs 上接收从 顶点着色器 sprite-vs 传递过来的顶点参数。
编辑前,片元着色器 sprite-fs 如下。其中 in vec4 color
这样的 in
输入变量,从 顶点着色器 sprite-vs 中接收了对应变量。
CCProgram sprite-fs %{
precision highp float;
#include <builtin/internal/embedded-alpha>
#include <builtin/internal/alpha-test>
in vec4 color;
#if USE_TEXTURE
in vec2 uv0;
#pragma builtin(local)
layout(set = 2, binding = 12) uniform sampler2D cc_spriteTexture;
#endif
vec4 frag () {
vec4 o = vec4(1, 1, 1, 1);
#if USE_TEXTURE
o *= CCSampleWithAlphaSeparated(cc_spriteTexture, uv0);
#if IS_GRAY
float gray = 0.2126 * o.r + 0.7152 * o.g + 0.0722 * o.b;
o.r = o.g = o.b = gray;
#endif
#endif
o *= color;
ALPHA_TEST(o);
return o;
}
}%
增加对应我们新增顶点参数的 in
输入变量。
CCProgram sprite-fs %{
...
in vec4 color;
// 旋转速度
in float rotateSpeed;
// 旋转中心
in vec2 rotateCenter;
// 是否顺时针旋转
in float clockwise;
// 扭曲度
in float distort;
#if USE_TEXTURE
in vec2 uv0;
#pragma builtin(local)
layout(set = 2, binding = 12) uniform sampler2D cc_spriteTexture;
#endif
...
}%
4. 使用参数并实现效果。
如何实现不是本文关注点,这里直接给出完成后的 片元着色器 sprite-fs 的代码。
CCProgram sprite-fs %{
precision highp float;
#include <builtin/uniforms/cc-global>
#include <builtin/internal/embedded-alpha>
#include <builtin/internal/alpha-test>
in vec4 color;
// 旋转速度
in float rotateSpeed;
// 旋转中心
in vec2 rotateCenter;
// 是否顺时针旋转
in float clockwise;
// 扭曲度
in float distort;
#define PI 3.1415926535897932384626433832795
#if USE_TEXTURE
in vec2 uv0;
#pragma builtin(local)
layout(set = 2, binding = 12) uniform sampler2D cc_spriteTexture;
#endif
float yOflineOnX(float k, float b, float x) {
return k * x + b;
}
float xOflineOnY(float k, float b, float y) {
return (y - b) / k;
}
bool isBetween(float value, float min, float max) {
return value >= min && value <= max;
}
vec2 findFarthestFittingPoint(vec2 dir, vec2 rotateCenter) {
vec2 farFitPoint = vec2(0.0);
float len4fit = 0.0;
float xSign = sign(dir.x);
float slope = dir.y / (xSign * max(abs(dir.x), 0.00000001));
slope = clamp(slope, -9999999999.9, 9999999999.9);
float yIntercept = rotateCenter.y - slope * rotateCenter.x;
yIntercept = clamp(yIntercept, -9999999999.9, 9999999999.9);
vec2 checkVal = vec2(0.0, yOflineOnX(slope, yIntercept, 0.0));
vec2 check2center = checkVal - rotateCenter;
if (isBetween(checkVal.y, 0.0, 1.0) && dot(dir, check2center) > 0.0) {
farFitPoint = checkVal;
len4fit = length(check2center);
}
checkVal = vec2(1.0, yOflineOnX(slope, yIntercept, 1.0));
check2center = checkVal - rotateCenter;
float len4check = length(check2center);
if (isBetween(checkVal.y, 0.0, 1.0) && dot(dir, check2center) > 0.0 && len4check > len4fit) {
farFitPoint = checkVal;
len4fit = len4check;
}
checkVal = vec2(xOflineOnY(slope, yIntercept, 0.0), 0.0);
check2center = checkVal - rotateCenter;
len4check = length(check2center);
if (isBetween(checkVal.x, 0.0, 1.0) && dot(dir, check2center) > 0.0 && len4check > len4fit) {
farFitPoint = checkVal;
len4fit = len4check;
}
checkVal = vec2(xOflineOnY(slope, yIntercept, 1.0), 1.0);
check2center = checkVal - rotateCenter;
len4check = length(check2center);
if (isBetween(checkVal.x, 0.0, 1.0) && dot(dir, check2center) > 0.0 && len4check > len4fit) {
farFitPoint = checkVal;
len4fit = len4check;
}
return farFitPoint;
}
vec2 rotateVector(vec2 vec, float angle) {
return vec2(
vec.x * cos(angle) - vec.y * sin(angle),
vec.x * sin(angle) + vec.y * cos(angle)
);
}
float easeOutBounce(float x){
float n1 = 7.5625 * distort;
float d1 = 2.75;
if (x < 1.0 / d1) {
return n1 * x * x;
} else if (x < 2.0 / d1) {
return n1 * (x -= 1.5 / d1) * x + 0.75;
} else if (x < 2.5 / d1) {
return n1 * (x -= 2.25 / d1) * x + 0.9375;
} else {
return n1 * (x -= 2.625 / d1) * x + 0.984375;
}
}
float easeInCirc(float x) {
return 1.0 - sqrt(1.0 - pow(x, 2.0 * distort));
}
vec4 frag () {
vec4 o = vec4(1.0);
#if USE_TEXTURE
float rotateRad = sign(clockwise) * cc_time.x * PI * rotateSpeed;
// 通过 uv转换 来实现旋转
vec2 dir = uv0 - rotateCenter;
vec2 farFitPoint = findFarthestFittingPoint(dir, rotateCenter);
float percent = length(dir) / length(farFitPoint - rotateCenter);
vec2 dirRotated = rotateVector(dir, rotateRad);
farFitPoint = findFarthestFittingPoint(dirRotated, rotateCenter);
vec2 uvRotated = rotateCenter + (farFitPoint - rotateCenter) * easeInCirc(percent);
o *= CCSampleWithAlphaSeparated(cc_spriteTexture, uvRotated);
#if IS_GRAY
float gray = 0.2126 * o.r + 0.7152 * o.g + 0.0722 * o.b;
o.r = o.g = o.b = gray;
#endif
#endif
o *= color;
ALPHA_TEST(o);
return o;
}
}%
5. 创建 rotate-sprite.mat,并使用 rotate-sprite.effect。
创建新的 material。
重命名为 rotate-sprite.mat。
修改材质所使用的 shader(.effect),选中 rotate-sprite.effect。
并激活 "USE TEXTURE",然后保存设置即可。
第二步、编写 RotateSprite.ts (一)
1. 新建一个 ts 脚本,命名为 RotateSprite.ts。
2. 删掉 start 和 update,并其继承 Sprite
3. 编写顶点参数相关逻辑
回顾 shader(.effect) 定义的顶点参数。
对应列出如下表,其中 gfx.Format
的值可以 查表 得。
字段 | glsl类型 | gfx.Format |
---|---|---|
in vec3 a_position | vec3 | RGB32F |
in vec2 a_texCoord | vec2 | RG32F |
in vec4 a_color | vec4 | RGBA32F |
in float a_rotateSpeed | float | R32F |
in vec2 a_rotateCenter | vec2 | RG32F |
in float a_clockwise | float | R32F |
in float a_distort | float | R32F |
?
对应该表复写 requestRenderData
。(请按需补充 引入 import)
@ccclass('RotateSprite')
export class RotateSprite extends Sprite {
public requestRenderData(drawInfoType?: __private._cocos_2d_renderer_render_draw_info__RenderDrawInfoType): RenderData {
const data = RenderData.add([
new gfx.Attribute(gfx.AttributeName.ATTR_POSITION, gfx.Format.RGB32F),
new gfx.Attribute(gfx.AttributeName.ATTR_TEX_COORD, gfx.Format.RG32F),
new gfx.Attribute(gfx.AttributeName.ATTR_COLOR, gfx.Format.RGBA32F),
new gfx.Attribute("a_rotateSpeed", gfx.Format.R32F),
new gfx.Attribute("a_rotateCenter", gfx.Format.RG32F),
new gfx.Attribute("a_clockwise", gfx.Format.R32F),
new gfx.Attribute("a_distort", gfx.Format.R32F),
]);
data.initRenderDrawInfo(this, drawInfoType);
this._renderData = data;
return data;
}
}
??
增加 顶点参数 对应的成员和属性。(请按需补充 引入 import)
@ccclass('RotateSprite')
export class RotateSprite extends Sprite {
@property({ type: CCFloat, visible: true })
private _rotateSpeed: number = 1;
@property({ type: Vec2, visible: true })
private _rotateCenter: Vec2 = new Vec2(0.5, 0.5);
@property({ type: CCBoolean, visible: true })
private _isClockWise: boolean = true;
@property({ type: CCFloat, visible: true })
private _distort: number = 1;
public get rotateSpeed(): number {
return this._rotateSpeed;
}
public set rotateSpeed(value: number) {
if (this._rotateSpeed == value) return;
this._rotateSpeed = value;
}
public get rotateCenter(): Vec2 {
return this._rotateCenter;
}
public set rotateCenter(value: Vec2) {
if (this._rotateCenter.equals(value)) return;
this._rotateCenter.set(value);
}
public get isClockWise(): boolean {
return this._isClockWise;
}
public set isClockWise(value: boolean) {
if (this._isClockWise == value) return;
this._isClockWise = value;
}
public get distort(): number {
return this._distort;
}
public set distort(value: number) {
if (this._distort == value) return;
this._distort = value;
}
...
}
到此为止,针对 RotateSprite
的编写暂告一段落,待我们完成 rotateAssembler.ts 的编写后再来补充后续。
第三步、编写 rotateAssembler.ts
1. 将 simple Assembler 复制出来。
打开 CCC 源码文件夹。
在编辑器中打开 resources 文件夹。
准确导航到 Sprite 使用的 simple.ts。
将文件复制到项目 assets 中。
重命名为 rotateAssembler.ts。
2. 修正代码报错。
首先将变量名从 simple
修改为 rotateAssembler
❤
首先将原本的 引入 import 全部注释掉
然后增加如下 引入 import
import { DynamicAtlasManager, IAssembler, IRenderData, RenderData, Sprite } from "cc";
❤❤
针对 dynamicAtlasManager.packToDynamicAtlas(sprite, frame);
的报错。
将其改为 DynamicAtlasManager.instance.packToDynamicAtlas(sprite, frame);
即可。
❤❤❤
针对这种 类型定义 的报错,直接把它们改成 any
。
现在应该就没有报错了。
3. 按照 rotate-sprite.effect 的 顶点参数定义 补充代码。
回顾 shader(.effect) 定义的顶点参数。
对应列出表。
字段 | 占位 | 偏移 |
---|---|---|
in vec3 a_position | 3 | 0 |
in vec2 a_texCoord | 2 | 3 |
in vec4 a_color | 4 | 5 |
in float a_rotateSpeed | 1 | 9 |
in vec2 a_rotateCenter | 2 | 10 |
in float a_clockwise | 1 | 12 |
in float a_distort | 1 | 13 |
?
首先,如果 updateUVs
在设置顶点参数值时写死了偏移值。
那么我们需要修改 updateUVs
成下面的样子。
updateUVs(sprite: Sprite) {
if (!sprite.spriteFrame) return;
const renderData = sprite.renderData!;
const vData = renderData.chunk.vb;
const uv = sprite.spriteFrame.uv;
let offset = 3;
let count = 0;
for (let i = 0; i < 4; i++, offset += renderData.floatStride) {
vData[offset] = uv[count++];
vData[offset + 1] = uv[count++];
}
},
??
然后是 in float a_rotateSpeed
旋转速度,占位 1,偏移位 9。
增加对应函数 updateRotateSpeed(sprite: RotateSprite)
。
updateRotateSpeed(sprite: RotateSprite) {
const renderData = sprite.renderData!;
const vData = renderData.chunk.vb;
let offset = 9;
for (let i = 0; i < 4; i++, offset += renderData.floatStride) {
vData[offset] = sprite.rotateSpeed;
}
}
???
然后是 in vec2 a_rotateCenter
旋转中心,占位 2,偏移位 10。
增加对应函数 updateRotateCenter(sprite: RotateSprite)
。
updateRotateCenter(sprite: RotateSprite) {
const renderData = sprite.renderData!;
const vData = renderData.chunk.vb;
let offset = 10;
for (let i = 0; i < 4; i++, offset += renderData.floatStride) {
vData[offset] = sprite.rotateCenter.x;
vData[offset + 1] = sprite.rotateCenter.y;
}
},
????
然后是 in float a_clockwise
是否顺时针旋转,占位 1,偏移位 12。
增加对应函数 updateaClockwise(sprite: RotateSprite)
。
updateaClockwise(sprite: RotateSprite) {
const renderData = sprite.renderData!;
const vData = renderData.chunk.vb;
let offset = 12;
for (let i = 0; i < 4; i++, offset += renderData.floatStride) {
vData[offset] = sprite.isClockWise ? 1 : -1;
}
},
?????
然后是 in float a_distort
扰乱程度,占位 1,偏移位 13。
增加对应函数 updateaDistort(sprite: RotateSprite)
。
updateaDistort(sprite: RotateSprite) {
const renderData = sprite.renderData!;
const vData = renderData.chunk.vb;
let offset = 13;
for (let i = 0; i < 4; i++, offset += renderData.floatStride) {
vData[offset] = sprite.distort;
}
},
4. 修改 updateRenderData 方法