具体操作步骤
接下来以一个制造旋转效果的 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 |