첫 글을 Fract-ol로 시작하는 건 조금 늦은 감이 있는 듯하다.

하지만 이제라도 일기를 쓰고자 블로그를 개설한다!!

전적으로 내가 이해한 내용만으로 만 채울 것!!!!!

 

시작!


 

목    표

 

 

  1. Mandelbrot set, Julia set의 이해
  2. 간단한 예제들을 통해 mlx의 사용법 배우기
  3. mlx를 이용하여 Mandelbrot set, Julia set 만들어보기
  4. 색 입혀보기
  5. 확대 / 축소 구현하기
  6. Julia set 심화
  7. 또 다른 한 개의 Fractal 만들기

 

 

 

 

 

 


1. Mandelbrot set, Julia set의 이해

먼저, Mandelbrot은 빵 이름인지 julia는 무슨 사람 이름인지 모를 테니 WIKI의 힘을 빌리자!

https://ko.wikipedia.org/wiki/%EB%A7%9D%EB%8D%B8%EB%B8%8C%EB%A1%9C_%EC%A7%91%ED%95%A9

 

망델브로 집합 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 망델브로 집합(영어: Mandelbrot set)은 브누아 망델브로가 고안한 프랙탈의 일종이다. 망델브로 집합은 다음 점화식으로 정의된 수열이 발산하지 않는 성질을 갖

ko.wikipedia.org

https://ko.wikipedia.org/wiki/%EC%A5%98%EB%A6%AC%EC%95%84_%EC%A7%91%ED%95%A9

 

쥘리아 집합 - 위키백과, 우리 모두의 백과사전

 

ko.wikipedia.org

간단히 요약하자면 Mandelbrot set과 Julia set은 다음과 같은 점화식을 사용한다.

 

$f(z_{n}) = z_{n}^{2} + c$

 

여기서 우리는 $z_n$이 무엇인지 $c$가 무엇인지 확인하고 진행하여야 한다.

 

Mandelbrot set은 $z_{n}$이 발산하지 않게 하는 $c$의 집합이다.

Julia set은 $z_{n}$이 발산하지 않게 하는 $c$의 집합이다.

이게 뭔 소리야 하겠지만 두 집합의 초기값이 다르고 두 점화식에서의 고정된 값이 다르기에 다른 그래프가 나오는 것이다.

 

Mandelbrot set에서 $z_{0} = 0 + 0i ,  c = 좌표$이다.

Julia set에서는 $z_{0} = 좌표 ,    c = 고정된 복소수 값$이다.

 

★즐거운 수학 시간★

 

점화식은 아래와 같이 사용한다.

 

$f(z_{n}) = z_{n+1} = z_{n}^{2} + c$

 

여기서 복소수 $z_{n} = a + bi$ 라고 하고 복소수 $c = c_{Re} + c_{Im}i$ 라고 하자 ㄱㄱ

 

$f(z_{n}) = z_{n+1} = a^{2} - b^{2} + c_{Re} + (2ab + c_{Im})i$

 

즉, $z_{n+1}$의 Real은 $a^{2} - b^{2} + c_{Re}$    $z_{n+1}$의 Imaginary는 $2ab + c_{Im}$ 라고 된다.

 

여기서 발산하는 조건을 코딩으로 사용하기에는 너무 어렵다.

 

여기서 탈출 조건을 아래처럼 잡자!

  1. $|a^{2} - b^{2} + c_{Re}| > 4$
  2. iteration을 돌리되 iteration_max보다 커졌을 때 탈출한다.

두 탈출 조건 중 하나라도 만족하지 못한다면 탈출해야 한다.

하지만 두 탈출 조건에서의 이후 표현이 다르다.

 

 

조건 1을 만족하지 못했을 때는 Mandelbrot set과 Julia set에 속하지 못한다.

조건 2를 만족하지 못했을 때는 Mandelbrot set과 Julia set에 속한다. 

 

위 사항들을 조금만 더 세밀하게 본다면 조건 1을 만족하지 못했을 때 iteration의 값은 색 입히기 할 때 확실히 사용된다.

 

 

2. 간단한 예제들을 통해 mlx의 사용법 배우기

 

여러 가지 예제들을 가진 notion과 blog들이 존재한다.

따라 해야지만 mlx에 있는 함수들이 어떻게 사용되고 어떻게 진행되는지를 확인할 수 있다.

참고로 나는 이론 공부는 죽어라 싫어하는 입장이라 항상 예제들을 찾아다니는 입장이다.

 

이 예제들은 내가 정리한 것보단 블로그들의 예제들과 그 밑에 설명까지 있기에 가져다 써야겠다.

블로그 주인 분들에게 무한한 감사를!!!!!!!!

 

https://velog.io/@parksj3205/miniLibX%EB%A1%9C-%EC%9C%88%EB%8F%84%EC%9A%B0-%EC%83%9D%EC%84%B1%EA%B3%BC-%EA%B0%84%EB%8B%A8%ED%95%9C-%EB%8F%84%ED%98%95-%EA%B7%B8%EB%A6%AC%EA%B8%B0

 

[miniRT] 1. miniLibX로 윈도우 생성과 간단한 도형 그리기

miniRT/cub3d 프로젝트는 miniLibX 그래픽 라이브러리를 사용하여 구현합니다.그러므로 먼저 miniLibX로 윈도우를 생성하고 간단한 도형을 그려보겠습니다.1\. 그래픽 시스템 연결우선, 작성한 프로그램

velog.io

 

https://velog.io/@naranghae/miniRT-%EC%A7%84%ED%96%89-1-mlx-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-1

 

miniRT 진행(1) mlx 튜토리얼 -1

검은 화면이 만들어진다. mlx_new_window(mlx_ptr, 500, 500, "mlx_test"); 에서 500은 각각 x, y 해상도이고,다음 " "안에 들어가는 것은 만들어진 화면의 이름을 나타낸다.https://developer.mozilla.o

velog.io

 

mlx함수들의 설명을 한 번에 볼 수 있는 블로그

https://42kchoi.tistory.com/229

 

MiniLibX 파헤치기

harm-smits.github.io/42docs/ 의 번역본입니다. 리눅스 및 windows 환경이신 분들은 원문에서 세팅방법을 참고해주세요. 42 인트라넷에 이것과 관련된 영상이 있긴 합니다. 아래 링크에서 확인하세요. https

42kchoi.tistory.com

 

정말 정말 이해가 잘 되는 예제들이 있는 깃허브

https://github.com/taelee42/mlx_example

 

taelee42/mlx_example

Some examples for those who try to use MLX library for 42 subject CUB3D or miniRT. And some helpful links. - taelee42/mlx_example

github.com

 

 

3. mlx를 이용하여 Mandelbrot set, Julia set 만들어보기

 

#include	"mlx.h"
#include	<stdio.h>
#include	<stdlib.h>
#include	<math.h>

#define		X_EVENT_KEY_PRESS		2
#define		X_EVENT_KEY_EXIT		17
#define		X_EVENT_MOUSE_PRESS		4
#define		X_EVENT_MOUSE_MOTION	6
#define		WIN_WIDTH				800
#define		WIN_HEIGHT				600
#define		KEY_ESC					53
#define		ITER_MAX				100

typedef	struct	s_img{
	void		*img_ptr;
	char		*data;
	int			size_l;
	int			bpp;
	int			endian;
}				t_img;

typedef	struct	s_mlx{
	void		*mlx_ptr;
	void		*win;
	t_img		img;
}				t_mlx;

int		mandelbrot(int count_w, int count_h, int iter)
{
	double c_re;
	double c_im;
	double x;
	double x_new;
	double y;

	c_re = ((count_w - WIN_WIDTH / 2) * 3.0 / WIN_WIDTH) - 0.5;
	c_im = ((WIN_HEIGHT / 2) - count_h) * 2.0 / WIN_HEIGHT;
	x = 0;
	y = 0;
	while ((pow(x, 2.0) + pow(y, 2.0) < 4) && (iter < ITER_MAX))
	{
		x_new = pow(x, 2.0) - pow(y, 2.0) + c_re;
		y = 2 * x * y + c_im;
		x = x_new;
		iter++;
	}
	return (iter);
}

int		julia(int count_w, int count_h, int iter, t_img *img)
{
	double	c_re;
	double	c_im;
	double	x;
	double	x_new;
	double	y;

	c_re = -0.75;
	c_im = 0;
	x = ((count_w - WIN_WIDTH / 2) * 4.0 / WIN_WIDTH);
	y = ((WIN_HEIGHT / 2) - count_h) * 4.0 / WIN_HEIGHT;
	while ((pow(x, 2.0) + pow(y, 2.0) < 4) && (iter < ITER_MAX))
	{
		x_new = pow(x, 2.0) - pow(y, 2.0) + c_re;
		y = 2 * x * y + c_im;
		x = x_new;
		iter++;
	}
	return (iter);
}

int		window_init(t_mlx *mlx)
{
	if (!(mlx->mlx_ptr = mlx_init()))
		return (0);
	if (!(mlx->win = mlx_new_window(mlx->mlx_ptr, WIN_WIDTH, WIN_HEIGHT, "A simple example")))
		return (0);
	if (!(mlx->img.img_ptr = mlx_new_image(mlx->mlx_ptr, WIN_WIDTH, WIN_HEIGHT)))
		return (0);
	if (!(mlx->img.data = mlx_get_data_addr(mlx->img.img_ptr, &mlx->img.bpp, &mlx->img.size_l, &mlx->img.endian)))
		return (0);
	return (1);
}

void	my_mlx_pixel_put(t_img *img, int x, int y, int color)
{
	char	*dst;

	dst = img->data + (x * img->bpp / 8) + (y * img->size_l);
	*(unsigned int *)dst = color;
}

void	put_pixel(t_img *img)
{
	int		iter;
	int		count_w;
	int		count_h;

	count_h = -1;
	while (++count_h <= WIN_HEIGHT)
	{
		count_w = -1;
		while (++count_w <= WIN_WIDTH)
		{
			iter = mandelbrot(count_w, count_h, 0);
			//iter = julia(count_w, count_h, 0, img);
			if (iter < ITER_MAX)
				my_mlx_pixel_put(img, count_w, count_h, 0x00FFFFFF);
			else
				my_mlx_pixel_put(img, count_w, count_h, 0x00000000);
		}
	}
}

int		key_press(int keycode)
{
	if (keycode == KEY_ESC)
		exit(0);
	else
		return (0);
	return(0);
}

int		close(int keycode)
{
	exit(0);
}

int		main(void)
{
	t_mlx	mlx;

	if (!window_init(&mlx))
		return (0);
	put_pixel(&mlx.img);
	mlx_put_image_to_window(mlx.mlx_ptr, mlx.win, mlx.img.img_ptr, 0, 0);
	mlx_hook(mlx.win, X_EVENT_KEY_PRESS, 0, key_press, 0);
	mlx_hook(mlx.win, X_EVENT_KEY_EXIT, 0, close, 0);
	mlx_loop(mlx.mlx_ptr);
	return (0);	
}

 

위 코드를 간단히 리뷰하자!!!!

  1. window와 구조체를 초기화한다.
  2. put_pixel로 mandelbrot을 실행시켜 iteration에 따라 색을 달리한다.
  3. mlx_put_image_to_window에서 offset을 계산하고 색을 넣는다. (offset을 계산하는데 이는 rgb의 자료형을 봐야 한다.)
  4. mlx_loop를 통해 hook이 들어오는지 확인한다.
  5. hook이 들어왔을 땐 mlx_hook을 통해 함수를 실행한다.

4. 색 입혀보기

 

위에서 변화된 것과 추가된 것만 표기하자!!!

int		color_set(int iter)
{
	double	r;
	double	g;
	double	b;
	int		color;

	r = sin(0.3 * (double)iter);
	g = sin(0.3 * (double)iter + 3) * 127 + 128;
	b = sin(0.3 * (double)iter + 3) * 127 + 128;
	color = ((int)(255.999 * r) << 16) + ((int)(255.999 * g) << 8) + ((int)(255.999 * b));
	return (color);
}

void	put_pixel(t_img *img)
{
	int		iter;
    int		color;
	int		count_w;
	int		count_h;

	count_h = -1;
	while (++count_h <= WIN_HEIGHT)
	{
		count_w = -1;
		while (++count_w <= WIN_WIDTH)
		{
			iter = mandelbrot(count_w, count_h, 0);
			//iter = julia(count_w, count_h, 0, img);
			if (iter < ITER_MAX)
			{
         		   	color = color_set(iter);
        		   	my_mlx_pixel_put(img, count_w, count_h, 0x00FFFFFF);
			}
            else
				my_mlx_pixel_put(img, count_w, count_h, 0x00000000);
		}
	}
}

위의 코드는 사실 이해하기엔 아직 너무 어렵다.

간단히 알아보면 iteration의 값에 따라 color가 바뀌고 그에 따른 수식들을 정리해서 color라는 변수에 비트 연산을 통해 넣어준 것이다.

 

5. 확대 / 축소 구현하기

 

마우스의 좌표를 찾고 그에 따라 폭과 높이를 Mouse Scroll UP/DOWN에 따라 비율적으로 min_width, max_width, min_height, max_height을 늘이고 줄이는 작업을 한다.

 

확대/축소했을 때

$width = width_{max}- width_{min}$

$height = height_{min} - height_{min}$

$zoom = 확대/축소를 위한 비율$

 

여기서 min과 max를 비율적으로 줄이고 늘여야 한다.

$width_{max} ±= zoom * \frac {width - x_{좌표}} {width}$

$height_{max} ±= zoom * \frac {height - y_{좌표}} {height}$

$width_{min} ±= zoom * \frac {x_{좌표}} {width}$

$height_{min} ±= zoom * \frac {y_{좌표}} {height}$

 

이렇게 계산한 값은 Mandelbrot과 Julia을 계산할 때만 쓰이고 계산 후 결괏값으로 나온 iteration에 대한 pixel에 RGB를 넣을 때는 우리가 갖고 있는 800X600이 입력되어야 한다.

 

int		mouse_event(int button, int x, int y, t_mlx *mlx)
{
	if (button == SCROLL_UP)
	{
		mlx->zoom.max_width -= ZOOM * ((mlx->zoom.width - x) / mlx->zoom.width);
		mlx->zoom.min_width += ZOOM * (x / mlx->zoom.width);
		mlx->zoom.max_height -= ZOOM * ((mlx->zoom.height - y) / mlx->zoom.height);
		mlx->zoom.min_height += ZOOM * (y / mlx->zoom.width);
	}
	else if (button == SCROLL_DOWN)
	{
		mlx->zoom.max_width += ZOOM * ((mlx->zoom.width - x) / mlx->zoom.width);
		mlx->zoom.min_width -= ZOOM * (x / mlx->zoom.width);
		mlx->zoom.max_height += ZOOM * ((mlx->zoom.height - y) / mlx->zoom.height);
		mlx->zoom.min_height -= ZOOM * (y / mlx->zoom.height);
	}
	else
		return (0);
	mlx->zoom.width = mlx->zoom.max_width - mlx->zoom.min_width;
	mlx->zoom.height = mlx->zoom.max_height - mlx->zoom.min_height;
	return (0);
}

void	put_pixel(t_mlx *mlx)
{
	int		iter;
	int		color;
	double	count_w;
	double	count_h;
	int		x_idx;
	int		y_idx;

	y_idx = -1;
	count_h = mlx->zoom.min_height;
	while (++y_idx <= WIN_HEIGHT)
	{
		x_idx = -1;
		count_w = mlx->zoom.min_width;
		while (++x_idx <= WIN_WIDTH)
		{
			iter = mandelbrot(count_w, count_h, 0, mlx);
			if (iter < ITER_MAX)
			{
				color = color_set(iter);
				my_mlx_pixel_put(&mlx->img, x_idx, y_idx, color);
			}
			else
				my_mlx_pixel_put(&mlx->img, x_idx, y_idx, 0x00000000);
			count_w += mlx->zoom.width / WIN_WIDTH;
		}
		count_h += mlx->zoom.height / WIN_HEIGHT;
	}
}

하지만 여기선 시작하는 좌표의 위치만 달라질 뿐 확대/축소가 되질 않는다.

왜지.....

 

원인은 mandelbrot set을 계산하는 함수에서 c_re과 c_im에서 찾았다.

원래는 800X600의 window에서 마우스의 좌표를 가져오는데 mandelbrot set은 표현되는 범위가 Real : (-2, 1), Imaginary : (-1, 1)이므로 축소를 시켜줘야 한다.

즉, 이렇게 축소시켜주는 단계에서 화면에 나타날 이미지의 크기가 지정되어있었다.... 꺾꺾꺾

 

완전히 갈아엎었다.

이전에는 mandelbrot을 계산하는 곳에서 좌표를 계산했지만 지금 작성한 로직은 스크롤을 하면 x, y의 좌표가 움직이지 않고 똑같이 그대로 있어야 한다는 생각이 base로 잡혔고 그에 따라 계산하는데 zoom의 비율은 현재 상태에서 0.1씩 증가하고 감소하게 작성했으며 픽셀의 색을 정하는 첫 시작인 $c_{re}$ 와  $c_{im}$가 $width_{min} ,  height_{min}$ 이 되게 만들었다.

 

이제 여기서 생각해봐야 하는 점은 우리가 x, y 좌표를 그대로 유지시키면서 어떻게 $c_{re}$ 와  $c_{im}$를 변화시킬 것인지 생각해봐야 한다.

 

결국 도달한 결론은 아래와 같다. 글씨 못씀 주의

 

 

그리고 우리는 이런 결과를 볼 수 있게 된다.

 

아주 기괴하고 요상한 그림이 되어 버린다.

정말 뿌듯하다...

 

6. Julia set 심화

여기에서는  julia set에서의 고정값 c를 마우스의 좌표로 지정하여 형태를 변형시키는 방법을 진행할 것이다.

처음에 우리가 해야 할 것은 마우스의 모션을 감지하는 hook을 만들고 mouse_move라는 함수를 만들면서 마우스의 좌표(800x600)를 실제 좌표계의 좌표(4x4)로 변환하는 작업을 거쳐야 한다.

 

이 과정은 간단하게 생각해서 일차함수의 좌표변환과 같다.

우리가 받아오는 좌표의 원점은 왼쪽 위의 꼭짓점이다.

그래서 원점을 중앙으로 만들어준 뒤 800의 width를 4로, 600의 height를 4로 표현해주면 된다.

x의 좌표 = $\frac {4(x - \frac {width} {2})} {width}$

y의 좌표 = $\frac {4(\frac {height} {2} - y)} {height}$

그리고 나는 확대를 할 때는 고정이 되게 만들고 싶어서 key f를 누르면 fix 되도록 설정해놓았다.

결과는 아래와 같다.

 

 

7. 또 다른 한 개의 Fractal 만들기

또 다른 한 개의 fractal은 burning ship으로 선택했다.

사실 snow flake를 하고 싶었는데 점화식 찾기를 실패해서 burning ship을 선택을 했는데 엄청 간단해서 바로 만들었다...

burning ship은 mandelbrot과 다른 점은 딱 한 가지이다.

점화식

$z_{n+1} = (z_{n})^{2} + c$

$z_{n+1} = a^{2} - b^{2} + c_{re} + (|2 * a * b| + c_{im})$

즉, 절댓값 하나 추가한 것으로 놀랍게도 다른 그림이 생성이 된다.

 

색이 요상해서 burning ship으로 보이진 않는데 아직 smoothly와 gradient를 이해하기엔 아주 아쉬운 지능인 것 같다,,,,

이후엔 꼭 snow frake에 검정 배경에 빛이 나는 솔로 눈송이를 만들어 볼 예정이다.

'42일기' 카테고리의 다른 글

python Dict, set 활용  (0) 2021.10.15
Philosophers  (2) 2021.07.09
Push_swap  (0) 2021.06.24
Minitalk(2)  (0) 2021.06.17
Minitalk(1)  (4) 2021.06.13

+ Recent posts