修复导览三维模型材质反光过强
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
cxk
2026-07-20 18:18:19 +08:00
parent 3cc0336806
commit 65ba9720f8
3 changed files with 157 additions and 0 deletions

View File

@@ -92,6 +92,7 @@ import {
type PoiDomLabelAnchor,
type PoiDomLabelKind
} from './poiDomLabels'
import { applyGuideModelMaterialPolicy } from './modelMaterialPolicy'
type ViewMode = 'overview' | 'floor' | 'multi'
type CameraPreset = 'top' | 'oblique'
@@ -2923,6 +2924,8 @@ const loadModelWithFallback = async (
}
const prepareModel = (model: THREE.Object3D) => {
applyGuideModelMaterialPolicy(model)
model.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.castShadow = false

View File

@@ -0,0 +1,85 @@
import * as THREE from 'three'
export const GUIDE_MODEL_MATERIAL_POLICY = {
envMapIntensity: 0.42,
nonMetalRoughnessFloor: 0.48,
nonMetalSpecularIntensityCap: 0.6,
metalnessThreshold: 0.5,
transparentOpacityThreshold: 0.99,
transmissionThreshold: 0.01
} as const
const isTransparentOrGlass = (material: THREE.MeshStandardMaterial) => {
const physicalMaterial = material as THREE.MeshPhysicalMaterial
return material.transparent
|| material.opacity < GUIDE_MODEL_MATERIAL_POLICY.transparentOpacityThreshold
|| physicalMaterial.transmission > GUIDE_MODEL_MATERIAL_POLICY.transmissionThreshold
}
const MATERIAL_POLICY_KEY = '__guideModelMaterialPolicyApplied'
const applyNonMetalRoughnessFloor = (material: THREE.MeshStandardMaterial) => {
if (material.roughness < GUIDE_MODEL_MATERIAL_POLICY.nonMetalRoughnessFloor) {
material.roughness = GUIDE_MODEL_MATERIAL_POLICY.nonMetalRoughnessFloor
}
// GLB roughness textures can still produce mirror-like texels when the
// exported scalar factor is already 1. Clamp after Three.js samples that map.
if (!material.roughnessMap || material.userData[MATERIAL_POLICY_KEY]) return
const originalOnBeforeCompile = material.onBeforeCompile
const originalCacheKey = material.customProgramCacheKey
material.onBeforeCompile = (shader, renderer) => {
originalOnBeforeCompile(shader, renderer)
shader.fragmentShader = shader.fragmentShader.replace(
'#include <roughnessmap_fragment>',
`#include <roughnessmap_fragment>\nroughnessFactor = max(roughnessFactor, ${GUIDE_MODEL_MATERIAL_POLICY.nonMetalRoughnessFloor.toFixed(2)});`
)
}
material.customProgramCacheKey = () => `${originalCacheKey.call(material)}|guide-non-metal-roughness-floor`
material.userData[MATERIAL_POLICY_KEY] = true
material.needsUpdate = true
}
const applyNonMetalSpecularCap = (material: THREE.MeshStandardMaterial) => {
if (!(material instanceof THREE.MeshPhysicalMaterial)) return
material.specularIntensity = Math.min(
material.specularIntensity,
GUIDE_MODEL_MATERIAL_POLICY.nonMetalSpecularIntensityCap
)
}
/**
* Normalizes imported GLB PBR factors at one rendering boundary. Textures,
* alpha and deliberate metal/glass material classes remain authored by the model.
*/
export const applyGuideModelMaterialPolicy = (object: THREE.Object3D) => {
let pbrMaterialCount = 0
let nonMetalMaterialCount = 0
let protectedMaterialCount = 0
object.traverse((child) => {
if (!(child instanceof THREE.Mesh)) return
const materials = Array.isArray(child.material) ? child.material : [child.material]
materials.forEach((material) => {
if (!(material instanceof THREE.MeshStandardMaterial)) return
pbrMaterialCount += 1
material.envMapIntensity = GUIDE_MODEL_MATERIAL_POLICY.envMapIntensity
const isMetal = material.metalness >= GUIDE_MODEL_MATERIAL_POLICY.metalnessThreshold
if (isMetal || isTransparentOrGlass(material)) {
protectedMaterialCount += 1
return
}
nonMetalMaterialCount += 1
applyNonMetalRoughnessFloor(material)
applyNonMetalSpecularCap(material)
})
})
return { pbrMaterialCount, nonMetalMaterialCount, protectedMaterialCount }
}