Add fog material creation and management in ViewportHost

This commit is contained in:
2026-04-07 11:33:35 +02:00
parent f6cb305243
commit d4587ebfcd
2 changed files with 138 additions and 37 deletions

View File

@@ -1704,7 +1704,7 @@ export class ViewportHost {
for (const brush of Object.values(document.brushes)) { for (const brush of Object.values(document.brushes)) {
const geometry = buildBoxBrushDerivedMeshData(brush).geometry; const geometry = buildBoxBrushDerivedMeshData(brush).geometry;
const contactPatches = brush.volume.mode === "water" ? this.collectViewportWaterContactPatches(document, brush) : []; const contactPatches = brush.volume.mode === "water" ? this.collectViewportWaterContactPatches(document, brush) : [];
const materials = BOX_FACE_IDS.map((faceId) => this.createFaceMaterial(brush, faceId, document.materials[brush.faces[faceId].materialId ?? ""], this.getFaceHighlightState(brush.id, faceId), volumeRenderPaths, contactPatches)); const materials = this.createFogMaterialSet(brush, volumeRenderPaths) ?? BOX_FACE_IDS.map((faceId) => this.createFaceMaterial(brush, faceId, document.materials[brush.faces[faceId].materialId ?? ""], this.getFaceHighlightState(brush.id, faceId), volumeRenderPaths, contactPatches));
const mesh = new Mesh(geometry, materials); const mesh = new Mesh(geometry, materials);
const brushSelected = isBrushSelected(selection, brush.id); const brushSelected = isBrushSelected(selection, brush.id);
mesh.userData.brushId = brush.id; mesh.userData.brushId = brush.id;
@@ -1736,7 +1736,7 @@ export class ViewportHost {
this.applyShadowState(); this.applyShadowState();
} }
configureFogVolumeMesh(mesh, materials) { configureFogVolumeMesh(mesh, materials) {
const fogMaterials = materials.filter((material) => material instanceof ShaderMaterial && material.uniforms["localCameraPosition"] !== undefined); const fogMaterials = Array.from(new Set(materials.filter((material) => material instanceof ShaderMaterial && material.uniforms["localCameraPosition"] !== undefined)));
if (fogMaterials.length === 0) { if (fogMaterials.length === 0) {
return; return;
} }
@@ -1747,6 +1747,46 @@ export class ViewportHost {
} }
}; };
} }
createFogMaterialSet(brush, volumeRenderPaths) {
if (brush.volume.mode !== "fog" || this.displayMode === "wireframe" || this.displayMode === "authoring") {
return null;
}
const highlightStates = BOX_FACE_IDS.map((faceId) => this.getFaceHighlightState(brush.id, faceId));
const selectedFace = highlightStates.includes("selected");
const hoveredFace = !selectedFace && highlightStates.includes("hovered");
const quality = volumeRenderPaths.fog === "quality";
const densityBoost = selectedFace ? 1.08 : hoveredFace ? 1.04 : 1;
const opacityBoost = selectedFace ? 0.08 : hoveredFace ? 0.04 : 0;
if (quality) {
const fogMaterial = createFogQualityMaterial({
colorHex: brush.volume.fog.colorHex,
density: brush.volume.fog.density * densityBoost,
padding: brush.volume.fog.padding,
time: this.volumeTime,
halfSize: {
x: brush.size.x * 0.5,
y: brush.size.y * 0.5,
z: brush.size.z * 0.5
},
opacityMultiplier: densityBoost,
colorLift: selectedFace ? 0.08 : hoveredFace ? 0.04 : 0
});
this.volumeAnimatedUniforms.push(fogMaterial.animationUniform);
return BOX_FACE_IDS.map(() => fogMaterial.material);
}
const baseOpacity = Math.max(0.08, Math.min(0.82, brush.volume.fog.density * 0.9 + 0.1));
const fogMaterial = new MeshStandardMaterial({
color: brush.volume.fog.colorHex,
emissive: brush.volume.fog.colorHex,
emissiveIntensity: 0.04,
roughness: 1,
metalness: 0,
transparent: true,
opacity: Math.min(0.92, baseOpacity + opacityBoost),
depthWrite: false
});
return BOX_FACE_IDS.map(() => fogMaterial);
}
rebuildEntityMarkers(document, selection) { rebuildEntityMarkers(document, selection) {
this.clearEntityMarkers(); this.clearEntityMarkers();
for (const entity of getEntityInstances(document.entities)) { for (const entity of getEntityInstances(document.entities)) {
@@ -2511,10 +2551,9 @@ export class ViewportHost {
renderObjects.edges.material.color.setHex(brushSelected ? BRUSH_SELECTED_EDGE_COLOR : brushHovered && this.whiteboxSelectionMode === "object" ? BRUSH_HOVERED_EDGE_COLOR : BRUSH_EDGE_COLOR); renderObjects.edges.material.color.setHex(brushSelected ? BRUSH_SELECTED_EDGE_COLOR : brushHovered && this.whiteboxSelectionMode === "object" ? BRUSH_HOVERED_EDGE_COLOR : BRUSH_EDGE_COLOR);
const previousMaterials = renderObjects.mesh.material; const previousMaterials = renderObjects.mesh.material;
const contactPatches = brush.volume.mode === "water" ? this.collectViewportWaterContactPatches(this.currentDocument, brush) : []; const contactPatches = brush.volume.mode === "water" ? this.collectViewportWaterContactPatches(this.currentDocument, brush) : [];
renderObjects.mesh.material = BOX_FACE_IDS.map((faceId) => this.createFaceMaterial(brush, faceId, this.currentDocument?.materials[brush.faces[faceId].materialId ?? ""], this.getFaceHighlightState(brush.id, faceId), volumeRenderPaths, contactPatches)); renderObjects.mesh.material = this.createFogMaterialSet(brush, volumeRenderPaths) ?? BOX_FACE_IDS.map((faceId) => this.createFaceMaterial(brush, faceId, this.currentDocument?.materials[brush.faces[faceId].materialId ?? ""], this.getFaceHighlightState(brush.id, faceId), volumeRenderPaths, contactPatches));
for (const material of previousMaterials) { this.configureFogVolumeMesh(renderObjects.mesh, renderObjects.mesh.material);
material.dispose(); this.disposeUniqueMaterials(previousMaterials);
}
const hoveredEdgeId = this.hoveredSelection.kind === "brushEdge" && this.hoveredSelection.brushId === brush.id ? this.hoveredSelection.edgeId : null; const hoveredEdgeId = this.hoveredSelection.kind === "brushEdge" && this.hoveredSelection.brushId === brush.id ? this.hoveredSelection.edgeId : null;
const hoveredVertexId = this.hoveredSelection.kind === "brushVertex" && this.hoveredSelection.brushId === brush.id ? this.hoveredSelection.vertexId : null; const hoveredVertexId = this.hoveredSelection.kind === "brushVertex" && this.hoveredSelection.brushId === brush.id ? this.hoveredSelection.vertexId : null;
for (const edgeHelper of renderObjects.edgeHelpers) { for (const edgeHelper of renderObjects.edgeHelpers) {
@@ -2541,6 +2580,11 @@ export class ViewportHost {
} }
} }
} }
disposeUniqueMaterials(materials) {
for (const material of new Set(materials)) {
material.dispose();
}
}
clearLocalLights() { clearLocalLights() {
for (const renderObjects of this.localLightRenderObjects.values()) { for (const renderObjects of this.localLightRenderObjects.values()) {
this.localLightGroup.remove(renderObjects.group); this.localLightGroup.remove(renderObjects.group);
@@ -2562,9 +2606,7 @@ export class ViewportHost {
vertexHelper.mesh.material.dispose(); vertexHelper.mesh.material.dispose();
} }
renderObjects.mesh.geometry.dispose(); renderObjects.mesh.geometry.dispose();
for (const material of renderObjects.mesh.material) { this.disposeUniqueMaterials(renderObjects.mesh.material);
material.dispose();
}
renderObjects.edges.geometry.dispose(); renderObjects.edges.geometry.dispose();
renderObjects.edges.material.dispose(); renderObjects.edges.material.dispose();
} }

View File

@@ -2306,16 +2306,18 @@ export class ViewportHost {
const geometry = buildBoxBrushDerivedMeshData(brush).geometry; const geometry = buildBoxBrushDerivedMeshData(brush).geometry;
const contactPatches = brush.volume.mode === "water" ? this.collectViewportWaterContactPatches(document, brush) : []; const contactPatches = brush.volume.mode === "water" ? this.collectViewportWaterContactPatches(document, brush) : [];
const materials = BOX_FACE_IDS.map((faceId) => const materials =
this.createFaceMaterial( this.createFogMaterialSet(brush, volumeRenderPaths) ??
brush, BOX_FACE_IDS.map((faceId) =>
faceId, this.createFaceMaterial(
document.materials[brush.faces[faceId].materialId ?? ""], brush,
this.getFaceHighlightState(brush.id, faceId), faceId,
volumeRenderPaths, document.materials[brush.faces[faceId].materialId ?? ""],
contactPatches this.getFaceHighlightState(brush.id, faceId),
) volumeRenderPaths,
); contactPatches
)
);
const mesh = new Mesh(geometry, materials); const mesh = new Mesh(geometry, materials);
const brushSelected = isBrushSelected(selection, brush.id); const brushSelected = isBrushSelected(selection, brush.id);
@@ -2358,8 +2360,12 @@ export class ViewportHost {
} }
private configureFogVolumeMesh(mesh: Mesh<BufferGeometry, Material[]>, materials: Material[]) { private configureFogVolumeMesh(mesh: Mesh<BufferGeometry, Material[]>, materials: Material[]) {
const fogMaterials = materials.filter( const fogMaterials = Array.from(
new Set(
materials.filter(
(material): material is ShaderMaterial => material instanceof ShaderMaterial && material.uniforms["localCameraPosition"] !== undefined (material): material is ShaderMaterial => material instanceof ShaderMaterial && material.uniforms["localCameraPosition"] !== undefined
)
)
); );
if (fogMaterials.length === 0) { if (fogMaterials.length === 0) {
@@ -2375,6 +2381,55 @@ export class ViewportHost {
}; };
} }
private createFogMaterialSet(
brush: BoxBrush,
volumeRenderPaths: { fog: "performance" | "quality"; water: "performance" | "quality" }
): Material[] | null {
if (brush.volume.mode !== "fog" || this.displayMode === "wireframe" || this.displayMode === "authoring") {
return null;
}
const highlightStates = BOX_FACE_IDS.map((faceId) => this.getFaceHighlightState(brush.id, faceId));
const selectedFace = highlightStates.includes("selected");
const hoveredFace = !selectedFace && highlightStates.includes("hovered");
const quality = volumeRenderPaths.fog === "quality";
const densityBoost = selectedFace ? 1.08 : hoveredFace ? 1.04 : 1;
const opacityBoost = selectedFace ? 0.08 : hoveredFace ? 0.04 : 0;
if (quality) {
const fogMaterial = createFogQualityMaterial({
colorHex: brush.volume.fog.colorHex,
density: brush.volume.fog.density * densityBoost,
padding: brush.volume.fog.padding,
time: this.volumeTime,
halfSize: {
x: brush.size.x * 0.5,
y: brush.size.y * 0.5,
z: brush.size.z * 0.5
},
opacityMultiplier: densityBoost,
colorLift: selectedFace ? 0.08 : hoveredFace ? 0.04 : 0
});
this.volumeAnimatedUniforms.push(fogMaterial.animationUniform);
return BOX_FACE_IDS.map(() => fogMaterial.material);
}
const baseOpacity = Math.max(0.08, Math.min(0.82, brush.volume.fog.density * 0.9 + 0.1));
const fogMaterial = new MeshStandardMaterial({
color: brush.volume.fog.colorHex,
emissive: brush.volume.fog.colorHex,
emissiveIntensity: 0.04,
roughness: 1,
metalness: 0,
transparent: true,
opacity: Math.min(0.92, baseOpacity + opacityBoost),
depthWrite: false
});
return BOX_FACE_IDS.map(() => fogMaterial);
}
private rebuildEntityMarkers(document: SceneDocument, selection: EditorSelection) { private rebuildEntityMarkers(document: SceneDocument, selection: EditorSelection) {
this.clearEntityMarkers(); this.clearEntityMarkers();
@@ -3454,20 +3509,21 @@ export class ViewportHost {
const previousMaterials = renderObjects.mesh.material; const previousMaterials = renderObjects.mesh.material;
const contactPatches = brush.volume.mode === "water" ? this.collectViewportWaterContactPatches(this.currentDocument, brush) : []; const contactPatches = brush.volume.mode === "water" ? this.collectViewportWaterContactPatches(this.currentDocument, brush) : [];
renderObjects.mesh.material = BOX_FACE_IDS.map((faceId) => renderObjects.mesh.material =
this.createFaceMaterial( this.createFogMaterialSet(brush, volumeRenderPaths) ??
brush, BOX_FACE_IDS.map((faceId) =>
faceId, this.createFaceMaterial(
this.currentDocument?.materials[brush.faces[faceId].materialId ?? ""], brush,
this.getFaceHighlightState(brush.id, faceId), faceId,
volumeRenderPaths, this.currentDocument?.materials[brush.faces[faceId].materialId ?? ""],
contactPatches this.getFaceHighlightState(brush.id, faceId),
) volumeRenderPaths,
); contactPatches
)
);
this.configureFogVolumeMesh(renderObjects.mesh, renderObjects.mesh.material);
for (const material of previousMaterials) { this.disposeUniqueMaterials(previousMaterials);
material.dispose();
}
const hoveredEdgeId = this.hoveredSelection.kind === "brushEdge" && this.hoveredSelection.brushId === brush.id ? this.hoveredSelection.edgeId : null; const hoveredEdgeId = this.hoveredSelection.kind === "brushEdge" && this.hoveredSelection.brushId === brush.id ? this.hoveredSelection.edgeId : null;
const hoveredVertexId = this.hoveredSelection.kind === "brushVertex" && this.hoveredSelection.brushId === brush.id ? this.hoveredSelection.vertexId : null; const hoveredVertexId = this.hoveredSelection.kind === "brushVertex" && this.hoveredSelection.brushId === brush.id ? this.hoveredSelection.vertexId : null;
@@ -3507,6 +3563,12 @@ export class ViewportHost {
} }
} }
private disposeUniqueMaterials(materials: Material[]) {
for (const material of new Set(materials)) {
material.dispose();
}
}
private clearLocalLights() { private clearLocalLights() {
for (const renderObjects of this.localLightRenderObjects.values()) { for (const renderObjects of this.localLightRenderObjects.values()) {
this.localLightGroup.remove(renderObjects.group); this.localLightGroup.remove(renderObjects.group);
@@ -3530,10 +3592,7 @@ export class ViewportHost {
vertexHelper.mesh.material.dispose(); vertexHelper.mesh.material.dispose();
} }
renderObjects.mesh.geometry.dispose(); renderObjects.mesh.geometry.dispose();
this.disposeUniqueMaterials(renderObjects.mesh.material);
for (const material of renderObjects.mesh.material) {
material.dispose();
}
renderObjects.edges.geometry.dispose(); renderObjects.edges.geometry.dispose();
renderObjects.edges.material.dispose(); renderObjects.edges.material.dispose();