Kzeroko KTM2

对话镜头与地形淡化

按会话持有的肩后构图、演员朝向、移动控制与近处地形着色器覆盖范围。

更新于 2026/9/30 11 分钟阅读

对话镜头以肩后视角跟随玩家和说话人,每次重新评估视野最多搜索 32 个候选位置。会话与实体身份检查使镜头跟随当前对话,独立的地形着色器补丁则淡化任何脚本镜头附近的几何体。

它做什么

实现位于 client.camera.compat 和 client.camera.fade,均为内部集成类型,不是公共 API 门面。Blabber 桥接为普通和父级对话启动 isekaiexpansion:dialogue_humanoid,并在关闭时传递当前会话 ID;父级对话开始时还会认领对话实体。本仓库将第三方 Blabber 锁定为 0.5.1-build36,这里描述的是模组自身的桥接钩子。

镜头将已加载动画作为跟随对话锚点的偏移,使用 YawAlign.NONE 和对话播放选项。它保持动画末帧,以 5 刻(0.25 秒)混入,保留 HUD 和对话输入,并在自己持有的镜头停止后恢复原视角。内置动画是中性姿态,实际构图与 FOV 由镜头支架提供。

运行时模型

阶段所有权与检查
服务端启动发送会话 UUID、动画 ID、玩家实体 ID/UUID 和说话人实体 ID/UUID;会话 ID 为 null 时不发送
客户端准入要求世界与本地玩家存在、本地玩家 UUID 匹配,且没有活动或待启动的完整过场
等待追踪最多尝试 40 次刻末更新(每秒 20 刻时为 2 秒);两个实体 ID 都必须解析到预期 UUID
镜头支架持有原实体对象和客户端世界,每刻更新构图,并采样插值后的眼睛/位置
停止匹配会话 UUID 或 null 通配停止会将倒计数设为 2;某次刻末更新开始时计数已为零才清理
清理释放对话移动控制,仅当活动锚点仍持有此支架实例时停止镜头

追踪重试上限不保证实际等待时长。尚未观察到 Blabber 对话处理器时,缺失界面超过 10 刻便清理请求;曾观察到后,缺失超过 2 刻便清理。因此,界面检查可能在用完 40 次追踪尝试前结束请求。

新请求若具有相同动画和演员 UUID,且已有支架仍有效并归本会话所有,可复用该支架,只更新会话令牌并取消待停止状态。这样在对话界面之间切换时无需重启镜头。否则先清理旧对话状态,再安装新请求。

断线、世界对象改变、本地玩家死亡、完整过场接管、演员失效或镜头所有权丢失都会清理会话。有效支架要求两个不同、存活且未移除的实体位于当前客户端世界,彼此距离不超过 24 方块。清理会核对活动锚点的支架实例,不会停止无关的替代镜头。镜头控制器调用仍受镜头场景 API所述生命周期约束。

构图与搜索

下列值来自 DialogueCameraSettings.defaults(),不是用户配置:

设置类型默认值含义
distancenumber2.55基础肩后距离,单位方块,乘以两名演员较大眼高除以 1.62 的比例。
minDistancenumber1.25候选距离下限,单位方块。
maxDistancenumber6理想距离上限,单位方块。
clearancenumber0.22镜头碰撞检测盒的半边长,单位方块。
followWeightnumber0.35位置与说话人屏幕 X 的逐刻插值权重。
sideHysteresisnumber1.5测试不同角度时增加的评分代价。
verticalFovnumber35基础垂直 FOV,单位度,窄窗口中会扩大。
speakerScreenXnumber0.60说话人眼睛的归一化屏幕 X,另一侧镜头镜像为 0.40。
eyeScreenYnumber0.34从屏幕顶部计算的归一化眼睛 Y。

枢轴使用玩家眼睛的 X/Z,高度向说话人眼睛靠近垂直差的四分之一。双方水平距离平方超过 0.04 时更新玩家指向说话人的水平轴;近乎重合时保留原轴,首次构建必要时使用玩家偏航。

搜索在首次更新、每 4 个更新刻(0.2 秒),或目标镜头检测盒受阻时执行。候选角度为 135、−135、120、−120、150、−150、165 和 −165 度;距离取理想值的 1、0.8、0.6、0.4 倍,均受最小距离约束。

分数越低越好。替代候选计入换角惩罚后仍未改善评分时,保留当前候选:

评分项代价
偏离理想距离距离差绝对值 ×2
偏离首选肩后角度角度绝对值与 135 度的差,再除以 20
前景玩家眼睛位置屏幕 X 误差 ×12,目标为 0.24 或 0.76;屏幕 Y 误差 ×8
玩家眼睛位于镜头后方+40
玩家头顶高于屏幕 Y 0.04超出量 ×30
镜头碰撞检测盒受阻+100
指向说话人眼睛的射线受阻+25
镜头到任一眼睛的距离平方小于 0.64+20
玩家包围盒扩大 0.1 后与说话人视线相交+35
候选角度不同+1.5

碰撞使用盒状空间检测,可见性使用方块视觉形状射线并忽略流体。惩罚均为有限值,因此完全封闭的房间仍可能选中受阻镜头。这是有界的最优候选搜索,不保证可见性,也不是寻路器。

位置与屏幕 X 以 0.35 权重接近目标。位置跳变的距离平方超过 36 时,两端插值位置一起跳到目标。平滑后位置受阻时改用目标位置,不沿两者之间执行扫掠检测。焦点取插值后的说话人眼睛,构图滚转保持为零。

窄窗口 FOV 从半角正切计算:乘以 max(1, (16 / 9) / max(0.5, aspect)),再换回角度并限制到 90。计算使用窗口帧缓冲宽高比,不修改用户的游戏 FOV 选项。

约定

朝向与输入

持有有效支架时,对话会话在每个客户端刻开始取得 PlayerLocomotionOverride.Source.DIALOGUE 控制。优先级为 COMPULSION < DIALOGUE < CINEMATIC。生效的保持状态清除前后/横向输入、跳跃和潜行,并消除水平惯性,但保留垂直速度,因此开始对话不会让正在下落的玩家悬空。

对话持有玩家时,鼠标视角增量变为零。仅对话所有权不会启用完整过场的鼠标按键/滚轮锁,对话界面仍可接收输入。玩家朝向在继承的客户端玩家更新之后应用;服务端生物朝向在生物更新末尾执行,先检查追踪的对话玩家和活动会话目标。

朝向规则值
偏航逼近剩余环绕误差的 0.4 倍,每次调用最多 18 度
身体逼近同样的逼近规则,上限 12 度
头部偏移限制在共享的相对身体 75 度范围内
俯仰剩余误差的 0.4 倍,单步限制为 ±12 度,最终范围 −85–85 度
注视控制请求偏航/俯仰上限 18/12 度

朝向要求双方不同、存活、未移除且处于同一世界,转向者还不能睡眠。服务端生物在存在目标、攻击标记、攻击目标大脑记忆,或 NPC 战斗阶段非空闲时,让出朝向控制。若对话玩家的队伍/观众查询表明正在播放服务端过场,也会让出控制。这里修改实体已有的姿态字段,不另装身体动画。

近处地形淡化

CameraMixin 每次更新镜头时推进 CameraBlockFade,依据是控制器是否返回了脚本姿态。对话、镜头 API 和完整过场姿态均包含在内,即使混合权重仍为零。没有姿态的待处理请求本身不会启用淡化。

包络值行为
近距离0.35 方块
远距离1.8 方块
进入时间常量0.14 秒
退出时间常量0.2 秒
单帧经过时间上限0.1 秒
直接贴合目标的容差距目标强度 0.001

存在脚本姿态时,指数包络趋近强度 1,否则趋近 0。世界改变会重置包络,断线会重置保存的世界、时间与强度。这只改变渲染,不修改方块、碰撞或区块网格。

注入的片段函数根据片段坐标、视口和逆投影重建视图空间位置。覆盖率为 smoothstep(near, far, distance),不透明度按包络强度从 1 插值到覆盖率。稳定的屏幕空间噪声阈值据此丢弃片段。全强度时,近距离以内的几何体消失,远距离以外保持不变,中间区域呈现抖动淡化,不使用 alpha 混合。

渲染路径与限制

路径补丁覆盖
原版名为 rendertype_solid、rendertype_cutout、rendertype_cutout_mipped、rendertype_translucent 和 rendertype_tripwire 的片段程序,允许命名空间前缀
Sodium源 sodium:blocks/block_layer_opaque.fsh,GL 程序绑定时上传 uniform
Iris 配合 Sodium 地形管线返回的主视图不透明、镂空和半透明地形片段源;不修改阴影 getter

Sodium 和 Iris mixin 按各自模组 ID 是否存在启用。这些是当前仓库实现的钩子,不保证所有外部渲染器或光影包都能接受生成的程序。

补丁先遮蔽注释,再寻找第一个 void main() 或 void main(void),在其前插入声明,并在函数开头调用丢弃逻辑。若任意位置已出现标记 iseCameraFadeStrength、找不到匹配的 main,或去注释后的源中含 early_fragment_tests,则原样返回。标记检查包含注释,因此注释里出现它也会阻止注入。

Uniform 位置属于各个已链接程序包装器。没有强度 uniform 时不上传;权重为零时仅上传强度,活动时读取当前投影与 GL 视口。地形路径不涵盖任意实体、方块实体、粒子或自定义着色器程序,也不修改 Iris 阴影源。因此,主视图中隐藏的材质仍可能投下阴影。

扩展方式

普通对话使用现有 Blabber 开始/关闭钩子。内部切换其他动画时,服务端调用必须携带真实活动对话会话及说话人:

void sendDialogueShot(ServerPlayerEntity player, Entity speaker) {
    var session = Blabber.getActiveDialogueSession(player).orElse(null);
    if (session != null) {
        DialogueCameraNetworking.sendStart(
                player, session.sessionId(),
                new Identifier("isekaiexpansion", "dialogue_humanoid"),
                player, speaker
        );
    }
}

在服务端线程运行该调用,服务端初始化路径不得加载纯客户端支架类。接收端随后解析实体和动画,发送本身没有播放确认。关闭指定对话时使用匹配的会话令牌,null 明确表示通配停止。

支架自行构造默认设置,目前修改构图需改实现默认值,或添加显式设置传入路径。新增构图规则应在评分与最终输出中使用同一套构图/投影辅助方法。新增渲染器桥接必须只修改目标片段路径,并为对应链接程序上传 uniform,不能跨程序复用位置。

验证

  • 打开普通和父级对话,再在相同演员的对话间切换。确认支架延续,旧停止令牌不能关闭新会话。
  • 延迟说话人追踪、让不同 UUID 复用实体 ID,并在重试次数耗尽前关闭界面,检查清理及缺失界面阈值。
  • 测试高矮说话人、近乎重合的演员、宽窄窗口、墙壁、角落和完全受阻房间,检查两侧肩后选择及 FOV 上限。
  • 在移动或下落时开始对话,再关闭、进入战斗或启动完整过场,确认水平保持、垂直运动与朝向控制权。
  • 用其他镜头替换对话镜头,再发送旧关闭事件,确认仅释放对话移动控制。
  • 分别在原版、Sodium 和 Iris 中检查淡化进出、维度切换与资源重载。包括有阴影地形和含 early_fragment_tests 的着色器;跳过补丁时预期源保持不变。
  • 这些检查描述源码预期行为,不代表已认证视觉输出或未测试光影包的兼容性。

相关页面