ComfyLab
Uni3C ControlNet: Control de Cámara para Wan 2.1

Uni3C ControlNet: Control de Cámara para Wan 2.1

24GB VRAM (RTX 3090 o equivalente) para la fase de Wan, más un entorno Docker aparte con passthrough de GPU para la fase de renderizado de la nube de puntos VRAM Avanzado 14 min Wan 2.1 I2V 14B (GGUF Q6_K) + Uni3C ControlNet + LoRA distilled speed
Savien

Read in English →

Quería saber si el soporte nativo recién estrenado de ComfyUI para Uni3C ControlNet (integrado en el core el 29 de julio) podía producir de verdad un vídeo de Wan 2.1 que siguiera una trayectoria de cámara real, no solo un prompt que la describiera. Llegar hasta ahí me costó dos bloqueos reales y nada glamurosos: un checkpoint que no carga tal como se publica, y una librería de renderizado 3D que se niega a compilar contra el CUDA de esta máquina. Aquí está el camino completo, incluidas las partes que fallaron la primera vez.

De un vistazo: Uni3C ControlNet para Wan en ComfyUI

AspectoDetalles
Qué controlaTrayectoria de cámara (distancia, elevación, rotación, offsets)
Modelo baseWan 2.1 I2V 14B
Soporte nativo en ComfyUI desdev0.29.0 (29 de julio de 2026)
Licencia del ControlNetApache 2.0 (ewrfcas/Uni3C)
VRAM usada (fase Wan)~21GB de pico en una RTX 3090 de 24GB
Fase de renderizado de nube de puntosSeparada, necesita pytorch3d — corrió en Docker en esta prueba
Trayectoria de cámara probadaorbit (rotación de 360°)
Duración de la prueba33 frames, 768x480, 16fps

Qué hace realmente Uni3C ControlNet

Uni3C (Unifying Precisely 3D-Enhanced Camera and Human Motion Controls for Video Generation, DAMO Academy/Alibaba, SIGGRAPH Asia 2025) es un módulo tipo ControlNet para Wan que separa el control de cámara de la generación de contenido. En vez de escribir “la cámara orbita lentamente alrededor del sujeto” en el prompt y esperar que el modelo lo interprete de forma consistente, le das un vídeo de guía real: una nube de puntos generada a partir de tu imagen de referencia, renderizada siguiendo exactamente la trayectoria de cámara que especifiques (distancia, ángulo de elevación, rotación, offset x/y/z, distancia focal).

ComfyUI integró soporte nativo para esto el 29 de julio de 2026 (WanUni3CControlnetApply en comfy_extras/nodes_model_patch.py, detectado automáticamente a partir de la clave controlnet_patch_embedding.weight del checkpoint). Se aplica sobre un pipeline estándar de Wan 2.1 — sin modelo base separado, sin requisito de varias GPUs.

👉 El punto clave: Uni3C no sustituye tu prompt ni tu imagen de referencia — añade una tercera entrada, un vídeo de trayectoria de cámara renderizado, y dirige la generación para que la siga.


Bloqueo #1: El checkpoint publicado no carga tal cual

El primer paso natural es descargar ewrfcas/Uni3C/controlnet.pth de HuggingFace (Apache 2.0, ~4GB) y apuntar el ModelPatchLoader de ComfyUI a él. Falla con un error de clave faltante.

El motivo: el loader de ComfyUI detecta Uni3C automáticamente buscando claves planas como proj_out.0.weight y controlnet_blocks.0.ffn.0.bias. El checkpoint crudo anida casi todo un nivel más abajo, bajo un prefijo controlnet.controlnet.proj_out.0.weight, controlnet.controlnet_blocks.0.ffn.0.bias. Solo controlnet_patch_embedding.* y controlnet_mask_embedding.* ya están planas.

La solución es una conversión de una línea, que se ejecuta una sola vez:

import torch
from safetensors.torch import save_file

sd = torch.load("controlnet.pth", map_location="cpu", weights_only=True)
out = {k[len("controlnet."):] if k.startswith("controlnet.") else k: w.contiguous()
       for k, w in sd.items()}
save_file(out, "wan_uni3c_controlnet_comfy.safetensors")

Coloca el .safetensors resultante en ComfyUI/models/model_patches/ (no en controlnet/ — Uni3C usa el ModelPatchLoader genérico, cuyo tipo de carpeta es model_patches). Confirmado que carga bien: el log del servidor muestra Requested to load WanUni3CControlnet justo al lado del modelo Wan base, sin ningún error de clave.

⚠️ Importante: si ves un error de clave faltante al cargar cualquier checkpoint de Uni3C en ComfyUI, comprueba si tiene un prefijo controlnet. antes de asumir que el archivo está corrupto o es la versión equivocada.


Bloqueo #2: pytorch3d no compila contra un toolkit de CUDA más reciente

Uni3C ControlNet solo dirige la generación si ya tienes un vídeo de trayectoria de cámara para dárselo. Producir ese vídeo a partir de una sola imagen de referencia es la Fase 1 del pipeline oficial (alibaba-damo-academy/Uni3C, cam_render.py): estimación de profundidad monocular (DepthPro de Apple) → segmentación de primer plano/fondo (CarveKit) → renderizado de nube de puntos siguiendo la trayectoria de cámara pedida (pytorch3d).

La propia documentación de instalación de pytorch3d lista soporte oficial hasta PyTorch 2.4.1. Esta máquina corre CUDA 13.3 con PyTorch 2.12.1 — ambos mucho más recientes. Compilar pytorch3d desde fuente contra esa combinación falló con un error de parseo de templates en C++ dentro de las propias cabeceras de PyTorch (ATen/core/List_inl.h), lanzado por el propio nvcc:

error: se necesita 'typename' antes de 'decltype(...)::difference_type'
command '/opt/cuda/bin/nvcc' failed with exit code 1

Para descartar que fuera cosa del compilador del host, repetí exactamente la misma compilación forzando un GCC más antiguo (15 en vez del 16.1.1 del sistema) como compilador de host de nvcc vía -ccbin. Confirmado aplicado en el log de compilación — fallo idéntico, misma línea. Eso descartó el compilador del host: el error viene del propio frontend de código CUDA de nvcc parseando las cabeceras de PyTorch de forma más estricta de lo que PyTorch 2.12.1 anticipa, no de GCC.

La solución: no pelear contra el toolchain del host, aislar la dependencia. Una imagen Docker fijada a pytorch/pytorch:2.4.1-cuda12.1-cudnn9-devel — PyTorch 2.4.1, CUDA 12.1, exactamente la combinación que pytorch3d soporta oficialmente — compiló pytorch3d 0.7.9 limpio a la primera, sin parches, sin flags:

FROM pytorch/pytorch:2.4.1-cuda12.1-cudnn9-devel
ENV FORCE_CUDA=1
ENV TORCH_CUDA_ARCH_LIST="8.6"
RUN apt-get update && apt-get install -y --no-install-recommends \
    git ffmpeg libsm6 libxext6 libglm-dev
RUN git clone --depth 1 https://github.com/facebookresearch/pytorch3d.git /opt/pytorch3d \
    && cd /opt/pytorch3d && pip install --no-build-isolation -e .
RUN pip install carvekit --no-deps
COPY requirements.txt /tmp/uni3c_requirements.txt
RUN grep -v -E "^xfuser|^ipdb" /tmp/uni3c_requirements.txt > /tmp/req_filtered.txt \
    && pip install -r /tmp/req_filtered.txt

(xfuser se excluye a propósito — solo hace falta para la inferencia en paralelo multi-GPU de Uni3C, no para renderizar la nube de puntos de una sola imagen.)

El passthrough de GPU al contenedor necesitó nvidia-container-toolkit (no instalado por defecto en este sistema), más generar un CDI spec y apuntar el runtime de Docker a él:

sudo pacman -S nvidia-container-toolkit   # o el equivalente de tu distro
sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi   # comprobación

👉 Lo que importa: esto no es un problema específico de pytorch3d — cualquier librería con extensiones CUDA compiladas contra un toolkit muy reciente puede chocar con el mismo muro. Una imagen Docker fijada es una solución más fiable que perseguir flags de compilador en el host.


Fase 1: Renderizando la trayectoria de cámara (Docker)

Con la imagen construida (docker build -t uni3c-render .), la Fase 1 corre el cam_render.py oficial contra una imagen de referencia, montando el repo de Uni3C, una carpeta de salida, y la caché de HuggingFace (los pesos de DepthPro y CarveKit se descargan solos la primera vez):

docker run --rm --gpus all \
  -v /ruta/a/Uni3C:/workspace/Uni3C \
  -v /ruta/a/outputs:/workspace/outputs \
  -v uni3c_hf_cache:/root/.cache/huggingface \
  uni3c-render \
  python3 cam_render.py --reference_image "data/demo/rtx_gpu_desk.png" \
                         --output_path "/workspace/outputs/rtx_gpu_orbit" \
                         --traj_type "orbit"

La imagen de referencia se generó localmente con Krea 2 Turbo (8 pasos, cfg 1, euler, scheduler simple, seed 424242) — una elección deliberada a juego con la estética habitual de escritorio-y-RGB de ComfyLab, y con separación real de profundidad (GPU en primer plano, teclado en plano medio, monitor/lámpara al fondo) porque una imagen plana no le da nada al renderizador de nube de puntos con lo que trabajar.

Imagen de referencia: RTX GPU con ventiladores RGB sobre un escritorio de noche, generada con Krea 2 Turbo

Imagen de referencia usada para el render de trayectoria de cámara. La separación de profundidad real entre primer plano, plano medio y fondo importa — una escena plana no le da nada al renderizador de nube de puntos con lo que trabajar.

--traj_type orbit es una de las trayectorias de cámara predefinidas de Uni3C: una rotación completa de 360° alrededor del sujeto (d_phi=-360, todo lo demás por defecto). Los movimientos de cámara grandes y deliberados quedan mejor como demo que los sutiles — la misma lección de nuestra prueba de transferencia de cámara con IC-LoRA Cameraman.

Salida: render.mp4 (81 frames, 768x480, 16fps — la nube de puntos siguiendo la trayectoria orbital), más render_mask.mp4, pcd.ply (la nube de puntos 3D real, para inspeccionar), y cam_info.json.

Salida de la Fase 1: la nube de puntos renderizada siguiendo la trayectoria orbital. Este es el vídeo de guía que consume Uni3C ControlNet — no el resultado final.


Fase 2: Wan 2.1 + Uni3C ControlNet (ComfyUI, nativo)

De vuelta en ComfyUI (sin Docker aquí — esta fase corre en la misma instalación que cualquier otro workflow de Wan), render.mp4 se carga como un vídeo normal y se pasa a WanUni3CControlnetApply junto al modelo Wan base y el ControlNet convertido:

UnetLoaderGGUF (wan2.1-i2v-14b-480p-Q6_K.gguf)
  → LoraLoaderModelOnly (LoRA distilled de lightx2v)
  → ModelSamplingSD3 (shift 8.0)
  → WanUni3CControlnetApply (+ ModelPatchLoader, + VAE, + render_video desde VHS_LoadVideo)
  → KSampler (6 pasos, cfg 1, euler, simple)
  → VAEDecode → VHS_VideoCombine

Comprobación de cordura primero: ¿el ControlNet se aplica de verdad?

Antes de fiarme de una corrida completa, probé el pipeline con un render_video deliberadamente equivocado: la imagen de referencia repetida 17 veces como “vídeo” (sin ninguna trayectoria de cámara real), para comprobar que el pipeline mecánico carga y corre sin OOM.

Resultado: corrió limpio (VRAM pico 20,9GB, ~2 min), pero con cero movimiento de cámara en la salida. Eso no es un bug — es la confirmación de que el ControlNet está dirigiendo la generación de verdad: al decirle “la cámara no se mueve”, suprimió el movimiento que Wan 2.1 + el LoRA distilled normalmente producirían por su cuenta. Un ControlNet sin efecto real habría dejado pasar algo de movimiento de todas formas.

La corrida real

Mismo pipeline, render.mp4 del render de órbita real esta vez. 33 frames, 768x480, corrida en frío (proceso de ComfyUI recién arrancado, nada precargado):

MétricaValor
Tiempo total (carga + muestreo + decode)195s (~3,25 min)
VRAM pico~21GB / 24,5GB (muestreado cada 15s)
Pasos6 (euler, simple, cfg 1)
Salida33 frames, 768x480, 16fps

Resultado final: Wan 2.1 I2V 14B + Uni3C ControlNet, siguiendo la trayectoria orbital real de 360° de la Fase 1. Compárese con la prueba del placeholder estático de arriba — este sí se mueve.

👉 Lo que aprendí: la corrida con el placeholder estático no fue tiempo perdido — es la forma más barata de separar “¿cargó y se aplicó el ControlNet?” de “¿la trayectoria de cámara que le di produjo el movimiento que esperaba?”. Prueba con un input deliberadamente equivocado antes de fiarte de uno real.


Descarga del workflow

🏗️ Workflow: Wan 2.1 I2V + Uni3C ControlNet

🧠 VRAM: 24GB 📡 MODEL: Wan 2.1 I2V 14B (GGUF Q6_K) + Uni3C ControlNet + LoRA distilled speed

Este es solo el workflow del lado de ComfyUI (Fase 2) — espera un vídeo de trayectoria de cámara ya renderizado como entrada, producido aparte con el pipeline Docker descrito arriba. El Dockerfile de la Fase 1 no es un asset descargable de ComfyUI (no corre dentro de ComfyUI en absoluto), está reproducido completo en la sección “Bloqueo #2” de arriba.

Modelos necesarios:

ModeloFuenteRuta local
Wan 2.1 I2V 14B (GGUF Q6_K)city96/repacks GGUF de la comunidadmodels/diffusion_models/
Uni3C ControlNet (convertido)ewrfcas/Uni3C + el script de conversión de arribamodels/model_patches/
LoRA distilled speedKijai/WanVideo_comfy (lightx2v)models/loras/
Text encoder UMT5-XXLComfy-Org/Wan_2.1_ComfyUI_repackagedmodels/text_encoders/
VAE de Wan 2.1Comfy-Org/Wan_2.1_ComfyUI_repackagedmodels/vae/

Preguntas Frecuentes

¿Qué controla exactamente Uni3C ControlNet en Wan 2.1?

La trayectoria de cámara. Le das un vídeo de guía renderizado (una nube de puntos renderizada a partir de tu imagen de referencia siguiendo una trayectoria de cámara — distancia, elevación, rotación, offsets) y dirige el vídeo generado para que siga ese mismo movimiento de cámara, en vez de depender solo del prompt para describirlo. Es un ControlNet nativo de ComfyUI (integrado en el core en la v0.29.0), aplicado sobre el modelo de difusión Wan 2.1.

¿Por qué el checkpoint crudo de Uni3C en HuggingFace no funciona directamente en ComfyUI?

El checkpoint publicado en ewrfcas/Uni3C anida casi todas las claves bajo un prefijo controlnet. (por ejemplo controlnet.proj_out.0.weight), pero el ModelPatchLoader nativo de ComfyUI lee claves planas (proj_out.0.weight, controlnet_blocks.0.ffn.0.bias…) sin ese prefijo. Cargarlo tal cual lanza un error de clave faltante. La solución es una conversión de una línea: quitar el prefijo controlnet. de cada clave que lo tenga y volver a guardar como .safetensors.

¿Por qué necesito Docker solo para generar el vídeo de trayectoria de cámara?

Porque la fase de renderizado de nube de puntos (Fase 1 del pipeline oficial de Uni3C, alibaba-damo-academy/Uni3C) depende de pytorch3d, y pytorch3d solo tiene soporte oficial hasta PyTorch 2.4.1. En una máquina con un toolkit de CUDA más reciente (13.3 en este caso) falla al compilar con un error de parseo de templates en las propias cabeceras de PyTorch, confirmado independiente del compilador C++ del host al probar con dos versiones distintas de GCC y obtener el mismo fallo idéntico ambas veces. Un contenedor fijado a pytorch/pytorch:2.4.1-cuda12.1-cudnn9-devel, exactamente la combinación que pytorch3d soporta, compiló limpio a la primera.

¿Necesito varias GPUs para correr esto?

No. Ambas fases corrieron en una sola RTX 3090 (24GB). La fase de renderizado de nube de puntos ni siquiera toca el modelo Wan — es un paso separado y mucho más ligero (estimación de profundidad + renderizado de nube de puntos). La VRAM llegó a un pico de unos 21GB durante el muestreo de Wan + Uni3C, con unos 3,5GB de margen libre.

¿Es lo mismo que la función de transferencia de movimiento humano de Uni3C?

No — este artículo cubre solo la mitad de control de cámara de Uni3C (PCDController, Fase 1+2 del repo oficial). El paper también describe una fase de alineación de pose/movimiento humano (alineación Hamer, listada como no publicada todavía en el propio TODO del repo oficial en el momento de esta prueba) que no se probó aquí.


Limitaciones y qué no se probó

  • Una sola GPU, una sola corrida de prueba. Todo esto corrió una vez en una RTX 3090. Sin datos de varianza de corridas repetidas, sin otros modelos de GPU probados.
  • Solo se probó la trayectoria orbit. Uni3C expone siete parámetros (distancia, elevación, rotación, offset x/y/z, distancia focal) más varias trayectorias predefinidas (free1-free5, swing1, swing2, orbit). Solo orbit se corrió de extremo a extremo aquí.
  • Docker es un segundo entorno real que mantener, no una instalación de un solo comando. Si tu host de ComfyUI ya corre una combinación de PyTorch/CUDA que pytorch3d soporta de forma nativa, puede que ni siquiera lo necesites — prueba primero el pip install normal.
  • La transferencia de movimiento humano (alineación Hamer) no se probó — no estaba disponible en el repo oficial en el momento de esta prueba.
  • 33 frames a 768x480 es una resolución/duración de prueba modesta, no una corrida de longitud de producción.

Conclusión: Un ControlNet nativo real, con un coste de configuración real

Uni3C ControlNet funciona, y funciona de forma nativa en ComfyUI sin ningún custom node para el paso de generación en sí. Pero “soporte nativo integrado en el core” no significa “cero configuración” — el checkpoint necesita conversión, y la fase de renderizado de trayectoria de cámara necesita una dependencia (pytorch3d) que no se lleva bien con un toolkit de CUDA muy reciente. Docker esquiva eso limpiamente, al coste de un segundo entorno que mantener.

🏆 Nuestra recomendación

Si quieres movimiento de cámara real y controlable en la salida de Wan 2.1 → Uni3C ControlNet merece la configuración. Convierte el checkpoint una sola vez, guarda la imagen Docker para cuando necesites una trayectoria de cámara nueva, y comprueba con un input deliberadamente equivocado cualquier ControlNet nuevo antes de fiarte de una corrida real — es la forma más barata de detectar “cargado pero sin aplicarse de verdad” antes de gastar tiempo de GPU en la corrida de verdad.


Sigue Leyendo

Si el control de cámara específicamente es lo que buscas, ya cubrimos un enfoque distinto sobre LTX 2.3: transferencia de movimiento de cámara con IC-LoRA Cameraman v2, que transfiere movimiento de un vídeo de referencia real en vez de una trayectoria de cámara paramétrica. Si quieres generar tu propia imagen de referencia igual que se hizo aquí, revisa nuestra guía de Krea 2 Turbo.

Preguntas frecuentes

¿Qué controla exactamente Uni3C ControlNet en Wan 2.1?
La trayectoria de cámara. Le das un vídeo de guía renderizado (una nube de puntos renderizada a partir de tu imagen de referencia siguiendo una trayectoria de cámara -- distancia, elevación, rotación, offsets) y dirige el vídeo generado para que siga ese mismo movimiento de cámara, en vez de depender solo del prompt para describirlo. Es un ControlNet nativo de ComfyUI (integrado en el core en la v0.29.0), aplicado sobre el modelo de difusión Wan 2.1.
¿Por qué el checkpoint crudo de Uni3C en HuggingFace no funciona directamente en ComfyUI?
El checkpoint publicado en ewrfcas/Uni3C anida casi todas las claves bajo un prefijo controlnet. (por ejemplo controlnet.proj_out.0.weight), pero el ModelPatchLoader nativo de ComfyUI lee claves planas (proj_out.0.weight, controlnet_blocks.0.ffn.0.bias...) sin ese prefijo. Cargarlo tal cual lanza un error de clave faltante. La solución es una conversión de una línea: quitar el prefijo controlnet. de cada clave que lo tenga y volver a guardar como .safetensors.
¿Por qué necesito Docker solo para generar el vídeo de trayectoria de cámara?
Porque la fase de renderizado de nube de puntos (Fase 1 del pipeline oficial de Uni3C, alibaba-damo-academy/Uni3C) depende de pytorch3d, y pytorch3d solo tiene soporte oficial hasta PyTorch 2.4.1. En una máquina con un toolkit de CUDA más reciente (13.3 en este caso) falla al compilar con un error de parseo de templates en las propias cabeceras de PyTorch -- confirmado independiente del compilador C++ del host, probado con dos versiones distintas de GCC con el mismo fallo idéntico. Un contenedor fijado a pytorch/pytorch:2.4.1-cuda12.1-cudnn9-devel, exactamente la combinación que pytorch3d soporta oficialmente, compiló limpio a la primera.
¿Necesito varias GPUs para correr esto?
No. Ambas fases corrieron en una sola RTX 3090 (24GB). La fase de renderizado de nube de puntos ni siquiera toca el modelo Wan -- es un paso separado y mucho más ligero (estimación de profundidad + renderizado de nube de puntos). La VRAM llegó a un pico de unos 21GB durante el muestreo de Wan + Uni3C, con unos 3,5GB de margen libre.
¿Es lo mismo que la función de transferencia de movimiento humano de Uni3C?
No -- este artículo cubre solo la mitad de control de cámara de Uni3C (PCDController, Fase 1+2 del repo oficial). El paper también describe una fase de alineación de pose/movimiento humano (alineación Hamer, listada como no publicada todavía en el propio TODO del repo oficial en el momento de esta prueba) que no se probó aquí.
Compartir X LinkedIn

También te puede interesar