diff --git a/furniture-3d-system/src/app/asset-center/material-library/MaterialLibraryClient.tsx b/furniture-3d-system/src/app/asset-center/material-library/MaterialLibraryClient.tsx
index 0e6878b7..45ece190 100644
--- a/furniture-3d-system/src/app/asset-center/material-library/MaterialLibraryClient.tsx
+++ b/furniture-3d-system/src/app/asset-center/material-library/MaterialLibraryClient.tsx
@@ -255,6 +255,7 @@ function FinishWorkbenchPreviewCard({ form }: { form: FinishForm }) {
const textureSize = imageUrl
? `${Math.max(72, Math.round((form.textureRepeat || 1) * 96))}%`
: '100% 100%'
+ const metalHighlightOpacity = Math.min(0.5, Math.max(0, form.metalness * (1 - form.roughness * 0.55)))
return (
@@ -283,9 +284,18 @@ function FinishWorkbenchPreviewCard({ form }: { form: FinishForm }) {
backgroundSize: `100% 100%, 100% 100%, ${textureSize}`,
backgroundPosition: 'center',
opacity: Math.max(0.35, Math.min(1, form.opacity)),
- filter: `saturate(${1 + form.metalness * 0.18}) contrast(${1 + form.metalness * 0.14}) brightness(${1 - form.roughness * 0.04})`,
+ filter: `brightness(${1 - form.roughness * 0.04})`,
}}
>
+
diff --git a/furniture-3d-system/src/lib/3d/__tests__/engine-highlight-resources.test.ts b/furniture-3d-system/src/lib/3d/__tests__/engine-highlight-resources.test.ts
new file mode 100644
index 00000000..cc48e04b
--- /dev/null
+++ b/furniture-3d-system/src/lib/3d/__tests__/engine-highlight-resources.test.ts
@@ -0,0 +1,20 @@
+import * as THREE from "three";
+import { normalizeTooBrightMaterial } from "@/lib/3d/engine-highlight-resources";
+
+describe("normalizeTooBrightMaterial", () => {
+ it("保留材质编辑器写入的金属度、粗糙度和颜色", () => {
+ const material = new THREE.MeshStandardMaterial({
+ color: "#f2f2f2",
+ metalness: 0.92,
+ roughness: 0.12,
+ envMapIntensity: 1.4,
+ });
+
+ normalizeTooBrightMaterial(material);
+
+ expect(material.metalness).toBeCloseTo(0.92);
+ expect(material.roughness).toBeCloseTo(0.12);
+ expect(material.envMapIntensity).toBeCloseTo(1.4);
+ expect(material.color.getHexString()).toBe("f2f2f2");
+ });
+});
diff --git a/furniture-3d-system/src/lib/3d/__tests__/material-manager.test.ts b/furniture-3d-system/src/lib/3d/__tests__/material-manager.test.ts
new file mode 100644
index 00000000..4e27b48a
--- /dev/null
+++ b/furniture-3d-system/src/lib/3d/__tests__/material-manager.test.ts
@@ -0,0 +1,46 @@
+import * as THREE from "three";
+import { MaterialManager, type MaterialConfig } from "@/lib/3d/material-manager";
+
+function createMaterialConfig(overrides: Partial = {}): MaterialConfig {
+ return {
+ id: "material-metal",
+ name: "测试金属",
+ categoryId: "category-metal",
+ categoryName: "金属",
+ thumbnailUrl: null,
+ textureUrl: null,
+ normalMapUrl: null,
+ roughnessMapUrl: null,
+ metalnessMapUrl: null,
+ aoMapUrl: null,
+ color: "#c9ccd0",
+ metalness: 0.92,
+ roughness: 0.22,
+ opacity: 1,
+ textureRepeat: 1,
+ textureRotation: 0,
+ textureSizeMm: 600,
+ ...overrides,
+ };
+}
+
+describe("MaterialManager", () => {
+ it("高金属度材质默认获得更强环境反射", () => {
+ const material = MaterialManager.getInstance().createImmediateMaterial(
+ createMaterialConfig(),
+ );
+
+ expect(material).toBeInstanceOf(THREE.MeshStandardMaterial);
+ expect(material.metalness).toBeCloseTo(0.92);
+ expect(material.roughness).toBeCloseTo(0.22);
+ expect(material.envMapIntensity).toBeCloseTo(1.65);
+ });
+
+ it("显式环境反射强度优先于金属默认值", () => {
+ const material = MaterialManager.getInstance().createImmediateMaterial(
+ createMaterialConfig({ envMapIntensity: 1.18 }),
+ );
+
+ expect(material.envMapIntensity).toBeCloseTo(1.18);
+ });
+});
diff --git a/furniture-3d-system/src/lib/3d/engine-highlight-resources.ts b/furniture-3d-system/src/lib/3d/engine-highlight-resources.ts
index 51b81824..f22a4341 100644
--- a/furniture-3d-system/src/lib/3d/engine-highlight-resources.ts
+++ b/furniture-3d-system/src/lib/3d/engine-highlight-resources.ts
@@ -400,73 +400,8 @@ export function normalizeTooBrightMaterial(
emissiveIntensity?: number;
};
- if (colorLikeMaterial.color instanceof THREE.Color) {
- const originalColor = colorLikeMaterial.color.clone();
- const maxChannel = Math.max(
- originalColor.r,
- originalColor.g,
- originalColor.b,
- );
- if (maxChannel > 1) {
- colorLikeMaterial.color.multiplyScalar(1 / maxChannel);
- }
-
- const r = colorLikeMaterial.color.r;
- const g = colorLikeMaterial.color.g;
- const b = colorLikeMaterial.color.b;
- const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
- const chroma = Math.max(r, g, b) - Math.min(r, g, b);
- const isNearNeutral = chroma < 0.22;
- if (isNearNeutral && luminance > 0.38) {
- const grayStrength = THREE.MathUtils.clamp((luminance - 0.38) / 0.62, 0, 1);
- const hasColorMap = Boolean(colorLikeMaterial.map);
- const nearWhiteGray = hasColorMap
- ? 0.46 - grayStrength * 0.08
- : 0.4 - grayStrength * 0.1;
- const grayTarget = new THREE.Color().setScalar(nearWhiteGray);
- colorLikeMaterial.color.lerp(grayTarget, hasColorMap ? 0.92 : 0.98);
- if (typeof colorLikeMaterial.roughness === "number") {
- colorLikeMaterial.roughness = Math.max(
- colorLikeMaterial.roughness,
- 0.95,
- );
- }
- if (typeof colorLikeMaterial.envMapIntensity === "number") {
- colorLikeMaterial.envMapIntensity = Math.min(
- colorLikeMaterial.envMapIntensity,
- 0.03,
- );
- }
- if (typeof colorLikeMaterial.metalness === "number") {
- colorLikeMaterial.metalness = Math.min(colorLikeMaterial.metalness, 0.02);
- }
- if (
- colorLikeMaterial.emissive instanceof THREE.Color &&
- typeof colorLikeMaterial.emissiveIntensity === "number"
- ) {
- colorLikeMaterial.emissive.setScalar(0);
- colorLikeMaterial.emissiveIntensity = Math.min(
- colorLikeMaterial.emissiveIntensity,
- 0.01,
- );
- }
- }
- }
-
- if (typeof colorLikeMaterial.roughness === "number") {
- colorLikeMaterial.roughness = THREE.MathUtils.clamp(
- colorLikeMaterial.roughness,
- 0.25,
- 1,
- );
- }
- if (typeof colorLikeMaterial.metalness === "number") {
- colorLikeMaterial.metalness = THREE.MathUtils.clamp(
- colorLikeMaterial.metalness,
- 0,
- 0.5,
- );
- }
+ // 材质编辑器的物理参数要在 3D 世界里按设计师填写值生效。
+ // 这里不再归一化 color / roughness / metalness / envMapIntensity。
if (colorLikeMaterial.map) {
colorLikeMaterial.map.colorSpace = THREE.SRGBColorSpace;
diff --git a/furniture-3d-system/src/lib/3d/material-manager.ts b/furniture-3d-system/src/lib/3d/material-manager.ts
index be1c0a8b..d69931fa 100644
--- a/furniture-3d-system/src/lib/3d/material-manager.ts
+++ b/furniture-3d-system/src/lib/3d/material-manager.ts
@@ -42,6 +42,36 @@ export interface MaterialCategoryWithMaterials {
type RuntimeMaterial = THREE.MeshStandardMaterial | THREE.MeshPhysicalMaterial
+function isMetalLikeMaterial(config: MaterialConfig): boolean {
+ const text = `${config.categoryName ?? ''} ${config.name}`.toLowerCase()
+ return (
+ config.metalness >= 0.65 ||
+ text.includes('金属') ||
+ text.includes('不锈钢') ||
+ text.includes('钢') ||
+ text.includes('铝') ||
+ text.includes('铜') ||
+ text.includes('铁') ||
+ text.includes('metal') ||
+ text.includes('steel') ||
+ text.includes('aluminum') ||
+ text.includes('brass')
+ )
+}
+
+function resolveEnvMapIntensity(config: MaterialConfig, options: { isGlassLikeMaterial: boolean }): number {
+ if (typeof config.envMapIntensity === 'number') {
+ return config.envMapIntensity
+ }
+ if (options.isGlassLikeMaterial) {
+ return 1.2
+ }
+ if (isMetalLikeMaterial(config)) {
+ return 1.65
+ }
+ return 1
+}
+
export class MaterialManager {
private static instance: MaterialManager | null = null
private materialCache: Map = new Map()
@@ -135,7 +165,7 @@ export class MaterialManager {
depthWrite: safeOpacity >= 1,
})
- material.envMapIntensity = resolvedConfig.envMapIntensity ?? (isGlassLikeMaterial ? 1.2 : 1)
+ material.envMapIntensity = resolveEnvMapIntensity(resolvedConfig, { isGlassLikeMaterial })
material.userData = {
...material.userData,
textureSizeMm: resolvedConfig.textureSizeMm ?? 600,
diff --git a/furniture-3d-system/src/lib/3d/material-pbr-presets.ts b/furniture-3d-system/src/lib/3d/material-pbr-presets.ts
index 76e383f4..d7697790 100644
--- a/furniture-3d-system/src/lib/3d/material-pbr-presets.ts
+++ b/furniture-3d-system/src/lib/3d/material-pbr-presets.ts
@@ -164,7 +164,7 @@ const TEXTURE_LIBRARY = {
const CATEGORY_DEFAULT_PRESETS: Record = {
wood: { metalness: 0, roughness: 0.62, opacity: 1, textureRepeat: 1.8, envMapIntensity: 0.55 },
solid: { metalness: 0, roughness: 0.42, opacity: 1, textureRepeat: 1, envMapIntensity: 0.4 },
- metal: { metalness: 1, roughness: 0.28, opacity: 1, textureRepeat: 1.3, envMapIntensity: 1.25 },
+ metal: { metalness: 1, roughness: 0.28, opacity: 1, textureRepeat: 1.3, envMapIntensity: 1.65 },
glass: {
metalness: 0,
roughness: 0.08,