This commit is contained in:
2026-10-03 05:40:26 +08:00
parent faf799837f
commit 7bcbcc69ea
7 changed files with 96 additions and 44 deletions
+50 -16
View File
@@ -2,9 +2,10 @@
extends McpTestSuite
## 回归测试:脸部那套「UV 矩形 + 预设 + 画笔」的契约。
##
## body_mesh 现在只有一个材质 mat_body,脸部不再单独换材质,而是靠 face_uv_min / face_uv_size
## 在这块 UV 上圈出脸部矩形。**模型重导或改 UV 之后,脸就会画歪、被裁掉、甚至画到别的部位上,
## 而且不报任何错** —— 所以这里把数值钉死,改模型时先让测试红一次。
## body_mesh 只有一个材质 mat_body,脸部不再单独换材质,而是由 shader 顶部的
## face_center / face_size 在本体 UV 上圈出表情矩形。**模型重导或改 UV 之后,脸就会画歪、
## 被裁掉、甚至糊到肚子上,而且不报任何错** —— 所以这里把「矩形必须落在头部孤岛里」钉死,
## 改模型时先让测试红一次。数值本身从 shader 源码里读,方便随手调。
const PLAYER_SCENE_PATH := "res://character/player/player.tscn"
const SHADER_PATH := "res://character/player/face/face_paint.gdshader"
@@ -12,10 +13,7 @@ const PRESET_DIR := "res://character/player/face/presets"
const BODY_MESH_PATH := "Body/mouse/MouseRig/Skeleton3D/body_mesh"
const BODY_MATERIAL_NAME := "mat_body"
## FaceRig / shader 里脸部矩形的默认值;必须和 shader 的 uniform 默认值一致
const FACE_UV_MIN := Vector2(0.695, 0.682)
const FACE_UV_SIZE := Vector2(0.301, 0.314)
## UV 是浮点,两边各写一份,给点容差
## UV 是浮点,给点容差
const EPS := 0.004
@@ -23,6 +21,31 @@ func suite_name() -> String:
return "face"
func _shader_code() -> String:
var file := FileAccess.open(SHADER_PATH, FileAccess.READ)
assert_true(file != null, "%s 加载失败" % SHADER_PATH)
if file == null:
return ""
return file.get_as_text()
## 从 shader 源码里抓 `uniform vec2 名字 = vec2(a, b);` 的两个数。
func _shader_vec2(code: String, name: String) -> Vector2:
var marker := "uniform vec2 %s = vec2(" % name
var start := code.find(marker)
assert_true(start >= 0, "shader 里找不到 uniform vec2 %s 的默认值" % name)
if start < 0:
return Vector2(INF, INF)
start += marker.length()
var stop := code.find(")", start)
if stop < 0:
return Vector2(INF, INF)
var parts := code.substr(start, stop - start).split(",")
if parts.size() != 2:
return Vector2(INF, INF)
return Vector2(float(parts[0].strip_edges()), float(parts[1].strip_edges()))
func _player() -> Node:
var packed := load(PLAYER_SCENE_PATH) as PackedScene
assert_true(packed != null, "%s 未能加载" % PLAYER_SCENE_PATH)
@@ -114,6 +137,11 @@ func test_player_has_face_rig_wired() -> void:
assert_eq(rig.surface_index, 0, "body_mesh 只剩一个材质,FaceRig 必须覆盖 surface 0")
assert_true(rig.get_node_or_null(rig.mesh_path) is MeshInstance3D,
"FaceRig.mesh_path(%s)解析不到 MeshInstance3D —— 换模型后路径要跟着改" % rig.mesh_path)
# 表情位置/大小只写在 shader 里;脚本一旦也传这三个 uniform,就会把调好的值盖掉
var script_text := FileAccess.open("res://character/player/face/face_rig.gd", FileAccess.READ).get_as_text()
for name in ["face_center", "face_size", "face_flip_y"]:
assert_true(not script_text.contains("set_shader_parameter(\"%s\"" % name),
"face_rig.gd 又在传 %s 了 —— shader 顶部调好的值会被脚本覆盖" % name)
func test_body_mesh_has_single_material() -> void:
@@ -139,20 +167,26 @@ func test_body_mesh_has_single_material() -> void:
## 脸部矩形必须整块落在头部孤岛里,否则表情会画到身体别的部位上。
func test_face_uv_rect_inside_head_island() -> void:
## 表情矩形(中心 ± 半个大小)必须整块落在头部孤岛里,否则表情会糊到别的部位上。
func test_face_rect_inside_head_island() -> void:
var code := _shader_code()
if code.is_empty():
return
var center := _shader_vec2(code, "face_center")
var size := _shader_vec2(code, "face_size")
if not center.is_finite() or not size.is_finite():
return
var head := _head_island_uv_rect()
assert_true(head.size.x > 0.0 and head.size.y > 0.0, "没算出头部 UV 孤岛 —— 模型结构变了?")
if head.size.x <= 0.0:
return
# 允许负边长(负数 = 把该轴翻过来),所以先归一化成 min/max
var rect_min := Vector2(minf(FACE_UV_MIN.x, FACE_UV_MIN.x + FACE_UV_SIZE.x),
minf(FACE_UV_MIN.y, FACE_UV_MIN.y + FACE_UV_SIZE.y))
var rect_max := Vector2(maxf(FACE_UV_MIN.x, FACE_UV_MIN.x + FACE_UV_SIZE.x),
maxf(FACE_UV_MIN.y, FACE_UV_MIN.y + FACE_UV_SIZE.y))
var rect_min := center - size * 0.5
var rect_max := center + size * 0.5
assert_true(rect_min.x >= head.position.x - EPS and rect_min.y >= head.position.y - EPS,
"脸部矩形起点 %s 跑出了头部 UV 孤岛 %s" % [rect_min, head])
"表情矩形左下角 %s 跑出了头部 UV 孤岛 %s(中心 %s 大小 %s)" % [rect_min, head, center, size])
assert_true(rect_max.x <= head.end.x + EPS and rect_max.y <= head.end.y + EPS,
"脸部矩形终点 %s 超出了头部 UV 孤岛 %s —— 模型改过 UV?" % [rect_max, head])
"表情矩形右上角 %s 超出了头部 UV 孤岛 %s(中心 %s 大小 %s)—— 模型改过 UV 还是值调过头了?"
% [rect_max, head, center, size])
func test_face_shader_has_expected_uniforms() -> void:
@@ -163,7 +197,7 @@ func test_face_shader_has_expected_uniforms() -> void:
var code := file.get_as_text()
for required in [
"body_tex", "body_tint", "surface_roughness", "surface_metallic",
"face_uv_min", "face_uv_size", "face_uv_flip_y",
"face_center", "face_size", "face_flip_y",
"base_tex", "base_uv_min", "base_uv_size", "paint_tex",
]:
assert_true(code.contains("uniform ") and code.contains(required), "shader 少了 uniform %s" % required)