mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-06-01 11:57:24 +08:00
feat: Add gaussian splat nodes (#14190)
Some checks are pending
Detect Unreviewed Merge / detect (push) Waiting to run
Python Linting / Run Ruff (push) Waiting to run
Python Linting / Run Pylint (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Waiting to run
Execution Tests / test (macos-latest) (push) Waiting to run
Execution Tests / test (ubuntu-latest) (push) Waiting to run
Execution Tests / test (windows-latest) (push) Waiting to run
Test server launches without errors / test (push) Waiting to run
Unit Tests / test (macos-latest) (push) Waiting to run
Unit Tests / test (ubuntu-latest) (push) Waiting to run
Unit Tests / test (windows-2022) (push) Waiting to run
Some checks are pending
Detect Unreviewed Merge / detect (push) Waiting to run
Python Linting / Run Ruff (push) Waiting to run
Python Linting / Run Pylint (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Waiting to run
Execution Tests / test (macos-latest) (push) Waiting to run
Execution Tests / test (ubuntu-latest) (push) Waiting to run
Execution Tests / test (windows-latest) (push) Waiting to run
Test server launches without errors / test (push) Waiting to run
Unit Tests / test (macos-latest) (push) Waiting to run
Unit Tests / test (ubuntu-latest) (push) Waiting to run
Unit Tests / test (windows-2022) (push) Waiting to run
This commit is contained in:
parent
cd45f42a83
commit
c37d2a0dac
@ -5,7 +5,7 @@ from comfy_api.internal.singleton import ProxiedSingleton
|
|||||||
from comfy_api.internal.async_to_sync import create_sync_class
|
from comfy_api.internal.async_to_sync import create_sync_class
|
||||||
from ._input import ImageInput, AudioInput, MaskInput, LatentInput, VideoInput
|
from ._input import ImageInput, AudioInput, MaskInput, LatentInput, VideoInput
|
||||||
from ._input_impl import VideoFromFile, VideoFromComponents
|
from ._input_impl import VideoFromFile, VideoFromComponents
|
||||||
from ._util import VideoCodec, VideoContainer, VideoComponents, MESH, VOXEL, File3D
|
from ._util import VideoCodec, VideoContainer, VideoComponents, MESH, VOXEL, SPLAT, File3D
|
||||||
from . import _io_public as io
|
from . import _io_public as io
|
||||||
from . import _ui_public as ui
|
from . import _ui_public as ui
|
||||||
from comfy_execution.utils import get_executing_context
|
from comfy_execution.utils import get_executing_context
|
||||||
@ -143,6 +143,7 @@ class Types:
|
|||||||
VideoComponents = VideoComponents
|
VideoComponents = VideoComponents
|
||||||
MESH = MESH
|
MESH = MESH
|
||||||
VOXEL = VOXEL
|
VOXEL = VOXEL
|
||||||
|
SPLAT = SPLAT
|
||||||
File3D = File3D
|
File3D = File3D
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ if TYPE_CHECKING:
|
|||||||
from comfy_api.internal import (_ComfyNodeInternal, _NodeOutputInternal, classproperty, copy_class, first_real_override, is_class,
|
from comfy_api.internal import (_ComfyNodeInternal, _NodeOutputInternal, classproperty, copy_class, first_real_override, is_class,
|
||||||
prune_dict, shallow_clone_class)
|
prune_dict, shallow_clone_class)
|
||||||
from comfy_execution.graph_utils import ExecutionBlocker
|
from comfy_execution.graph_utils import ExecutionBlocker
|
||||||
from ._util import MESH, VOXEL, SVG as _SVG, File3D
|
from ._util import MESH, VOXEL, SPLAT, SVG as _SVG, File3D
|
||||||
|
|
||||||
|
|
||||||
class FolderType(str, Enum):
|
class FolderType(str, Enum):
|
||||||
@ -684,6 +684,10 @@ class Voxel(ComfyTypeIO):
|
|||||||
class Mesh(ComfyTypeIO):
|
class Mesh(ComfyTypeIO):
|
||||||
Type = MESH
|
Type = MESH
|
||||||
|
|
||||||
|
@comfytype(io_type="SPLAT")
|
||||||
|
class Splat(ComfyTypeIO):
|
||||||
|
Type = SPLAT
|
||||||
|
|
||||||
|
|
||||||
@comfytype(io_type="FILE_3D")
|
@comfytype(io_type="FILE_3D")
|
||||||
class File3DAny(ComfyTypeIO):
|
class File3DAny(ComfyTypeIO):
|
||||||
@ -2320,6 +2324,7 @@ __all__ = [
|
|||||||
"LossMap",
|
"LossMap",
|
||||||
"Voxel",
|
"Voxel",
|
||||||
"Mesh",
|
"Mesh",
|
||||||
|
"Splat",
|
||||||
"File3DAny",
|
"File3DAny",
|
||||||
"File3DGLB",
|
"File3DGLB",
|
||||||
"File3DGLTF",
|
"File3DGLTF",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from .video_types import VideoContainer, VideoCodec, VideoComponents
|
from .video_types import VideoContainer, VideoCodec, VideoComponents
|
||||||
from .geometry_types import VOXEL, MESH, File3D
|
from .geometry_types import VOXEL, MESH, SPLAT, File3D
|
||||||
from .image_types import SVG
|
from .image_types import SVG
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@ -9,6 +9,7 @@ __all__ = [
|
|||||||
"VideoComponents",
|
"VideoComponents",
|
||||||
"VOXEL",
|
"VOXEL",
|
||||||
"MESH",
|
"MESH",
|
||||||
|
"SPLAT",
|
||||||
"File3D",
|
"File3D",
|
||||||
"SVG",
|
"SVG",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -11,13 +11,32 @@ class VOXEL:
|
|||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
|
|
||||||
|
class SPLAT:
|
||||||
|
"""A batch of 3D Gaussian splats in render-ready (activated, world-space) form.
|
||||||
|
|
||||||
|
Tensors are (B, N, ...) and zero-padded to a common N across the batch; `counts` (B,) holds the
|
||||||
|
real per-item lengths (None when rows are uniform and no slicing is needed). SH coefficients are
|
||||||
|
stored as (B, N, K, 3) with K = (sh_degree + 1)**2; the DC (diffuse) term is sh[..., 0, :].
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, positions: torch.Tensor, scales: torch.Tensor, rotations: torch.Tensor,
|
||||||
|
opacities: torch.Tensor, sh: torch.Tensor, counts: torch.Tensor | None = None):
|
||||||
|
self.positions = positions # (B, N, 3) world-space centers
|
||||||
|
self.scales = scales # (B, N, 3) linear (positive) per-axis std
|
||||||
|
self.rotations = rotations # (B, N, 4) quaternion wxyz (normalized)
|
||||||
|
self.opacities = opacities # (B, N, 1) in [0, 1]
|
||||||
|
self.sh = sh # (B, N, K, 3) spherical-harmonic color coefficients
|
||||||
|
self.counts = counts # (B,) real lengths, or None
|
||||||
|
|
||||||
|
|
||||||
class MESH:
|
class MESH:
|
||||||
def __init__(self, vertices: torch.Tensor, faces: torch.Tensor,
|
def __init__(self, vertices: torch.Tensor, faces: torch.Tensor,
|
||||||
uvs: torch.Tensor | None = None,
|
uvs: torch.Tensor | None = None,
|
||||||
vertex_colors: torch.Tensor | None = None,
|
vertex_colors: torch.Tensor | None = None,
|
||||||
texture: torch.Tensor | None = None,
|
texture: torch.Tensor | None = None,
|
||||||
vertex_counts: torch.Tensor | None = None,
|
vertex_counts: torch.Tensor | None = None,
|
||||||
face_counts: torch.Tensor | None = None):
|
face_counts: torch.Tensor | None = None,
|
||||||
|
unlit: bool = False):
|
||||||
|
|
||||||
assert (vertex_counts is None) == (face_counts is None), \
|
assert (vertex_counts is None) == (face_counts is None), \
|
||||||
"vertex_counts and face_counts must be provided together (both or neither)"
|
"vertex_counts and face_counts must be provided together (both or neither)"
|
||||||
@ -30,6 +49,8 @@ class MESH:
|
|||||||
# these hold the real per-item lengths (B,). None means rows are uniform and no slicing is needed.
|
# these hold the real per-item lengths (B,). None means rows are uniform and no slicing is needed.
|
||||||
self.vertex_counts = vertex_counts
|
self.vertex_counts = vertex_counts
|
||||||
self.face_counts = face_counts
|
self.face_counts = face_counts
|
||||||
|
# Render flat / emissive (no scene lighting) when saved, e.g. for gaussian-splat-derived meshes.
|
||||||
|
self.unlit = unlit
|
||||||
|
|
||||||
|
|
||||||
class File3D:
|
class File3D:
|
||||||
|
|||||||
1663
comfy_extras/nodes_gaussian_splat.py
Normal file
1663
comfy_extras/nodes_gaussian_splat.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@ from comfy.cli_args import args
|
|||||||
from comfy_api.latest import ComfyExtension, IO, Types
|
from comfy_api.latest import ComfyExtension, IO, Types
|
||||||
|
|
||||||
|
|
||||||
def pack_variable_mesh_batch(vertices, faces, colors=None, uvs=None, texture=None):
|
def pack_variable_mesh_batch(vertices, faces, colors=None, uvs=None, texture=None, unlit=False):
|
||||||
# Pack lists of (Nᵢ, *) vertex/face/color/uv tensors into padded batched tensors,
|
# Pack lists of (Nᵢ, *) vertex/face/color/uv tensors into padded batched tensors,
|
||||||
# stashing per-item lengths as runtime attrs so consumers can recover the real slice.
|
# stashing per-item lengths as runtime attrs so consumers can recover the real slice.
|
||||||
# colors and uvs are 1:1 with vertices, so they're padded to max_vertices and read with vertex_counts.
|
# colors and uvs are 1:1 with vertices, so they're padded to max_vertices and read with vertex_counts.
|
||||||
@ -54,7 +54,7 @@ def pack_variable_mesh_batch(vertices, faces, colors=None, uvs=None, texture=Non
|
|||||||
|
|
||||||
return Types.MESH(packed_vertices, packed_faces,
|
return Types.MESH(packed_vertices, packed_faces,
|
||||||
uvs=packed_uvs, vertex_colors=packed_colors, texture=texture,
|
uvs=packed_uvs, vertex_colors=packed_colors, texture=texture,
|
||||||
vertex_counts=vertex_counts, face_counts=face_counts)
|
vertex_counts=vertex_counts, face_counts=face_counts, unlit=unlit)
|
||||||
|
|
||||||
|
|
||||||
def get_mesh_batch_item(mesh, index):
|
def get_mesh_batch_item(mesh, index):
|
||||||
@ -77,7 +77,7 @@ def get_mesh_batch_item(mesh, index):
|
|||||||
|
|
||||||
|
|
||||||
def save_glb(vertices, faces, filepath, metadata=None,
|
def save_glb(vertices, faces, filepath, metadata=None,
|
||||||
uvs=None, vertex_colors=None, texture_image=None):
|
uvs=None, vertex_colors=None, texture_image=None, unlit=False):
|
||||||
"""
|
"""
|
||||||
Save PyTorch tensor vertices and faces as a GLB file without external dependencies.
|
Save PyTorch tensor vertices and faces as a GLB file without external dependencies.
|
||||||
|
|
||||||
@ -234,6 +234,17 @@ def save_glb(vertices, faces, filepath, metadata=None,
|
|||||||
textures = []
|
textures = []
|
||||||
samplers = []
|
samplers = []
|
||||||
materials = []
|
materials = []
|
||||||
|
extensions_used = []
|
||||||
|
if unlit and texture_png_bytes is None:
|
||||||
|
# Flat, light-independent shading (KHR_materials_unlit): COLOR_0 is shown as-is, matching how a
|
||||||
|
# gaussian splat renders (emissive). Without this the viewer lights the mesh and washes the colours.
|
||||||
|
materials.append({
|
||||||
|
"pbrMetallicRoughness": {"baseColorFactor": [1.0, 1.0, 1.0, 1.0], "metallicFactor": 0.0, "roughnessFactor": 1.0},
|
||||||
|
"extensions": {"KHR_materials_unlit": {}},
|
||||||
|
"doubleSided": True,
|
||||||
|
})
|
||||||
|
extensions_used.append("KHR_materials_unlit")
|
||||||
|
primitive["material"] = 0
|
||||||
if texture_png_bytes is not None and "TEXCOORD_0" in primitive_attributes:
|
if texture_png_bytes is not None and "TEXCOORD_0" in primitive_attributes:
|
||||||
buffer_views.append({
|
buffer_views.append({
|
||||||
"buffer": 0,
|
"buffer": 0,
|
||||||
@ -271,6 +282,8 @@ def save_glb(vertices, faces, filepath, metadata=None,
|
|||||||
gltf["textures"] = textures
|
gltf["textures"] = textures
|
||||||
if materials:
|
if materials:
|
||||||
gltf["materials"] = materials
|
gltf["materials"] = materials
|
||||||
|
if extensions_used:
|
||||||
|
gltf["extensionsUsed"] = extensions_used
|
||||||
|
|
||||||
if metadata:
|
if metadata:
|
||||||
gltf["asset"]["extras"] = metadata
|
gltf["asset"]["extras"] = metadata
|
||||||
@ -376,7 +389,8 @@ class SaveGLB(IO.ComfyNode):
|
|||||||
save_glb(vertices_i, faces_i, os.path.join(full_output_folder, f), metadata,
|
save_glb(vertices_i, faces_i, os.path.join(full_output_folder, f), metadata,
|
||||||
uvs=uvs_i,
|
uvs=uvs_i,
|
||||||
vertex_colors=v_colors,
|
vertex_colors=v_colors,
|
||||||
texture_image=tex_img)
|
texture_image=tex_img,
|
||||||
|
unlit=getattr(mesh, "unlit", False))
|
||||||
results.append({
|
results.append({
|
||||||
"filename": f,
|
"filename": f,
|
||||||
"subfolder": subfolder,
|
"subfolder": subfolder,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user