티스토리 뷰

728x90
반응형

제주 4·3 용천수 Scalar Field  생성 문서

 

  • Scalar Field (스칼라 필드)
  • 각 복셀에 숫자 하나 (0~1)
  • 용천수까지 거리/영향도

 

 

목적

제주 지형에서 각 지점으로부터 가장 가까운 용천수까지의 거리를 계산하여 텍스처로 출력. Unity VFX Graph에서 파티클이 용천수로 끌려가는 효과에 사용.


데이터

  • 제주 지형: HeightField (1080 x 1800, 높이 ~50)
  • 용천수 포인트: 1025개, GeoJSON에서 로드하여 제주 좌표계로 변환됨

Houdini 노드 체인

1. heightfield_file1 (제주 지형 로드)
2. python (용천수 GeoJSON 로드)
3. transform (좌표 변환)
4. pointwrangle1 (용천수를 지형 높이로 올림)
   └─ 코드: pos.y = volumesample(1, "height", pos);

5. HeightField Copy Layer
   └─ Source: height
   └─ Destination: spring_dist

6. Volume Wrangle (거리 계산)
   └─ 입력 1: HeightField (height, mask, spring_dist)
   └─ 입력 2: 용천수 포인트

7. HeightField Remap (0-1 정규화)
   └─ Input Min: 578.666
   └─ Input Max: 1796.660
   └─ Output Min: 0
   └─ Output Max: 1

8. HeightField Output (EXR 저장)

Volume Wrangle 코드

if (@primnum == 2) {
    float total = 0.0;
    float radius = 20.0;  // 20m
    
    for(int i = 0; i < 1025; i++) {
        vector sp = point(1, "P", i);
        
        float dx = @P.x - sp.x;
        float dz = @P.z - sp.z;
        float dist = sqrt(dx*dx + dz*dz);
        
        if (dist < radius) {
            total += 1.0 - (dist / radius);
        }
    }
    
    f@spring_dist = clamp(total, 0.0, 1.0);
}

코드 설명

  • @primnum == 2: spring_dist 레이어만 처리 (primitive 2번)
  • volumeindextopos(): 복셀 인덱스를 월드 좌표로 변환
  • npoints(1): 두 번째 입력(용천수 포인트) 개수
  • point(1, "P", i): 용천수 포인트 위치 가져오기
  • XZ 평면 거리만 계산: 높이 차이 무시

 


주요 문제와 해결

문제 1: s@name 작동 안 함

증상: if (s@name == "spring_dist") 조건이 실행 안 됨

원인: HeightField의 volume primitive는 name 속성이 비어있음

해결: @primnum으로 직접 지정

  • primitive 0 = height
  • primitive 1 = mask
  • primitive 2 = spring_dist

 


HeightField Output 설정

Output Type: Deep Raster
Type: 32b Floating Point
Filename: $HIP/spring_distance.exr
Layers: spring_dist

내보내기:

  1. HeightField Output 노드 선택
  2. "Save to Disk" 버튼 클릭
  3. $HIP/spring_distance.exr 파일 생성됨

Unity 사용법

1. 텍스처 Import

  • spring_distance.exr를 Unity 프로젝트로 드래그

2. VFX Graph에서 사용

Sample Texture2D
├─ Texture: spring_distance
├─ UV: Particle Position (XZ를 0-1로 정규화)
└─ Output: distance (float)

Force
└─ Direction: 용천수 방향으로 계산
└─ Strength: 1.0 - distance (가까울수록 강함)

3. 값 해석

  • 0 (검은색): 용천수 바로 근처 → 최대 끌림
  • 1 (흰색): 용천수에서 가장 멀리 → 최소 끌림

결과물

  • 파일: spring_distance.exr (32-bit floating point, single channel)
  • 해상도: 540 x 900 (HeightField 해상도와 동일)
  • 값 범위: 0.0 ~ 1.0 (정규화됨)
  • 의미: 각 복셀에서 가장 가까운 용천수까지의 2D 거리

참고사항

  • 2D 수평 거리만 계산 (Y축 높이 무시)
  • 용천수 포인트 개수: 1025개
  • 거리 원본 범위: 578 ~ 1796 (미터 단위)

원인

Volume Wrangle에서 npoints(), nprimitives() 같은 geometry 쿼리 함수가 두 번째 입력에 대해 제대로 작동하지 않음.

해결

직접 loop으로 point 개수 확인하거나, 알고 있는 개수 하드코딩:

 
 
c
// ✅ 해결책 1: 알고 있는 개수 사용
for(int i = 0; i < 1025; i++) {
    vector sp = point(1, "P", i);
    // ...
}

// ✅ 해결책 2: loop으로 카운트
int count = 0;
for(int i = 0; i < 10000; i++) {
    vector testP = point(1, "P", i);
    if(testP.x == 0 && testP.y == 0 && testP.z == 0) break;
    count = i + 1;
}
```

---

## 최종 작동 코드

### 노드 구조
```
heightfield_file1 (540×900×1 HeightField)
  ↓
heightfield_copylayer1
  - Source: height
  - Destination: spring_dist
  - Copy Source Data: ✓
  ↓
volumewrangle1
  - Input 0: heightfield_copylayer1
  - Input 1: pointwrangle1 (용천수 1025개)
  ↓
heightfield_visualize1 (확인용)
  ↓
heightfield_output1 (spring_distance.exr)

Volume Wrangle 코드

 
 
c
if (@primnum == 2) {
    float total = 0.0;
    float radius = 20.0;  // 영향 반경 (m)
    
    // npoints(1) 대신 직접 1025 사용
    for(int i = 0; i < 1025; i++) {
        vector sp = point(1, "P", i);
        
        // XZ 평면 거리만 계산 (Y 무시)
        float dx = @P.x - sp.x;
        float dz = @P.z - sp.z;
        float dist = sqrt(dx*dx + dz*dz);
        
        if (dist < radius) {
            float influence = 1.0 - (dist / radius);
            total += influence;
        }
    }
    
    f@spring_dist = clamp(total, 0.0, 1.0);
}

디버깅 과정에서 발견한 것들

1. HeightField에서 @P는 이미 월드 좌표

 
 
c
// ❌ 불필요
vector worldPos = volumeindextopos(0, @primnum, @P);

// ✅ 직접 사용
float dx = @P.x - sp.x;

2. @primnum으로 레이어 구분

  • @primnum == 0: height
  • @primnum == 1: mask
  • @primnum == 2: spring_dist

3. Y축 차이 문제

  • 복셀 Y: -1078 (volumeindextopos 사용 시)
  • 용천수 Y: 0.03
  • 해결: XZ 평면 거리만 계산 (2D)

4. 해상도 확인 중요

  • 정상: 540 × 900 × 1
  • 비정상: 540 × 1 (망가진 상태)
  • i@resx, i@resz로 확인

주요 실수들

❌ 틀린 접근

 
 
c
// pcfind는 Y값도 고려해서 실패
int nearSprings[] = pcfind(1, "P", worldPos, 100.0, 10);

// npoints()가 작동 안 함
int npts = npoints(1);

// volumeindextopos()가 이상한 Y 반환
vector worldPos = volumeindextopos(0, @primnum, @P);

✅ 올바른 접근

 
 
c
// 직접 loop
for(int i = 0; i < 1025; i++)

// @P 직접 사용
float dx = @P.x - sp.x;

// XZ만 계산
float dist = sqrt(dx*dx + dz*dz);

결과

  • Min/Max: 0.0 ~ 1.0
  • 빨강: 용천수 20m 근처 (영향 강함)
  • 흰색: 용천수 없는 지역 (한라산 중심부)
  • 바다: mask로 제외됨

교훈

  1. Volume Wrangle의 geometry 함수는 제한적 - 특히 두 번째 입력
  2. 디버깅 = 작은 단계로 - setdetailattrib으로 중간값 확인
  3. HeightField @P는 월드 좌표 - 변환 불필요
  4. 2D 거리로 충분 - Y축 무시
  5. 함수 안 되면 직접 구현 - npoints() 대신 loop
728x90
반응형
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/12   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함
반응형