01. 하늘그리기

hekang·2021년 1월 19일
0

miniRT

목록 보기
3/3

하늘그리기

전 글에서 간단한 창을 띄워봤으니 이번엔 하늘색 그라데이션이 들어간 창을 띄워보는 코드를 작성해보았다.

많은 데이터들을 함수들간에 주고 받기 위해서 몇개의 구조체를 만들었으며 앞으로도 필요한 구조체는 추가할 예정이다.
vec구조체는 vector를 나타내는 용으로 쓰이나 때에따라 point의 (x, y, z)좌표를 나타내거나 color의 RGB([0 ~ 255],[0 ~ 255], [0 ~ 255])로 사용되기도 한다.
img_data 구조체를 만들어 color값이 들어갈 이미지 배열과 이미지의 너비, 높이를 넣어주었다.
우리가 바라보는 위치인 camera구조체에는 앞서 만든 img_data와 가로세로 비율인 aspect_ratio, 바라보는 카메라의 위치인 origin, 왼쪽 아래코너 lower_left_corner, horizontal, vertical로 구성하였으며 각 인자들에 대한 자세한 설명은 계산식과 함께 설명하겠다.


아이패드로 그려봤는데 아직 정갈하게 그리는 법을 모르겠다.
cam과 나타내어지는 image는 위 그림처럼 cam에서 ray r을 쐇을때 어떤 색상이 있는지를 계산하여 넣어주면 된다.
여기서 LLC(Lower Left Conrner)은 이미지상에서 실질적인 원점을 나타낸다. 따라서 viewport (u,v)와 image(x,y)는 다음의 식으로 계산된다.

u = (x / image_width) * viewport_horizotal
v = (y / image_height) * viewport_vertical

LLC를 구하는 방법은 image의 중심과 viewport의 중심을 맞추고 그 점을 (0, 0)이라고 봤을때 LLC의 좌표는 다음과 같다.

(-viewport_horizontal / 2, -viewport_vertical / 2, -focal_length)

나타낼 이미지는 위에서 아래로 그라데이션 처럼 보이는 하늘 이미지이고 이는 y축 기준으로 색이 변하는게 보인다. 그래서 ray의 y축 단위벡터를 구하고 그 값을 이용하여 이미지에 색상을 넣는다.

하늘 배경의 식은 다음 계산을 통해 구할 수 있다.

unit_dir = vec_unit(r->dir);
t = 0.5 * (unit_dir->y + 1.0);
tmp1 = vec_mul_const(vec_create(1.0, 1.0, 1.0), 1.0 - t);
tmp2 = vec_mul_const(vec_create(0.5, 0.7, 1.0), t);
return (vec_add(tmp1, tmp2));


추가 코드.

typedef struct      s_vec
{
    double          x;
    double          y;
    double          z;
}                   t_vec;

typedef struct      s_img_data
{
    int             **img;
    int             width;
    int             height;
}                   t_img_data;

typedef struct      s_camera
{
    t_img_data      *data;
    double          aspect_ratio;
    t_vec           *origin;
    t_vec           *lower_left_corner;
    t_vec           *horizontal;
    t_vec           *vertical;
}                   t_camera;

먼저 이미지 데이터 구조체를 채우기 위한 함수 create_img_data

t_img_data      *create_img_data(int width, int height)
{
    t_img_data  *result;
    int         h;
    int         w;
    
    result = (t_img_data *)malloc(sizeof(t_img_data)); //메모리할당
    result->height = height;
    result->width = width;
    result->img = (int **)malloc(sizeof(int *) * width);
    w = -1;
    while(++w < width)
    {
        result->img[w] = (int *)malloc(sizeof(int) * height);
        h = -1;
        while (++h < height)
            result->img[w][h] = 0; // 이미지의 모든 픽셀에 대한 값 초기화
    }
    return (result);
}

카메라 구조체를 채우기 위한 함수 create_cam

t_camera        *create_cam(double aspect_ratio)
{
    t_camera    *result;
    double      viewport_height;
    double      viewport_width;
    double      focal_length;

    result = (t_camera *)malloc(sizeof(t_camera));
    focal_length = 1.0;
    viewport_height = 2.0;
    viewport_width = viewport_height * aspect_ratio;
    result->origin = vec_create(0, 0, 0);
    result->horizontal = vec_create(viewport_width, 0, 0);
    result->vertical = vec_create(0, viewport_height, 0);
    result->lower_left_corner = vec_sub(result->origin, vec_create(
    viewport_width / 2, viewport_height / 2, focal_length));
    return (result);
}

하늘처럼 보이는 이미지를 만드는 함수 draw_sky

void        draw_sky(t_img_data *data, t_camera *cam)
{
    int     x;
    int     y;
    t_ray   *r;

    y = data->height;
    while (--y >= 0)
    {
        x = -1;
        while (++x < data->width)
        {
            double u;
            double v;

            u = (double)(x) / (data->width - 1);
            v = (double)(y) / (data->height - 1);
            r = create_ray(cam->origin
            ,   vec_add(vec_add(cam->lower_left_corner, vec_mul_const(cam->horizontal, u)), vec_sub(vec_mul_const(cam->vertical, v), cam->origin)));
            data->img[x][y] = get_color(ray_color(r));
        }
    }
}

여기서 사용되는 ray_color 와 get_color 그리고 일정한 범위내의 값으로 만들어주는 clamp 함수를 만들었다.


t_vec           *ray_color(t_ray *r)
{
    double      t;
    t_vec       *unit_dir;
    t_vec       *tmp1;
    t_vec       *tmp2;

    unit_dir = vec_unit(r->dir);
    t = 0.5 * (unit_dir->y + 1.0);
    tmp1 = vec_mul_const(vec_create(1.0, 1.0, 1.0), 1.0 - t);
    tmp2 = vec_mul_const(vec_create(0.5, 0.7, 1.0), t);
    return (vec_add(tmp1, tmp2));    
}
int             get_color(t_vec *color)
{
    int         x;
    int         y;
    int         z;
    // xyz 가 1보다 클 경우 앞의 색상에 영향을 줌.
    x = clamp(color->x, 0, 0.9999) * 256;
    y = clamp(color->y, 0, 0.9999) * 256;
    z = clamp(color->z, 0, 0.9999) * 256;
    return(x << 16 | y << 8 | z);
}

double	clamp(double x, double min, double max)
{
	if (x < min)
		return (min);
	if (x > max)
		return (max);
	return (x);
}

앞서 창을 만들었던 코드에 이미지를 넣는 내용을 추가.

void            mlx_show(t_img_data *data, char *title)
{
    t_vars      vars;
    t_mlx_data  *img;

    vars.mlx = mlx_init();
    vars.win = mlx_new_window(vars.mlx, data->width, data->height, title);

    img = (t_mlx_data *)malloc(sizeof(t_mlx_data));
    img->img = mlx_new_image(vars.mlx, data->width, data->height);
    img->addr = mlx_get_data_addr(img->img, &(img->bits_per_pixel), \
                    &(img->line_length), &(img->endian));
/*
**  mlx_get_data_addr()은 생성된 이미지에 대한 정보를 반환하며, 나중에 사용자가 수정할 수 있습니다. 
**  img_ptr 파라미터는 사용할 이미지를 지정합니다. 다음 3개의 파리미터는 3개의 서로 다른 유효한 정수의 주소입니다. 
**  bits_per_pixel은 픽셀의 색상을 표현하는데 필요한 비트의 수로 채워집니다(색 깊이(depth of the image)라고도 불립니다). 
**  size_line은 메모리 안에서 이미지 한 줄을 저장하는데 필요한 바이트의 수입니다. 이 정보는 이미지의 한 줄에서 다른 줄로
**  이동하는데 사용합니다. endian은 이미지의 픽셀 색상을 little (endian == 0) 또는 big (endian == 1)에 저장해야하는지 여부를 의미합니다.
 */
    mlx_draw_by_img_data(img, data);
    mlx_put_image_to_window(vars.mlx, vars.win, img->img, 0, 0);
    mlx_destroy_image(vars.mlx, img->img);
    //color_map(vars.mlx, vars.win, 300, 300);
	mlx_hook(vars.win, X_KEY_PRESS, 0, mlx_key_handle, 0);
    mlx_mouse_hook(vars.win, mouse_button_handle, 0);
    mlx_loop(vars.mlx);
    free(img);
}

이 함수들을 만들고 main문을 이용해 불러오면

    image_width = 400;
    aspect_ratio = 16.0 / 9.0;
    cam = create_cam(aspect_ratio);
    data = create_img_data(image_width, (int)(image_width / aspect_ratio));
    draw_sky(data, cam);
    mlx_show(data, "miniRT");

하늘 이미지를 출력 할 수 있다.

profile
hekang in 42Seoul.

0개의 댓글