[miniRT] #11 원기둥 구현 [추가]

sham·2022년 5월 20일
0

[miniRT]

목록 보기
11/19

광선을 쏘았을 때의 궤적인 1차 방정식과 구체인 원을 나타낼 수 있는 2차식이 존재한다. 근의 공식을 이용해 이 둘을 조합해 근의 유무에 따른 충돌을 구해낼 수 있다.

jseo님이 작성하신 게시물을 참조하여 개발하였다.

원기둥이란 기본적으로 원과 평면을 합친 것과 같다. 원기둥의 원통은 벡터 방향을 바라보고 원을 차곡차곡 쌓은 것과 같고, 위아래 뚜껑은 범위가 정해져있는 평면을 구하는 식이다.

원통 구현

판별식 공식

  • u : 광선의 방향 벡터
  • o : 원기둥의 방향 벡터
  • delta_P : 광선의 원점 - 원기둥의 중심점
  • A : u와 o를 외적한 벡터의 제곱
  • B : u와 o의 외적한 벡터와 delta_P와 o의 외적한 벡터를 내적
  • C : delta_P와 o의 외적한 벡터의 제곱 - r(원의 반지름)의 제곱
t_bool      hit_cylinder(t_object *cy_obj, t_ray *ray, t_hit_record *rec)
{
    t_cylinder *cy;

    double  a;
		double  half_b;
    double  c;
    t_vec3  u;
    t_vec3  o;
    t_vec3  delta_P;
    double r;

    double discriminant; // 판별식
    double sqrtd;
    double root;
    double hit_height;
    
    cy = cy_obj->element;
    u = ray->dir;
    o = cy->dir;
    r = cy->diameter / 2;
    delta_P = vminus(ray->origin, cy->center);
    a = vlength2(vcross(u, o));
    half_b = vdot(vcross(u, o), vcross(delta_P, o));
    c = vlength2(vcross(delta_P, o)) - pow(r, 2);
    discriminant = half_b * half_b - a * c;
    if (discriminant < 0) 
        return (FALSE);
    // 이 시점에서 판별식이 참이 나왔기에 근이 존재한다고 판단한다.
    sqrtd = sqrt(discriminant); 
    root = (-half_b - sqrtd) / a;  // 근의 공식 해, 작은 근부터 고려.
    if (root < rec->tmin || rec->tmax < root)
    {
    root = (-half_b + sqrtd) / a; 
        if (root < rec->tmin || rec->tmax < root)
        return (FALSE);
    }
    if (!(hit_height = cy_boundary(cy, ray_at(ray, root))))
        return (FALSE);

    rec->t = root; // 광선의 원점과 교점까지의 거리를 rec에 저장한다.
    rec->p = ray_at(ray, root); // 교점의 좌표를 rec에 저장한다.
    rec->normal = get_cylinder_normal(cy, rec->p, hit_height); // vmult(ray->dir, root)하면 안돼!!!
    set_face_normal(ray, rec);
    rec->albedo = cy_obj->albedo;
    return (TRUE);
}
At2+Bt+C=0At^2 + Bt + C = 0
A=DDA = D \cdot D
B=2D(OC)B = 2D \cdot (O - C)
C=(OC)(OC)r2C = (O - C) \cdot (O - C) - r^2

이것을 우리가 잘 아는 근의 공식으로 바꾸어 주면 다음과 같다.

b±b24ac2a{-b \pm \sqrt{b^2 - 4ac} } \over 2a

b를 간략화시킨다면 다음과 같이 변형할 수 있다. (b = b/2)

b±b2aca{-b \pm \sqrt{b^2 - ac} } \over a

길이 구하기

판별식을 지나왔기 때문에 근이 존재(충돌했다)한다고 판정

판별식(b^2 - ac)의 제곱근이 +-를 하는 값이 되므로 광선과 충돌 지점까지의 거리는 (-half_b +- sqrtd) / a;가 된다. 작은 근이 가장 먼저 부딪힌 근이 될 것이므로 작은 근부터 체크해서 광선에 부딪힌 광선 중 가장 가까운 지를 체크한다.

원기둥 위아래

평면의 공식과 같은 공식을 사용한다. 범위가 한정된 평면이라고 생각하면 이해하기가 쉽다.

판별식 공식

  • C : 위아래 뚜껑인 원의 중심점
  • N : 원기둥의 방향 벡터(법선 벡터)
  • O : 광선의 출발점
  • P : 광선이 뻗어나가는 벡터
  • P0 : O에서 C로 향하는 벡터, C에서 O를 빼면 나온다.

Screen Shot 2022-03-16 at 4.40.48 PM.png

t=((CO)N)/DNt = ((C - O ) \cdot N) / D \cdot N
  • C : 위아래 뚜껑인 원의 중심점
  • F : 광선의 원점에서부터 C까지의 벡터
  • N : 원기둥의 방향 벡터(법선 벡터)
  • O : 광선의 출발점
  • D : 광선의 방향 벡터
  • t : 광선이 발사되어 충돌한 지점까지의 거리
int      hit_cylinder_cap(t_object *cy_obj, t_ray *ray, t_hit_record *rec, double height)
{
    const t_cylinder *cy = cy_obj->element;
    const double r = cy->diameter / 2;
    const t_vec3    circle_center = vplus(cy->center, vmult(cy->dir, height));
    const float root = vdot(vminus(circle_center, ray->origin), cy->dir) \
    / vdot(ray->dir, cy->dir);
    const float diameter = vlength(vminus(circle_center, ray_at(ray, root)));
	if (fabs(r) < fabs(diameter))
		return (0);
    if (root < rec->tmin || rec->tmax < root)
       return (0);
    rec->t = root; 
    rec->p = ray_at(ray, root);
    rec->normal = vunit(vminus(circle_center, ray->origin)); // vmult(ray->dir, root)하면 안돼!!!
    set_face_normal(ray, rec);
    rec->albedo = cy_obj->albedo;
    return (1);
}

광선의 원점에서부터 C까지의 벡터가 F라고 했을 때, F와 원기둥의 방향벡터인 o와의 내적으로 t를 구할 수 있다.

최종 코드

이를 토대로 작성한 최종 원기둥 구현 코드는 다음과 같다.


#include "structures.h"
#include "utils.h"
#include "trace.h"

int	cy_boundary(t_cylinder *cy, t_vec3 at_point, t_cylinops *c)
{
	double	hit_height;
	double	max_height;

	hit_height = vdot(vminus(at_point, cy->center), cy->dir);
	max_height = cy->height / 2;
	c->hit_height = hit_height;
	if (fabs(c->hit_height) > max_height)
		return (0);
	return (1);
}

t_vec3      get_cylinder_normal(t_cylinder *cy, t_vec3 at_point, double hit_height)
{
    t_point3 hit_center;
    t_vec3 normal;

    hit_center = vplus(cy->center, vmult(cy->dir, hit_height));
    normal = vminus(at_point, hit_center);

    return (vunit(normal));
}

int      hit_cylinder_cap(t_object *cy_obj, t_ray *ray, t_hit_record *rec, double height)
{
    const t_cylinder *cy = cy_obj->element;
    const double r = cy->diameter / 2;
    const t_vec3    circle_center = vplus(cy->center, vmult(cy->dir, height));
    const float root = vdot(vminus(circle_center, ray->origin), cy->dir) 
    const float diameter = vlength(vminus(circle_center, ray_at(ray, root)));
	if (fabs(r) < fabs(diameter))
		return (0);
    if (root < rec->tmin || rec->tmax < root)
       return (0);
    rec->t = root; 
    rec->p = ray_at(ray, root);
    rec->tmax = rec->t;
    if (0 < height)
        rec->normal = cy->dir;
    else
        rec->normal = vmult(cy->dir, -1);

    // rec->normal = vunit(vminus(circle_center, ray->origin)); // vmult(ray->dir, root)하면 안돼!!!
    set_face_normal(ray, rec);
    rec->albedo = cy_obj->albedo;
    return (1);
}

int      hit_cylinder_side(t_object *cy_obj, t_ray *ray, t_hit_record *rec)
{
    t_cylinder *cy;

 
    //a, b, c는 각각 t에 관한 근의 공식 2차 방정식의 계수
    double  a;
		double  half_b;
    double  c;
    t_vec3  u;
    t_vec3  o;
    t_vec3  delta_P;
    double r;

    double discriminant; // 판별식
    double sqrtd;
    double root;
    double hit_height;
    
    cy = cy_obj->element;
    u = ray->dir;
    o = cy->dir;
    r = cy->diameter / 2;
    delta_P = vminus(ray->origin, cy->center);
    a = vlength2(vcross(u, o));
    half_b = vdot(vcross(u, o), vcross(delta_P, o));
    c = vlength2(vcross(delta_P, o)) - pow(r, 2);
    discriminant = half_b * half_b - a * c;
    if (discriminant < 0) 
        return (0);
    // 이 시점에서 판별식이 참이 나왔기에 근이 존재한다고 판단한다.
    sqrtd = sqrt(discriminant); 
    root = (-half_b - sqrtd) / a;  // 근의 공식 해, 작은 근부터 고려.
    if (root < rec->tmin || rec->tmax < root)
    {
    root = (-half_b + sqrtd) / a; 
        if (root < rec->tmin || rec->tmax < root)
        return (0);
    }
    //    print_vec(vmult(ray->dir, root));
    // print_vec(ray_at(ray, root));
    if (!(hit_height = cy_boundary(cy, ray_at(ray, root))))
        return (0);

    rec->t = root; // 광선의 원점과 교점까지의 거리를 rec에 저장한다.
    rec->p = ray_at(ray, root); // 교점의 좌표를 rec에 저장한다.
    rec->normal = get_cylinder_normal(cy, rec->p, hit_height);
	  set_face_normal(ray, rec); 
    rec->albedo = cy_obj->albedo;
    return (1);
}

t_bool      hit_cylinder(t_object *cy_obj, t_ray *ray, t_hit_record *rec)
{
    const t_cylinder *cy = cy_obj->element;
    int result;

    result = 0;
    result += hit_cylinder_cap(cy_obj, ray, rec, cy->height / 2);
    result += hit_cylinder_cap(cy_obj, ray, rec, -(cy->height / 2));
    result += hit_cylinder_side(cy_obj, ray, rec);
    return (result);
}

레퍼런스

https://bigpel66.oopy.io/library/42/inner-circle/5

profile
씨앗 개발자

0개의 댓글