找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 47|回复: 0

[原创] 【演出组件】机变骰子

[复制链接]
发表于 6 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
本帖最后由 Maz马 于 2026-6-3 09:59 编辑

嘛,之前一直在琢磨DND肉鸽,然后不可避免地就要做骰子
但问题是找了很久都没有适合的动画或序列帧,又或者太零散做起来很麻烦
然后想了想,还得做不同的分支和能够无缝衔接的动画段...决定换个思路
立马解放了自己
Shader,是无敌哒!

预览

                               
登录/注册后可看大图



[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                  # 更新函数


#点击头像 查看我写的更多屎
粉身碎骨浑不怕,要留答辩在人间


评分

参与人数 1活力 +300 干货 +3 收起 理由
被诅咒的章鱼 + 300 + 3 马老师辛苦了

查看全部评分

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|RenPy中文空间 ( 苏ICP备17067825号 )

GMT+8, 2026-6-3 16:37 , Processed in 0.023146 second(s), 9 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表