본문 바로가기
C

3D Computer Graphics (3)

by oncerun 2023. 4. 8.
반응형

 

GPU Rendering Pipe Line을 공부하다 보면 Modeling 이후 좌표 변환을 수행하는 Vertex Shader 부분이 존재한다. 

 

Modeling 과정에서 만들어진 Polygon Mesh를 GPU가 입력을 받게 되는데, 이때 각 정점들은 메모리에 다양한 정보를 올린다. 

 

vertex array, noraml vertex array, texture coordinate 등등 정보를 가지는데, vertex Shader가 이를 한 번에 하나씩 로드하면서 여러 가지 연산을 수행한다. 

 

vertex shader는 프로그램이다. 그렇기 때문에 여러 가지 연산을 프로그래밍해야 하는데, 이때 다음과 같은 역할을 맡는다.

 

modeling 과정에서 생긴 object-space에서 world transform을 통해 world space로 변환을 수행한다.  view transform, 카메라의 좌표계로 다시 변환을 수행하고 projection transform을 통해 clip space까지 변환을 해야 하는 책임이 있다. 

 

이러한 프로그램을 작성하기 위해 OpenGL ES에서는 GLSL이라는 그래픽 언어를 지원하는데, 이는 마치 C 계열 언어와 비슷한 구조를 가진다. 

 

shadertoy 사이트의 예제 코드에서도 다양한 c계열 문법이 사용되는 것을 알 수 있는데, 내가 C언어의 공부가 필요하다는 것을 알았다. 

 

그래서 C언어에 대해 공부하려고 한다.  (사실 C언어 한 번 공부하고 싶었음.)

 

목표는 C언어의 문법을 이해하는 것이다. 

 

IDE가 하나  필요한데 무엇을 사용할까 고민중에 30일 무료 CLion을 사용해 보기로 했다. 

 

사용 방법은 공식 홈페이지를 참고하면서 공부했다.

 

https://www.jetbrains.com/help/clion/quick-cmake-tutorial.html#addtargets-reload

 

Quick CMake tutorial | CLion

 

www.jetbrains.com

 

 

파일 확장자부터 생소한데, 조금더 알아보자. 

 

C언어는 source code를 작성하여 source file을 생성하고 컴파일을 통해 목적파일(object file)로 변경한다. 

이후 linking을 통해 실행파일(execution file)을 만든다고 한다. 

 

 

소스파일?

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

 

아! 소스파일은 .c 라는 확장자를 가진 파일로 저장된다. 

 

그리고 컴파일러는 보통 C++와 C를 통합해서 컴파일할 수 있도록 만들어진다고 한다. 

 

그래서 C언어는 .c라는 확장자를 사용하여 저장해야 하고 C++는. cpp 파일로 저장하는 것이다. 

 

컴파일 이후 여러 목적파일(라이브러리), 목적파일을 링커를 통해 exe확장자를 갖는 실행파일을 만든다. 

 

C 프로그램은 특정 구조가 있다. 

 

도입부에는 프로그램 전체에 적용되는 사항을 기술하고  main() 함수는 가장 먼저 호출되는 함수로서 모든 프로그램에 반드시 존재해야 한다고 한다. 

(아 그래서 GLSL도 기본적으로 void main()이 있었구나 뜬금없이 main함수로 시작해서 뭔가했넵)

 

예약어의 종류를 조금 살펴보자. 

 

자료형 관련 예약어 : char, int, float, short, long , double, unsigned, union, enum, void,...

 

C언어는 MSB를 표현하는 자료형도 있다.  union은 메모리를 공유하게 해 준다는데 차차 알아가자

 

기억 관련 예약어도 있는데 다음과 같다. : auto, static, extern, register 

 

제어 관련은 다른 언어의 문법 예약어와 비슷하다 :  if-else, while, for, do-while, switch-case, break, return, continue.

 

기타 예약어로 분류되는 건 main, sizeof, include.. 

 

상수, 연산자, 주석은 타 프로그래밍 언어와 그리 다르지 않다. 

 

다음은 상수에 대해 알아보자. 

 

10진 상수는 0~9까지 숫자를 사용한다, 8진 상수는 0~7까지 사용하며 숫자 앞에 0을 붙인다.  이를 통해 10진 상수와 구별을 한다. 

 

16진 상수는 0~9, A~F까지 사용하며 0x, 0X를 붙인다. 

 

unsigned형 상수는 부호가 없는 상수이며, 숫자 뒤에 u나 U를 붙인다. 

 

long형 상수는 큰 길이의 정수를 표현하며 숫자뒤에 l이나 L을 붙이다. 

 

부동소수점은 기본 자료형이 double형이 기본자료형이라고 하고 float인 경우 문자뒤에 f를 붙인다.

 

단일 인용부호인 ''로 묶인 영문자나 숫자문자를 사용하면 이는 아스키코드 값으로 사용된다. 

 문자열 상수는 기억할 것이 문자열 끝에 null 문자를 자동으로 추가된다. (\0)

 이때 사용하는 문자형 자료형 중 char는 -128~127까지 표현되며 1바이트를 갖는다. 

 unsigned char 사용하게 되면 0~255까지의 범위를 표현하고 1바이트를 사용한다. 

 

 

C언어에도 열겨형이라는 emun이 존재한다. 

 

형식은 다음과 같다.

enum day {mon, tue, wed, thu, fri, sat, sun};
enum day {mon, tue, wed, thu, fri, sat, sun} today;

 

각 내부 값이 0부터 1씩 증가하는 정수로 치환이 된다고 한다. 

 

자료형 중에서는 확장형이라는 부분이 있다. 배열, 함수, 포인터, 구조체가 이에 해당한다. 

 

 

선행처리기? (preprocessor)

 

컴파일에 앞서 프로그램 선두에 선언된 지시자들을 미리 처리하는 역할을 수행한다. 

 

종류로는 다음과 같다.

 

선행처리기 설명
#include 파일 포함
#define 매크로 정의
#if #else #elif #endif 조건부 컴파일

 

선행처리기를 사용할 때는 세미콜론을 붙이지 않아도 되고, 한 줄에 하나의 명령만 쓰고, 소스 프로그램의 첫 부분에 위치한다.

 

#include?

 

#include는 C언어에서 제공되는 헤더파일을 자신의 소스파일에 읽어 들여 함께 컴파일하고자 할 때 사용한다. 

 

예를 들어 표준함수인 printf(), scanf() 등을 사용하려면 이 함수들의 prototype이 선언되어 있는 표준 입출력 헤더파일인 stdio.h를 #include 시켜야 한다. 

 

#define?

 

매크로를 정의할 때 사용한다. macro라고 하는 것은 단순이 치환되는 자료를 말한다. 

프로그램 작성 시에 명령이나 수식 또는 상수값이 자주 사용될 때 이들을 대표하는 이름을 붙여 사용하는 대상이다. 

 

매크로 상수를 정의

#define PI 3.141592

 

매크로 함수 정의

#define AREA(x)(3.1414582*(x)*(x))

선행처리기에 의한 단순 치환 방식이므로, 전달 인자의 자료형을 명시할 필요가 없고, 또 어떠한 자료형 변수를 인자로 전달해도 잘 동작한다. 한두 줄인 코드인 경우 함수로 정의하는 것보다 속도가 빠르다. 

 

#define은 값이나 코드 조각을 나타내는 기호 이름인 매크로를 정의하는 전처리기 지시문입니다. 전처리기가 #define 지시문을 만나면 코드가 컴파일되기 전에 정의된 매크로의 모든 항목을 해당 값 또는 코드로 바꿉니다. #define은 일반적으로 상수를 정의하거나 복잡한 표현식을 단순화하는 데 사용됩니다.

반면에 전역 변수는 모든 함수 외부에서 선언된 변수이며 프로그램의 모든 부분에서 액세스 할 수 있습니다. 전역 변수는 전체 프로그램에 걸쳐 확장되는 범위를 가지며 변수를 생성한 함수가 더 이상 실행되지 않는 경우에도 값을 유지합니다. 전역 변수는 서로 다른 함수 간에 데이터를 공유하거나 여러 함수에서 액세스해야 하는 정보를 저장하는 데 사용할 수 있습니다.


#define 및 전역 변수는 프로그램의 다른 부분에서 액세스 할 수 있는 값을 저장하는 데 사용할 수 있지만 서로 다른 콘텍스트에서 사용되며 프로그램 설계 및 실행에 대해 서로 다른 의미를 갖습니다. #define은 메모리를 사용하지 않는 컴파일 타임 대체이며 전역 변수는 메모리를 사용하고 런타임에 수정할 수 있습니다. 전역 변수는 이름 충돌, 변경 추적의 어려움, 의도하지 않은 수정으로 인한 잠재적인 버그와 같은 문제를 일으킬 수도 있습니다.

 

 

조건부 컴파일?

 

이것은 조건에 따라 프로그램을 컴파일하는 명령이다. 

 

#if와 #elif 다음에는 컴파일 여부를 결정하는 조건문이 필요하다. 주로 매크로 값이 사용된다.

#include <stdio.h>
#define COND 0

void main() {

    #if COND
        printf("Hello, World!\n");
    #else
        printf("Hello??, World!\n");
    #endif
}

 

왜 C언어는 조건부 컴파일을 사용할까?

 

C에서 조건부 컴파일을 사용하는 이유는 여러 가지가 있습니다. 그중 하나는 불필요한 코드를 배제하여 코드 크기와 성능을 최적화하는 것입니다. 예를 들어 특정 코드는 특정 플랫폼이나 구성에만 관련될 수 있으며 다른 플랫폼이나 구성에서는 안전하게 제외될 수 있습니다. 또 다른 이유는 프로그래머에게 유연성과 사용자 정의를 제공하는 것입니다. 조건부 컴파일을 사용하여 프로그래머는 사용자 기본 설정 또는 기타 조건에 따라 특정 기능을 포함하거나 제외하는 프로그램의 다른 버전을 만들 수 있습니다.

조건부 컴파일은 플랫폼별 코드를 구현하거나 플랫폼별 문제를 해결하는 데 사용할 수도 있습니다. 예를 들어 플랫폼마다 시스템 호출이나 라이브러리 함수가 다를 수 있으며 조건부 컴파일을 사용하여 각 플랫폼에 적합한 코드를 포함할 수 있습니다. 또한 조건부 컴파일을 사용하여 코드의 특정 섹션을 선택적으로 제외하거나 포함하여 문제가 있는 코드를 격리하고 디버그 할 수 있습니다.

 

 

여기까지 공부한 내용으로 Fragment Shader 코드를 보았는데 #define.. 과 같은 글이 그냥 주석인 줄 알았는데, 아니었고 매크로였다.  ㅋㅋㅋ

 

그럼 보통 PI, 2PI와 같은 상수를 계산할 때 자주 사용하는데 이러한 상수 값을 매크로로 선언하여 사용해도 될 것 같다. 

 

 

 

 

반응형

'C' 카테고리의 다른 글

구조체  (0) 2023.05.01
포인터와 배열.  (0) 2023.04.29
포인터  (0) 2023.04.29
C언어에서 배열을 어떻게 다룰까?  (0) 2023.04.17
입력 및 출력 프로그램  (0) 2023.04.16

댓글