[RenPy] 纯文本查看 复制代码
# 机变骰子
init python:
renpy.register_shader("Maz.DiceRoll",
variables = """
uniform sampler2D tex0;
uniform vec2 res0;
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
uniform float u_DiceRoll_scale;
uniform vec4 u_DiceRoll_color;
uniform vec4 u_DiceRoll_color1;
uniform vec4 u_DiceRoll_color2;
uniform vec4 u_DiceRoll_color3;
uniform vec4 u_DiceRoll_color4;
uniform vec4 u_DiceRoll_color5;
uniform vec4 u_DiceRoll_color6;
uniform float u_DiceRoll_impact; // 旋转圈数
uniform float u_DiceRoll_target; // 目标点数
uniform float u_DiceRoll_progress; // 动画进度
""",
vertex_300 = """
v_tex_coord = a_tex_coord;
""",
fragment_functions = """
// ========== 采样常量 ==========
#define MAX_STEPS 40 // 最大步进次数(越大越精确,性能越低)
#define SURF_DIST 0.04 // 表面检测阈值(越小越精确)
#define MAX_DIST 10.0 // 最大追踪距离
#define STEP_MULT 0.8 // 步进比例系数
#define NORMAL_EPS 0.001 // 法线采样间距
#define L(a) length(a)
#define PI 3.1415926535
vec3 my_round(vec3 x) {
return floor(x + 0.5);
}
// 3D 绕X轴旋转矩阵 (角度a)
mat3 rotX(float a) {
float c = cos(a), s = sin(a);
return mat3(1.0, 0.0, 0.0,
0.0, c, s,
0.0, -s, c);
}
// 3D 绕Y轴旋转矩阵 (角度a)
mat3 rotY(float a) {
float c = cos(a), s = sin(a);
return mat3(c, 0.0, -s,
0.0, 1.0, 0.0,
s, 0.0, c);
}
// 3D 绕Z轴旋转矩阵 (角度a)
mat3 rotZ(float a) {
float c = cos(a), s = sin(a);
return mat3(c, s, 0.0,
-s, c, 0.0,
0.0, 0.0, 1.0);
}
// 骰子有符号距离场 (SDF)
float diceSDF(vec3 p, mat3 rot) {
vec3 qq = rot * p;
vec3 I = my_round(qq * 0.5);
float r = L(qq - I - I) - (
(L(I) < 2.5 && (
(I.x > 1.0 ? I.y != 0.0 : L(I.zy) < 0.1) ||
(I.y > 0.0 ? I.z * I.x == -1.0 : I.z * I.z == I.x * I.x) ||
(I.z < 0.0 ? I.x == I.y : abs(I.x * I.y) == 1.0)
)) ? 0.7 : 0.0
);
qq = abs(qq);
return -min(-max(max(qq.x, max(qq.y, qq.z)) - 4.0, L(qq) - 5.5), r);
}
// 通过SDF梯度计算表面法线
vec3 calcNormal(vec3 p, mat3 rot) {
vec2 e = vec2(NORMAL_EPS, 0.0);
return normalize(vec3(
diceSDF(p + e.xyy, rot) - diceSDF(p - e.xyy, rot),
diceSDF(p + e.yxy, rot) - diceSDF(p - e.yxy, rot),
diceSDF(p + e.yyx, rot) - diceSDF(p - e.yyx, rot)
));
}
""",
fragment_300 = f"""
const vec2 BASE_RES = vec2({config.screen_width}.0,{config.screen_height}.0);
vec2 uv = v_tex_coord;
vec2 U = uv * BASE_RES;
float size = u_DiceRoll_scale;
// ===== 随机翻滚 + 转向目标面 =====
float t = u_DiceRoll_progress;
float spinAngle = u_DiceRoll_impact * 2.0 * PI * t;
// 先正面,乱滚圈数回到正面,然后定向翻滚对应点数角度,形成无缝可控动画
// 根据实测修正的对应角
float f = u_DiceRoll_target;
mat3 faceRot = mat3(1.0);
if (f < 1.5) {{ // 目标1点:绕Y转-90°
faceRot = rotY(-PI/2.0 * t);
}} else if (f < 2.5) {{ // 目标2点:绕X转-90°
faceRot = rotX(-PI/2.0 * t);
}} else if (f < 3.5) {{ // 目标3点:绕Y转180°
faceRot = rotY(PI * t);
}} else if (f < 4.5) {{ // 目标4点:无需旋转
faceRot = mat3(1.0);
}} else if (f < 5.5) {{ // 目标5点:绕X转+90°
faceRot = rotX(PI/2.0 * t);
}} else {{ // 目标6点:绕Y转+90°
faceRot = rotY(PI/2.0 * t);
}}
// 随机翻滚 旋转轴在动画过程中随时间摇摆,模拟全随机乱旋转
// 所有 wobble 分量在 t=0 和 t=1 时都归零(sin(n*π)=0),确保最终归位
float wobbleX = sin(t * PI * 1.0) * 0.8 + sin(t * PI * 3.0) * 0.3;
float wobbleY = sin(t * PI * 2.0) * 0.5 + sin(t * PI * 4.0) * 0.2;
float wobbleZ = sin(t * PI * 5.0) * 0.4;
mat3 wobbleRot = rotX(wobbleX) * rotY(wobbleY) * rotZ(wobbleZ);
// 在摇摆的坐标系中施加自旋,实现轴随动翻滚
mat3 spinRot = wobbleRot * rotY(spinAngle) * transpose(wobbleRot);
// 合成 翻滚 + 面朝向(翻滚结束后正确停在目标面)
mat3 rot = spinRot * faceRot;
vec3 q = vec3(BASE_RES, 1.0);
vec3 D = vec3((U+U - q.xy) / q.y * 0.3, -1.0);
vec3 p = 40.0 / q * size;
float t_dist = 1.0;
vec4 O = vec4(0.0);
bool hit = false;
for (int i = 0; i < MAX_STEPS; i++) {{
if (t_dist <= SURF_DIST) break;
float d = diceSDF(p, rot);
t_dist = d;
p += t_dist * D * STEP_MULT;
O += vec4(0.04);
hit = true;
}}
if (hit && t_dist < MAX_DIST) {{
// 计算表面法线
vec3 n = calcNormal(p, rot);
// ===== 判断所在面和凹坑 =====
vec3 qqq = rot * p;
vec3 III = my_round(qqq * 0.5);
// 该网格位置是否有凹坑
bool hasPit = (L(III) < 2.5 && (
(III.x > 1.0 ? III.y != 0.0 : L(III.zy) < 0.1) ||
(III.y > 0.0 ? III.z * III.x == -1.0 : III.z * III.z == III.x * III.x) ||
(III.z < 0.0 ? III.x == III.y : abs(III.x * III.y) == 1.0)
));
bool inPit = hasPit && (L(qqq - III - III) < 0.7);
// 确定面编号 1-6(与 faceRot 实际映射一致)
// 模型空间:+X→面6 -X→面1 +Y→面2 -Y→面5 +Z→面4 -Z→面3
vec3 qqqAbs = abs(qqq);
float faceNum = 1.0;
if (qqqAbs.x >= qqqAbs.y && qqqAbs.x >= qqqAbs.z) {{
faceNum = (qqq.x > 0.0) ? 6.0 : 1.0;
}} else if (qqqAbs.y >= qqqAbs.x && qqqAbs.y >= qqqAbs.z) {{
faceNum = (qqq.y > 0.0) ? 2.0 : 5.0;
}} else {{
faceNum = (qqq.z > 0.0) ? 4.0 : 3.0;
}}
// 选择颜色 凹坑用对应面的点色,否则用主体色
vec4 baseColor = u_DiceRoll_color;
if (inPit) {{
if (faceNum == 1.0) baseColor = u_DiceRoll_color1;
else if (faceNum == 2.0) baseColor = u_DiceRoll_color2;
else if (faceNum == 3.0) baseColor = u_DiceRoll_color3;
else if (faceNum == 4.0) baseColor = u_DiceRoll_color4;
else if (faceNum == 5.0) baseColor = u_DiceRoll_color5;
else baseColor = u_DiceRoll_color6;
}}
// 主光源方向(x,y,z)
vec3 lightDir = normalize(vec3(-0.4, -0.6, 0.7));
float diff = max(dot(n, lightDir), 0.0);
// 环境光 + 漫反射
float ambient = 0.30;
float brightness = ambient + diff * 0.70;
// 边缘光(rim light)增强立体感
vec3 viewDir = normalize(-D);
float rim = 1.0 - max(dot(n, viewDir), 0.0);
rim = pow(rim, 3.0) * 0.3;
brightness += rim;
O = vec4(vec3(brightness), 1.0);
O *= baseColor;
gl_FragColor = vec4(O.rgb, 1.0);
}} else {{
discard;
}}
""")
def update_DiceRoll(trans,st,at):
# 示例:当进度超过 0.5 且未播放过音效时,播放骰子落地声
# if st >= 0.5:
# renpy.sound.play("dice_roll.ogg")
return 0
transform MDH_DiceRoll(target=1.0):
mesh True
shader "Maz.DiceRoll"
u_DiceRoll_scale 3.0
u_DiceRoll_color Color("#ffffff").rgba # 骰子主体颜色
u_DiceRoll_color1 Color("#bb1c1c").rgba # 面1 点数颜色(+X)
u_DiceRoll_color2 Color("#3d3d3d").rgba # 面2 点数颜色(-X)
u_DiceRoll_color3 Color("#3d3d3d").rgba # 面3 点数颜色(+Y)
u_DiceRoll_color4 Color("#bb1c1c").rgba # 面4 点数颜色(-Y)
u_DiceRoll_color5 Color("#3d3d3d").rgba # 面5 点数颜色(+Z)
u_DiceRoll_color6 Color("#3d3d3d").rgba # 面6 点数颜色(-Z)
u_DiceRoll_impact 3.0 # 旋转圈数
u_DiceRoll_target target # 目标点数
u_DiceRoll_progress 0.0
easein 1.2 u_DiceRoll_progress 1.0 # 动画时长
function update_DiceRoll # 更新函数