diff --git a/Tools/ModelTranslator/bl_convert.py b/Tools/ModelTranslator/bl_convert.py index 8d0e8097..6dcdf4fe 100644 --- a/Tools/ModelTranslator/bl_convert.py +++ b/Tools/ModelTranslator/bl_convert.py @@ -39,11 +39,13 @@ def linear_to_srgb(c): 1.055 * np.power(c, 1.0 / 2.4) - 0.055) -def encode_base_texture_for_png(rgba): - """Base RGB is stored for Unity sRGB import; alpha keeps AO/opacity data.""" - out = np.array(rgba, dtype=np.float32, copy=True) - out[..., :3] = linear_to_srgb(out[..., :3]) - out[..., 3] = np.clip(out[..., 3], 0.0, 1.0) +def encode_base_texture_for_png(rgba, rgb_is_linear): + """Base RGB is stored for Unity sRGB import; alpha keeps AO/opacity data. + Image.pixels 对 8-bit 图返回原始字节值(已是 sRGB 编码,实测 blender 5.0), + 仅线性来源(float 图 / Principled 常量色)需要 linear->sRGB,否则双重 gamma 变亮。""" + out = np.clip(np.array(rgba, dtype=np.float32, copy=True), 0.0, 1.0) + if rgb_is_linear: + out[..., :3] = linear_to_srgb(out[..., :3]) return out @@ -280,8 +282,11 @@ def _convert_material(mat, model_name, outdir, max_size, used_snames, src_dir): base = np.empty((th, tw, 4), dtype=np.float32) if maps["basecolor"]: base[..., :3] = _scaled_pixels(maps["basecolor"][0], tw, th)[..., :3] + # 8-bit 图 pixels 已是 sRGB 编码值;float 图(EXR 等)才是线性 + rgb_is_linear = bool(maps["basecolor"][0].is_float) else: base[..., :3] = np.array(consts["basecolor"][:3], dtype=np.float32) + rgb_is_linear = True # Principled 常量色是线性值 if maps["alpha"]: img, ch = maps["alpha"] base[..., 3] = _channel(_scaled_pixels(img, tw, th), ch) @@ -330,7 +335,7 @@ def _convert_material(mat, model_name, outdir, max_size, used_snames, src_dir): stem = model_name if sname == model_name else "%s_%s" % (model_name, sname) base_png = os.path.abspath(os.path.join(outdir, "%s_base.png" % stem)) mix_png = os.path.abspath(os.path.join(outdir, "%s_mix.png" % stem)) - _save_png(base_png, encode_base_texture_for_png(base)) + _save_png(base_png, encode_base_texture_for_png(base, rgb_is_linear)) _save_png(mix_png, mix) return { diff --git a/Tools/ModelTranslator/tests/test_bl_convert.py b/Tools/ModelTranslator/tests/test_bl_convert.py index ec737eb3..4fe0934a 100644 --- a/Tools/ModelTranslator/tests/test_bl_convert.py +++ b/Tools/ModelTranslator/tests/test_bl_convert.py @@ -9,15 +9,36 @@ import bl_convert as bc class TestBaseTextureEncoding(unittest.TestCase): - def test_base_texture_rgb_is_encoded_as_srgb_and_alpha_is_preserved(self): + # Blender Image.pixels 对 8-bit 图返回原始字节值(不做色彩变换,实测 blender 5.0: + # 字节 32/128/192 -> 0.1255/0.502/0.7529),即 sRGB PNG 读出来已是 sRGB 编码值。 + # 仅线性来源(float 图 / Principled 常量色)需要 linear->sRGB。 + + def test_linear_source_rgb_is_encoded_as_srgb_and_alpha_is_preserved(self): rgba = np.array([[[0.0, 0.18, 0.5, 0.37]]], dtype=np.float32) - encoded = bc.encode_base_texture_for_png(rgba) + encoded = bc.encode_base_texture_for_png(rgba, rgb_is_linear=True) expected_rgb = np.array([0.0, 0.461356, 0.735357], dtype=np.float32) np.testing.assert_allclose(encoded[0, 0, :3], expected_rgb, rtol=0, atol=1e-5) self.assertAlmostEqual(float(encoded[0, 0, 3]), 0.37, places=6) + def test_byte_source_rgb_passes_through_unchanged(self): + # 8-bit 贴图像素已是 sRGB 编码,再编码一次会双重 gamma(well10k 变亮 bug) + rgba = np.array([[[0.1255, 0.502, 0.7529, 0.37]]], dtype=np.float32) + + encoded = bc.encode_base_texture_for_png(rgba, rgb_is_linear=False) + + np.testing.assert_allclose(encoded[0, 0], rgba[0, 0], rtol=0, atol=1e-6) + + def test_out_of_range_values_are_clipped(self): + rgba = np.array([[[-0.1, 1.2, 0.5, 1.5]]], dtype=np.float32) + + encoded = bc.encode_base_texture_for_png(rgba, rgb_is_linear=False) + + self.assertEqual(float(encoded[0, 0, 0]), 0.0) + self.assertEqual(float(encoded[0, 0, 1]), 1.0) + self.assertEqual(float(encoded[0, 0, 3]), 1.0) + if __name__ == "__main__": unittest.main()