mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
## Summary
Adds `HDRIManager` to load `.hdr/.exr` files as equirectangular
environment maps via **three.js** `RGBELoader/EXRLoader`
- Uploads HDRI files to the server via `/upload/image` API so they
persist across page reloads
- Restores HDRI state (enabled, **intensity**, **background**) from node
properties on reload
- Auto-enables "**Show as Background**" on successful upload for
immediate visual feedback
- Hides standard directional lights when HDRI is active; restores them
when disabled
- Hides the Light Intensity control while HDRI is active (lights have no
effect when HDRI overrides scene lighting)
- Limits HDRI availability to PBR-capable formats (.gltf, .glb, .fbx,
.obj); automatically disables when switching to an incompatible model
- Adds intensity slider and "**Show as Background**" toggle to the HDRI
panel
## How to Use HDRI Environment Lighting
1. Load a 3D model using a Load3D or Load3DViewer node (supported
formats: .gltf, .glb, .fbx, .obj)
2. Open the control panel → go to the Light tab
3. Click the globe icon to open the **HDRI panel**
4. Click Upload HDRI and select a` .hdr` or `.exr` file
5. The environment lighting applies automatically — the scene background
also updates to preview the panorama
6. Use the intensity slider to adjust the strength of the environment
lighting
7. Toggle Show as Background to show or hide the HDRI panorama behind
the model
## Screenshots
https://github.com/user-attachments/assets/1ec56ef0-853e-452f-ae2b-2474c9d0d781
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10818-feat-load3d-add-optional-HDRI-environment-lighting-to-3D-preview-nodes-3366d73d365081ea8c7ad9226b8b1e2f)
by [Unito](https://www.unito.io)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Adds new HDRI loading/rendering path and persists new
`LightConfig.hdri` state, touching Three.js rendering, file uploads, and
node property restoration. Risk is moderate due to new async flows and
potential compatibility/performance issues with model switching and
renderer settings.
>
> **Overview**
> Adds optional **HDRI environment lighting** to Load3D previews,
including a new `HDRIManager` that loads `.hdr`/`.exr` files into
Three.js environment/background and exposes controls for enable/disable,
background display, and intensity.
>
> Extends `LightConfig` with an `hdri` block that is persisted on nodes
and restored on reload; `useLoad3d` now uploads HDRI files, loads them
into `Load3d`, maps scene light intensity to HDRI intensity, and
auto-disables HDRI when the current model format doesn’t support it.
>
> Updates the UI to include embedded HDRI controls under the Light panel
(with dismissable overlays and icon updates), adjusts light intensity
behavior when HDRI is active, and adds tests/strings/utilities
(`getFilenameExtension`, `mapSceneLightIntensityToHdri`, new constants)
to support the feature.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b12c9722dc. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Terry Jia <terryjia88@gmail.com>
173 lines
4.7 KiB
Vue
173 lines
4.7 KiB
Vue
<template>
|
|
<div class="flex flex-col">
|
|
<Button
|
|
v-tooltip.right="{ value: $t('load3d.showGrid'), showDelay: 300 }"
|
|
variant="textonly"
|
|
size="icon"
|
|
:class="cn('rounded-full', showGrid && 'ring-2 ring-white/50')"
|
|
:aria-label="$t('load3d.showGrid')"
|
|
@click="toggleGrid"
|
|
>
|
|
<i class="pi pi-table text-lg text-base-foreground" />
|
|
</Button>
|
|
|
|
<template v-if="!hdriActive">
|
|
<div v-if="!hasBackgroundImage">
|
|
<Button
|
|
v-tooltip.right="{
|
|
value: $t('load3d.backgroundColor'),
|
|
showDelay: 300
|
|
}"
|
|
variant="textonly"
|
|
size="icon"
|
|
class="rounded-full"
|
|
:aria-label="$t('load3d.backgroundColor')"
|
|
@click="openColorPicker"
|
|
>
|
|
<i class="pi pi-palette text-lg text-base-foreground" />
|
|
<input
|
|
ref="colorPickerRef"
|
|
type="color"
|
|
:value="backgroundColor"
|
|
class="pointer-events-none absolute m-0 size-0 p-0 opacity-0"
|
|
@input="
|
|
updateBackgroundColor(($event.target as HTMLInputElement).value)
|
|
"
|
|
/>
|
|
</Button>
|
|
</div>
|
|
|
|
<div v-if="!hasBackgroundImage">
|
|
<Button
|
|
v-tooltip.right="{
|
|
value: $t('load3d.uploadBackgroundImage'),
|
|
showDelay: 300
|
|
}"
|
|
variant="textonly"
|
|
size="icon"
|
|
class="rounded-full"
|
|
:aria-label="$t('load3d.uploadBackgroundImage')"
|
|
@click="openImagePicker"
|
|
>
|
|
<i class="pi pi-image text-lg text-base-foreground" />
|
|
<input
|
|
ref="imagePickerRef"
|
|
type="file"
|
|
accept="image/*"
|
|
class="pointer-events-none absolute m-0 size-0 p-0 opacity-0"
|
|
@change="uploadBackgroundImage"
|
|
/>
|
|
</Button>
|
|
</div>
|
|
</template>
|
|
|
|
<div v-if="hasBackgroundImage">
|
|
<Button
|
|
v-tooltip.right="{
|
|
value: $t('load3d.panoramaMode'),
|
|
showDelay: 300
|
|
}"
|
|
variant="textonly"
|
|
size="icon"
|
|
:class="
|
|
cn(
|
|
'rounded-full',
|
|
backgroundRenderMode === 'panorama' && 'ring-2 ring-white/50'
|
|
)
|
|
"
|
|
:aria-label="$t('load3d.panoramaMode')"
|
|
@click="toggleBackgroundRenderMode"
|
|
>
|
|
<i class="pi pi-globe text-lg text-base-foreground" />
|
|
</Button>
|
|
</div>
|
|
|
|
<PopupSlider
|
|
v-if="hasBackgroundImage && backgroundRenderMode === 'panorama'"
|
|
v-model="fov"
|
|
:tooltip-text="$t('load3d.fov')"
|
|
/>
|
|
|
|
<div v-if="hasBackgroundImage">
|
|
<Button
|
|
v-tooltip.right="{
|
|
value: $t('load3d.removeBackgroundImage'),
|
|
showDelay: 300
|
|
}"
|
|
variant="textonly"
|
|
size="icon"
|
|
class="rounded-full"
|
|
:aria-label="$t('load3d.removeBackgroundImage')"
|
|
@click="removeBackgroundImage"
|
|
>
|
|
<i class="pi pi-times text-lg text-base-foreground" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
|
|
import PopupSlider from '@/components/load3d/controls/PopupSlider.vue'
|
|
import Button from '@/components/ui/button/Button.vue'
|
|
import type { BackgroundRenderModeType } from '@/extensions/core/load3d/interfaces'
|
|
import { cn } from '@/utils/tailwindUtil'
|
|
|
|
const { hdriActive = false } = defineProps<{
|
|
hdriActive?: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'updateBackgroundImage', file: File | null): void
|
|
}>()
|
|
|
|
const showGrid = defineModel<boolean>('showGrid')
|
|
const backgroundColor = defineModel<string>('backgroundColor')
|
|
const backgroundImage = defineModel<string>('backgroundImage')
|
|
const backgroundRenderMode = defineModel<BackgroundRenderModeType>(
|
|
'backgroundRenderMode',
|
|
{ default: 'tiled' }
|
|
)
|
|
const fov = defineModel<number>('fov')
|
|
const hasBackgroundImage = computed(
|
|
() => backgroundImage.value && backgroundImage.value !== ''
|
|
)
|
|
|
|
const colorPickerRef = ref<HTMLInputElement | null>(null)
|
|
const imagePickerRef = ref<HTMLInputElement | null>(null)
|
|
|
|
const toggleGrid = () => {
|
|
showGrid.value = !showGrid.value
|
|
}
|
|
|
|
const updateBackgroundColor = (color: string) => {
|
|
backgroundColor.value = color
|
|
}
|
|
|
|
const openColorPicker = () => {
|
|
colorPickerRef.value?.click()
|
|
}
|
|
|
|
const openImagePicker = () => {
|
|
imagePickerRef.value?.click()
|
|
}
|
|
|
|
const uploadBackgroundImage = (event: Event) => {
|
|
const input = event.target as HTMLInputElement
|
|
|
|
if (input.files && input.files[0]) {
|
|
emit('updateBackgroundImage', input.files[0])
|
|
}
|
|
}
|
|
|
|
const removeBackgroundImage = () => {
|
|
emit('updateBackgroundImage', null)
|
|
}
|
|
|
|
const toggleBackgroundRenderMode = () => {
|
|
backgroundRenderMode.value =
|
|
backgroundRenderMode.value === 'panorama' ? 'tiled' : 'panorama'
|
|
}
|
|
</script>
|