fix texturing multi view

This commit is contained in:
Denis Kochetov
2025-05-08 02:22:23 +00:00
parent feaa3e03b1
commit ffc873a1d7
14 changed files with 96 additions and 292 deletions
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

@@ -1,5 +0,0 @@
一片绿色的树叶在白色背景上居中展现,清晰的纹理
一只棕白相间的仓鼠,站在白色背景前。照片采用居中构图方式,卡通风格
一盆绿色植物生长在红色花盆中,居中,写实
a pot of green plants grows in a red flower pot.
a lovely rabbit eating carrots
@@ -1,81 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<!-- Import the component -->
<script src="https://cdn.jsdelivr.net/npm/@google/model-viewer@3.1.1/dist/model-viewer.min.js" type="module"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const modelViewers = document.querySelectorAll('model-viewer');
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
modelViewers.forEach(modelViewer => {
modelViewer.setAttribute(
"environment-image",
"/static/env_maps/gradient.jpg"
);
// if (!isSafari) {
// modelViewer.setAttribute(
// "environment-image",
// "/static/env_maps/gradient.jpg"
// );
// } else {
// modelViewer.addEventListener('load', (event) => {
// const [material] = modelViewer.model.materials;
// let color = [43, 44, 46, 255];
// color = color.map(x => x / 255);
// material.pbrMetallicRoughness.setMetallicFactor(0.1); // 完全金属
// material.pbrMetallicRoughness.setRoughnessFactor(0.7); // 低粗糙度
// material.pbrMetallicRoughness.setBaseColorFactor(color); // CornflowerBlue in RGB
// });
// }
// modelViewer.addEventListener('load', (event) => {
// const [material] = modelViewer.model.materials;
// let color = [43, 44, 46, 255];
// color = color.map(x => x / 255);
// material.pbrMetallicRoughness.setMetallicFactor(0.1); // 完全金属
// material.pbrMetallicRoughness.setRoughnessFactor(0.7); // 低粗糙度
// material.pbrMetallicRoughness.setBaseColorFactor(color); // CornflowerBlue in RGB
// });
});
});
</script>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
}
.centered-container {
display: flex;
justify-content: center;
align-items: center;
border-radius: 8px;
border-color: #e5e7eb;
border-style: solid;
border-width: 1px;
}
</style>
</head>
<body>
<div class="centered-container">
<div class="column is-mobile is-centered">
<model-viewer id="modelviewer" style="height: #height#px; width: #width#px;"
rotation-per-second="10deg"
src="#src#" disable-tap
environment-image="neutral"
camera-target="0m 0m 0m"
camera-orbit="0deg 90deg 8m"
orientation="0deg 0deg 0deg"
shadow-intensity=".9"
ar auto-rotate
camera-controls>
</model-viewer>
</div>
</div>
</body>
</html>
@@ -1,136 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<!-- Import the component -->
<script src="https://cdn.jsdelivr.net/npm/@google/model-viewer@3.1.1/dist/model-viewer.min.js"
type="module"></script>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
}
.centered-container {
display: flex;
justify-content: center;
align-items: center;
}
.modelviewer-panel-button {
height: 30px;
margin: 4px 4px;
padding: 0px 14px;
background: white;
border-radius: 10px;
box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.modelviewer-panel-button.checked {
background: #6567C9;
color: white;
}
.modelviewer-panel-button:hover {
background-color: #e2e6ea;
}
.modelviewer-panel-button-container {
display: flex;
justify-content: space-around;
}
.centered-container {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
</head>
<body>
<div class="centered-container">
<div class="centered-container">
<div class="column is-mobile is-centered">
<model-viewer id="modelviewer" style="height: #height#px; width: #width#px;"
rotation-per-second="10deg"
src="#src#" disable-tap
environment-image="neutral"
camera-target="0m 0m 0m"
camera-orbit="0deg 90deg 12m"
orientation="0deg 0deg 0deg"
shadow-intensity=".9"
ar auto-rotate
camera-controls>
</model-viewer>
</div>
<div class="modelviewer-panel-button-container">
<div id="appearance-button" class="modelviewer-panel-button small checked" onclick="showTexture()">
Appearance
</div>
<div id="geometry-button" class="modelviewer-panel-button small" onclick="hideTexture()">Geometry</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const modelViewers = document.querySelectorAll('model-viewer');
modelViewers.forEach(modelViewer => {
modelViewer.addEventListener('load', (event) => {
const [material] = modelViewer.model.materials;
material.pbrMetallicRoughness.setMetallicFactor(0.1);
material.pbrMetallicRoughness.setRoughnessFactor(0.5);
});
});
});
var window_state = {};
function hideTexture() {
let appearanceButton = document.getElementById('appearance-button');
let geometryButton = document.getElementById('geometry-button');
appearanceButton.classList.remove('checked');
geometryButton.classList.add('checked');
let modelViewer = document.getElementById('modelviewer');
if (modelViewer.model.materials[0].pbrMetallicRoughness.baseColorTexture.texture === null) return;
window_state.textures = [];
for (let i = 0; i < modelViewer.model.materials.length; i++) {
window_state.textures.push(modelViewer.model.materials[i].pbrMetallicRoughness.baseColorTexture.texture);
}
window_state.exposure = modelViewer.exposure;
modelViewer.environmentImage = '/static/env_maps/gradient.jpg';
for (let i = 0; i < modelViewer.model.materials.length; i++) {
modelViewer.model.materials[i].pbrMetallicRoughness.baseColorTexture.setTexture(null);
}
modelViewer.exposure = 4;
}
function showTexture() {
let appearanceButton = document.getElementById('appearance-button');
let geometryButton = document.getElementById('geometry-button');
appearanceButton.classList.add('checked');
geometryButton.classList.remove('checked');
let modelViewer = document.getElementById('modelviewer');
if (modelViewer.model.materials[0].pbrMetallicRoughness.baseColorTexture.texture !== null) return;
modelViewer.environmentImage = '/static/env_maps/white.jpg';
for (let i = 0; i < modelViewer.model.materials.length; i++) {
modelViewer.model.materials[i].pbrMetallicRoughness.baseColorTexture.setTexture(window_state.textures[i]);
}
modelViewer.exposure = window_state.exposure;
}
</script>
</body>
</html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

+57 -17
View File
@@ -1,12 +1,3 @@
# Open Source Model Licensed under the Apache License Version 2.0
# and Other Licenses of the Third-Party Components therein:
# The below Model in this distribution may have been modified by THL A29 Limited
# ("Tencent Modifications"). All Tencent Modifications are Copyright (C) 2024 THL A29 Limited.
# Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved.
# The below software and/or models in this distribution may have been
# modified by THL A29 Limited ("Tencent Modifications").
# All Tencent Modifications are Copyright (C) THL A29 Limited.
# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT
# except for the third-party components listed below.
# Hunyuan 3D does not impose any additional limitations beyond what is outlined
@@ -87,7 +78,7 @@ class ImageProcessorV2:
interpolation=cv2.INTER_AREA)
bg = np.ones((result.shape[0], result.shape[1], 3), dtype=np.uint8) * 255
# bg = np.zeros((result.shape[0], result.shape[1], 3), dtype=np.uint8) * 255
mask = result[..., 3:].astype(np.float32) / 255
result = result[..., :3] * mask + bg * (1 - mask)
@@ -96,15 +87,13 @@ class ImageProcessorV2:
mask = mask.clip(0, 255).astype(np.uint8)
return result, mask
def __call__(self, image, border_ratio=0.15, to_tensor=True, return_mask=False, **kwargs):
if self.border_ratio is not None:
border_ratio = self.border_ratio
print(f"Using border_ratio from init: {border_ratio}")
def load_image(self, image, border_ratio=0.15, to_tensor=True):
if isinstance(image, str):
image = cv2.imread(image, cv2.IMREAD_UNCHANGED)
image, mask = self.recenter(image, border_ratio=border_ratio)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
elif isinstance(image, Image.Image):
image = image.convert("RGBA")
image = np.asarray(image)
image, mask = self.recenter(image, border_ratio=border_ratio)
@@ -115,13 +104,64 @@ class ImageProcessorV2:
if to_tensor:
image = array_to_tensor(image)
mask = array_to_tensor(mask)
if return_mask:
return image, mask
return image
return image, mask
def __call__(self, image, border_ratio=0.15, to_tensor=True, **kwargs):
if self.border_ratio is not None:
border_ratio = self.border_ratio
image, mask = self.load_image(image, border_ratio=border_ratio, to_tensor=to_tensor)
outputs = {
'image': image,
'mask': mask
}
return outputs
class MVImageProcessorV2(ImageProcessorV2):
"""
view order: front, front clockwise 90, back, front clockwise 270
"""
return_view_idx = True
def __init__(self, size=512, border_ratio=None):
super().__init__(size, border_ratio)
self.view2idx = {
'front': 0,
'left': 1,
'back': 2,
'right': 3
}
def __call__(self, image_dict, border_ratio=0.15, to_tensor=True, **kwargs):
if self.border_ratio is not None:
border_ratio = self.border_ratio
images = []
masks = []
view_idxs = []
for idx, (view_tag, image) in enumerate(image_dict.items()):
view_idxs.append(self.view2idx[view_tag])
image, mask = self.load_image(image, border_ratio=border_ratio, to_tensor=to_tensor)
images.append(image)
masks.append(mask)
zipped_lists = zip(view_idxs, images, masks)
sorted_zipped_lists = sorted(zipped_lists)
view_idxs, images, masks = zip(*sorted_zipped_lists)
image = torch.cat(images, 0).unsqueeze(0)
mask = torch.cat(masks, 0).unsqueeze(0)
outputs = {
'image': image,
'mask': mask,
'view_idxs': view_idxs
}
return outputs
IMAGE_PROCESSORS = {
"v2": ImageProcessorV2,
'mv_v2': MVImageProcessorV2,
}
DEFAULT_IMAGEPROCESSOR = 'v2'
@@ -53,38 +53,19 @@ class Hunyuan3DTexGenConfig:
class Hunyuan3DPaintPipeline:
@classmethod
def from_pretrained(cls, model_path, subfolder='hunyuan3d-paint-v2-0-turbo'):
original_model_path = model_path
if not os.path.exists(model_path):
# try local path
base_dir = os.environ.get('HY3DGEN_MODELS', '~/.cache/hy3dgen')
model_path = os.path.expanduser(os.path.join(base_dir, model_path))
delight_model_path = os.path.join(model_path, 'hunyuan3d-delight-v2-0')
multiview_model_path = os.path.join(model_path, subfolder)
delight_model_path = os.path.join(model_path, 'hunyuan3d-delight-v2-0')
multiview_model_path = os.path.join(model_path, subfolder)
if not os.path.exists(delight_model_path) or not os.path.exists(multiview_model_path):
try:
import huggingface_hub
# download from huggingface
model_path = huggingface_hub.snapshot_download(
repo_id=original_model_path, allow_patterns=["hunyuan3d-delight-v2-0/*"]
)
model_path = huggingface_hub.snapshot_download(
repo_id=original_model_path, allow_patterns=[f'{subfolder}/*']
)
delight_model_path = os.path.join(model_path, 'hunyuan3d-delight-v2-0')
multiview_model_path = os.path.join(model_path, subfolder)
return cls(Hunyuan3DTexGenConfig(delight_model_path, multiview_model_path, subfolder))
except ImportError:
logger.warning(
"You need to install HuggingFace Hub to load models from the hub."
)
raise RuntimeError(f"Model path {model_path} not found")
else:
return cls(Hunyuan3DTexGenConfig(delight_model_path, multiview_model_path, subfolder))
raise FileNotFoundError(f"Model path {original_model_path} not found and we could not find it at huggingface")
if os.path.exists(delight_model_path) and os.path.exists(multiview_model_path):
return cls(Hunyuan3DTexGenConfig(
delight_model_path,
multiview_model_path,
subfolder
))
raise FileNotFoundError(
f"Required models not found in local paths: {delight_model_path} or {multiview_model_path}"
)
def __init__(self, config):
self.config = config
self.models = {}
+28 -23
View File
@@ -4330,10 +4330,10 @@ class Load_Hunyuan3D_V2_ShapeGen_Pipeline:
repo, subfolder, def_steps = self._MODES[generation_mode]
use_safe = (weights_format == "safetensors")
pipe = self._build_pipe(repo, subfolder, use_safe, flash_vdm)
pipe.default_steps = def_steps
pipe.num_inference_steps = def_steps
return (pipe,)
class Load_Hunyuan3D_V2_TexGen_Pipeline:
class Load_Hunyuan3D_V2_TexGen_Pipeline:
CATEGORY = "Comfy3D/Algorithm"
RETURN_TYPES = ("DIFFUSERS_PIPE",)
RETURN_NAMES = ("texgen_pipe",)
@@ -4348,32 +4348,35 @@ class Load_Hunyuan3D_V2_TexGen_Pipeline:
def INPUT_TYPES(cls):
return {"required": {
"generation_mode": (list(cls.MODEL2REPO.keys()),),
"weights_format": (["safetensors","ckpt"],),
}}
@staticmethod
def _ensure_weights(repo_id: str, subfolder: str, use_safetensors: bool):
base_dir = os.path.join(CKPT_DIFFUSERS_PATH, repo_id)
os.makedirs(base_dir, exist_ok=True)
target_dir = os.path.join(base_dir, subfolder)
if not os.path.exists(target_dir):
def _download_required_weights(self, repo_id, subfolder):
ckpt_download_dir = os.path.join(CKPT_DIFFUSERS_PATH, repo_id)
os.makedirs(ckpt_download_dir, exist_ok=True)
# Only download the "delight" and target submodel directories
for folder in ["hunyuan3d-delight-v2-0", subfolder]:
snapshot_download(
repo_id=repo_id,
local_dir=ckpt_download_dir,
repo_type="model",
local_dir=base_dir,
resume_download=True,
ignore_patterns = HF_DOWNLOAD_IGNORE
force_download=False,
ignore_patterns=HF_DOWNLOAD_IGNORE
)
def load(self, generation_mode, weights_format):
repo_id,subfolder=self.MODEL2REPO[generation_mode]
Load_Hunyuan3D_V2_TexGen_Pipeline._ensure_weights(repo_id,subfolder,
weights_format=="safetensors")
pipe=Hunyuan3DPaintPipeline.from_pretrained(
model_path=repo_id,
subfolder=subfolder,
def load(self, generation_mode):
repo_id, subfolder = self.MODEL2REPO[generation_mode]
self._download_required_weights(repo_id, subfolder)
local_repo_dir = os.path.join(CKPT_DIFFUSERS_PATH, repo_id)
pipe = Hunyuan3DPaintPipeline.from_pretrained(
model_path=local_repo_dir,
subfolder=subfolder
)
return (pipe.to("cuda",torch.float16),)
return (pipe.to("cuda", torch.float16),)
class Hunyuan3D_V2_Paint_Model_Turbo_MV:
"""
@@ -4443,7 +4446,8 @@ class Multi_Background_Remover:
self,
image_front,
image_back=None,
image_left=None
image_left=None,
image_right=None
):
rmbg = BackgroundRemover()
@@ -4451,7 +4455,8 @@ class Multi_Background_Remover:
k: v for k, v in {
"front": image_front,
"back": image_back,
"left": image_left
"left": image_left,
"right": image_right
}.items() if v is not None
}
@@ -4504,7 +4509,7 @@ class Hunyuan3D_V2_ShapeGen_MV:
if len(images) == 1:
image = images[0]
else:
directions = ["front", "back", "left"]
directions = ["front", "back", "left", "right"]
image = {k: v for k, v in zip(directions, images)}
steps = shapegen_pipe.default_steps if num_inference_steps == 0 else num_inference_steps