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)) {
const geometry = buildBoxBrushDerivedMeshData(brush).geometry;
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 brushSelected = isBrushSelected(selection, brush.id);
mesh.userData.brushId = brush.id;
@@ -1736,7 +1736,7 @@ export class ViewportHost {
this.applyShadowState();
}
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) {
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) {
this.clearEntityMarkers();
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);
const previousMaterials = renderObjects.mesh.material;
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));
for (const material of previousMaterials) {
material.dispose();
}
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));
this.configureFogVolumeMesh(renderObjects.mesh, renderObjects.mesh.material);
this.disposeUniqueMaterials(previousMaterials);
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;
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() {
for (const renderObjects of this.localLightRenderObjects.values()) {
this.localLightGroup.remove(renderObjects.group);
@@ -2562,9 +2606,7 @@ export class ViewportHost {
vertexHelper.mesh.material.dispose();
}
renderObjects.mesh.geometry.dispose();
for (const material of renderObjects.mesh.material) {
material.dispose();
}
this.disposeUniqueMaterials(renderObjects.mesh.material);
renderObjects.edges.geometry.dispose();
renderObjects.edges.material.dispose();
}

View File

@@ -2306,16 +2306,18 @@ export class ViewportHost {
const geometry = buildBoxBrushDerivedMeshData(brush).geometry;
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 brushSelected = isBrushSelected(selection, brush.id);
@@ -2358,8 +2360,12 @@ export class ViewportHost {
}
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
)
)
);
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) {
this.clearEntityMarkers();
@@ -3454,20 +3509,21 @@ export class ViewportHost {
const previousMaterials = renderObjects.mesh.material;
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
)
);
this.configureFogVolumeMesh(renderObjects.mesh, renderObjects.mesh.material);
for (const material of previousMaterials) {
material.dispose();
}
this.disposeUniqueMaterials(previousMaterials);
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;
@@ -3507,6 +3563,12 @@ export class ViewportHost {
}
}
private disposeUniqueMaterials(materials: Material[]) {
for (const material of new Set(materials)) {
material.dispose();
}
}
private clearLocalLights() {
for (const renderObjects of this.localLightRenderObjects.values()) {
this.localLightGroup.remove(renderObjects.group);
@@ -3530,10 +3592,7 @@ export class ViewportHost {
vertexHelper.mesh.material.dispose();
}
renderObjects.mesh.geometry.dispose();
for (const material of renderObjects.mesh.material) {
material.dispose();
}
this.disposeUniqueMaterials(renderObjects.mesh.material);
renderObjects.edges.geometry.dispose();
renderObjects.edges.material.dispose();