OpenGL를 기반으로 UI 렌더러를 만들 때 고민 중 하나가 효율적으로 도형을 그리는 방법이 뭘까...였다.

 

1. OpenGL 기본 배경

 일단 기본 배경을 설명하자면, OpenGL에는 도형을 그리는 여러 방법이 있다.

 

 첫 번째는 OpenGL 1.0 Version에서 사용하던 'glBegin()-glVertex()-glEnd()' 방식을 사용한 방법인데, 이 방법의 경우에는 그리고자 하는 도형(Premitive)을 glBegin()을 통해 정의하고, 해당 도형의 모든 정점을 glVertex()을 호출하여 정의 하면 된다. 모든 정점을 다 정의한 후에는 glEnd()을 호출하여 정점 정의가 끝났음을 알리면 된다. 이 방법의 장점은 작성 난이도는 낮다는 것이고, 단점은 쓸대없이 많은 양의 CPU Call로 인한 성능 저하이다. (ex. 원의 경우 최소 360개의 정점에 대해 glVertex()로 정의해야한다.)

 

//사각형 그리기
glBegin(GL_QUADS);

glVertex2d(0, 0);
glVertex2d(width, 0);
glVertex2d(width, height);
glVertex2d(0, height);

glEnd();

 

(물론 이 외에도 Fixed Function만 이용 가능하기 때문에 사용자가 원하는 특수 처리를 못한다는 점이 있으나, 현재 포스팅하는 내용은 단순히 도형을 그리는 것에 초점이 맞춰져 있기 때문에 자세한 설명은 제외.)

 

 두 번째는 OpenGL 2.0 Version부터 등장한 Shader를 사용하는 방법이다. 이 방법은 간단하게 말하자면, 개발자가 GLSL 이라는 쉐이더 언어를 통해 화면 상의 각 픽셀의 색상값을 지정하는 것이다. (사실 이 부분은 Fragment Shader에 대한 설명이다) OpenGL은 각각의 픽셀에 대해 Shader Program을 실행시켜 컬러값을 도출한다. 이때 수 많은 Pixel에 대해 하나하나 CPU를 통해 호출하여 처리하면 느리겠지만, OpenGL은 GPU 위에서 병렬연산을 통해 빠르게 처리한다. 이 방법의 장점은 적은 양의 CPU Call이 필요하다는 점이며, 단점은 입문자가 접근하기 어렵게 느낄 수 있다는 점이다.

 

// 원 렌더링 Shader 예시
#version 330 core

uniform vec4 color;
in vec2 pos; // pixel 좌표 (0~1 범위로 normalize한 좌표)

float drawCircle() {
    float radius = length(pos);
    return step(radius, 1.0); // radius 값이 1.0 이하면 해당 pixel의 alpha 값을 1로 설정하여 원을 그린다.
}

void main() {
    float alpha = drawCircle();
    gl_FragColor = vec4(color.xyz, color.w * alpha);
}

 

(또한 Shader를 사용하면 각 Pixel을 자유롭게 조작할 수 있기 때문에 Motion Blur, Anti-Aliasing, Lighting등과 같은 특수처리를 쉽게 할 수 있다.)

 

2. 문제 발단

 나는 두가지 방법 중 유연성과 성능이 좋은 Shader를 사용하는 방법을 선택했다. 여기까지는 다들 납득이 갈 것이다.

문제는 직접 GLSL을 통해 각 도형에 대한 Shader를 만들고 난 후에 생겼다.

 

 도형을 그리는 알고리즘 자체가 비효율적이었던 것이다. (너무 별 생각없이 짰다..) 간단히 Round Rectangle을 그리는 로직으로 예를 들자면, 첫 번째로 Pixel 위치가 모서리 범위(주황색 범위) 안에 있는지 검사하고, 만약 안에 있으면 둥근 모서리 안 쪽(노랑색 범위)에 위치해 있는지를 검사하는 식이었다. 결과적으로 하늘색 + 노란색 부분만 칠하면 원했던 RoundRectangle을 그릴 수 있었다.

 

RoundRectangle 렌더링 알고리즘

 아무래도 Shader를 작성해보지 않은 분들은 '이게 왜 비효율적이야?'라고 생각할 수도 있다. 하지만 Shader의 경우 GPU 상에서 각 Pixel에 대해 병렬로 실행되는 만큼, 적은 분기(if 문)와 코드 양이 중요하다. 문제는 내 알고리즘은 각 도형을 그릴 때 무조건 한 두개 이상의 분기가 존재했고, 실제 구현 시 코드 길이도 길면 15줄까지 되었다.. (수학을 못해서...) 물론 이 자체로도 첫 번째 방법 보다야 성능이 좋았지만 나는 만족하지 못했다. 

 

3. SDF(Signed Distance Field)를 통한 문제 해결

 로직을 개선하고자 방법을 찾아보던 중 SDF(Signed Distance Field)라는 것을 발견했다. SDF는 테두리로 부터의 거리를 연산하는 알고리즘인데, 이때 앞에 부호화가 붙은 걸 보면 알 수 있듯이 테두리 안쪽에서의 거리는 마이너스로, 바깥에서의 거리는 플러스로 연산한다.

 

 따라서 도형의 SDF만 연산할 수 있다면, 도형을 채울 때는 SDF 값이 0 이하인 Pixel은 색을 칠하면 되고, 도형의 테두리를 그릴 때는 SDF값이 0인 Pixel만 칠하면 된다. (테두리 두께도 SDF를 통해 조작할 수 있다.)

 

// 사각형 SDF
float rectangleSDF( in vec2 pos, in vec2 size ) {
    vec2 distance = abs(pos) - size ;
    return length(max(distance, 0.0)) + min(max(distance.x,distance.y), 0.0);
}

// 둥근 모서리 사각형 SDF
float rectangleSDF( in vec2 pos, in vec2 size, float radius) {
    vec2 distance = abs(pos) - size + radius;
    return length(max(distance, 0.0)) + min(max(distance.x,distance.y), 0.0) - radius;
}

// SDF 사용 예제
void main() {
    float alpha = 1.0;
    float sdf = shapeSDF(); // 그리고자 하는 도형의 sdf 계산 (인자는 생략함)

    if( fill == 1.0 ) {
        // sdf가 0.0 보다 작으면 alpha 값을 1로 설정 (채우기)
    } else {
        // sdf가 0.0이면 alpha 값을 1로 설정 (테두리 그리기)
    }   
    gl_FragColor = vec4(color.xyz, color.w * alpha);
}

 

4. 마무리

 실제로 SDF를 적용했더니 코드양도 대폭 줄고 성능도 개선이 되어서 만족했다. 일단 지금 내 수준에서는 여기까지가 최선이라고 생각된다. 이후에 만약 더 좋은 방법을 찾는다면, 추가로 포스팅할 생각이다.

 

오타나 잘못된 내용 있으면 코멘트 부탁드립니다.

 

참고 링크

https://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm

5. 시스템 해킹 (System hacking)



 안녕하세요. 벌써 한 주가 다 지나가 버리고 새로이 월요일이 왔네요. 끔찍합니다. 정말로요. 어찌되었든 오늘은 오랜만에 '시스템 해킹'을 주제로 해킹 관련 포스팅을 할까 합니다. 





 '시스템 해킹'의 정의에 대해 알아보도록 하겠습니다. 사실 시스템 해킹에 대한 정확한 정의는 나와있지 않습니다. 그렇기에 제가 생각하는 시스템 해킹의 정의를 말씀드리도록 하겠습니다. '시스템해킹'이란 '시스템의 취약점을 이용한 공격을 통해 루트 권한을 획득한 후 악성코드(malware)나 백도어(backdoor)를 설치 및 실행하도록 하는 것'이라고 생각합니다. 물론 무조건 공격자가 해당 시스템에 악성코드나 백도어를 설치해 놓는 것은 아닙니다. 공격자에 따라 루트 권한 획득 후 하는 행동이 다르겠죠. 그럼에도 저렇게 적어놓은 이유는 많은 사례들을 살펴보면 악성코드나 백도어를 설치해 놓기 때문입니다. 



 <시스템 해킹의 정의>

   -시스템의 취약점을 이용한 공격을 통해 루트 권한을 획득한 후 악성코드(malware)나 백도어(backdoor)를 설치 및 실행하도록 하는 것

   

  


 시스템 해킹 기법의 종류로는 여러가지가 있는데요, 대표적인 것들을 뽑자면 'stack buffer overflow', 'race condition', 'RTL', 'ROP', 'command injection', 'heap buffer overflow'등이 있습니다. (오늘 이 기법들 하나 하나를 다 다루어 볼려고 했으나, 한 포스팅에서 진행하기에는 너무 양이 많아서 앞으로 각각의 공격들을 하나씩 다룰 예정입니다.)




 그럼 오늘은 시스템 해킹의 정의로 포스팅을 마무리 짓겠습니다. 아무래도 시스템 해킹 공격 기법의 종류에 대해 언급을 하지 않으니 포스팅이 초라하네요. 그래서 이번주에는 시스템 해킹 기법 중 2가지를 추가로 더 포스팅하도록 하겠습니다. 읽어주셔서 감사합니다. (다른 의견이나, 틀린 부분 있으면 comment 남겨주세요) 

 

'정보보안' 카테고리의 다른 글

4. Worm  (2) 2018.01.11
3.메모리 보호기법  (2) 2018.01.05
Anti Reversing  (0) 2017.12.27
Ransomware  (0) 2017.12.18

2. 함수(function)



 안녕하세요. 어제는 '집합'에 대해 포스팅했었는데요, 오늘은 그에 이어 '함수'에 대해 포스팅을 하도록 하겠습니다. (정말 포스팅하는 게 보통 일이 아닙니다. ㅠㅠ)



 우선 '함수'의 정의에 대해 알아보겠습니다. 함수란 '변수 x,y에 대해 x 값이 변함에 따라 y 값도 x 값에 하나의 값으로 대응하는 것'입니다. 좀 더 깊게 들어가면 '변수 x,y의 값들을 모아 각각 x,y 집합이라 놓고 집합 x의 각 원소에 집합 y의 원소가 오직 하나씩만 대응하는 것'이라고 할 수 있습니다. 한마디로 함수에서 중요한 키워드는 '대응'이라고 할 수 있습니다. 하지만 여기서 헷갈리지 말하야하실 것은, 함수는 1대 1 대응이 아닙니다. x 집합에 있는 1개 이상의 원소가 y 집합의 한 가지 원소에만 대응해도 함수라고 합니다. 한마디로 x 집합의 한 원자가 2개의 y 집합의 원소와 대응되는 것은 안되지만 y집합의 원소들은 여러개의 x 집합의 원소와 대응되도 상관이 없다는 말이죠.

  이제 함수의 정의를 알아봤으니, 함수를 표현하는 방법에 대해 알아보겠습니다. 함수를 표현할 때는 두가지 방법을 이용하는데요, 보통 'f(x)=y'이런식으로 쓰고, 종종 f:x->y'이런식으로 사용하죠.





<함수의 정의>

  -변수 x,y에 대해 x 값이 변함에 따라 y 값도 x 값에 하나의 값으로 대응하는 것

  -변수 x,y의 값들을 모아 각각 x,y 집합이라 놓고 집합 x의 각 원소에 집합 y의 원소가 오직 하나씩만 대응하는 것

<함수 표현 방법>
  -f(x)=y

  -f:x->y



 자 다음으로는 함수와 관련된 용어들을 풀어보겠습니다. 우선 위에 그림에서 볼 수 있는 'X집합'은 정의역(domain)이라고 하고, 'y집합'을 공역(codomain)이라고 합니다. 그리고 함수 f의 x에 대한 함숫값인 y1,y2,y3는 치역(range)라고 하죠. 자 그럼 살짝 공역과 치역의 정의가 헷갈리실 겁니다. 똑같이 y집합을 지칭하고 있으니까요. 공역과 치역의 관계는 치역이 공역의 부분집합이라고 보시면 됩니다. 치역은 공역과 달리 x에 대응하는 y집합의 원소만을 포함하기 때문에 y집합 내의 모든 원소가 포함되지 않을 수 있기 때문이죠.(정의역과 공역에 대한 정의가 없다면 정의역과 공역은 실수 전체의 집합을 의미합니다.)


<함수 용어 정리>

  함수 f:x->y일 때

   -정의역(domain) : x 집합

   -공역(codomain) : y 집합

   -치역(range) : f(x) 집합





 오늘은 함수의 정의와 함수 용어 정리로 포스팅을 마치겠습니다. 물론 함수의 종류(일대일 함수, 일대일 대응 함수...)에 대해서 더 쓸 수 있지만 그 쪽은 딱히 다룰 필요를 못 느꼈기 때문에 여기서 마치겠습니다. 틀린 내용이 있다면 comment 남겨주세요!



'수학' 카테고리의 다른 글

1. 집합(Set)  (3) 2018.01.23

+ Recent posts