본문 바로가기
Metal

Raytracing으로 구 그리기

by ARpple 2024. 3. 31.
⛔ Metal Shader를 사용한 방법이 아니라 CPU를 사용해서 픽셀마다 색상을 직접 지정해 그리내는 렌더링 방법,
     GPU를 사용해서 나타낸 것은 아니다.

필요 데이터 구조

  • Ray - 광선
    • 광선의 시작점
    • 광선의 방향
  • Hit - 충돌시 유의미한 데이터
    • 광선과 충돌 지점까지 거리 (d)
    • 충돌한 위치 (position)
    • 충돌한 위치의 법선 백터 (normalize)
  • Sphere - 구
    • 구의 중심 좌표
    • 구의 반지름
    • 구의 재질(색상)
  • Raytracer - 광 추적기
    • 스크린의 사이즈
    • 물체 (구 정보를 여기에 포함시킨다.)
    • ⇒ 화면상에 픽셀들에서 광선을 내보내서 충돌하는 물체의 빛, 재질의 정보를 얻어오는 역할이다.

필요 메서드

      • intersectRayCollision
        특정 Ray가 본인에게 충돌하는지 확인하는 메서드
    • RayTracer
      1. transformScreenToWorld ⇒ 2차원 스크린 좌표를 3차원 Metal 좌표계로 변환하는 메서드
      2. traceRay ⇒ 광선이 물체(구)와 충돌한 후 충돌 정보(Hit)를 가져오는 메서드
      3. render  위에서 정의한 메서드들을 이용해서 Metal에서 Rendering 할 수 있는 Vertex 배열을 만들고 반환하는 메서드
        1. 스크린 좌표 정보들을 기반으로 전체 순회하며 로직 실행한다.
        2. transformScreenToWorld 수행 (Metal 좌표계에서 스크린 좌표계에서 나오는 광선의 방향은 (0,0,1)로 통일함)
        3. traceRay 수행
        4. 각각의 좌표에 맞게 색상 변환 후, Metal에서 Rendering 할 수 있는 Vertex 배열 반환

💡 traceRay 구현 아이디어: 구의 방정식과 직선의 방정식을 이용해 접점에서 Hit의 거리(d)를 찾자

 

Line–sphere intersection - Wikipedia

From Wikipedia, the free encyclopedia The three possible line-sphere intersections: 1. No intersection. 2. Point intersection. 3. Two point intersection. In analytic geometry, a line and a sphere can intersect in three ways: No intersection at all Intersec

en.wikipedia.org

  • X는 Hit 위치⇒ C: 구의 중심, R: 구의 반지름⇒ O: 직선 시작점, D: 스칼라 직선 거리, U: 직선 방향(유닛 벡터)
  • 직선의 방정식: X = O + d dot u
  • 구의 방정식: (X - C)^2 = r^2

 

위에 Wikipedia 식 참고

d = -[u dot (o - c)] ± sqrt(Z)
Z = [u dot (o - c)]^2 - (|o-c|^2 - r^2)

  • Z가 0보다 작다: 직선이 구와 만나지 않는다. -> 무시 or 배경처리...
  • Z가 0이다: 직선이 구와 한 점에서 만난다.
  • Z가 0보다 크다: 직선이 구와 두 점에서 만난다. (관통한다.)
✅ Hit의 법선 정규 백터 = normalize(Hit Position - 구의 중심 Position)

💡 Renderer 코드

    func render() -> [Vertex]{
        var vertices:[Vertex] = Array(repeating: .init(position: .zero, color: .zero), count: height * width)
        for j in (0..<height){
            for i in (0..<width){
                let pixelPosWorld = self.transformScreenToWorld(posScreen: .init(x: Float(i), y: Float(j)))
                let rayDir = vector_float3(0, 0, 1.0)
                var ray = Ray(start: pixelPosWorld, dir: rayDir)
                let positionColor = vector_float4(traceRay(ray: &ray), 1.0)
                vertices[j * width + i] = Vertex(position: .init(x: pixelPosWorld.x, y: pixelPosWorld.y), color: positionColor)
            }
        }
        return vertices
    }

 

댓글