1. Mockup引擎架構
Design Image + Product Template → Mockup Engine → Product Preview
│
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────────┐
│ 2D Warp │ │ 3D Render │ │ AI Mockup │
│(fast,web)│ │(quality) │ │ (photorealistic)│
│ │ │ │ │ │
│Perspective│ │ Three.js │ │Stable Diff │
│Transform │ │ + Blender │ │+ ControlNet │
│+ Overlay │ │ │ │ │
└────┬─────┘ └────┬──────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
Quick Preview High-Quality AI-Enhanced
(~100ms) (~2-5 seconds) (~10-30 seconds)
Web Editor Product Page Marketing
2. 2D透視變換模型
// Fast 2D mockup: overlay design trên product photo sử dụng perspective transform
interface MockupTemplate2D {
id: string;
product: string; // 'tshirt_front', 'hoodie_back', 'mug_wrap'
// Product photo
baseImage: string; // Product photo URL
// Design placement area (4-corner polygon for perspective)
designArea: {
topLeft: { x: number; y: number };
topRight: { x: number; y: number };
bottomLeft: { x: number; y: number };
bottomRight: { x: number; y: number };
};
// Color variants
colorVariants: Array<{
color: string; // '#ffffff', '#000000', '#1a1a2e'
baseImage: string; // Product photo in this color
}>;
// Shadow/overlay for realism
shadowOverlay?: string; // Shadow layer to composite on top
displacementMap?: string; // Fabric wrinkle displacement
}
async function generate2DMockup(
design: Buffer,
template: MockupTemplate2D,
productColor: string,
): Promise<Buffer> {
const { designArea } = template;
// 1. Perspective transform design to match product surface
const warped = await perspectiveTransform(design, {
srcCorners: getDesignCorners(design),
dstCorners: [
designArea.topLeft,
designArea.topRight,
designArea.bottomLeft,
designArea.bottomRight,
],
});
// 2. Apply displacement map (wrinkle/fold effect)
const displaced = template.displacementMap
? await applyDisplacement(warped, template.displacementMap, { strength: 0.3 })
: warped;
// 3. Composite layers
const baseImage = await loadImage(
template.colorVariants.find(v => v.color === productColor)?.baseImage
|| template.baseImage
);
const composited = await sharp(baseImage)
.composite([
{ input: displaced, blend: 'multiply', left: 0, top: 0 }, // Design
...(template.shadowOverlay
? [{ input: await loadImage(template.shadowOverlay), blend: 'multiply' as const }]
: []),
])
.toBuffer();
return composited;
}
3. 使用 Three.js 進行 3D 渲染
// Three.js based 3D mockup renderer (runs server-side with headless GL)
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
interface Product3DModel {
id: string;
product: string;
modelUrl: string; // GLTF/GLB model
textureMapping: {
meshName: string; // Mesh trong model để apply texture
uvChannel: number; // UV channel for design placement
designUVRegion: UVRect; // UV coords for design area only
};
cameraPresets: CameraPreset[];
lighting: LightingSetup;
}
interface CameraPreset {
name: string; // 'front', 'angle_left', '45_top', 'back'
position: THREE.Vector3;
lookAt: THREE.Vector3;
fov: number;
}
async function render3DMockup(
design: Buffer,
model: Product3DModel,
cameraPreset: string,
options: RenderOptions,
): Promise<Buffer> {
// 1. Setup scene
const scene = new THREE.Scene();
const camera = setupCamera(model.cameraPresets.find(p => p.name === cameraPreset)!);
const renderer = createHeadlessRenderer(options.width, options.height);
// 2. Load 3D model
const gltf = await loadGLTF(model.modelUrl);
scene.add(gltf.scene);
// 3. Apply design as texture
const designTexture = new THREE.TextureLoader().load(
bufferToDataURL(design)
);
designTexture.flipY = false;
designTexture.colorSpace = THREE.SRGBColorSpace;
// Find target mesh and apply design
const targetMesh = gltf.scene.getObjectByName(model.textureMapping.meshName);
if (targetMesh instanceof THREE.Mesh) {
const material = targetMesh.material as THREE.MeshStandardMaterial;
material.map = designTexture;
material.needsUpdate = true;
}
// 4. Setup lighting
setupLighting(scene, model.lighting);
// 5. Add environment map for reflections (product photography studio)
const envMap = await loadHDRI('/environments/studio-softbox.hdr');
scene.environment = envMap;
scene.background = options.transparentBg
? null
: new THREE.Color(options.backgroundColor);
// 6. Render
renderer.render(scene, camera);
// 7. Extract image
return extractRenderedImage(renderer);
}
// Multi-angle batch render
async function renderAllAngles(
design: Buffer,
model: Product3DModel,
): Promise<Map<string, Buffer>> {
const results = new Map<string, Buffer>();
for (const preset of model.cameraPresets) {
const image = await render3DMockup(design, model, preset.name, {
width: 1200,
height: 1200,
transparentBg: false,
backgroundColor: '#f5f5f5',
});
results.set(preset.name, image);
}
return results;
}
4.人工智慧增強的真實感模型
// AI mockup: Sử dụng Stable Diffusion + ControlNet để tạo mockup photorealistic
interface AIMockupRequest {
design: Buffer;
productType: string; // 'tshirt', 'hoodie', 'tote_bag'
scene: string; // 'model_wearing', 'flat_lay', 'hanging', 'lifestyle'
modelDescription?: string; // 'young woman, urban setting, smiling'
backgroundColor?: string;
}
async function generateAIMockup(req: AIMockupRequest): Promise<Buffer> {
// 1. Generate base product image with ControlNet (design as reference)
const scenePrompt = buildMockupPrompt(req);
// 2. Use IP-Adapter to inject the design onto the product
const result = await sdxlPipeline.generate({
prompt: scenePrompt.positive,
negative_prompt: scenePrompt.negative,
// ControlNet: canny edges of design layout
controlnet: [{
model: 'canny',
image: await generateDesignPlacementGuide(req.design, req.productType),
conditioning_scale: 0.6,
}],
// IP-Adapter: inject actual design content
ip_adapter: {
model: 'ip_adapter_plus',
image: req.design,
scale: 0.7,
},
width: 1024,
height: 1024,
num_inference_steps: 35,
guidance_scale: 7.5,
});
return result.images[0];
}
// Scene-specific prompt builder
function buildMockupPrompt(req: AIMockupRequest): { positive: string; negative: string } {
const sceneTemplates: Record<string, string> = {
model_wearing: `professional product photography, ${req.modelDescription || 'person'} wearing a ${req.productType} with the design, studio lighting, white background, commercial quality`,
flat_lay: `flat lay product photography, ${req.productType} laid flat on clean surface, top-down view, studio lighting, commercial catalog style`,
hanging: `${req.productType} on wooden hanger, clean white wall background, professional product photography, soft shadows`,
lifestyle: `lifestyle product photography, ${req.productType} in urban outdoor setting, natural lighting, fashion editorial style`,
};
return {
positive: sceneTemplates[req.scene] || sceneTemplates.flat_lay,
negative: 'blurry, low quality, distorted design, wrong design placement, extra limbs',
};
}
5. AR試戴(WebXR)
// AR Try-on: User xem product trên người qua camera
interface ARTryOnModule {
// Initialize AR session
startSession(videoElement: HTMLVideoElement): Promise<ARSession>;
// Apply design to detected body
applyDesign(design: Buffer, session: ARSession): void;
// Capture screenshot
capture(): Promise<Buffer>;
}
interface ARSession {
// Body pose detection (MediaPipe / TensorFlow.js)
bodyTracker: BodyPoseTracker;
// 3D product overlay
productOverlay: THREE.Group;
// Real-time rendering
renderer: THREE.WebGLRenderer;
status: 'initializing' | 'tracking' | 'lost' | 'error';
}
// Client-side AR implementation
class WebARTryOn {
private poseDetector: PoseDetector;
private renderer: THREE.WebGLRenderer;
async init(canvas: HTMLCanvasElement, video: HTMLVideoElement) {
// 1. Setup camera feed
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'user', width: 1280, height: 720 },
});
video.srcObject = stream;
// 2. Initialize pose detection (MediaPipe BlazePose)
this.poseDetector = await createPoseDetector('BlazePose', {
runtime: 'mediapipe',
modelType: 'full',
});
// 3. Setup Three.js overlay renderer
this.renderer = new THREE.WebGLRenderer({
canvas,
alpha: true, // Transparent background for overlay
});
}
async renderFrame(design: THREE.Texture) {
// 1. Detect body landmarks
const poses = await this.poseDetector.estimatePoses(video);
if (poses.length === 0) return;
const pose = poses[0];
const torso = extractTorsoRegion(pose.keypoints);
// 2. Position 3D product model to match torso
this.productMesh.position.set(torso.centerX, torso.centerY, 0);
this.productMesh.scale.set(torso.width / 100, torso.height / 100, 1);
this.productMesh.rotation.z = torso.rotation;
// 3. Apply design texture
(this.productMesh.material as THREE.MeshBasicMaterial).map = design;
// 4. Render overlay
this.renderer.render(this.scene, this.camera);
}
}
6. 批量模型生成
// Batch: Tạo mockup cho toàn bộ catalog
interface BatchMockupJob {
designs: Array<{ id: string; imageUrl: string }>;
products: Array<{ id: string; template: MockupTemplate2D }>;
colors: string[];
angles: string[];
}
async function generateCatalogMockups(job: BatchMockupJob): Promise<MockupResult[]> {
const results: MockupResult[] = [];
const total = job.designs.length * job.products.length * job.colors.length;
// Parallel processing with concurrency limit
const queue = new PQueue({ concurrency: 10 });
for (const design of job.designs) {
for (const product of job.products) {
for (const color of job.colors) {
queue.add(async () => {
const designImage = await downloadImage(design.imageUrl);
const mockup = await generate2DMockup(designImage, product.template, color);
// Upload to CDN
const url = await uploadToCDN(mockup, {
path: `mockups/${design.id}/${product.id}/${color}.webp`,
format: 'webp',
quality: 85,
});
results.push({
designId: design.id,
productId: product.id,
color,
mockupUrl: url,
});
});
}
}
}
await queue.onIdle();
return results;
}
七、總結
| 方法 | 速度 | 品質 | 使用案例 |
|---|---|---|---|
| 2D 透视变换 | 〜100毫秒 | 好 | 在編輯器中即時預覽 |
| 3D Three.js 渲染 | 〜2-5秒 | 高 | 產品頁面,多角度 |
| 人工智慧真實感 | 〜10-30秒 | 非常高 | 行銷、社群媒體 |
| AR試穿 | 即時 | 中等 | 客戶體驗,轉換率提升 |
| 批量生成 | 非同步 | 各不相同 | 目錄生成、批量列表 |
2D 模型 — 透視變換+位移貼圖+陰影疊加,~100ms,用於即時編輯器預覽
3D 模型 — Three.js + GLTF 模型 + PBR 材質、多角度攝影機預設、工作室燈光
人工智慧模型 — SDXL + ControlNet + IP 轉接器,用於行銷的逼真產品照片
AR試穿 — MediaPipe 身體追蹤 + WebGL 疊加,在瀏覽器上即時
批次處理 — 並行佇列(PQueue),整個目錄的並行模型