簡介
頭像在那裡,設計也在那裡——現在我們需要將襯衫渲染到頭像上。這不僅是將紋理貼平,還必須有布料模擬:布料必須自然垂墜,褶皺處有皺紋,設計必須根據襯衫的表面變形。
1. 服裝渲染管線
Design (PNG) + Avatar (3D mesh) + Garment Template
│ │ │
└───────────┬───────────┘ │
│ │
┌────────▼────────┐ ┌─────────────▼──────────────┐
│ UV Mapping │ │ Cloth Simulation │
│ Design → texture │ │ Draping, wrinkles, gravity │
└────────┬────────┘ └─────────────┬──────────────┘
│ │
└───────────────┬─────────────────────┘
│
┌────────▼────────┐
│ Final Rendering │
│ Lighting, shadow │
│ Multi-view output│
└─────────────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
Front View Side View Back View
2.服裝模板系統
class GarmentTemplate:
"""Template 3D mesh cho các loại áo"""
TEMPLATES = {
"tshirt_regular": {
"mesh": "garments/tshirt_regular.obj",
"uv_map": "garments/tshirt_regular_uv.png",
"print_zones": {
"front_chest": {"uv_range": (0.2, 0.15, 0.8, 0.65)},
"back_print": {"uv_range": (0.2, 0.15, 0.8, 0.70)},
"left_chest": {"uv_range": (0.55, 0.20, 0.75, 0.40)},
},
},
"tshirt_oversize": {
"mesh": "garments/tshirt_oversize.obj",
"uv_map": "garments/tshirt_oversize_uv.png",
"print_zones": {
"front_chest": {"uv_range": (0.15, 0.12, 0.85, 0.68)},
"back_print": {"uv_range": (0.15, 0.12, 0.85, 0.72)},
},
},
}
def get_template(
self, garment_type: str, size: str
) -> trimesh.Trimesh:
template = self.TEMPLATES[garment_type]
mesh = trimesh.load(template["mesh"])
# Scale mesh theo size
scale_factor = self._get_scale(size)
mesh.apply_scale(scale_factor)
return mesh
3.設計→服裝紋理
class DesignToTexture:
"""Map design lên UV texture của áo"""
def apply_design(
self,
garment_template: dict,
design: Image.Image,
print_zone: str = "front_chest",
shirt_color: str = "#FFFFFF",
) -> Image.Image:
"""
Tạo texture image cho garment mesh
1. Start với shirt_color background
2. Map design vào đúng UV zone
"""
# Create base texture (shirt color)
texture_size = 2048
texture = Image.new(
"RGBA", (texture_size, texture_size),
self._hex_to_rgba(shirt_color),
)
# Get UV range for print zone
zone = garment_template["print_zones"][print_zone]
uv = zone["uv_range"] # (u_min, v_min, u_max, v_max)
# Map design to UV coordinates
target_x = int(uv[0] * texture_size)
target_y = int(uv[1] * texture_size)
target_w = int((uv[2] - uv[0]) * texture_size)
target_h = int((uv[3] - uv[1]) * texture_size)
# Resize design to fit zone
design_resized = design.resize(
(target_w, target_h),
Image.Resampling.LANCZOS,
)
# Paste design onto texture
texture.paste(design_resized, (target_x, target_y), design_resized)
return texture
4.布料模擬(簡化)
class ClothSimulator:
"""Simulate cloth draping trên body"""
def drape_garment(
self,
garment_mesh: trimesh.Trimesh,
body_mesh: trimesh.Trimesh,
) -> trimesh.Trimesh:
"""
Basic cloth simulation:
1. Fit garment roughly to body
2. Push garment vertices outward from body surface
3. Add wrinkle displacement
"""
# 1. Align garment to body
garment_aligned = self._align_to_body(
garment_mesh, body_mesh
)
# 2. Collision detection & resolution
garment_fitted = self._resolve_collisions(
garment_aligned, body_mesh,
offset=0.5, # 0.5cm clearance
)
# 3. Add wrinkles
garment_wrinkled = self._add_wrinkles(
garment_fitted, body_mesh
)
return garment_wrinkled
def _resolve_collisions(
self,
garment: trimesh.Trimesh,
body: trimesh.Trimesh,
offset: float,
) -> trimesh.Trimesh:
"""Push garment vertices ở bên trong body ra ngoài"""
import numpy as np
vertices = garment.vertices.copy()
# For each garment vertex, check if inside body
for i, v in enumerate(vertices):
# Find closest point on body surface
closest, distance, face_id = \
body.nearest.on_surface([v])
if distance[0] < offset:
# Push outward along body surface normal
normal = body.face_normals[face_id[0]]
vertices[i] = closest[0] + normal * offset
garment.vertices = vertices
return garment
def _add_wrinkles(
self,
garment: trimesh.Trimesh,
body: trimesh.Trimesh,
) -> trimesh.Trimesh:
"""Add realistic wrinkle displacement"""
import numpy as np
vertices = garment.vertices.copy()
normals = garment.vertex_normals
# Simple noise-based wrinkles
noise = np.random.normal(0, 0.1, vertices.shape)
# More wrinkles at joints (armpits, waist)
for i, v in enumerate(vertices):
# Armpit zone: more wrinkle amplitude
if -0.15 < v[0] < 0.15 and 0.1 < v[1] < 0.3:
noise[i] *= 2.5
# Waist zone
if v[1] < -0.2:
noise[i] *= 1.5
vertices += normals * noise * 0.003
garment.vertices = vertices
return garment
5. 多視圖渲染
class MultiViewRenderer:
"""Render avatar + garment từ nhiều góc"""
def render_views(
self,
avatar: AvatarMesh,
garment: trimesh.Trimesh,
views: list[str] = ["front", "side", "back"],
) -> dict[str, Image.Image]:
scene = self._create_scene(avatar, garment)
results = {}
camera_angles = {
"front": (0, 0, 3), # z-axis front
"side": (3, 0, 0), # x-axis side
"back": (0, 0, -3), # -z-axis back
"three_quarter": (2, 0.5, 2), # 3/4 view
}
for view_name in views:
camera_pos = camera_angles[view_name]
image = self._render_from_angle(
scene, camera_pos
)
results[view_name] = image
return results
def _create_scene(
self, avatar: AvatarMesh, garment: trimesh.Trimesh
) -> trimesh.Scene:
scene = trimesh.Scene()
scene.add_geometry(avatar.mesh, node_name="body")
scene.add_geometry(garment, node_name="garment")
return scene
def _render_from_angle(
self,
scene: trimesh.Scene,
camera_position: tuple,
resolution: tuple = (800, 1200),
) -> Image.Image:
"""Render scene from specific camera angle"""
import pyrender
# Convert trimesh scene to pyrender
pr_scene = pyrender.Scene()
for name, mesh in scene.geometry.items():
pr_mesh = pyrender.Mesh.from_trimesh(mesh)
pr_scene.add(pr_mesh)
# Camera
camera = pyrender.PerspectiveCamera(yfov=np.pi / 4)
camera_pose = self._look_at(
eye=camera_position,
target=(0, 0, 0),
up=(0, 1, 0),
)
pr_scene.add(camera, pose=camera_pose)
# Lights
light = pyrender.DirectionalLight(
color=[1.0, 1.0, 1.0], intensity=3.0
)
pr_scene.add(light, pose=camera_pose)
# Render
renderer = pyrender.OffscreenRenderer(*resolution)
color, depth = renderer.render(pr_scene)
renderer.delete()
return Image.fromarray(color)
總結
服裝效果圖:
- 服裝模板 — 用於襯衫的預先建造 3D 網格
- UV 映射 — 將設計映射到紋理上的正確列印區域
- 布料模擬——碰撞解決、皺紋生成
- 多視圖 — 渲染前視圖、側視圖、後視圖、四分之三視圖
- Pyrender — 用於伺服器端影像產生的離屏渲染
下一篇文章:即時虛擬試穿 — 在瀏覽器中進行 360° 旋轉和動畫。