posts

3D 웨이퍼맵 좌표계 시스템 완전 가이드

Oct 1, 2025 updated Oct 1, 2025 3dcssimplementationjavascripttypescript

목차

  1. 좌표계란 무엇인가?
  2. 웨이퍼맵에서 좌표계가 필요한 이유
  3. 데이터 좌표계 (Data Coordinate System)
  4. 웨이퍼 로컬 좌표계 (Wafer Local Coordinate System)
  5. Three.js 월드 좌표계 (World Coordinate System)
  6. 카메라 뷰 좌표계 (View Coordinate System)
  7. 화면 좌표계 (Screen Coordinate System)
  8. 좌표 변환이 필요한 이유
  9. 실제 변환 과정 단계별 설명
  10. Unit과 Scale의 중요성

좌표계란 무엇인가?

기본 개념

좌표계(Coordinate System)는 공간상의 위치를 숫자로 표현하는 방법입니다. 일상생활에서도 우리는 다양한 좌표계를 사용합니다:

  • 주소: "서울시 강남구 테헤란로 123번길"
  • GPS: "위도 37.5665°, 경도 126.9780°"
  • 건물 내: "3층 304호"

3D 좌표계의 기본 요소

3D 공간에서는 세 개의 축을 사용합니다:

  • X축: 좌우 방향 (Left ↔ Right)
  • Y축: 상하 방향 (Up ↔ Down)
  • Z축: 앞뒤 방향 (Forward ↔ Backward)

오른손 법칙 (Right-handed System)

Three.js는 오른손 좌표계를 사용합니다:

오른손을 펼쳐서: 👍 엄지: X축 (우측이 양수) 👆 검지: Y축 (위쪽이 양수)
👌 중지: Z축 (앞쪽이 양수)

웨이퍼맵에서 좌표계가 필요한 이유

문제 상황

웨이퍼맵을 구현할 때 다양한 좌표계를 다뤄야 하는 이유:

  1. 데이터 소스의 다양성

    • 측정 장비마다 다른 좌표 기준
    • KLARF 파일의 표준화된 형식
    • 물리적 웨이퍼의 실제 크기
  2. 렌더링 엔진의 요구사항

    • Three.js만의 고유한 좌표 체계
    • GPU 최적화를 위한 특별한 단위
    • 성능을 위한 정규화 필요
  3. 사용자 인터페이스

    • 브라우저 화면의 픽셀 좌표
    • 마우스 클릭 위치 변환
    • 터치 이벤트 처리

실제 예시로 이해하기

📊 원시 데이터: "Shot #15가 웨이퍼 중심에서 우측 50.2mm, 위로 30.5mm 위치에 있음" ↓ 🔄 변환 과정: 여러 좌표계를 거쳐 변환 ↓
🖥️ 최종 화면: "화면에서 320픽셀, 240픽셀 위치에 노란색 사각형으로 표시"

데이터 좌표계 (Data Coordinate System)

정의

측정 장비나 파일에서 제공되는 원시 데이터의 좌표계

특징

  • 원점: 장비나 표준에 따라 다름
  • 단위: 보통 밀리미터(mm) 또는 마이크로미터(μm)
  • 형식: JSON, KLARF, CSV 등 다양한 파일 형식

실제 데이터 예시

{
"waferInfo": {
"diameter": 300,        // 웨이퍼 지름 300mm
"thickness": 0.7,       // 두께 0.7mm
"notchPosition": "bottom"
},
"shots": [
{
"id": 1,
"x": 50.2,           // 웨이퍼 중심에서 우측 50.2mm
"y": 30.5,           // 웨이퍼 중심에서 위로 30.5mm
"z": 0.1,            // 웨이퍼 표면에서 0.1mm 높이
"value": 85.2,       // 측정값
"status": "good"
},
{
"id": 2,
"x": -20.1,          // 웨이퍼 중심에서 좌측 20.1mm
"y": 45.3,           // 웨이퍼 중심에서 위로 45.3mm
"z": 0.15,           // 웨이퍼 표면에서 0.15mm 높이
"value": 92.1,
"status": "bad"
}]
}

데이터 검증 단계

function validateDataCoordinates(data) {
const waferRadius = data.waferInfo.diameter / 2;  // 150mm

return data.shots.filter(shot => {
// 웨이퍼 경계 내부에 있는지 확인
const distance = Math.sqrt(shot.x * shot.x + shot.y * shot.y);
const isInBounds = distance <= waferRadius;

    // Z축 범위 확인 (0 ~ 웨이퍼 두께)
    const validHeight = shot.z >= 0 && shot.z <= data.waferInfo.thickness;

    return isInBounds && validHeight;
});
}

웨이퍼 로컬 좌표계 (Wafer Local Coordinate System)

정의

웨이퍼의 물리적 중심점을 원점으로 하는 좌표계

핵심 특징

  • 원점: 웨이퍼의 정확한 중심 (0, 0, 0)
  • 단위: 밀리미터(mm) - 실제 물리적 크기와 1:1 대응
  • 축 정의:
    • X축: 우측이 양수 (-150mm ~ +150mm, 300mm 웨이퍼 기준)
    • Y축: 위쪽이 양수 (-150mm ~ +150mm)
    • Z축: 웨이퍼 표면 위쪽이 양수 (0mm ~ 0.7mm)

웨이퍼 로컬 좌표계의 장점

  1. 직관적: 웨이퍼의 실제 물리적 크기와 일치
  2. 측정 친화적: 측정 장비의 좌표와 직접 연결
  3. 표준화: 웨이퍼 크기에 상관없이 일관된 중심 기준

좌표 범위 계산

class WaferLocalCoordinate {
constructor(diameter, thickness) {
this.diameter = diameter;     // 300mm
this.thickness = thickness;   // 0.7mm
this.radius = diameter / 2;   // 150mm
}

// 웨이퍼 내부 점인지 확인
isValidPosition(x, y, z) {
const distance = Math.sqrt(x * x + y * y);
return distance <= this.radius && z >= 0 && z <= this.thickness;
}

// 경계 정보 반환
getBounds() {
return {
xMin: -this.radius,    // -150mm
xMax: this.radius,     //  150mm
yMin: -this.radius,    // -150mm  
yMax: this.radius,     //  150mm
zMin: 0,               //    0mm
zMax: this.thickness   //  0.7mm
};
}
}

웨이퍼 좌표계에서의 Shot 배치 예시

// 300mm 웨이퍼에 127개 Shot이 배치된 예시
const waferCoord = new WaferLocalCoordinate(300, 0.7);

const shots = [
{ id: 1,  x:   0.0, y:   0.0, z: 0.1 },  // 웨이퍼 중심
{ id: 2,  x:  50.2, y:  30.5, z: 0.1 },  // 우측 상단
{ id: 3,  x: -60.1, y: -45.3, z: 0.15 }, // 좌측 하단
{ id: 4,  x: 140.0, y:   0.0, z: 0.12 }, // 웨이퍼 가장자리
// ... 123개 더
];

// 각 Shot이 웨이퍼 내부에 있는지 검증
shots.forEach(shot => {
if (waferCoord.isValidPosition(shot.x, shot.y, shot.z)) {
console.log(`Shot ${shot.id}: 유효한 위치`);
} else {
console.error(`Shot ${shot.id}: 웨이퍼 경계 밖!`);
}
});

Three.js 월드 좌표계 (World Coordinate System)

정의

Three.js 3D 엔진에서 사용하는 가상 3D 공간의 좌표계

핵심 특징

  • 원점: 3D Scene의 중심 (0, 0, 0)
  • 단위: Three.js 유닛 (임의 단위, 보통 1 unit = 1 meter 같은 느낌)
  • 축 정의: 오른손 좌표계
    • X축: 우측이 양수
    • Y축: 위쪽이 양수
    • Z축: 앞쪽(화면 밖으로)이 양수

왜 Three.js만의 좌표계가 필요한가?

1. GPU 최적화

// 웨이퍼 좌표 (mm 단위)를 Three.js 좌표로 변환
const SCALE_FACTOR = 0.01;  // 1mm = 0.01 Three.js unit

// 🚫 잘못된 방법: 실제 크기 그대로 사용
const badPosition = new THREE.Vector3(150, 150, 0.7);  // 너무 큰 숫자
// GPU가 처리하기 어려운 큰 숫자들, 정밀도 손실 가능

// ✅ 올바른 방법: 적절한 크기로 스케일링
const goodPosition = new THREE.Vector3(1.5, 1.5, 0.007);  // 적당한 크기
// GPU가 효율적으로 처리할 수 있는 범위

2. 수치적 안정성

// 부동소수점 정밀도 문제 해결
const waferX = 150.123456789;  // mm
const threeX = waferX * 0.01;  // 1.50123456789 (더 안정적)

// Three.js는 단정밀도(32bit) 부동소수점 사용
// 너무 큰 숫자는 정밀도 손실 발생

3. 카메라와 조명 계산

// Three.js 카메라는 특정 거리 범위에서 최적화됨
const camera = new THREE.PerspectiveCamera(
75,           // 시야각
aspect,       // 화면비
0.1,          // Near clipping plane
1000          // Far clipping plane
);

// 웨이퍼 좌표계(mm)를 그대로 사용하면
// Near: 0.1mm, Far: 1000mm - 너무 작은 범위
// Three.js 좌표계로 변환하면
// Near: 0.1, Far: 1000 - 적절한 범위

웨이퍼 → Three.js 좌표 변환

class CoordinateTransformer {
constructor() {
this.SCALE_FACTOR = 0.01;    // 1mm = 0.01 Three.js unit
this.BASE_HEIGHT = 0.0;      // 웨이퍼 베이스 높이
}

// 웨이퍼 좌표 → Three.js 좌표
waferToThreeJS(waferX, waferY, waferZ) {
return {
x: waferX * this.SCALE_FACTOR,           // 50.2mm → 0.502
y: waferY * this.SCALE_FACTOR,           // 30.5mm → 0.305  
z: (waferZ + this.BASE_HEIGHT) * this.SCALE_FACTOR  // 0.1mm → 0.001
};
}

// Three.js 좌표 → 웨이퍼 좌표 (역변환)
threeJSToWafer(threeX, threeY, threeZ) {
return {
x: threeX / this.SCALE_FACTOR,           // 0.502 → 50.2mm
y: threeY / this.SCALE_FACTOR,           // 0.305 → 30.5mm
z: (threeZ / this.SCALE_FACTOR) - this.BASE_HEIGHT  // 0.001 → 0.1mm
};
}
}

Three.js 객체 생성 예시

// 웨이퍼 베이스 (300mm 지름, 0.7mm 두께)
const waferGeometry = new THREE.CylinderGeometry(
1.5,          // 상단 반지름 (150mm → 1.5 units)
1.5,          // 하단 반지름 (150mm → 1.5 units)  
0.007,        // 높이 (0.7mm → 0.007 units)
64            // 원 세그먼트 수 (부드러운 원형)
);

const waferMaterial = new THREE.MeshStandardMaterial({
color: 0xCCCCCC,        // 회색
transparent: true,
opacity: 0.3,           // 반투명
roughness: 0.8          // 약간 거친 표면
});

const waferMesh = new THREE.Mesh(waferGeometry, waferMaterial);
waferMesh.position.set(0, 0, 0);  // 원점에 배치

카메라 뷰 좌표계 (View Coordinate System)

정의

사용자가 보는 시점을 기준으로 하는 좌표계

핵심 특징

  • 원점: 카메라의 위치
  • 축 정의: 카메라가 바라보는 방향을 기준
    • X축: 카메라 기준 우측
    • Y축: 카메라 기준 위쪽
    • Z축: 카메라 기준 앞쪽 (카메라가 바라보는 반대 방향)

다양한 카메라 시점

1. Top View (위에서 내려다보기)

// 웨이퍼를 위에서 내려다보는 시점
const topViewCamera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
topViewCamera.position.set(0, 5, 0);      // 웨이퍼 위 5 units
topViewCamera.lookAt(0, 0, 0);             // 웨이퍼 중심을 바라봄

// 이 시점에서는:
// - 웨이퍼가 원형으로 보임 (2D 웨이퍼맵과 유사)
// - Z축(높이) 정보가 색상이나 크기로 표현됨

2. Isometric View (비스듬히 보기)

// 3D 입체감을 느낄 수 있는 시점
const isoViewCamera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
isoViewCamera.position.set(3, 3, 3);      // 대각선 위치
isoViewCamera.lookAt(0, 0, 0);             // 웨이퍼 중심을 바라봄

// 이 시점에서는:
// - 웨이퍼의 두께와 Shot의 높이 차이를 직접 확인 가능
// - 입체적인 시각화 효과

3. Side View (옆에서 보기)

// 웨이퍼를 옆에서 보는 시점
const sideViewCamera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
sideViewCamera.position.set(5, 0, 0);     // 웨이퍼 옆쪽
sideViewCamera.lookAt(0, 0, 0);           // 웨이퍼 중심을 바라봄

// 이 시점에서는:
// - 웨이퍼가 얇은 선으로 보임
// - Shot의 높이 분포를 프로파일로 확인 가능

카메라 컨트롤러

class CameraController {
constructor(camera, controls) {
this.camera = camera;
this.controls = controls;

    // 미리 정의된 시점들
    this.presetViews = {
      top: { position: [0, 5, 0], target: [0, 0, 0] },
      iso: { position: [3, 3, 3], target: [0, 0, 0] },
      side: { position: [5, 0, 0], target: [0, 0, 0] }
    };
}

// 특정 시점으로 부드럽게 이동
animateToView(viewName, duration = 1000) {
const view = this.presetViews[viewName];
if (!view) return;

    // 현재 위치에서 목표 위치로 부드럽게 이동
    this.animateCamera(
      this.camera.position,
      new THREE.Vector3(...view.position),
      duration
    );
}

// 카메라 애니메이션
animateCamera(startPos, endPos, duration) {
const startTime = Date.now();

    const animate = () => {
      const elapsed = Date.now() - startTime;
      const progress = Math.min(elapsed / duration, 1);

      // 부드러운 easing 함수 적용
      const easeProgress = this.easeInOutCubic(progress);

      // 현재 위치 계산
      this.camera.position.lerpVectors(startPos, endPos, easeProgress);

      if (progress < 1) {
        requestAnimationFrame(animate);
      }
    };

    animate();
}

// Easing 함수 (부드러운 애니메이션)
easeInOutCubic(t) {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}
}

화면 좌표계 (Screen Coordinate System)

정의

브라우저 화면의 픽셀 기준 좌표계

핵심 특징

  • 원점: 화면 좌상단 (0, 0)
  • 단위: 픽셀 (px)
  • 축 정의:
    • X축: 우측이 양수 (0 ~ canvas.width)
    • Y축: 아래쪽이 양수 (0 ~ canvas.height) ⚠️ 주의: 일반적인 수학 좌표와 반대

화면 좌표계의 특별한 점

1. Y축 방향 반전

// 일반적인 수학/3D 좌표계: Y축 위쪽이 양수
// 화면 좌표계: Y축 아래쪽이 양수

const mathY = 100;          // 수학 좌표계에서 위쪽
const screenY = canvas.height - mathY;  // 화면 좌표계로 변환

2. Device Pixel Ratio 고려

// 고해상도 디스플레이 지원
const dpr = window.devicePixelRatio || 1;
canvas.width = displayWidth * dpr;
canvas.height = displayHeight * dpr;
canvas.style.width = displayWidth + 'px';
canvas.style.height = displayHeight + 'px';

마우스/터치 이벤트 처리

class ScreenInteraction {
constructor(canvas, camera) {
this.canvas = canvas;
this.camera = camera;
this.raycaster = new THREE.Raycaster();
this.mouse = new THREE.Vector2();
}

// 마우스 클릭 → 3D 공간 변환
handleClick(event) {
// 1단계: 브라우저 이벤트 좌표 가져오기
const rect = this.canvas.getBoundingClientRect();
const clientX = event.clientX - rect.left;
const clientY = event.clientY - rect.top;

    // 2단계: 화면 좌표 → 정규화된 장치 좌표 (NDC)
    this.mouse.x = (clientX / rect.width) * 2 - 1;   // -1 ~ 1
    this.mouse.y = -(clientY / rect.height) * 2 + 1; // -1 ~ 1 (Y축 반전)

    // 3단계: NDC → 3D 공간 광선
    this.raycaster.setFromCamera(this.mouse, this.camera);

    // 4단계: 3D 객체와의 교차점 찾기
    const intersects = this.raycaster.intersectObjects(selectableObjects);

    if (intersects.length > 0) {
      const clickedObject = intersects[0].object;
      const clickedPoint = intersects[0].point;

      console.log('클릭된 3D 위치:', clickedPoint);
      this.handleObjectClick(clickedObject);
    }
}

// 3D 위치 → 화면 좌표 변환 (툴팁 표시용)
worldToScreen(worldPosition) {
const vector = worldPosition.clone();
vector.project(this.camera);

    const rect = this.canvas.getBoundingClientRect();

    return {
      x: (vector.x + 1) * rect.width / 2,
      y: (-vector.y + 1) * rect.height / 2
    };
}
}

좌표 변환이 필요한 이유

1. 데이터 소스와 렌더링 엔진의 차이


📊 측정 데이터: "웨이퍼 중심에서 50.2mm 우측"
↓ (변환 이유: 실제 물리적 크기)
🥏 웨이퍼 좌표: (50.2, 30.5, 0.1) mm
↓ (변환 이유: GPU 최적화, 수치 안정성)
🌍 Three.js 좌표: (0.502, 0.305, 0.001) units
↓ (변환 이유: 사용자 시점 적용)
📹 카메라 좌표: 시점별 다른 값
↓ (변환 이유: 화면 픽셀로 표시)
🖥️ 화면 좌표: (320, 240) pixels

2. 성능 최적화

// ❌ 좌표 변환 없이 직접 사용 시 문제점
const shots = waferData.shots;  // 1000개 Shot
shots.forEach(shot => {
// 매번 계산 수행 → 성능 저하
const threeX = shot.x * 0.01;
const threeY = shot.y * 0.01;
const threeZ = shot.z * 0.01;

// Three.js 객체 생성
const geometry = new THREE.BoxGeometry(0.02, 0.02, 0.005);
const material = new THREE.MeshStandardMaterial();
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(threeX, threeY, threeZ);

scene.add(mesh);  // 1000번의 scene.add() 호출
});

// ✅ 좌표 변환 + 최적화된 렌더링
const transformer = new CoordinateTransformer();
const positions = new Float32Array(shots.length * 3);
const colors = new Float32Array(shots.length * 3);

// 한 번에 모든 좌표 변환
shots.forEach((shot, index) => {
const threePos = transformer.waferToThreeJS(shot.x, shot.y, shot.z);
const i = index * 3;

positions[i] = threePos.x;
positions[i + 1] = threePos.y;
positions[i + 2] = threePos.z;
});

// InstancedMesh로 한 번에 렌더링
const geometry = new THREE.BoxGeometry(0.02, 0.02, 0.005);
const material = new THREE.MeshStandardMaterial();
const instancedMesh = new THREE.InstancedMesh(geometry, material, shots.length);

3. 상호작용 지원

// 마우스 클릭 → Shot 선택 과정
class ShotSelector {
handleClick(event) {
// 1. 화면 좌표 → 정규화된 좌표
const mouse = this.screenToNDC(event);

    // 2. 카메라 + 마우스 → 3D 광선
    this.raycaster.setFromCamera(mouse, this.camera);

    // 3. 광선 + 3D 객체 → 교차점 계산
    const intersects = this.raycaster.intersectObject(this.shotMesh);

    if (intersects.length > 0) {
      // 4. Three.js 좌표 → 웨이퍼 좌표 변환
      const threePos = intersects[0].point;
      const waferPos = this.transformer.threeJSToWafer(
        threePos.x, threePos.y, threePos.z
      );

      // 5. 웨이퍼 좌표로 Shot 정보 표시
      this.showShotInfo(waferPos);
    }
}
}

실제 변환 과정 단계별 설명

단계 1: 데이터 파싱 및 검증

class DataProcessor {
parseRawData(rawData) {
// 원시 데이터 구조 분석
const waferInfo = {
diameter: rawData.waferInfo.diameter,    // 300mm
thickness: rawData.waferInfo.thickness,  // 0.7mm
center: [0, 0, 0]                       // 웨이퍼 중심
};

    // Shot 데이터 검증 및 정리
    const validShots = rawData.shots
      .filter(shot => this.isValidShot(shot, waferInfo))
      .map(shot => ({
        id: shot.id,
        waferX: shot.x,        // 웨이퍼 좌표계 X
        waferY: shot.y,        // 웨이퍼 좌표계 Y  
        waferZ: shot.z,        // 웨이퍼 좌표계 Z
        value: shot.value,     // 측정값
        normalizedValue: this.normalizeValue(shot.value)  // 0~1 정규화
      }));

    return { waferInfo, shots: validShots };
}

isValidShot(shot, waferInfo) {
const radius = waferInfo.diameter / 2;
const distance = Math.sqrt(shot.x * shot.x + shot.y * shot.y);

    return distance <= radius && 
           shot.z >= 0 && 
           shot.z <= waferInfo.thickness;
}
}

단계 2: 웨이퍼 → Three.js 좌표 변환

class CoordinateTransformer {
constructor() {
this.SCALE_FACTOR = 0.01;      // 1mm = 0.01 Three.js unit
this.HEIGHT_MULTIPLIER = 10;   // Z축 강조 (선택사항)
}

transformShotData(shots) {
return shots.map(shot => {
// 기본 위치 변환
const threePos = this.waferToThreeJS(
shot.waferX,
shot.waferY,
shot.waferZ
);

      // 색상 계산 (측정값 기반)
      const color = this.valueToColor(shot.normalizedValue);

      // 높이 조정 (옵션)
      const enhancedZ = threePos.z * this.HEIGHT_MULTIPLIER;

      return {
        ...shot,
        threeX: threePos.x,
        threeY: threePos.y,
        threeZ: enhancedZ,
        color: color
      };
    });
}

valueToColor(normalizedValue) {
// 25단계 색상 스펙트럼 (Blue → Green → Yellow → Red)
const hue = (1 - normalizedValue) * 240;  // 240° (blue) → 0° (red)
return new THREE.Color().setHSL(hue / 360, 1.0, 0.5);
}
}

단계 3: Three.js 객체 생성

class ShotRenderer {
createShotMesh(transformedShots) {
const shotCount = transformedShots.length;

    // 기본 Shot 형태 (2mm × 2mm × 0.5mm 박스)
    const geometry = new THREE.BoxGeometry(0.02, 0.02, 0.005);
    const material = new THREE.MeshStandardMaterial({
      vertexColors: true  // 각 인스턴스별 색상 지원
    });

    // InstancedMesh 생성 (고성능)
    this.instancedMesh = new THREE.InstancedMesh(
      geometry, 
      material, 
      shotCount
    );

    // 각 Shot의 변환 매트릭스와 색상 설정
    const matrix = new THREE.Matrix4();

    transformedShots.forEach((shot, index) => {
      // 위치 설정
      matrix.setPosition(shot.threeX, shot.threeY, shot.threeZ);
      this.instancedMesh.setMatrixAt(index, matrix);

      // 색상 설정
      this.instancedMesh.setColorAt(index, shot.color);
    });

    // GPU로 데이터 전송
    this.instancedMesh.instanceMatrix.needsUpdate = true;
    this.instancedMesh.instanceColor.needsUpdate = true;

    return this.instancedMesh;
}
}

단계 4: 카메라 및 조명 설정

class SceneSetup {
setupCamera(canvas) {
const aspect = canvas.clientWidth / canvas.clientHeight;

    this.camera = new THREE.PerspectiveCamera(
      75,     // FOV (시야각)
      aspect, // 화면 비율
      0.1,    // Near clipping plane
      1000    // Far clipping plane
    );

    // 초기 카메라 위치 (Top View)
    this.camera.position.set(0, 5, 0);
    this.camera.lookAt(0, 0, 0);

    return this.camera;
}

setupLighting(scene) {
// 환경광 (전체적인 밝기)
const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
scene.add(ambientLight);

    // 방향광 (그림자와 입체감)
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(10, 10, 5);
    directionalLight.castShadow = true;

    // 그림자 설정
    directionalLight.shadow.mapSize.width = 2048;
    directionalLight.shadow.mapSize.height = 2048;
    directionalLight.shadow.camera.near = 0.5;
    directionalLight.shadow.camera.far = 50;

    scene.add(directionalLight);
}
}

단계 5: 렌더링 루프

class RenderLoop {
constructor(renderer, scene, camera) {
this.renderer = renderer;
this.scene = scene;
this.camera = camera;
this.isRunning = false;
}

start() {
this.isRunning = true;
this.animate();
}

animate() {
if (!this.isRunning) return;

    requestAnimationFrame(() => this.animate());

    // 컨트롤 업데이트 (마우스/키보드 입력 처리)
    if (this.controls) {
      this.controls.update();
    }

    // 씬 렌더링 (3D → 2D 화면 변환)
    this.renderer.render(this.scene, this.camera);

    // 성능 모니터링
    if (this.stats) {
      this.stats.update();
    }
}

stop() {
this.isRunning = false;
}
}

Unit과 Scale의 중요성

왜 Unit이 중요한가?

1. 일관성 유지

// 🚫 단위가 섞인 잘못된 예시
const waferDiameter = 300;        // mm
const shotSize = 0.02;           // Three.js units  
const cameraDistance = 5000;     // ??? 

// ✅ 일관된 단위 사용
const UNIT = {
  WAFER_MM_TO_THREE: 0.01,       // 1mm = 0.01 Three.js units
  SHOT_SIZE_MM: 2,               // Shot 크기 2mm
  CAMERA_DISTANCE_MM: 500        // 카메라 거리 500mm
};

const waferRadius = 150 * UNIT.WAFER_MM_TO_THREE;  // 1.5 units
const shotSize = 2 * UNIT.WAFER_MM_TO_THREE;       // 0.02 units
const cameraDistance = 500 * UNIT.WAFER_MM_TO_THREE; // 5 units

2. 스케일 비율 관리

class ScaleManager {
  constructor() {
    // 실제 물리적 크기 (mm)
    this.PHYSICAL = {
      WAFER_DIAMETER: 300,    // 300mm
      WAFER_THICKNESS: 0.7,   // 0.7mm
      SHOT_SIZE: 2,           // 2mm × 2mm
      SHOT_HEIGHT: 0.1        // 0.1mm
    };

    // Three.js 표현 크기
    this.VISUAL = {
      WAFER_DIAMETER: 3.0,    // 3.0 units
      WAFER_THICKNESS: 0.007, // 0.007 units (너무 얇아서 잘 안 보임!)
      SHOT_SIZE: 0.02,        // 0.02 units
      SHOT_HEIGHT: 0.001      // 0.001 units (너무 얇아서 잘 안 보임!)
    };

    // 시각적 강조를 위한 스케일링
    this.ENHANCED = {
      THICKNESS_MULTIPLIER: 10,   // 두께 10배 확대
      HEIGHT_MULTIPLIER: 50       // 높이 50배 확대
    };
  }

  // 실제 크기 → 시각적 크기
  getVisualScale(physicalValue, type = 'normal') {
    const baseScale = physicalValue * 0.01;  // mm → Three.js units

    switch (type) {
      case 'thickness':
        return baseScale * this.ENHANCED.THICKNESS_MULTIPLIER;
      case 'height':
        return baseScale * this.ENHANCED.HEIGHT_MULTIPLIER;
      default:
        return baseScale;
    }
  }
}

3. 성능에 미치는 영향

// GPU 친화적인 숫자 범위
const OPTIMAL_RANGE = {
  MIN: 0.001,      // 너무 작으면 정밀도 손실
  MAX: 1000,       // 너무 크면 계산 오류
  SWEET_SPOT: [0.1, 10]  // 가장 안정적인 범위
};

class PerformanceOptimizer {
  checkCoordinateRange(coordinates) {
    const values = coordinates.flat();
    const min = Math.min(...values);
    const max = Math.max(...values);

    if (min < OPTIMAL_RANGE.MIN) {
      console.warn(`좌표값이 너무 작습니다: ${min}`);
    }

    if (max > OPTIMAL_RANGE.MAX) {
      console.warn(`좌표값이 너무 큽니다: ${max}`);
    }

    const inSweetSpot = values.every(v => 
      v >= OPTIMAL_RANGE.SWEET_SPOT[0] && 
      v <= OPTIMAL_RANGE.SWEET_SPOT[1]
    );

    return {
      isOptimal: inSweetSpot,
      min, max,
      recommendation: inSweetSpot ? 
        "최적화된 범위입니다" : 
        "스케일 팩터 조정을 권장합니다"
    };
  }
}

Scale Factor 결정 방법

class ScaleFactorCalculator {
  // 웨이퍼 크기에 따른 최적 스케일 팩터 계산
  calculateOptimalScale(waferDiameter) {
    // 목표: 웨이퍼가 Three.js에서 2~4 units 크기가 되도록
    const targetSize = 3.0;  // 3 units
    const scaleFactor = targetSize / waferDiameter;

    return {
      scaleFactor,
      waferRadius: waferDiameter / 2 * scaleFactor,
      shotSize: 2 * scaleFactor,  // 2mm Shot
      explanation: `${waferDiameter}mm 웨이퍼를 ${targetSize} units로 표현`
    };
  }

  // 여러 웨이퍼 크기에 대한 스케일 팩터
  getStandardScales() {
    return {
      wafer200mm: this.calculateOptimalScale(200),  // 0.015
      wafer300mm: this.calculateOptimalScale(300),  // 0.01  
      wafer450mm: this.calculateOptimalScale(450)   // 0.0067
    };
  }
}

마무리

좌표계 변환은 복잡해 보이지만, 각 단계의 목적을 이해하면 훨씬 명확해집니다:

  1. 데이터 좌표계: 측정 장비나 파일의 원시 형태
  2. 웨이퍼 로컬 좌표계: 물리적 웨이퍼의 실제 크기와 1:1 대응
  3. Three.js 월드 좌표계: GPU 최적화되고 수치적으로 안정적인 형태
  4. 카메라 뷰 좌표계: 사용자가 보는 시점
  5. 화면 좌표계: 브라우저에서 픽셀로 표현

각 변환 단계는 성능, 정확성, 사용성을 위해 꼭 필요한 과정이며, 이를 통해 실제 웨이퍼의 물리적 특성을 손실 없이 3D 화면에 표현할 수 있습니다.

핵심은 일관된 단위 체계적절한 스케일 팩터를 유지하여, 데이터의 의미를 보존하면서도 최적의 시각화 성능을 달성하는 것입니다.

웨이퍼맵 3D 구현 청사진 완성 보고서

📋 요청사항 분석 및 완료 현황

원본 요청사항

사용자가 요청한 핵심 내용:

  1. 기존 문서 분석 - wafermap/document/new 파일들과 wafermap-3d 관련 문서들 검토
  2. 웨이퍼맵-2025-06-27.md 분석 - 최신 요구사항 파악
  3. 구현 vs 라이브러리 구분 - 직접 구현할 부분과 Three.js에서 제공하는 부분 명확히 구분
  4. 컴포넌트 아키텍처 - 모든 내부 파싱과 처리를 담당하는 단일 컴포넌트 설계
  5. 파일 목록과 아키텍처 - 상세한 파일 구조 및 책임 분담
  6. 청사진 문서화 - 상호작용과 데이터 관리, 최적화는 배제하고 핵심 렌더링에 집중

✅ 완료된 문서 및 산출물

1. 📊 기존 문서 분석 완료

분석한 주요 문서들:

  • 데이터-플로우.puml - 4단계 데이터 처리 과정
  • 좌표계-변환-시스템.puml - 5단계 좌표 변환 시스템
  • 웨이퍼맵-2025-06-27.md - 상세한 기능 정의 및 구현 방안
  • 웨이퍼맵 3D 설계 문서.md - 전체 아키텍처 설계

2. 🆕 신규 생성 문서

📄 wafermap-3d-implementation-blueprint.md (607줄)

완전한 구현 청사진 문서

핵심 내용:

  • 구현 vs 라이브러리 명확한 구분

    • Three.js 제공: Scene, Camera, Geometry, Material 등
    • 직접 구현: 데이터 파싱, 좌표 변환, 색상 매핑, 렌더링 로직
  • 컴포넌트 아키텍처

    <Wafermap3D>
    ├── 📊 DataProcessor          # 데이터 파싱 및 검증
    ├── 🎨 Scene3DContainer       # Three.js Scene 래퍼
    ├── 🎮 UIOverlay              # HTML/CSS UI 레이어
    └── 🔧 SystemManager          # 내부 시스템 관리
    
  • 상세한 파일 구조

    src/components/wafermap-3d/
    ├── Wafermap3D.tsx                    # 메인 컴포넌트
    ├── core/                             # 핵심 시스템
    ├── rendering/                        # 렌더링 시스템
    ├── ui/                               # UI 컴포넌트
    ├── stores/                           # 상태 관리
    ├── types/                            # 타입 정의
    └── utils/                            # 유틸리티
    
  • 완전한 구현 코드 예제

    • 데이터 처리 로직
    • 좌표계 변환 시스템
    • 색상 매핑 알고리즘
    • InstancedMesh 기반 렌더링
    • Zustand 상태 관리

🎨 wafermap-3d-component-architecture.puml (268줄)

시각적 아키텍처 다이어그램

핵심 내용:

  • 색상 코딩으로 구분

    • 🟦 Three.js 제공 (가져다 쓰는 것)
    • 🟩 직접 구현 (새로 만들어야 하는 것)
    • 🟨 React 생태계 (React Three Fiber 등)
  • 컴포넌트 간 의존성 관계 명확히 표시

  • 구현 우선순위 4단계로 제시

  • 패키지 구조 시각적 표현

🎯 핵심 성과

1. 명확한 구현 vs 라이브러리 구분

Three.js에서 제공하는 것 (가져다 쓰는 것)

// 3D 엔진 코어
Scene, PerspectiveCamera, WebGLRenderer
Vector3, Matrix4, Color, Raycaster

// 지오메트리 클래스
CylinderGeometry, BoxGeometry, ExtrudeGeometry, InstancedMesh

// 재질 & 조명
MeshStandardMaterial, AmbientLight, DirectionalLight

// 컨트롤 & 확장
OrbitControls, CSS3DRenderer

// React Three Fiber
Canvas, useFrame, useThree, Html

직접 구현해야 하는 것

// 데이터 처리 레이어
WaferDataProcessor, CoordinateTransformer, ColorMapper

// 렌더링 로직
ShotRenderingSystem, FocusSpotRenderingSystem, WaferGeometrySystem

// 컴포넌트 아키텍처
WaferStore (Zustand), UI Components, 상태 관리

2. 컴포넌트 완전 설계

메인 컴포넌트 인터페이스

interface Wafermap3DProps {
  data: WaferData;                    // 필수 데이터
  initialMode?: 'shot' | 'focusSpot'; // 기본 설정
  initialView?: 'top' | 'side' | 'iso';
  width?: number;                     // 스타일링
  height?: number;
  backgroundColor?: string;
}

내부 처리 흐름

  1. 데이터 입력 → DataProcessor로 검증 및 파싱
  2. 좌표 변환 → CoordinateTransformer로 웨이퍼→월드 좌표
  3. 색상 매핑 → ColorMapper로 25단계 스펙트럼 생성
  4. 렌더링 → InstancedMesh 기반 고성능 렌더링

3. 상세한 파일 구조 및 책임 분담

핵심 시스템 (core/)

  • DataProcessor.ts - 데이터 검증 및 파싱
  • CoordinateTransformer.ts - 좌표계 변환 (웨이퍼↔월드)
  • ColorMapper.ts - 25단계 색상 스펙트럼 매핑
  • GeometryFactory.ts - 지오메트리 생성 팩토리

렌더링 시스템 (rendering/)

  • Scene3DContainer.tsx - Canvas 래퍼, 조명 설정
  • WaferGeometry.tsx - 웨이퍼 원통 + 노치 렌더링
  • ShotRenderer.tsx - InstancedMesh 기반 Shot 렌더링
  • FocusSpotRenderer.tsx - ExtrudeGeometry 기반 Focus Spot
  • CameraController.tsx - 카메라 시점 제어

UI 시스템 (ui/)

  • Toolbar3D.tsx - 모드 전환, 시점 선택, 캡처
  • Legend.tsx - 색상 범례, 선택된 Shot 표시
  • ModeSelector.tsx - Shot/Focus Spot 모드 전환
  • ViewControls.tsx - Top/Side/Iso 시점 제어

4. 구현 우선순위 로드맵

1단계 (핵심 기능)

  • DataProcessor - 데이터 파싱 및 검증
  • CoordinateTransformer - 좌표계 변환
  • WaferGeometry - 웨이퍼 원통 렌더링
  • Scene3DContainer - 기본 3D Scene 설정

2단계 (Shot 모드)

  • ColorMapper - 색상 매핑 시스템
  • ShotRenderer - InstancedMesh 기반 Shot 렌더링
  • waferStore - 기본 상태 관리

3단계 (UI 시스템)

  • Toolbar3D - 모드 전환 및 시점 제어
  • Legend - 색상 범례 표시
  • UIOverlay - HTML/CSS 오버레이

4단계 (Focus Spot 모드)

  • FocusSpotRenderer - 다각형 Focus Spot 렌더링
  • 고급 UI 기능들

🔧 기술적 혁신

1. 좌표계 변환 시스템

// 웨이퍼 좌표계 → Three.js 월드 좌표계
waferToWorld(waferCoord: { x: number; y: number; z: number }): Vector3 {
  return new Vector3(
    waferCoord.x * 0.01,      // X축 그대로
    waferCoord.z * 0.01,      // Z → Y (높이)
    -waferCoord.y * 0.01      // Y → -Z (깊이, 뒤집기)
  );
}

2. 25단계 색상 스펙트럼

// HSL 색상 공간에서 파랑→빨강 그라데이션
generateColorSpectrum(steps: number = 25): Color[] {
  for (let i = 0; i < steps; i++) {
    const hue = (240 - (i / (steps - 1)) * 240) / 360;
    const color = new Color().setHSL(hue, 1, 0.5);
    colors.push(color);
  }
}

3. InstancedMesh 기반 고성능 렌더링

// 100-1000개 Shot을 단일 Draw Call로 렌더링
<instancedMesh
  args={[new BoxGeometry(1, 1, 0.1), new MeshStandardMaterial(), shots.length]}
>
  <instancedBufferAttribute
    attach="geometry-attributes-instanceColor"
    args={[new Float32Array(shots.length * 3), 3]}
  />
</instancedMesh>

📊 웨이퍼맵-2025-06-27.md 요구사항 대응

Common 기능 대응

  • Wafer - CylinderGeometry로 원형 웨이퍼 구현
  • Scene - Three.js Scene, OrbitControls 기본 설정
  • Mode - Zustand 상태 관리로 모드별 렌더링 분기
  • Toolbar - React 컴포넌트로 UI 구성

Shot Mode 기능 대응

  • 카메라 시점 - Three.js Camera 위치 및 각도 설정
  • Shot 그리기 - InstancedMesh로 성능 최적화, 25단계 색상 스펙트럼
  • Shot 선택 - Raycasting으로 선택 감지 (나중에 구현)
  • 텍스트 삽입 - Three.js Sprite 또는 CSS3DRenderer 활용

Focus Spot Mode 기능 대응

  • Focus Spot 그리기 - ExtrudeGeometry로 다각형 생성
  • 툴팁 - CSS3DRenderer 활용 (나중에 구현)
  • 영역 선택 - 복잡한 선택 알고리즘 (나중에 구현)

🎉 결론

완전히 충족된 요구사항

  1. 기존 문서 분석 완료 - 모든 관련 문서 검토 및 분석
  2. 구현 vs 라이브러리 명확한 구분 - 상세한 분류 및 코드 예제
  3. 컴포넌트 완전 설계 - 단일 컴포넌트 아키텍처
  4. 상세한 파일 구조 - 책임 분담 및 구현 우선순위
  5. 시각적 청사진 - PUML 다이어그램으로 아키텍처 표현

추가 제공된 가치

  • 완전한 구현 코드 예제 - 즉시 개발 시작 가능
  • 구현 우선순위 로드맵 - 단계별 개발 가이드
  • 기술적 혁신 방안 - 좌표 변환, 색상 매핑, 고성능 렌더링
  • 요구사항 대응표 - 웨이퍼맵-2025-06-27.md 완전 대응

이제 이 청사진을 바탕으로 고성능 웨이퍼맵 3D 시스템을 성공적으로 구현할 수 있습니다! 🚀

📁 생성된 파일 목록

  1. wafermap-3d-implementation-blueprint.md - 완전한 구현 청사진 (607줄)
  2. wafermap-3d-component-architecture.puml - 시각적 아키텍처 다이어그램 (268줄)
  3. answer.md - 종합 보고서 (현재 파일)