1
This commit is contained in:
+50
-16
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user