<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>OnePaperHoon Blog</title>
    <link>https://onepaperhoon.tistory.com/</link>
    <description>한장훈님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Tue, 28 Jul 2026 08:18:32 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>onepaperhoon</managingEditor>
    <image>
      <title>OnePaperHoon Blog</title>
      <url>https://tistory1.daumcdn.net/tistory/7861961/attach/065f34707a52443a890cde8d39b559ab</url>
      <link>https://onepaperhoon.tistory.com</link>
    </image>
    <item>
      <title>Linux ioctl, 디바이스를 제어하는 전용 채널</title>
      <link>https://onepaperhoon.tistory.com/20</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;`read()`/`write()`로는 데이터를 주고받을 수 있지만, &lt;br /&gt;&quot;이 센서의 샘플링 레이트를 변경해줘&quot; 같은 제어 명령은 어떻게 전달할까요?&lt;br /&gt;그 답이 `ioctl`입니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. ioctl이 왜 필요한가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Linux에서 디바이스와 통신하는 기본 방법은 `read()`와 `write()`입니다.&lt;br /&gt;그런데 이 두 가지로 표현하기 어려운 명령들이 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775469935344&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;샘플링 레이트를 1000Hz로 설정해줘&quot;
&quot;현재 온도를 읽어줘&quot;
&quot;버퍼를 초기화해줘&quot;
&quot;카운터 값을 99로 교환하고 기존 값을 돌려줘&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 디바이스 고유의 제어 명령을 전달하는 시스템 콜이 `ioctl`(Input/Output Control)입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775470025653&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 유저스페이스에서 호출하는 방식 */
int ioctl(int fd, unsigned long request, void *arg);&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;인자&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;`fd`&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;열린 디바이스 파일 디스크립터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;`request`&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;명령 코드 (매직 넘버 + 번호 + 방향 + 크기 인코딩)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;`arg (void *)`&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;명령에 따라 정수값 또는 구조체 포인터&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. ioctl은 언제 쓰나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디바이스를 다루다 보면 read()/write()로 표현하기 애매한 순간이 생깁니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;1c&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;&quot;샘플링 레이트를 1000Hz로 바꿔줘&quot;
&quot;현재 버퍼를 비워줘&quot;
&quot;카운터 값을 99로 설정하고 기존 값을 돌려줘&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 &quot;읽고 쓰는&quot; 것이 아니라 디바이스에 &lt;b&gt;명령을 내리는&lt;/b&gt; 상황입니다. ioctl은 바로 이 용도로 설계된 시스템 콜입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 Linux의 주요 드라이버들이 이 방식을 씁니다.&lt;/p&gt;
&lt;div&gt;드라이버ioctl 용도
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;V4L2 (카메라)&lt;/td&gt;
&lt;td&gt;해상도, 프레임레이트 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ALSA (사운드)&lt;/td&gt;
&lt;td&gt;샘플링 레이트, 채널 수 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;네트워크 소켓&lt;/td&gt;
&lt;td&gt;IP 주소, 인터페이스 정보 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTY (시리얼)&lt;/td&gt;
&lt;td&gt;Baud rate, 흐름 제어 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. ioctl 명령 코드 설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ioctl의 핵심은 명령 코드(command number)를 어떻게 정의하느냐입니다. 커널은 명령 코드를 32비트 정수로 인코딩합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775470691324&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;32비트 명령 코드 구조:
┌────────────┬────────────┬─────────────────┬──────────────────┐
│  direction│    size   │  type (magic)  │   number (nr)  │
│   2 bits  │  14 bits  │   8 bits       │    8 bits      │
└────────────┴────────────┴─────────────────┴──────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 직접 계산하면 실수가 생깁니다. 커널이 제공하는 매크로를 씁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`ioctl_cmd.h` &amp;mdash; 커널과 유저가 함께 공유하는 헤더&lt;/p&gt;
&lt;pre id=&quot;code_1775470772661&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#ifndef IOCTL_CMD_H
#define IOCTL_CMD_H

#include &amp;lt;linux/ioctl.h&amp;gt;

/* Magic number &amp;mdash; 이 드라이버의 고유 식별자 */
#define IOCTL_MAGIC 'k'

/* 명령어 정의 */
#define IOCTL_RESET     _IO  (IOCTL_MAGIC, 0)            /* 데이터 없음 */
#define IOCTL_GET_COUNT _IOR (IOCTL_MAGIC, 1, int)       /* 커널 &amp;rarr; 유저 */
#define IOCTL_SET_MSG   _IOW (IOCTL_MAGIC, 2, char[256]) /* 유저 &amp;rarr; 커널 */
#define IOCTL_GET_MSG   _IOR (IOCTL_MAGIC, 3, char[256]) /* 커널 &amp;rarr; 유저 */
#define IOCTL_EXCHANGE  _IOWR(IOCTL_MAGIC, 4, int)       /* 양방향 */

#define IOCTL_MAXNR 4

#endif&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;매크로 4종 정리&lt;/h4&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 118px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;매크로&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;방향&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;데이터 크기 포함&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;용도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;`_IO(magic, nr)`&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;아니오&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;단순 제어 (reset, start 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;`_IOR(magic, nr, type)`&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;커널 &amp;rarr; 유저&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;예&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;값 읽기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;`_IOW(magic, nr, type)`&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;유저 &amp;rarr; 커널&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;예&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;값 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;`_IOWR(magic, nr, type)`&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;양방향&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;예&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;값 교환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Magic Number란?&lt;/h4&gt;
&lt;pre id=&quot;code_1775470859065&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define IOCTL_MAGIC 'k'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령 코드의 상위 8비트를 차지하는 드라이버 고유 식별자입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 드라이버 명령 코드와 충돌하지 않도록 드라이버마다 다른 문자를 씁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 소스의 `Documentation/userspace-api/ioctl/ioctl-number.rst`에 이미 할당된 매직 번호 목록이 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헤더 파일 공유 전략: `ioctl_cmd.h`를 커널 모듈(`chardev_ioctl.c`)과 &lt;br /&gt;유저 프로그램(`test_ioctl.c`) 양쪽에서 #include 합니다. &lt;br /&gt;명령 코드를 한 곳에서 관리해 불일치를 원천 차단합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 커널 측 구현 - `unlocked_ioctl`&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file_operations에 등록&lt;/p&gt;
&lt;pre id=&quot;code_1775470956624&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static struct file_operations fops = {
    .owner          = THIS_MODULE,
    .open           = device_open,
    .release        = device_release,
    .read           = device_read,
    .write          = device_write,
    .unlocked_ioctl = device_ioctl,  /* ioctl 핸들러 */
};&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;unlocked_ioctl vs ioctl&lt;/h4&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 80px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;필드&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;커널 버전&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;특징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;`.ioctl`&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;2.6.36 이전&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;호출 전 BKL(Big Kernel Lock) 자동 획득&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;`.unlocked_ioctl`&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;2.6.36 이후&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;BKL 없이 호출 &amp;mdash; 드라이버가 동기화 책임&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 커널에서는 반드시 `. unlocked_ioctl`을 써야 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ioctl 핸들러 구현&lt;/h4&gt;
&lt;pre id=&quot;code_1775471013809&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static long device_ioctl(struct file *file, unsigned int cmd,
                         unsigned long arg)
{
    int value;
    char user_msg[MSG_SIZE];

    /* ① 매직 넘버 검증 &amp;mdash; 엉뚱한 명령 차단 */
    if (_IOC_TYPE(cmd) != IOCTL_MAGIC)
        return -ENOTTY;

    /* ② 명령 번호 범위 검증 */
    if (_IOC_NR(cmd) &amp;gt; IOCTL_MAXNR)
        return -ENOTTY;

    /* ③ 명령별 처리 */
    switch (cmd) {

    case IOCTL_RESET:
        access_count = 0;
        strcpy(message, &quot;Reset!&quot;);
        break;

    case IOCTL_GET_COUNT:
        /* 커널 &amp;rarr; 유저: copy_to_user() */
        if (copy_to_user((int __user *)arg, &amp;amp;access_count, sizeof(int)))
            return -EFAULT;
        break;

    case IOCTL_SET_MSG:
        /* 유저 &amp;rarr; 커널: copy_from_user() */
        if (copy_from_user(user_msg, (char __user *)arg, MSG_SIZE))
            return -EFAULT;
        user_msg[MSG_SIZE - 1] = '\0';
        strcpy(message, user_msg);
        break;

    case IOCTL_GET_MSG:
        if (copy_to_user((char __user *)arg, message, MSG_SIZE))
            return -EFAULT;
        break;

    case IOCTL_EXCHANGE:
        /* 유저 값 읽기 &amp;rarr; 기존 카운터 반환 &amp;rarr; 새 값 적용 */
        if (copy_from_user(&amp;amp;value, (int __user *)arg, sizeof(int)))
            return -EFAULT;
        if (copy_to_user((int __user *)arg, &amp;amp;access_count, sizeof(int)))
            return -EFAULT;
        access_count = value;
        break;

    default:
        return -ENOTTY;  /* 알 수 없는 명령 */
    }

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 -ENOTTY를 반환하나?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Not a typewriter&quot;의 약자로, 역사적으로 터미널 제어에서 유래했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘날에는 &lt;b&gt;&quot;이 디바이스는 이 ioctl을 지원하지 않는다&quot;는&lt;/b&gt; POSIX 표준 오류 코드로 쓰입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;`IOCTL_EXCHANGE`&lt;/b&gt; 양방향 동작 원리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인터 하나로 값을 읽고 같은 포인터에 다른 값을 씁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775471054548&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;유저:  value = 999  &amp;rarr;  ioctl(fd, IOCTL_EXCHANGE, &amp;amp;value)
                              │
커널:  copy_from_user &amp;rarr; value 읽음 (999)
       copy_to_user  &amp;rarr; access_count 반환
       access_count  = 999  적용
                              │
유저:  value 에 기존 카운터 값이 들어있음&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 유저스페이스 측 구현&lt;/h3&gt;
&lt;pre id=&quot;code_1775471089464&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &quot;ioctl_cmd.h&quot;   /* 커널과 동일한 명령 코드 사용 */
#include &amp;lt;sys/ioctl.h&amp;gt;
#include &amp;lt;fcntl.h&amp;gt;

int main(void)
{
    int fd = open(&quot;/dev/ioctl_dev&quot;, O_RDWR);
    if (fd &amp;lt; 0) { perror(&quot;open&quot;); return 1; }

    int count;
    char message[256];

    /* 카운터 읽기 */
    ioctl(fd, IOCTL_GET_COUNT, &amp;amp;count);
    printf(&quot;Count: %d\n&quot;, count);

    /* 메시지 설정 */
    strcpy(message, &quot;Hello from OnePaperHoon!&quot;);
    ioctl(fd, IOCTL_SET_MSG, message);

    /* 메시지 읽기 */
    memset(message, 0, sizeof(message));
    ioctl(fd, IOCTL_GET_MSG, message);
    printf(&quot;Message: %s\n&quot;, message);

    /* 값 교환 */
    int value = 999;
    ioctl(fd, IOCTL_EXCHANGE, &amp;amp;value);
    printf(&quot;Exchanged, got back: %d\n&quot;, value);

    /* 리셋 */
    ioctl(fd, IOCTL_RESET, NULL);

    close(fd);
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 빌드 및 실행&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;파일 구성&lt;/h4&gt;
&lt;pre id=&quot;code_1775471104998&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;day5-ioctl/
├── chardev_ioctl.c   # 커널 모듈
├── ioctl_cmd.h       # 명령 코드 정의 (공유 헤더)
├── test_ioctl.c      # 유저 테스트 프로그램
└── Makefile&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Makefile 구성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Makefile은 커널 모듈과 유저 프로그램을 함께 빌드하고, 디바이스 파일 생성까지 관리합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775471128512&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;obj-m := chardev_ioctl.o
KDIR  := /lib/modules/$(shell uname -r)/build
PWD   := $(shell pwd)
USER_PROG := test_ioctl

all: module userspace

module:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

userspace:
	gcc -o $(USER_PROG) test_ioctl.c

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean
	rm -f $(USER_PROG)

load:
	sudo insmod chardev_ioctl.ko
	dmesg | tail -10

create-dev:
	@read -p &quot;Enter major number: &quot; MAJOR; \
	sudo mknod /dev/ioctl_dev c $$MAJOR 0; \
	sudo chmod 666 /dev/ioctl_dev

test:
	./$(USER_PROG)
	dmesg | tail -20

unload:
	sudo rmmod chardev_ioctl
	sudo rm -f /dev/ioctl_dev

.PHONY: all module userspace clean load create-dev test unload&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Makefile 타깃 요약&lt;/h4&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;명령어&lt;/td&gt;
&lt;td&gt;동작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`make`&lt;/td&gt;
&lt;td&gt;커널 모듈 + 유저 프로그램 빌드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`make load`&lt;/td&gt;
&lt;td&gt;모듈 로드 + dmesg&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`make create-dev`&lt;/td&gt;
&lt;td&gt;`/dev/ioctl_dev` 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`make test`&lt;/td&gt;
&lt;td&gt;테스트 프로그램 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`make unload`&lt;/td&gt;
&lt;td&gt;모듈 언로드 + 디바이스 파일 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실행 순서&lt;/h4&gt;
&lt;pre id=&quot;code_1775471174051&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 빌드
$ make

# 2. 모듈 로드
$ make load
[ 100.001] ioctl_dev: Registered with major 240
[ 100.002] ioctl_dev: Create device:
[ 100.003]   sudo mknod /dev/ioctl_dev c 240 0

# 3. /dev 파일 생성 (메이저 번호는 dmesg 확인)
$ make create-dev
Enter major number: 240
/dev/ioctl_dev 생성 완료

# 4. 테스트 실행
$ make test
====================================
ioctl Test Program
====================================

1. Getting count...
   Current count: 1

2. Setting message...
   Message set: Hello from OnePaperHoon!

3. Getting message...
   Message: Hello from OnePaperHoon!

4. Exchanging value...
   Sending: 999
   Received: 1

5. Getting count (after exchange)...
   Current count: 999

6. Resetting...
   Reset done!

7. Getting count (after reset)...
   Current count: 0

====================================
Test Complete!
====================================

# 5. 언로드
$ make unload&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ioctl은 /proc보다 복잡하지만, 실제 드라이버에서 훨씬 많이 사용됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령 코드는 `_IO` / `_IOR` / `_IOW` / `_IOWR` 매크로로 만든다.&lt;br /&gt;헤더 파일을 커널과 유저가 공유해 명령 코드 불일치를 막는다.&lt;br /&gt;핸들러에서 매직 넘버를 검증하고 알 수 없는 명령은 `-ENOTTY`를 반환한다.&lt;br /&gt;unlocked_ioctl을 쓰면 동기화는 드라이버가 직접 책임진다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라(`V4 L2`), 사운드(`ALSA`), 네트워크(`SIOCGIFADDR`) 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Linux의 주요 드라이버 인터페이스는 대부분 `ioctl` 기반입니다. 이 구조를 이해하면 그 코드들이 낯설지 않을 겁니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Linux Kernel Documentation: Documentation/userspace-api/ioctl/ioctl-number.rst&lt;/li&gt;
&lt;li&gt;linux/ioctl.h 커널 소스&lt;/li&gt;
&lt;li&gt;man 2 ioctl&lt;/li&gt;
&lt;li&gt;LDD3(Linux Device Drivers 3rd Edition) &amp;mdash; Chapter 6. Advanced Char Driver Operations&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Dev/Linux</category>
      <category>CharDevice</category>
      <category>ioctl</category>
      <category>KernelModule</category>
      <category>Linux</category>
      <category>raspberrypi</category>
      <category>드라이버</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/20</guid>
      <comments>https://onepaperhoon.tistory.com/20#entry20comment</comments>
      <pubDate>Mon, 6 Apr 2026 19:40:42 +0900</pubDate>
    </item>
    <item>
      <title>Linux/proc 파일시스템, 커널과 대화하기</title>
      <link>https://onepaperhoon.tistory.com/19</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;`cat /proc/cpuinfo`,`cat /proc/meminfo` - 써본 적 있으신가요?&lt;br /&gt;이 파일들은 디스크에 없습니다. 커널이 즉석에서 만들어냅니다. 오늘은 그 원리를 직접 구현해 봅니다.&amp;nbsp;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. /proc 파일시스템이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 이런 명령을 써본 적 있을 겁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775203414576&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cat /proc/cpuinfo      # CPU 정보
$ cat /proc/meminfo      # 메모리 사용량
$ cat /proc/uptime       # 부팅 후 경과 시간&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일들은 실제로 디스크에 존재하지 않습니다. `/proc`은 가상 파일 시스템(virtual File System)입니다.&lt;br /&gt;커널이 메모리 위에 만들어 놓은 인터페이스로,&amp;nbsp; `cat`으로 읽으면 커널 함수가 호출되어 데이터를 즉석에서 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775203579265&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;디스크
┌──────────────────┐
│ /home, /etc, ...│  &amp;larr; 실제 파일
└──────────────────┘

메모리 (커널)
┌──────────────────┐
│ /proc/cpuinfo   │  &amp;larr; cat 하면 커널 함수 호출
│ /proc/meminfo   │
│ /proc/mymodule  │  &amp;larr; 우리가 만들 것
└──────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메커니즘을 이용하면 커널 모듈이 유저스페이스 앱과 파일처럼 통신할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 동작 원리 - cat 하면 무슨 일이 생기나?&lt;/h3&gt;
&lt;pre id=&quot;code_1775203666563&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;유저스페이스                     커널스페이스

cat /proc/onepaperhoon_info
  │
  │  open() 시스템 콜
  ▼
  proc_open()  ─────────────►  single_open(file, proc_show, NULL)
                                  seq_file 초기화

  │  read() 시스템 콜
  ▼
  seq_read()   ─────────────►  proc_show(seq_file *m, void *v)
                                  seq_printf(m, &quot;Access count: %lu\n&quot;, ...)
                                  버퍼에 데이터 채움

  │  결과 출력
  ▼
Access count: 1
Current jiffies: 4298765432
...


echo reset &amp;gt; /proc/onepaperhoon_info
  │
  │  write() 시스템 콜
  ▼
  proc_write() ─────────────►  copy_from_user(cmd, buffer, count)
                                  &quot;reset&quot; 감지 &amp;rarr; access_count = 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 `cat`과 `echo`가 일반 파일처럼 동작하지만, 실제로는 커널 함수를 호출한다는 점입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 핵심 API 설명&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ① proc_ops &amp;mdash; 커널 5.6부터 필수&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1775204610816&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static const struct proc_ops proc_fops = {
    .proc_open    = proc_open,
    .proc_read    = seq_read,
    .proc_write   = proc_write,
    .proc_lseek   = seq_lseek,
    .proc_release = single_release,
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 5.6 이전에는 `file_operations`를 썼습니다. 5.6부터 `/proc`전용 `struct proc_ops`가 도입됐습니다.&lt;br /&gt;Raspberry Pi의 커널 6.x에서는 반드시 `proc_ops`를 써야 합니다. `file_operations`를 쓰면 컴파일 에러가 납니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ② seq_file &amp;mdash; 버퍼 걱정 없이 출력&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`/proc`파일을 읽을 때 단순히 문자열을 반환하면 대용량 데이터에서 페이지 경계 문제가 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`seq_file`은 커널이 제공하는 순차 출력 인터페이스로, 버퍼 관리를 자동으로 처리해 줍니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 122px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;함수&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;single_open(file, show_fn, data)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;단일 페이지 출력용 seq_file 초기화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;seq_printf(m, fmt, ...)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;seq_file 버퍼에 포맷 출력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;seq_read&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;표준 read 핸들러 (직접 구현 불필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;single_release&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;single_open 대응 release 핸들러&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;③ copy_from_user() &amp;mdash; 유저 &amp;rarr; 커널 안전 복사&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1775204951847&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (copy_from_user(cmd, buffer, count))
    return -EFAULT;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저스페이스와 커널스페이스는 메모리 주소 공간이 분리되어 있습니다. `echo reset` 으로 데이터를 쓰면 그 데이터는 유저 공간 메모리에 있습니다. 커널에서 직접 포인터로 접근하면 페이지 폴트나 보안 문제가 생깁니다. `copy_from_user()`가 유저 공간 -&amp;gt; 커널 공간으로 안전하게 복사해 줍니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ④ `__user` 어노테이션&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1775205030069&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static ssize_t proc_write(struct file *file, const char __user *buffer, ...)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`__user`는 실행에 영향을 주지 않는 컴파일러 어노테이션입니다. `sparse` (커널 정적 분성 도구)가 이 포인터를 유저 공간 주소로 인식해, `copy_from_user()` 없이 직접 역참조 하면 경고를 발생시킵니다. 빌드 타임에 실수를 잡아주는 안전장치입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;⑤ jiffies &amp;mdash; 커널의 시간 단위&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1775205098270&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;seq_printf(m, &quot;Current jiffies: %lu\n&quot;, jiffies);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`jiffies`는 커널 부팅 이후 타이머 인터럽트가 발생한 횟수입니다. 1초에 `HZ`번 증가합니다. (보통 250 또는 1000).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`/proc/uptime` 같은 파일이 이 값으로 경과 시간을 계산합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 코드 전체 구조&lt;/h3&gt;
&lt;pre id=&quot;code_1775205182642&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;linux/init.h&amp;gt;
#include &amp;lt;linux/jiffies.h&amp;gt;
#include &amp;lt;linux/kernel.h&amp;gt;
#include &amp;lt;linux/module.h&amp;gt;
#include &amp;lt;linux/proc_fs.h&amp;gt;
#include &amp;lt;linux/seq_file.h&amp;gt;
#include &amp;lt;linux/uaccess.h&amp;gt;

#define PROC_NAME &quot;onepaperhoon_info&quot;

static unsigned long access_count = 0;

/* ① cat 할 때 호출 &amp;mdash; 출력 내용 정의 */
static int proc_show(struct seq_file *m, void *v)
{
    access_count++;
    seq_printf(m, &quot;====================================\n&quot;);
    seq_printf(m, &quot;OnePaperHoon Kernel Module Info\n&quot;);
    seq_printf(m, &quot;====================================\n&quot;);
    seq_printf(m, &quot;Access count: %lu\n&quot;, access_count);
    seq_printf(m, &quot;Current jiffies: %lu\n&quot;, jiffies);
    seq_printf(m, &quot;Module: %s\n&quot;, THIS_MODULE-&amp;gt;name);
    seq_printf(m, &quot;====================================\n&quot;);
    return 0;
}

/* ② open 시 seq_file 초기화 */
static int proc_open(struct inode *inode, struct file *file)
{
    return single_open(file, proc_show, NULL);
}

/* ③ echo 할 때 호출 &amp;mdash; &quot;reset&quot; 명령 처리 */
static ssize_t proc_write(struct file *file, const char __user *buffer,
                           size_t count, loff_t *pos)
{
    char cmd[10];

    if (count &amp;gt; sizeof(cmd) - 1)
        return -EINVAL;

    if (copy_from_user(cmd, buffer, count))
        return -EFAULT;

    cmd[count] = '\0';

    if (strncmp(cmd, &quot;reset&quot;, 5) == 0) {
        access_count = 0;
        printk(KERN_INFO &quot;proc_basic: Counter reset!\n&quot;);
    }
    return count;
}

/* ④ proc_ops 등록 (커널 5.6+) */
static const struct proc_ops proc_fops = {
    .proc_open    = proc_open,
    .proc_read    = seq_read,
    .proc_write   = proc_write,
    .proc_lseek   = seq_lseek,
    .proc_release = single_release,
};

static struct proc_dir_entry *proc_entry;

/* ⑤ insmod 시 /proc 파일 생성 */
static int __init proc_basic_init(void)
{
    proc_entry = proc_create_data(PROC_NAME, 0666, NULL, &amp;amp;proc_fops, NULL);
    if (!proc_entry) {
        printk(KERN_ERR &quot;proc_basic: Failed to create /proc/%s\n&quot;, PROC_NAME);
        return -ENOMEM;
    }
    printk(KERN_INFO &quot;proc_basic: Created /proc/%s\n&quot;, PROC_NAME);
    return 0;
}

/* ⑥ rmmod 시 /proc 파일 제거 */
static void __exit proc_basic_exit(void)
{
    if (proc_entry)
        remove_proc_entry(PROC_NAME, NULL);
    printk(KERN_INFO &quot;proc_basic: Total accesses: %lu\n&quot;, access_count);
}

module_init(proc_basic_init);
module_exit(proc_basic_exit);

MODULE_LICENSE(&quot;GPL&quot;);
MODULE_AUTHOR(&quot;OnePaperHoon&quot;);
MODULE_DESCRIPTION(&quot;Basic /proc filesystem example - Week 5 Day 3&quot;);
MODULE_VERSION(&quot;1.0&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 자주 하는 실수 모음&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ❌ 실수 1: 커널 5.6 미만 API 사용&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1775205223518&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 나쁜 예 &amp;mdash; 5.6 이상에서 컴파일 에러 */
static const struct file_operations proc_fops = {
    .open    = proc_open,
    .read    = seq_read,
    ...
};

/* 좋은 예 &amp;mdash; 5.6 이상 필수 */
static const struct proc_ops proc_fops = {
    .proc_open = proc_open,
    .proc_read = seq_read,
    ...
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Raspberry Pi 커널 6.x에서는 반드시 `proc_ops`를 써야 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ 실수 2: copy_from_user() 없이 직접 접근&lt;/h4&gt;
&lt;pre id=&quot;code_1775205237114&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 나쁜 예 &amp;mdash; 커널 패닉 또는 보안 취약점 */
static ssize_t proc_write(struct file *file, const char __user *buffer, ...)
{
    char cmd[10];
    memcpy(cmd, buffer, count);  /* 직접 접근 &amp;mdash; 절대 금지 */
}

/* 좋은 예 */
if (copy_from_user(cmd, buffer, count))
    return -EFAULT;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`__user` 포인터를 직접 역참조하면 커널 패닉이 발생합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ 실수 3: remove_proc_entry() 누락&lt;/h4&gt;
&lt;pre id=&quot;code_1775205249483&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 나쁜 예 &amp;mdash; exit에서 제거 안 함 */
static void __exit proc_basic_exit(void)
{
    /* remove_proc_entry() 깜빡! */
    printk(KERN_INFO &quot;Unloaded\n&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈이 제거된 후에도 `/proc` 엔트리가 남아 있으면, `cat /proc/onepaperhoon_info` 시 커널 패닉이 발생합니다. `exit` 함수에서 반드시 `remove_proc_entry()`를 호출하세요.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ 실수 4: count 검증 없이 copy_from_user()&lt;/h4&gt;
&lt;pre id=&quot;code_1775205277269&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 나쁜 예 &amp;mdash; 버퍼 오버플로우 가능 */
if (copy_from_user(cmd, buffer, count))
    return -EFAULT;

/* 좋은 예 &amp;mdash; 크기 검증 먼저 */
if (count &amp;gt; sizeof(cmd) - 1)
    return -EINVAL;
if (copy_from_user(cmd, buffer, count))
    return -EFAULT;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`count`를 먼저 검증하지 않으면 버퍼 오버플로우가 생길 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`/proc` 파일시스템은 커널 모듈과 유저스페이스가 대화하는 가장 단순한 방법입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/proc 파일은 디스크에 없다 &amp;mdash; cat 하면 커널 함수가 실행된다.&lt;br /&gt;커널 5.6부터 proc_ops를 써야 한다.&lt;br /&gt;유저 &amp;rarr; 커널 데이터 복사는 반드시 copy_from_user()를 거쳐야 한다.&lt;br /&gt;remove_proc_entry() 없이 rmmod 하면 커널 패닉이 난다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 `/dev` 디렉터리에 파일을 만들어 유저스페이스 앱과 `read()`/`write()`/`ioctl()`로 통신하는 &lt;b&gt;Character Device Driver&lt;/b&gt;를 다룰 예정입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고 자료&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Linux Kernel Documentation: Documentation/filesystems/proc.rst&lt;/li&gt;
&lt;li&gt;linux/proc_fs.h, linux/seq_file.h 커널 소스&lt;/li&gt;
&lt;li&gt;linux/uaccess.h &amp;mdash; copy_from_user() 구현&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Dev/Linux</category>
      <category>KernelModule</category>
      <category>Linux</category>
      <category>proc</category>
      <category>raspberrypi</category>
      <category>드라이버</category>
      <category>커널</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/19</guid>
      <comments>https://onepaperhoon.tistory.com/19#entry19comment</comments>
      <pubDate>Fri, 3 Apr 2026 17:36:38 +0900</pubDate>
    </item>
    <item>
      <title>Linux Kernel Module Parameters</title>
      <link>https://onepaperhoon.tistory.com/18</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Module Parameter가 왜 필요한가요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 만든 Hello 모듈은 동작이 고정되어 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인사 횟수를 바꾸거나 이름을 바꾸려면 코드를 수정하고 다시 빌드해야 했죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 드라이버에서는 이런 상황이 자주 생깁니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GPIO 핀 번호를 하드코딩하지 않고 로드 시 지정하고 싶다.&lt;/li&gt;
&lt;li&gt;디버그 로그를 켜고 끄고 싶다&lt;/li&gt;
&lt;li&gt;디바이스 개수나 버퍼 크기를 유연하게 조정하고 싶다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 Module Parameter를 씁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775037332561&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 파라미터 없이 로드 - 기본값 사용
sudo insmode params.ko

# 파라미터 지정해서 로드
sudo insmode params.ko count=3 name=&quot;Hoon&quot; debug=1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 수정 없이 동작을 바꿀 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. module_param() 기본 문법&lt;/h3&gt;
&lt;pre id=&quot;code_1775037399204&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module_param(변수명, 타입, 권한);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 가지만 기억하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775037432423&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* ① 변수 선언 &amp;mdash; 기본값도 여기서 설정 */
static int   count = 1;
static char *name  = &quot;OnePaperHoon&quot;;
static bool  debug = false;

/* ② module_param()으로 외부에 노출 */
module_param(count, int,  0644);
module_param(name,  charp, 0644);
module_param(debug, bool, 0644);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수를 먼저 선언하고 기본값을 설정한 뒤,`module_param()`으로 커널에 등록하는 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`insmod`시 파라미터를 넘기지 않으면 선언 시의 기본값이 그대로 사용됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 파라미터 타입 종류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`module_param()`의 두 번째 인자로 쓸 수 있는 타입들입니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;타입&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;C 변수 타입&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;int&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;int&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;정수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;long&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;long&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;긴 정수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;short&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;short&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;짧은 정수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;uint&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;unsigned int&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;부호 없는 정수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;ulong&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;unsigned long&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;부호 없는 긴 정수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;bool&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;bool&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;true / false (1 / 0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;charp&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;char *&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;문자열 포인터&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서 쓴 타입은 세 가지입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775037690389&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int   count = 1;       /* int   &amp;mdash; 정수 */
static char *name  = &quot;...&quot;;   /* charp &amp;mdash; 문자열 */
static bool  debug = false;   /* bool  &amp;mdash; true/false */&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;문자열은 `char`가 아니라 반드시 `charp`를 씁니다. `char`로 쓰면 컴파일 에러가 납니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 권한 (Permission) 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`module_param()`의 세 번쨰 인자는 &lt;b&gt;sysfs에서의 접근 권한&lt;/b&gt;입니다. Linux 파일 권한과 동일한 8진수 표기법을 씁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775037816610&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module_param(count, int, 0644);
/*                        &amp;uarr;
 *   0 = 특수 권한 없음
 *   6 = owner: read(4) + write(2)
 *   4 = group: read(4)
 *   4 = other: read(4)
 */&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;권한값&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;sysfs 노출&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;읽기&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;쓰기&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;용도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;`0000`&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;노출 안 됨&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;-&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;-&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;로드 시에만 설정 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;`0444`&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;노출&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;가능&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;불가&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;읽기 전용 파라미터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;`0644`&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;노출&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;가능&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;가능&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;런타임 변경 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 세 파라미터 모두 `0644`로 설정했습니다. 즉 모듈 로드 후에도 sysfs를 통해 값을 읽고 쓸 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;커널 코드에서 권한값을 쓸 때는 `S_IRUGO`,`S_IWUSR` 같은 매크로를 쓰는 것이 더 명확합니다.&lt;br /&gt;`S_IRUGO`는 모든 사용자 읽기 허용, `S_IWUSR`은 owner 쓰기 허용입니다.&lt;br /&gt;`0644`는 `S_IRUGO | S_IWUSR`과 동일합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. MODULE_PARM_DESC()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터에 설명을 붙입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775038129786&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MODULE_PARM_DESC(count, &quot;Number of greetings (default=1)&quot;);
MODULE_PARM_DESC(name,  &quot;Name to greet (default=OnePaperHoon)&quot;);
MODULE_PARM_DESC(debug, &quot;Enable debug mode (default=false)&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해두면 `modinfo` 명령으로 파라미터 설명을 볼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775038160584&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ modinfo params.ko
filename:       params.ko
description:    Kernel Module with Parameters - Week 5 Day 2
author:         OnePaperHoon
license:        GPL
version:        1.0
parm:           count:Number of greetings (default=1) (int)
parm:           name:Name to greet (default=OnePaperHoon) (charp)
parm:           debug:Enable debug mode (default=false) (bool)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협업하거나 나중에 다시 볼 때 `modinfo` 한 방으로 파라미터 목록과 의미를 확인할 수 있어서 반드시 작성하는 습관을 들이는 게 좋습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. sysfs를 통한 런타임 변경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`0644` 권한으로 등록한 파라미터는 모듈 로드 후에도 stsfs를 통해 값을 바꿀 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775038288718&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 파라미터 위치
/sys/module/&amp;lt;모듈명&amp;gt;/parameters/&amp;lt;파라미터명&amp;gt;

# 현재 값 읽기
$ cat /sys/module/params/parameters/count
1

# 런타임에 값 변경
$ echo 5 | sudo tee /sys/module/params/parameters/count
5

# 변경 확인
$ cat /sys/module/params/parameters/count
5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, sysfs로 값을 바꿔도 이미 실행된 Init 함수는 다시 실행되지 않습니다.&lt;br /&gt;런타임 변경이 의미 있으려면 모듈 내부 로직이 파라미터 변수를&amp;nbsp;&lt;b&gt;주기적으로 참조&lt;/b&gt;하거나, 변경 시 콜백을 처리하는 구조여야 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 실습 - Raspberry Pi에서 직접 확인하기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;파일 구성&lt;/h4&gt;
&lt;pre id=&quot;code_1775038403651&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;params_module/
├── params.c
└── Makefile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Makefile&lt;/h4&gt;
&lt;pre id=&quot;code_1775038451451&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Week 5 Day 2: Module Parameters
# Makefile for params module

obj-m := params.o

KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
	@echo &quot;=========================================&quot;
	@echo &quot;Building params.ko kernel module...&quot;
	@echo &quot;=========================================&quot;
	$(MAKE) -C $(KDIR) M=$(PWD) modules
	@echo &quot;&quot;
	@echo &quot;Build complete! Generated files:&quot;
	@ls -lh params.ko 2&amp;gt;/dev/null || echo &quot;Build failed!&quot;

clean:
	@echo &quot;Cleaning build files...&quot;
	$(MAKE) -C $(KDIR) M=$(PWD) clean
	@echo &quot;Clean complete!&quot;

# 파라미터 없이 로드
load:
	@echo &quot;=========================================&quot;
	@echo &quot;Loading module (default parameters)...&quot;
	@echo &quot;=========================================&quot;
	sudo insmod params.ko
	@echo &quot;&quot;
	@echo &quot;Kernel log:&quot;
	dmesg | tail -15

# 파라미터와 함께 로드
load-custom:
	@echo &quot;=========================================&quot;
	@echo &quot;Loading module with custom parameters...&quot;
	@echo &quot;=========================================&quot;
	sudo insmod params.ko count=3 name=&quot;Hoon&quot; debug=true
	@echo &quot;&quot;
	@echo &quot;Kernel log:&quot;
	dmesg | tail -15

# 언로드
unload:
	@echo &quot;=========================================&quot;
	@echo &quot;Unloading module...&quot;
	@echo &quot;=========================================&quot;
	sudo rmmod params
	@echo &quot;&quot;
	@echo &quot;Kernel log:&quot;
	dmesg | tail -10

# 모듈 정보
info:
	@echo &quot;=========================================&quot;
	@echo &quot;Module Information:&quot;
	@echo &quot;=========================================&quot;
	modinfo params.ko

# /sys 확인
sysfs:
	@echo &quot;=========================================&quot;
	@echo &quot;Module parameters in /sys:&quot;
	@echo &quot;=========================================&quot;
	@ls -la /sys/module/params/parameters/ 2&amp;gt;/dev/null || echo &quot;Module not loaded&quot;
	@echo &quot;&quot;
	@echo &quot;Parameter values:&quot;
	@cat /sys/module/params/parameters/* 2&amp;gt;/dev/null || echo &quot;Module not loaded&quot;

# 파라미터 런타임 변경
change-param:
	@echo &quot;=========================================&quot;
	@echo &quot;Changing parameters at runtime...&quot;
	@echo &quot;=========================================&quot;
	@echo &quot;Before:&quot;
	@cat /sys/module/params/parameters/count
	@echo &quot;&quot;
	@echo &quot;Changing count to 5...&quot;
	@echo 5 | sudo tee /sys/module/params/parameters/count
	@echo &quot;&quot;
	@echo &quot;After:&quot;
	@cat /sys/module/params/parameters/count

help:
	@echo &quot;Available targets:&quot;
	@echo &quot;  make              - Build module&quot;
	@echo &quot;  make clean        - Clean build files&quot;
	@echo &quot;  make load         - Load with default params&quot;
	@echo &quot;  make load-custom  - Load with custom params&quot;
	@echo &quot;  make unload       - Unload module&quot;
	@echo &quot;  make info         - Show module info&quot;
	@echo &quot;  make sysfs        - Check /sys parameters&quot;
	@echo &quot;  make change-param - Change parameter at runtime&quot;

.PHONY: all clean load load-custom unload info sysfs change-param help&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;명령어&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;동작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;`make`&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;모듈 빌드 -&amp;gt; `params.ko` 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;`make load`&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;기본 파라미터로 로드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;`make load-custom`&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;`const=3 name=&quot;Hoon&quot; debug=true`로 로드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;`make unload`&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;모듈 언로드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;`make info`&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;`modinfo`로 파라미터 목록 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;`make sysfs`&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;`/sys`경로에서 현재 파라미터 값 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;`make change-param`&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;런타임에 `count`를 5로 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;`make clean`&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;빌드 파일 정리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;params.c 전체&lt;/h4&gt;
&lt;pre id=&quot;code_1775038516233&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 *
 * params.c - Kernel Module with Parameters
 * Week 5 Day 2: Module Parameters
 */

#include &amp;lt;linux/init.h&amp;gt;
#include &amp;lt;linux/kernel.h&amp;gt;
#include &amp;lt;linux/module.h&amp;gt;
#include &amp;lt;linux/moduleparam.h&amp;gt;

/*
 * 모듈 파라미터 선언
 */
static int count = 1;
static char *name = &quot;OnepaperHoon&quot;;
static bool debug = false;

/*
 * module_param(변수명, 타입, 권한)
 * 권한:
 *   0000 - /sys에 나타나지 않음
 *   0444 - 읽기 전용
 *   0644 - 읽기 / 쓰기
 */

module_param(count, int, 0644);
MODULE_PARM_DESC(count, &quot;Number of greetings (default=1)&quot;);

module_param(name, charp, 0644);
MODULE_PARM_DESC(name, &quot;Name to greet (default=OnePaperGoon)&quot;);

module_param(debug, bool, 0644);
MODULE_PARM_DESC(debug, &quot;Enable debug mode (default=false)&quot;);

/*
 * 모듈 초기화
 */
static int __init params_init(void) {
  int i;

  printk(KERN_INFO &quot;=======================================\n&quot;);
  printk(KERN_INFO &quot;Module Parameters Demo\n&quot;);
  printk(KERN_INFO &quot;=======================================\n&quot;);

  if (debug) {
    printk(KERN_DEBUG &quot;[DEBUG] Module loaded with:\n&quot;);
    printk(KERN_DEBUG &quot; count = %d\n&quot;, count);
    printk(KERN_DEBUG &quot; name = %s\n&quot;, name);
    printk(KERN_DEBUG &quot; debug = %s\n&quot;, debug ? &quot;true&quot; : &quot;false&quot;);
  }

  // count만큼 인사 출력
  for (i = 0; i &amp;lt; count; i++) {
    printk(KERN_INFO &quot;Hello, %s&quot;, name);
  }
  printk(&quot;====================================\n&quot;);
  return 0;
}

static void __exit params_exit(void) {
  printk(KERN_INFO &quot;======================================\n&quot;);
  printk(KERN_INFO &quot;GoodBye, %s!\n&quot;, name);
  printk(KERN_INFO &quot;Module unloaded\n&quot;);
  printk(KERN_INFO &quot;======================================\n&quot;);
}

module_init(params_init);
module_exit(params_exit);

MODULE_LICENSE(&quot;GPL&quot;);
MODULE_AUTHOR(&quot;OnePaperHoon&quot;);
MODULE_DESCRIPTION(&quot;Kernel Module with Parameters - Week 5 Day 2&quot;);
MODULE_VERSION(&quot;1.0&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;빌드 및 실행&lt;/h4&gt;
&lt;pre id=&quot;code_1775038619634&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ make
=========================================
Building params.ko kernel module...
=========================================
make -C /lib/modules/6.1.21+/build M=/home/pi/params modules
  CC [M]  params.o
  LD [M]  params.ko
Build complete! Generated files:
-rw-r--r-- 1 pi pi 7.2K params.ko

$ make load-custom
=========================================
Loading module with custom parameters...
=========================================
sudo insmod params.ko count=3 name=&quot;Hoon&quot; debug=true
Kernel log:
[DEBUG] count = 3
[DEBUG] name  = Hoon
[DEBUG] debug = true
Hello, Hoon!
Hello, Hoon!
Hello, Hoon!

$ make sysfs
=========================================
Module parameters in /sys:
=========================================
-rw-r--r-- 1 root root 4096 /sys/module/params/parameters/count
-rw-r--r-- 1 root root 4096 /sys/module/params/parameters/debug
-rw-r--r-- 1 root root 4096 /sys/module/params/parameters/name
Parameter values:
3
true
Hoon&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. 마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Module Parameter는 드라이버를 유연하게 만드는 핵심 도구입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;`module_param(변수, 타입, 권한)`으로 파라미터를 등록한다.&lt;br /&gt;`MODULE_PARM_DESC()`로 설명을 반드시 붙인다.&lt;br /&gt;`0644` 권한이면 sysfs로 런타임 변경도 가능하다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 드라이버 코드를 보면 GPIO 핀 번호, 디바이스 주소, 디버그 레벨 같은 값들이 대부분 `module_param()`으로 노출되어 있습니다.&lt;br /&gt;이제 그 코드가 낯설지 않을 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 `/proc`파일 시스템을 통해 커널 내부 정보를 유저 스페이스에서 읽는 방법을 다룰 예정입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Linux Kernel Documentation: `Documetation/driver-api/basics.rst`&lt;/li&gt;
&lt;li&gt;`linux/moduleparam.h` 커널 소스&lt;/li&gt;
&lt;li&gt;`modinfo`,`sysfs` man page&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Dev/Linux</category>
      <category>KernelModule</category>
      <category>Linux</category>
      <category>module_param</category>
      <category>raspberrypi</category>
      <category>드라이버</category>
      <category>커널</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/18</guid>
      <comments>https://onepaperhoon.tistory.com/18#entry18comment</comments>
      <pubDate>Wed, 1 Apr 2026 19:27:46 +0900</pubDate>
    </item>
    <item>
      <title>Linux Kernel Module 개발 입문 - Hello World 부터 시작하기</title>
      <link>https://onepaperhoon.tistory.com/17</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 커널 모듈이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Linux 커널은 하나의 거대한 프로그램입니다. 그런데 모든 드라이버를 커널에 정적으로 포함시키면 커널 크기가 엄청나게 커지고, 새 드라이버를 추가할 때마다 커널을 다시 컴파일해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하는 것이 &lt;b&gt;Kernel Module(커널 모듈)&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 모듈 = &lt;b&gt;실행 중인 커널에 동적으로 추가하거나 제거할 수 있는 코드 조각&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;USB를 꽂으면 드라이버가 자동으로 로드되고, 뽑으면 언로드되는 것이 바로 커널 모듈 덕분입니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Linux 커널 (실행 중)
┌─────────────────────────────────────┐
│  Core Kernel                     │
│  ┌──────────┐  ┌──────────┐        │
│  │ Module A │  │ Module B │  &amp;larr; 동적 로드/언로드 가능
│  └──────────┘  └──────────┘        │
│  ┌──────────┐                     │
│  │ Module C │  &amp;larr; 우리가 만들 것    │
│  └──────────┘                     │
└─────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 모듈로 만들 수 있는 것들:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;디바이스 드라이버&lt;/b&gt; (GPIO, I2C, SPI, UART 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파일시스템&lt;/b&gt; (ext4, FAT 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네트워크 프로토콜&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시스템 콜 후킹&lt;/b&gt; (보안, 모니터링)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 유저스페이스 vs 커널스페이스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 모듈을 이해하려면 이 개념이 반드시 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Linux는 메모리를 두 영역으로 엄격하게 분리합니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;메모리 공간

높은 주소 ┌─────────────────────┐
          │                  │
          │   Kernel Space   │ &amp;larr; 커널, 드라이버, 모듈
          │   (보호된 영역)    │    직접 하드웨어 접근 가능
          │                  │
          ├────────────────────┤ &amp;larr; 경계선 (ARM: 0xC0000000)
          │                  │
          │   User Space     │ &amp;larr; 일반 앱 (bash, python 등)
          │   (제한된 영역)    │    하드웨어 직접 접근 불가
          │                  │
낮은 주소 └─────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유저스페이스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반 애플리케이션이 실행되는 영역&lt;/li&gt;
&lt;li&gt;하드웨어에 &lt;b&gt;직접 접근 불가&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;잘못된 메모리 접근 &amp;rarr; 프로세스만 죽음 (시스템은 안전)&lt;/li&gt;
&lt;li&gt;printf(), malloc(), read() 같은 libc 함수 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커널스페이스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커널과 드라이버가 실행되는 영역&lt;/li&gt;
&lt;li&gt;하드웨어에 &lt;b&gt;직접 접근 가능&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;잘못된 메모리 접근 &amp;rarr; &lt;b&gt;커널 패닉, 시스템 다운&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;libc 사용 불가 &amp;rarr; printk(), kmalloc() 같은 커널 API 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 커널 모듈은 커널스페이스에서 실행됩니다. printf() 대신 printk(), malloc() 대신 kmalloc()을 써야 합니다. 잘못 짜면 시스템이 죽습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유저스페이스에서 하드웨어에 접근하려면?&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;crmsh&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;User App
   │
   │  시스템 콜 (read, write, ioctl ...)
   ▼
Kernel (드라이버)
   │
   │  레지스터 접근
   ▼
Hardware&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저 앱은 시스템 콜을 통해 커널에 요청하고, 커널(드라이버)이 대신 하드웨어를 제어합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 커널 모듈 기본 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 모듈의 최소 구조는 딱 두 함수입니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;#include &amp;lt;linux/init.h&amp;gt;
#include &amp;lt;linux/module.h&amp;gt;

/* 모듈 로드 시 실행 &amp;mdash; insmod 할 때 */
static int __init hello_init(void)
{
    printk(KERN_INFO &quot;Hello, Kernel!\n&quot;);
    return 0;  /* 0 반환 = 성공, 음수 반환 = 실패 */
}

/* 모듈 언로드 시 실행 &amp;mdash; rmmod 할 때 */
static void __exit hello_exit(void)
{
    printk(KERN_INFO &quot;Goodbye, Kernel!\n&quot;);
}

module_init(hello_init);  /* init 함수 등록 */
module_exit(hello_exit);  /* exit 함수 등록 */

/* 모듈 메타데이터 */
MODULE_LICENSE(&quot;GPL&quot;);
MODULE_AUTHOR(&quot;OnePaperHoon&quot;);
MODULE_DESCRIPTION(&quot;Hello Kernel Module&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;각 요소 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;__init / __exit 매크로&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;static int __init hello_init(void)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;__init은 이 함수가 초기화 시에만 쓰인다는 힌트입니다. 모듈 로드 후 해당 함수 코드를 메모리에서 해제해 공간을 절약합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;printk() vs printf()&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;lisp&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;printk(KERN_INFO &quot;Hello, Kernel!\n&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널에는 printf()가 없습니다. 대신 printk()를 씁니다. 앞의 KERN_INFO는 로그 레벨입니다.&lt;/p&gt;
&lt;div&gt;로그 레벨의미
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;KERN_EMERG&lt;/td&gt;
&lt;td&gt;시스템 불능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KERN_ERR&lt;/td&gt;
&lt;td&gt;에러&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KERN_WARNING&lt;/td&gt;
&lt;td&gt;경고&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KERN_INFO&lt;/td&gt;
&lt;td&gt;일반 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KERN_DEBUG&lt;/td&gt;
&lt;td&gt;디버그&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MODULE_LICENSE(&quot;GPL&quot;)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반드시 선언해야 합니다. GPL이 아닌 라이선스를 선언하면 일부 커널 심볼을 사용할 수 없고, 로드 시 &quot;tainted kernel&quot; 경고가 뜹니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Makefile 작성법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 모듈은 일반 C 프로그램과 빌드 방식이 다릅니다. 커널 빌드 시스템(Kbuild)을 활용합니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;makefile&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;# Makefile

obj-m += hello.o          # 빌드할 모듈 이름 (.o 확장자)

# Raspberry Pi에서 직접 빌드할 경우
KDIR := /lib/modules/$(shell uname -r)/build

all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Makefile 핵심 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;obj-m&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;obj-m은 &quot;이 파일을 모듈로 빌드하라&quot;는 Kbuild 문법입니다. obj-y는 커널에 정적으로 포함시키는 것이고, obj-m은 동적 모듈(.ko 파일)로 빌드합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;$(KDIR)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 실행 중인 커널의 빌드 디렉토리입니다. uname -r로 커널 버전을 가져와 경로를 자동으로 맞춥니다. 크로스 컴파일(다른 머신에서 빌드)할 때는 이 경로를 대상 보드의 커널 소스로 바꿉니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-C $(KDIR) M=$(PWD)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 빌드 시스템 디렉토리로 이동해서(-C), 현재 디렉토리(M=$(PWD))의 모듈을 빌드하라는 뜻입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 빌드 및 실행 &amp;mdash; insmod / rmmod / lsmod&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;빌드&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;elixir&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;$ make&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공하면 hello.ko 파일이 생성됩니다. .ko가 커널 오브젝트(Kernel Object), 즉 모듈 파일입니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;brainfuck&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;$ ls -l
-rw-r--r-- hello.c
-rw-r--r-- Makefile
-rw-r--r-- hello.ko   &amp;larr; 이게 생겼으면 성공&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모듈 로드 &amp;mdash; insmod&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;elixir&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;$ sudo insmod hello.ko&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;insmod는 &lt;b&gt;ins&lt;/b&gt;ert &lt;b&gt;mod&lt;/b&gt;ule의 약자입니다. 커널에 모듈을 삽입합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로드 확인 &amp;mdash; lsmod&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;$ lsmod | grep hello
hello                  16384  0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lsmod는 현재 로드된 모듈 목록을 보여줍니다. grep으로 필터링해서 확인합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;printk 출력 확인 &amp;mdash; dmesg&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;$ dmesg | tail -5
[  123.456789] Hello, Kernel!&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;printk() 출력은 터미널에 바로 보이지 않습니다. &lt;b&gt;커널 로그 버퍼&lt;/b&gt;에 저장되고, dmesg 명령으로 확인합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모듈 언로드 &amp;mdash; rmmod&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;$ sudo rmmod hello
$ dmesg | tail -5
[  123.456789] Hello, Kernel!
[  145.678901] Goodbye, Kernel!&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rmmod는 &lt;b&gt;r&lt;/b&gt;e&lt;b&gt;m&lt;/b&gt;ove &lt;b&gt;mod&lt;/b&gt;ule의 약자입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;명령어 정리&lt;/h3&gt;
&lt;div&gt;명령어기능
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;insmod hello.ko&lt;/td&gt;
&lt;td&gt;모듈 로드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rmmod hello&lt;/td&gt;
&lt;td&gt;모듈 언로드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lsmod&lt;/td&gt;
&lt;td&gt;로드된 모듈 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;modinfo hello.ko&lt;/td&gt;
&lt;td&gt;모듈 정보 출력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dmesg&lt;/td&gt;
&lt;td&gt;커널 로그 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 실습 &amp;mdash; Raspberry Pi에서 Hello, Kernel!&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 준비&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 커널 헤더 설치 (Raspberry Pi OS 기준)
$ sudo apt update
$ sudo apt install kernel-headers-$(uname -r)

# 설치 확인
$ ls /lib/modules/$(uname -r)/build
```

### 파일 구성
```
hello_module/
├── hello.c
└── Makefile&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;hello.c&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;#include &amp;lt;linux/init.h&amp;gt;
#include &amp;lt;linux/module.h&amp;gt;
#include &amp;lt;linux/kernel.h&amp;gt;

static int __init hello_init(void)
{
    printk(KERN_INFO &quot;[hello] module loaded\n&quot;);
    printk(KERN_INFO &quot;[hello] kernel version: %s\n&quot;, UTS_RELEASE);
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO &quot;[hello] module unloaded\n&quot;);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE(&quot;GPL&quot;);
MODULE_AUTHOR(&quot;OnePaperHoon&quot;);
MODULE_DESCRIPTION(&quot;Hello Kernel Module for Raspberry Pi&quot;);
MODULE_VERSION(&quot;1.0&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;빌드 및 실행&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;elixir&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;# 빌드
$ make
make -C /lib/modules/6.1.21+/build M=/home/pi/hello_module modules
  CC [M]  /home/pi/hello_module/hello.o
  MODPOST /home/pi/hello_module/Module.symvers
  CC [M]  /home/pi/hello_module/hello.mod.o
  LD [M]  /home/pi/hello_module/hello.ko
make[1]: Leaving directory '/usr/src/linux-headers-6.1.21+'

# 로드
$ sudo insmod hello.ko

# 확인
$ dmesg | tail -3
[ 1234.567] [hello] module loaded
[ 1234.568] [hello] kernel version: 6.1.21+

# 언로드
$ sudo rmmod hello
$ dmesg | tail -1
[ 1289.012] [hello] module unloaded&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 자주 하는 실수 모음&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 실수 1: printf() 사용&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;/* 나쁜 예 &amp;mdash; 컴파일 에러 */
printf(&quot;Hello\n&quot;);

/* 좋은 예 */
printk(KERN_INFO &quot;Hello\n&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 모듈에서 libc 함수는 사용할 수 없습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 실수 2: init에서 0이 아닌 값 반환&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;static int __init hello_init(void)
{
    /* 초기화 실패 상황 */
    return 1;  /* 나쁜 예 &amp;mdash; 양수 반환 */
    return -ENOMEM;  /* 좋은 예 &amp;mdash; errno 음수값 반환 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;init 함수는 실패 시 반드시 음수 errno 값을 반환해야 합니다. 양수를 반환하면 예상치 못한 동작이 생길 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 실수 3: 커널 버전 불일치&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;$ sudo insmod hello.ko
insmod: ERROR: could not insert module hello.ko: Invalid module format&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈은 &lt;b&gt;빌드한 커널 버전과 로드할 커널 버전이 정확히 일치&lt;/b&gt;해야 합니다. 커널 업데이트 후에는 반드시 재빌드하세요.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;# 현재 커널 버전 확인
$ uname -r
6.1.21+

# 모듈이 빌드된 커널 버전 확인
$ modinfo hello.ko | grep vermagic
vermagic: 6.1.21+ SMP preempt mod_unload modversions ARMv7&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 모듈은 Linux 드라이버 개발의 시작점입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커널 모듈은 커널스페이스에서 실행된다 &amp;mdash; 실수하면 시스템이 죽는다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;printf 대신 printk, malloc 대신 kmalloc.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;빌드한 커널 버전과 로드할 커널 버전이 일치해야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hello World 수준이지만, 이 구조를 이해하면 GPIO 제어, I2C 드라이버, character device 구현까지 자연스럽게 이어집니다.&lt;/p&gt;</description>
      <category>Dev/Linux</category>
      <category>KernelModule</category>
      <category>Linux</category>
      <category>raspberrypi</category>
      <category>드라이버</category>
      <category>임베디드</category>
      <category>커널</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/17</guid>
      <comments>https://onepaperhoon.tistory.com/17#entry17comment</comments>
      <pubDate>Sat, 28 Mar 2026 20:19:34 +0900</pubDate>
    </item>
    <item>
      <title>STM32 NVIC, 인터럽트 우선순위 완전 정복</title>
      <link>https://onepaperhoon.tistory.com/16</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;버튼 누르면 UART 수신이 씹혀요&quot;, &quot;두 인터럽트가 동시에 터졌는데 어떻게 되나요?&quot;&lt;br /&gt;NVIC를 모르면 반드시 겪는 문제들입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 인터럽트가 뭔가요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 기본적으로 코드를 위에서 아래로 순서대로 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 현실에서는 &quot;지금 당장&quot; 처리해야 할 일이 생깁니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버튼이 눌렸다&lt;/li&gt;
&lt;li&gt;UART로 데이터가 들어왔다&lt;/li&gt;
&lt;li&gt;타이머가 만료됐다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 CPU에게 &quot;하던 일 잠깐 멈추고 이거 먼저 처리해&quot;라고 신호를 보내는 것이 인터럽트 (Interrupt)입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774534005665&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;일반 실행 흐름:

main() ──────────────────────────────────────►
         [코드 A] [코드 B]  [코드 C] [코드 D]


인터럽트 발생 시:

main() ──────────────┐         ┌──────────────►
         [코드 A] [코드 B]     [코드 C] [코드 D]
                     │         &amp;uarr;
                     ▼         │ 복귀
                  [ISR 실행] ───┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;CPU는 하던 일을 잠깐 멈추고 ISR(Interrupt Service Routine)을 실행한 뒤, 원래 자리로 돌아옵니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. NVIC란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NVIC = Nested Vectored Interrupt Controller&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름을 풀면&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;단어&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Nested&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;인터럽트 안에서 더 높은 우선순위 인터럽트가 끼어들 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Vectored&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;각 인터럽트마다 처리 함수(ISR) 주소가 벡터 테이블에 등록됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Interrupt Controller&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;인터럽트를 관리하는 하드웨어 블록&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 &quot;인터럽트 교통정리 담당자&quot;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 인터럽트가 동시에 발생했을 때:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 걸 먼저 처리할지 결정하고&lt;/li&gt;
&lt;li&gt;더 급한 인터럽트가 오면 현재 ISR을 중단하고 그것부터 처리하게 해주는&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하드웨어 모듈이 NVIC입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt; NVIC는 ARM Cortex-M 코어에 내장된 기능입니다.&lt;br /&gt;STM32 뿐 아니라 Cortex-M 기반 MCU라면 모두 동일한 개념이 적용됩니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. NVIC 핵심 개념&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선순위 숫자, 낮을수록 높다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NVIC에서 우선순위 숫자가 낮을수록 높습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774534264871&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Priority 0  &amp;rarr;  가장 높은 우선순위 (가장 급함)
Priority 1  &amp;rarr;  그 다음
Priority 2  &amp;rarr;  그 다음
...
Priority 15 &amp;rarr;  가장 낮은 우선순위&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 헷갈리지만, &quot;&lt;b&gt;0번이 1등&lt;/b&gt;&quot;이라고 기억하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Priority Group (우선순위 그룹)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STM32는 우선순위를 두 파트로 나눠서 관리합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774534327698&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;4비트 우선순위 레지스터
┌────────────────────────────────────────┐
│  Preemption Priority │ Subpriority  │
└────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 4비트를 어떻게 나눌지 결정하는 것이&lt;b&gt; Priority Group&lt;/b&gt; 설정입니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 157px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;Priority Group&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;Preemption 비트&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;Subpriority 비트&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;Preemption 단계&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;Subpriority 단계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 34px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;NVIC_PRIORITYGROUP_0&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;0비트&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;4비트&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;1단계&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;16단계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 34px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;NVIC_PRIORITYGROUP_1&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;1비트&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;3비트&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;2단계&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;8단계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 34px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;NVIC_PRIORITYGROUP_2&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;2비트&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;2비트&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;4단계&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 34px;&quot;&gt;4단계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;NVIC_PRIORITYGROUP_3&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;3비트&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;1비트&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;8단계&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;2단계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;NVIC_PRIORITYGROUP_4&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;4비트&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;0비트&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;16단계&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;1단계&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STM32 HAL의 기본값은 NVIC_PRIORITYGROUP_4 (Preemption 4비트, Subpriority 없음)입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Preemption vs Subpriority, 뭐가 다른가요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 NVIC에서 가장 헷갈리는 부분입니다. 핵심만 짚겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Preemption Priority (선점 우선순위)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;실행 중인 ISR을 중단시킬 수 있는가&lt;/b&gt;&quot;를 결정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774534590152&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;상황: ISR_A 실행 중 &amp;rarr; ISR_B 발생

ISR_B의 Preemption이 ISR_A보다 높으면:
  ISR_A 중단 &amp;rarr; ISR_B 실행 &amp;rarr; ISR_A 재개  &amp;larr; 선점(Preemption) 발생

ISR_B의 Preemption이 ISR_A보다 낮거나 같으면:
  ISR_A 완료 &amp;rarr; ISR_B 실행  &amp;larr; 선점 없음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Subpriority (부우 선순위)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;같은 Preemption 우선순위끼리 동시에 발생했을 때 누가 먼저냐&lt;/b&gt;&quot;를 결정합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Subpriority는 선점을 일으키지 않습니다&lt;/li&gt;
&lt;li&gt;동시에 pending 상태일 때 처리 순서만 결정합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;비교 항목&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Preemption Priority&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Subpriority&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;선점 가능 여부&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;가능&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;영향 범위&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;실행 중인 ISR을 끊음&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;대기 중인 인터럽트 순서만 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;중요도&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;더 중요&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;덜 중요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt; 실무 팁 : 대부분의 STM32 프로젝트에서는 NVIC_PRIORITYGROUP_4를 쓰고 Preemption만 관리합니다. Subpriority까지 쓰면 복잡도만 올라갑니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. STM32 CubeMX에서 NVIC 설정하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CubeMX에서 NVIC 설정은 두 곳에서 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;① System Core &amp;rarr; NVIC 탭&lt;/p&gt;
&lt;pre id=&quot;code_1774535251389&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Pinout &amp;amp; Configuration
└── System Core
    └── NVIC
        ├── Priority Group 선택  &amp;larr; 전체 그룹 설정
        └── 각 인터럽트별 Enable / Priority 설정&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;② 각 Peripheral 설정 탭의 NVIC 섹션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 USART2를 설정할 때:&lt;/p&gt;
&lt;pre id=&quot;code_1774535300582&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Connectivity &amp;rarr; USART2 &amp;rarr; NVIC Settings 탭
    [✓] USART2 global interrupt   Preemption: 1   Sub: 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 예시 &amp;mdash; 버튼(EXTI) + UART 수신 공존&lt;/p&gt;
&lt;pre id=&quot;code_1774535312316&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Priority Group: NVIC_PRIORITYGROUP_4 (Preemption 4비트)

EXTI0 (버튼)         Preemption: 2
USART2 (UART RX)     Preemption: 1  &amp;larr; 더 높은 우선순위
TIM2 (타이머)         Preemption: 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UART 수신이 더 급하다고 판단해 우선순위를 높게 (숫자 낮게) 설정한 예입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 실제 코드로 확인하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CubeMX가 생성해 주는 코드를 보면 NVIC 설정이 어떻게 되는지 이해할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HAL이 생성하는 초기화 코드&lt;/p&gt;
&lt;pre id=&quot;code_1774535361684&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* main.c 또는 stm32f4xx_hal_msp.c */

/* Priority Group 설정 (HAL_Init() 내부에서 자동 호출) */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

/* USART2 인터럽트 설정 */
HAL_NVIC_SetPriority(USART2_IRQn, 1, 0);  // Preemption=1, Sub=0
HAL_NVIC_EnableIRQ(USART2_IRQn);

/* EXTI0 (버튼) 인터럽트 설정 */
HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);   // Preemption=2, Sub=0
HAL_NVIC_EnableIRQ(EXTI0_IRQn);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ISR 등록 &amp;mdash; stm32 f4 xx_it.c&lt;/p&gt;
&lt;pre id=&quot;code_1774535370411&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* USART2 인터럽트 핸들러 */
void USART2_IRQHandler(void)
{
    HAL_UART_IRQHandler(&amp;amp;huart2);  // HAL이 내부에서 콜백 호출
}

/* EXTI0 인터럽트 핸들러 */
void EXTI0_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수에서 실제 처리&lt;/p&gt;
&lt;pre id=&quot;code_1774535379917&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* UART 수신 완료 콜백 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart-&amp;gt;Instance == USART2)
    {
        /* 수신된 데이터 처리 */
        /* ISR 안이므로 최대한 짧게 &amp;mdash; 플래그 세우고 main에서 처리 권장 */
        rx_flag = 1;
    }
}

/* GPIO EXTI 콜백 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == GPIO_PIN_0)
    {
        /* 버튼 처리 */
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선점이 실제로 일어나는 상황&lt;/p&gt;
&lt;pre id=&quot;code_1774535391229&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 시나리오:
   EXTI0 ISR 실행 중 &amp;rarr; USART2 인터럽트 발생
   USART2 Preemption(1) &amp;lt; EXTI0 Preemption(2) &amp;rarr; 선점 발생 */

void EXTI0_IRQHandler(void)
{
    // 여기 실행 중...
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
    // &amp;uarr; 이 중간에 USART2 인터럽트가 오면
    //   EXTI0 ISR이 중단되고 USART2_IRQHandler가 먼저 실행됨
    //   USART2 완료 후 여기로 복귀
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 자주 하는 실수 모음&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ 실수 1: ISR 안에서 오래 걸리는 작업&lt;/h4&gt;
&lt;pre id=&quot;code_1774535435418&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 나쁜 예 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    HAL_Delay(100);          // ISR 안에서 delay &amp;mdash; 절대 금지!
    process_large_buffer();  // 무거운 연산 &amp;mdash; 금지!
}

/* 좋은 예 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    rx_flag = 1;  // 플래그만 세우고
                  // 실제 처리는 main loop에서
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ISR은 최대한 짧게, 플래그 세우고 빠져나오는 것이 원칙입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ 실수 2: FreeRTOS와 함께 쓸 때 우선순위 범위 착각&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FreeRTOS를 사용하는 경우 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 이상의 우선순위(낮은 숫자)를 가진 ISR 안에서는 FreeRTOS API를 호출하면 안 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774535512228&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* FreeRTOS 사용 시 &amp;mdash; ISR 안에서는 FromISR 버전 사용 */
xQueueSendFromISR(queue, &amp;amp;data, &amp;amp;xHigherPriorityTaskWoken);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ 실수 3: Priority Group 중간에 변경&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Priority Group을 바꾸면 이미 설정된 모든 인터럽트 우선순위 해석이 달라집니다. 반드시 초기화 시점에 한 번만 설정하세요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. 마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NVIC를 이해하면 임베디드 개발에서 겪는 타이밍 버그의 절반은 원인을 파악할 수 있게 됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;우선순위 숫자가 낮을수록 높다.&lt;br /&gt;Preemption은 선점, Subpriority는 대기 순서.&lt;br /&gt;ISR은 짧게, 무거운 일은 main loop에서.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 가지만 기억해도 NVIC 관련 문제의 대부분을 다룰 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 UART Interrupt 모드와 DMA 모드를 비교하며, NVIC 설정이 실제 통신 안정성에 어떤 영향을 주는지 실습해 볼 예정입니다.&lt;/p&gt;</description>
      <category>TIL</category>
      <category>Cortx-M</category>
      <category>CUBEMX</category>
      <category>NVIC</category>
      <category>STM32</category>
      <category>인터럽트</category>
      <category>임베디드</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/16</guid>
      <comments>https://onepaperhoon.tistory.com/16#entry16comment</comments>
      <pubDate>Tue, 24 Mar 2026 21:38:28 +0900</pubDate>
    </item>
    <item>
      <title>UART 통신, 제대로 이해하기</title>
      <link>https://onepaperhoon.tistory.com/15</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;임베디드를 시작하면 가장 먼저 만나는 통신 프로토콜, UART. &quot;그냥 시리얼 통신이잖아요&quot;라고 넘기기기엔 아는 만큼 보이는 게 달라집니다.&amp;nbsp;&amp;nbsp;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. UART가 뭔가요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UART는 &lt;b&gt;Universal Asynchronous Receiver/Transmitter&lt;/b&gt;의 약자입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름을 풀어보면 그 자체로 설명이 됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 67px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 16px;&quot;&gt;단어&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 16px;&quot;&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;Universal&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;다양한 디바이스와 호환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;Asynchronous&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;클럭 신호 없이 통신 (비동기)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;Receiver/Transmitter&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;송신과 수신을 모두 담당&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베디드 개발을 하다 보면 UART를 가장 먼저, 가장 많이 만납니다. MCU와 PC를 연결해 디버깅 메시지를 출력하거나, 두 MCU끼리 데이터를 주고받을 때 쓰입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 직렬 vs 병렬 통신&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UART를 이해하려면 먼저 &lt;b&gt;직렬(Serial)&lt;/b&gt; 통신이 무엇인지 알아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬 통신 (Parallel)&lt;/p&gt;
&lt;pre id=&quot;code_1774531187370&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;데이터: 0b10110101

선 8개 &amp;rarr; 한 번에 8비트 전송
D7 ──────────────
D6 ──────────────
D5 ──────────────
...
D0 ──────────────&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠르지만 선이 많이 필요하고, 거리가 멀어질수록 신호 간 &lt;b&gt;간섭(노이즈)&lt;/b&gt; 문제가 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직렬 통신 (Serial)&lt;/p&gt;
&lt;pre id=&quot;code_1774531225712&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;데이터: 0b10110101

선 1개 &amp;rarr; 1비트씩 순서대로 전송
TX ─ 1 ─ 0 ─ 1 ─ 1 ─ 0 ─ 1 ─ 0 ─ 1 ──&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선이 적고 안정적입니다. 느린 것처럼 보이지만, Baud Rate를 높이면 충분히 빠릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UART는 직렬 통신입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. UART의 동작 원리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UART는 단 2개의 선으로 통신합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774531315228&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MCU A                MCU B
  
  TX  ───────────────► RX
  RX  ◄─────────────── TX
  GND ─────────────── GND  &amp;larr; 반드시 공통 GND!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;GND를 꼭 연결해야 합니다. 전압 기준이 달라지면 신호를 제대로 읽지 못합니다.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;비동기(Asynchronous)란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I2C나 SPI 같은 통신은 클럭 신호(CLK)를 함께 보내서 &quot;지금 데이터 읽어&quot;라는 타이밍을 맞춥니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UART는 클럭이 없습니다. 그럼 어떻게 타이밍을 맞출까요?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;nbsp; 송수신 양쪽이 미리 같은 속도 (Baud Rate)를 약속합니다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약속된 속도를 기준으로 수신 측이 스스로 타이밍을 계산해 비트를 읽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 &quot;약속&quot;이 조금이라도 어긋나면 데이터가 깨집니다. 그래서 파라미터 설정이 중요합니다.&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 핵심 파라미터&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UART를 쓸 때 반드시 양쪽이 동일하게 설정해야 하는 4가지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;① Baud Rate (보드레이트)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1 초에 몇 비트를 전송하는가를 나타냅니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;주요 Baud Rate&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;특징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;9600&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;가장 기본값, 느리지만 안정적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;115200&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;임베디드 개발에서 가장 많이 씀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;921600이상&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;고속, 노이즈에 민감&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 115200 bps라면, 1비트를 전송하는 데 걸리는 시간은 약 8.68 &amp;mu;s입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;② 데이터 비트 (Data Bits)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번에 전송하는 데이터의 비트 수입니다. 보통 &lt;b&gt;8비트&lt;/b&gt;를 씁니다. (7비트도 가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;③ 패리티 비트 (Parity Bit)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류 검출을 위한 비트입니다. 보통 &lt;b&gt;None(없음)으로&lt;/b&gt; 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설정&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;None&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;패리티 없음 (가장 일반적)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Even&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1의 개수가 짝수가 되도록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Odd&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1의 개수가 홀수가 되도&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;④ 스톱 비트 (Stop Bit)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임의 끝을 알리는 비트입니다. 보통 &lt;b&gt;1비트&lt;/b&gt;를 씁니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  정리: &quot;115200 8N1&quot;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베디드 개발에서 UART 설정을 간단히 표현할 때 이런 식으로 씁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774531732219&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;115200  8   N   1
  │     │   │   └─ Stop bit: 1
  │     │   └───── Parity: None
  │     └───────── Data bits: 8
  └─────────────── Baud Rate: 115200&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. UART 프레임 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 데이터가 어떻게 전송되는지 프레임 구조를 보면 명확해집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774531799329&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;평상시 (Idle) &amp;rarr; Start &amp;rarr; D0 D1 D2 D3 D4 D5 D6 D7 &amp;rarr; (Parity) &amp;rarr; Stop &amp;rarr; Idle

HIGH ─────┐                                           ┌──────────
          │  S  0  1  0  1  0  0  0  1  N  P         │
LOW       └──────────────────────────────────────────────┘
          &amp;uarr;                                              &amp;uarr;
        Start bit (항상 LOW)                    Stop bit (항상 HIGH)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Idle 상태: 아무것도 안 보낼 때는 HIGH를 유지합니다&lt;/li&gt;
&lt;li&gt;Start bit: 전송 시작을 알리는 LOW 신호 1비트&lt;/li&gt;
&lt;li&gt;Data bits: LSB(최하위 비트)부터 순서대로 전송&lt;/li&gt;
&lt;li&gt;Stop bit: 전송 종료를 알리는 HIGH 신호&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. UART vs USART&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STM32 같은 MCU의 데이터시트를 보면 USART라는 용어가 자주 등장합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;UART&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;USART&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;풀네임&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Universal Aysnc R/T&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Universal Sync/Async R/T&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;클럭 라인&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;있음 (동기 모드 지원)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;일반 사용&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;비동기 통신&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;대부분 비동기로 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;USART는 UART의 상위 호환입니다. 동기 모드 (SCLK 포함)도 지원하지만, 실제로는 대부분 비동기 (UART) 모드로 사용합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 실제로 어디에 쓰이나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베디드 개발에서 UART가 활약하는 대표적인 상황들입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;디버깅 (가장 중요!)&lt;br /&gt;`printf()`로 변수값이나 상태 메시지를 PC 터미널에 출력합니다. 임베디드에서의 `console.log()`라고 생각하면 됩니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;GPS 모듈 연동&lt;br /&gt;GPS 모듈은 대부분 UART로 NMEA 포맷 데이터를 전송합니다.&lt;/li&gt;
&lt;li&gt;블루투스/Wi-Fi 모듈&lt;br /&gt;HC-06, ESP8266 같은 모듈이 UART로 MCU와 통신합니다.&lt;/li&gt;
&lt;li&gt;MCU 간 통신&amp;nbsp;&lt;br /&gt;두 MCU가 간단한 데이터를 주고받을 때 UART가 편리합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. 마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UART는 단순해 보이지만, 임베디드 개발의 기초 중 기초입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Baud Rate가 다르면&amp;nbsp; 데이터가 깨져서 해석이 불가능하다&lt;br /&gt;GND를 연결하지 않으면 통신이 안 된다.&lt;br /&gt;TX-&amp;gt;RX, RX-&amp;gt;TX로 교차 연결해야 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 가지만 기억해도 UART 관련트러블 슈팅의 절반은 해결됩니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>TIL</category>
      <category>MCU</category>
      <category>STM32</category>
      <category>UART</category>
      <category>시리얼 통신</category>
      <category>임베디드</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/15</guid>
      <comments>https://onepaperhoon.tistory.com/15#entry15comment</comments>
      <pubDate>Tue, 24 Mar 2026 21:38:09 +0900</pubDate>
    </item>
    <item>
      <title>[IPC] Shared Memory - 가장 빠른 프로세스 간 통신</title>
      <link>https://onepaperhoon.tistory.com/14</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글에서 FIFO (Named Pipe)를 다뤘는데, 실습하면서 한 가지 아쉬운 점이 있었습니다. 바로 &lt;b&gt;속도&lt;/b&gt;였죠. 간단한 메시지 주고받기에는 충분했지만, 큰 데이터를 전송하려니 느렸습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 느릴까요? FIFO는 Kernel을 거쳐서 데이터를 복사하기 때문입니다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;FIFO 방식:
Writer &amp;rarr; [User Space]
       &amp;darr; (복사!)
     [Kernel Space]
       &amp;darr; (또 복사!)
     [User Space] &amp;rarr; Reader

&amp;rarr; 2번 복사! 느림!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;메모리를 그냥 공유하면 안 되나?&quot; 라는 생각이 들었습니다. 그게 바로 오늘 다룰 &lt;b&gt;Shared Memory (공유 메모리)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Shared Memory란?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개념&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shared Memory는 여러 프로세스가 &lt;b&gt;같은 메모리 영역을 공유&lt;/b&gt;하는 IPC 방식입니다. 복사 없이 직접 접근하기 때문에 &lt;b&gt;IPC 중 가장 빠릅니다&lt;/b&gt;.&lt;/p&gt;
&lt;pre class=&quot;vhdl&quot;&gt;&lt;code&gt;Shared Memory 방식:
Process A ──&amp;rarr; [Shared Memory] &amp;larr;── Process B
             (공유된 메모리)

&amp;rarr; 복사 없음! 빠름!&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능 비교 (1MB 데이터 전송)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 벤치마크를 돌려봤습니다:&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;FIFO:            8.5 ms
Shared Memory:   0.8 ms

&amp;rarr; 약 10배 차이&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;FIFO vs Shared Memory&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;특징&lt;/th&gt;
&lt;th&gt;FIFO&lt;/th&gt;
&lt;th&gt;Shared Memory&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;속도&lt;/td&gt;
&lt;td&gt;보통 (~8ms)&lt;/td&gt;
&lt;td&gt;매우 빠름 (~0.8ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;복사&lt;/td&gt;
&lt;td&gt;2번 (User&amp;rarr;Kernel&amp;rarr;User)&lt;/td&gt;
&lt;td&gt;0번&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;동기화&lt;/td&gt;
&lt;td&gt;자동 (Kernel이 처리)&lt;/td&gt;
&lt;td&gt;수동 (Semaphore 필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용 난이도&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 크기&lt;/td&gt;
&lt;td&gt;작은 메시지&lt;/td&gt;
&lt;td&gt;큰 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;언제 뭘 쓸까?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간단한 통신 &amp;rarr; FIFO&lt;/li&gt;
&lt;li&gt;대용량 데이터 &amp;rarr; Shared Memory&lt;/li&gt;
&lt;li&gt;실시간 성능 중요 &amp;rarr; Shared Memory&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실전: Shared Memory 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이론은 여기까지! 직접 만들어 봅시다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;POSIX vs System V&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shared Memory에는 두 가지 API가 있습니다:&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;System V: 오래된 방식 (shmget, shmat)
POSIX:    최신 방식 (shm_open, mmap)

&amp;rarr; POSIX를 사용합니다 (더 깔끔함)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계: Shared Memory 생성&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;#include &amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;fcntl.h&amp;gt;

#define SHM_NAME &quot;/my_shm&quot;
#define SHM_SIZE 4096

int main(void)
{
    // 1. Shared Memory 객체 생성
    int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    if (fd == -1) {
        perror(&quot;shm_open&quot;);
        exit(1);
    }

    // 2. 크기 설정
    ftruncate(fd, SHM_SIZE);

    // 3. 메모리 매핑
    void *ptr = mmap(NULL, SHM_SIZE, 
                     PROT_READ | PROT_WRITE,
                     MAP_SHARED, fd, 0);

    if (ptr == MAP_FAILED) {
        perror(&quot;mmap&quot;);
        exit(1);
    }

    printf(&quot;Shared Memory created!\n&quot;);
    printf(&quot;Address: %p\n&quot;, ptr);

    // 이제 ptr을 일반 메모리처럼 사용!
    strcpy(ptr, &quot;Hello, Shared Memory!&quot;);

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계: Writer 프로그램&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// shm_writer.c
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#define SHM_NAME &quot;/chat_shm&quot;
#define SHM_SIZE 4096

int main(void)
{
    // SHM 생성
    int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    ftruncate(fd, SHM_SIZE);

    // 메모리 매핑
    char *ptr = mmap(NULL, SHM_SIZE, 
                     PROT_READ | PROT_WRITE,
                     MAP_SHARED, fd, 0);

    printf(&quot;=== Writer ===\n&quot;);
    printf(&quot;Enter messages (type 'quit' to exit):\n&quot;);

    while (1) {
        printf(&quot;You: &quot;);
        fgets(ptr, SHM_SIZE, stdin);

        if (strncmp(ptr, &quot;quit&quot;, 4) == 0)
            break;

        printf(&quot;Written to shared memory!\n&quot;);
        sleep(1);  // Reader가 읽을 시간
    }

    // 정리
    munmap(ptr, SHM_SIZE);
    close(fd);
    shm_unlink(SHM_NAME);

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3단계: Reader 프로그램&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// shm_reader.c
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#define SHM_NAME &quot;/chat_shm&quot;
#define SHM_SIZE 4096

int main(void)
{
    // SHM 열기
    int fd = shm_open(SHM_NAME, O_RDONLY, 0666);
    if (fd == -1) {
        perror(&quot;shm_open&quot;);
        exit(1);
    }

    // 메모리 매핑
    char *ptr = mmap(NULL, SHM_SIZE,
                     PROT_READ,
                     MAP_SHARED, fd, 0);

    printf(&quot;=== Reader ===\n&quot;);
    printf(&quot;Reading from shared memory...\n&quot;);

    char prev[SHM_SIZE] = {0};

    while (1) {
        // 내용이 바뀌었으면 출력
        if (strcmp(ptr, prev) != 0) {
            printf(&quot;Received: %s&quot;, ptr);
            strcpy(prev, ptr);

            if (strncmp(ptr, &quot;quit&quot;, 4) == 0)
                break;
        }

        usleep(100000);  // 100ms마다 체크
    }

    // 정리
    munmap(ptr, SHM_SIZE);
    close(fd);

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴파일 &amp;amp; 실행&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;# 컴파일 (중요: -lrt 옵션!)
$ gcc shm_writer.c -o writer -lrt
$ gcc shm_reader.c -o reader -lrt

# 터미널 1
$ ./reader
=== Reader ===
Reading from shared memory...

# 터미널 2
$ ./writer
=== Writer ===
Enter messages:
You: Hello!
Written to shared memory!

# 터미널 1 출력
Received: Hello!&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;함정: 동기화 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 실행했을 때 이상한 현상이 발생했습니다.&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;Writer: &quot;Hello World!&quot;
Reader: &quot;Hello Wor&quot;
Reader: &quot;Hello World!&quot;
Reader: &quot;Hello World!!@#$%&quot;  &amp;larr; 쓰레기 값!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제:&lt;/b&gt; Writer가 쓰는 도중에 Reader가 읽음!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Race Condition&lt;/h3&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;T0: Writer: ptr[0] = 'H'
T1: Writer: ptr[1] = 'e'
T2: Reader: 읽기! &amp;rarr; &quot;He&quot; (불완전!)
T3: Writer: ptr[2] = 'l'
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 바로 &lt;b&gt;Race Condition&lt;/b&gt;입니다. FIFO는 Kernel이 알아서 처리해줬는데, Shared Memory는 우리가 직접 처리해야 합니다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결책: Semaphore&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Semaphore로 동기화를 구현합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;#include &amp;lt;semaphore.h&amp;gt;

sem_t *sem;

// Writer
sem = sem_open(&quot;/my_sem&quot;, O_CREAT, 0666, 1);

sem_wait(sem);      // Lock
strcpy(ptr, msg);   // 쓰기
sem_post(sem);      // Unlock

// Reader
sem_wait(sem);      // Lock
strcpy(buf, ptr);   // 읽기
sem_post(sem);      // Unlock&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전한 코드:&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// shm_writer_sync.c
int main(void)
{
    int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    ftruncate(fd, SHM_SIZE);
    char *ptr = mmap(NULL, SHM_SIZE, 
                     PROT_READ | PROT_WRITE,
                     MAP_SHARED, fd, 0);

    // Semaphore 생성
    sem_t *sem = sem_open(&quot;/chat_sem&quot;, O_CREAT, 0666, 1);

    while (1) {
        printf(&quot;You: &quot;);
        char msg[256];
        fgets(msg, sizeof(msg), stdin);

        if (strncmp(msg, &quot;quit&quot;, 4) == 0)
            break;

        // Critical Section
        sem_wait(sem);
        strcpy(ptr, msg);
        sem_post(sem);

        printf(&quot;Written safely!\n&quot;);
    }

    // 정리
    sem_close(sem);
    sem_unlink(&quot;/chat_sem&quot;);
    munmap(ptr, SHM_SIZE);
    close(fd);
    shm_unlink(SHM_NAME);

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 안전합니다!&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Shared Memory의 특징&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 파일처럼 보이지만 메모리&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# SHM 확인
$ ls -l /dev/shm/
-rw-r--r-- 1 user user 4096 Feb 27 15:00 chat_shm

&amp;rarr; /dev/shm에 파일처럼 생김
&amp;rarr; 실제로는 RAM!&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 프로세스 종료 후에도 남아있음&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;$ ./writer  # 실행 후 종료

$ ls /dev/shm/
chat_shm  # 여전히 존재!

&amp;rarr; 명시적으로 삭제 필요: shm_unlink()&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 크기 제한&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;# 시스템 최대 크기 확인
$ cat /proc/sys/kernel/shmmax
18446744073692774399  # 약 16EB (충분함)

# 실제 사용 중인 SHM
$ ipcs -m&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무 활용 사례&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 고성능 데이터베이스&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;PostgreSQL, Redis 등:
- Index를 SHM에 캐싱
- 여러 프로세스가 동시 접근
- 빠른 읽기 성능&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 멀티미디어 처리&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;비디오/오디오 스트리밍:
- 프레임 버퍼를 SHM에
- Encoder/Decoder가 공유
- 복사 없이 처리 &amp;rarr; 실시간 가능&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. IPC 최적화&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;대용량 데이터 전송:
- 파일 공유
- 메모리 캐시
- 공유 큐&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 동기화 필수!&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;// ❌ 위험
strcpy(shm_ptr, data);  // Race Condition!

// ✅ 안전
sem_wait(sem);
strcpy(shm_ptr, data);
sem_post(sem);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 크기 설정 필수&lt;/h3&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;// ftruncate() 없으면 크기가 0!
shm_open(...);
ftruncate(fd, SIZE);  // 필수!
mmap(...);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 정리 필수&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// 프로그램 종료 시
munmap(ptr, SIZE);
close(fd);
shm_unlink(SHM_NAME);  // 삭제!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안 하면 /dev/shm에 파일 계속 남음!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;성능 측정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 벤치마크를 돌려봤습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 환경&lt;/h3&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;CPU: Apple M1
RAM: 16GB
OS: macOS (Linux 유사)
데이터: 1MB&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// FIFO
for (int i = 0; i &amp;lt; 1000; i++) {
    write(fifo_fd, data, 1024);
}
&amp;rarr; 평균: 8.5ms

// Shared Memory
for (int i = 0; i &amp;lt; 1000; i++) {
    memcpy(shm_ptr, data, 1024);
}
&amp;rarr; 평균: 0.8ms

속도 차이: 약 10배!&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디버깅 팁&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SHM 확인&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;# 현재 SHM 목록
$ ls -lh /dev/shm/

# SHM 크기 확인
$ du -h /dev/shm/chat_shm
4.0K    /dev/shm/chat_shm

# SHM 내용 보기
$ cat /dev/shm/chat_shm
Hello, Shared Memory!&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ipcs 명령어&lt;/h3&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;# System V SHM (옛날 방식)
$ ipcs -m

# 특정 SHM 삭제
$ ipcrm -m &amp;lt;shmid&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;gdb로 메모리 확인&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;$ gdb ./writer
(gdb) break main
(gdb) run
(gdb) print ptr
$1 = 0x7f1234567000

(gdb) x/10s 0x7f1234567000
0x7f1234567000: &quot;Hello&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shared Memory를 구현하면서 &lt;b&gt;속도와 안전성&lt;/b&gt;의 트레이드오프를 배웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배운 점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복사 없는 직접 접근 &amp;rarr; 10배 빠름&lt;/li&gt;
&lt;li&gt;동기화는 직접 해야 함 (Semaphore)&lt;/li&gt;
&lt;li&gt;ftruncate() 필수&lt;/li&gt;
&lt;li&gt;shm_unlink()로 정리 필수&lt;/li&gt;
&lt;li&gt;/dev/shm에서 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIFO는 간단하지만 느리고, Shared Memory는 빠르지만 복잡합니다. 상황에 맞게 선택하는 게 중요합니다.&lt;/p&gt;</description>
      <category>Dev/System Programming</category>
      <category>IPC</category>
      <category>shared memory</category>
      <category>공유 메모리</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/14</guid>
      <comments>https://onepaperhoon.tistory.com/14#entry14comment</comments>
      <pubDate>Fri, 27 Feb 2026 16:03:55 +0900</pubDate>
    </item>
    <item>
      <title>FIFO (Named Pipe) : 프로세스 간 통신의 기본</title>
      <link>https://onepaperhoon.tistory.com/13</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 간 통신(IPC)을 공부하다 보면 가장 먼저 만나는 개념이 Pipe입니다.&lt;br /&gt;그런데 Pipe는 부모-자식 프로세스처럼 혈연관계가 있는 프로세스들끼리만 통신할 수 있다는 제약이 있죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 완전히 독립적인 두 프로그램이 통신하려면 어떻게 해야 할까요?&lt;br /&gt;바로 여기서&amp;nbsp;&lt;b&gt;FIFO(Named Pipe)&lt;/b&gt;가 등장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서 FIFO의 개념부터 실제 구현까지, 직접 코드를 작성하며 겪은 문제들과 해결 과정을 공유하겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FIFO란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FIFO = First In First Out&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIFO는 &quot;First In First Out&quot;의 약자로, 먼저 들어간 데이터가 먼저 나오는 자료구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마치 은행 대기줄처럼 선착순으로 처리되죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1771745245072&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;입구 &amp;rarr;  [A][B][C][D]  &amp;rarr; 출구
        &amp;uarr;           &amp;darr;
      나중에       먼저
      들어감       나감&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Named Pipe = FIFO&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Linux에서 FIFO는 &quot;Named Pipe&quot;라고도 불립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 일반 Pipe와 달리&amp;nbsp;&lt;b&gt;파일시스템에 이름을 가진&lt;/b&gt;&amp;nbsp;&lt;b&gt;파일&lt;/b&gt;로 존재하기 때문입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771745348039&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ mkfifo /tmp/myfifo
$ ls -l /tmp/myfifo
prw-rw-r-- 1 user user 0 Feb 21 10:00 /tmp/myfifo
&amp;uarr;
p = pipe (FIFO)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 타입이 `p`로 표시되는 것을 볼 수 있습니다. 이것이 바로 FIFO 파일입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Pipe vs FIFO&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개념을 비교하면 차이가 명확해집니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;특징&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;Pipe&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;FIFO (Named Pipe)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;이름&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;익명 (이름 없음)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;파일명으로 접근&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;프로세스 관계&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;부모-자식 관계 필수&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;완전히 독립적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;파일시스템&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;존재하지 않음&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;파일로 존재&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;생성 방법&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;pipe()&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mkfifo()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;사용 예시&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;ls | grep&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;독립 프로그램 간 통신&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pipe의 제약&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771745599726&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Pipe는 fork() 필수
int pipefd[2];
pipe(pipefd);
fork();  // 부모-자식 관계!

if (pid == 0) {
    // 자식: read
} else {
    // 부모: write
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FIFO의 자유로움&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771745614893&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 프로세스 A (writer.c)
int fd = open(&quot;/tmp/myfifo&quot;, O_WRONLY);
write(fd, &quot;Hello&quot;, 5);

// 프로세스 B (reader.c) - 완전히 독립!
int fd = open(&quot;/tmp/myfifo&quot;, O_RDONLY);
read(fd, buf, 100);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실전: FIFO Chat 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이론만으로는 부족하죠. 직접 간단한 채팅 프로그램을 만들어 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1단계 : FIFO 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771745889047&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;sys/stat.h&amp;gt;

#define FIFO_PATH &quot;/tmp/chat_fifo&quot;

// FIFO 생성
if (mkfifo(FIFO_PATH, 0666) == -1) {
    perror(&quot;mkfifo&quot;);
    exit(1);
}

// 권한: 0666 = rw-rw-rw-&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2단계 : Writer 프로그램&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771745914662&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// fifo_writer.c
int main(void)
{
    char message[256];
    int fd;
    
    // FIFO 열기 (쓰기 모드)
    fd = open(FIFO_PATH, O_WRONLY);
    
    while (1) {
        printf(&quot;You: &quot;);
        fgets(message, sizeof(message), stdin);
        
        if (strcmp(message, &quot;quit\n&quot;) == 0)
            break;
        
        // 메시지 전송
        write(fd, message, strlen(message) + 1);
    }
    
    close(fd);
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3단계 : Reader 프로그램&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771745939245&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// fifo_reader.c
int main(void)
{
    char message[256];
    int fd;
    
    // FIFO 열기 (읽기 모드)
    fd = open(FIFO_PATH, O_RDONLY);
    
    while (1) {
        ssize_t n = read(fd, message, sizeof(message));
        
        if (n == 0) {  // EOF
            printf(&quot;Writer disconnected\n&quot;);
            break;
        }
        
        printf(&quot;Received: %s&quot;, message);
    }
    
    close(fd);
    unlink(FIFO_PATH);  // FIFO 삭제
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 방법&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771745954395&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 터미널 1
$ gcc fifo_reader.c -o reader
$ ./reader
Opening FIFO...
(Waiting for writer...)

# 터미널 2
$ gcc fifo_writer.c -o writer
$ ./writer
Connected!
You: Hello!
You: How are you?
You: quit&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;길이 프로토콜&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 메시지 길이를 먼저 보내는 프로토콜을 많이 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771747292725&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Writer
uint32_t msg_len = strlen(message) + 1;
write(fd, &amp;amp;msg_len, sizeof(msg_len));  // 길이 먼저
write(fd, message, msg_len);           // 메시지

// Reader
uint32_t msg_len;
read(fd, &amp;amp;msg_len, sizeof(msg_len));   // 길이 먼저
read(fd, message, msg_len);            // 정확히 msg_len만큼&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 메시지 경계를 정확히 알 수 있어서 훨씬 안정적입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FIFO의 특성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Blocking I/O&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIFO의 기본 동작은 blocking입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1771747878161&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Writer
fd = open(&quot;/tmp/myfifo&quot;, O_WRONLY);
// &amp;uarr; Reader가 열 때까지 블로킹!

// Reader
fd = open(&quot;/tmp/myfifo&quot;, O_RDONLY);
// &amp;uarr; Writer가 열 때까지 블로킹!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작 순서:&lt;/p&gt;
&lt;pre id=&quot;code_1771747893052&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;T0: Reader: open() 호출 &amp;rarr; 블로킹...
T1: Writer: open() 호출
T2: 둘 다 연결됨!
T3: Reader: open() 리턴
T4: Writer: open() 리턴&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;EOF 처리:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Writer가 FIFO를 닫으면 Reader는 EOF를 받습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771748401177&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Writer
write(fd, &quot;Last message&quot;, 13);
close(fd);  // EOF 전달!

// Reader
while (1) {
    n = read(fd, buf, size);
    if (n == 0) {  // EOF
        printf(&quot;Writer disconnected\n&quot;);
        break;
    }
    process(buf);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실무 활용 사례&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 로그 수집 시스템&lt;/p&gt;
&lt;pre id=&quot;code_1771748439893&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Application &amp;rarr; [FIFO] &amp;rarr; Log Collector &amp;rarr; File/DB

여러 애플리케이션이 하나의 FIFO에 로그를 쓰면
Log Collector가 중앙에서 수집/저장&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 프로세스 간 명령 전달&lt;/p&gt;
&lt;pre id=&quot;code_1771748452509&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Application &amp;rarr; [FIFO] &amp;rarr; Log Collector &amp;rarr; File/DB

여러 애플리케이션이 하나의 FIFO에 로그를 쓰면
Log Collector가 중앙에서 수집/저장&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 파이프라인 구축&lt;/p&gt;
&lt;pre id=&quot;code_1771748464059&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ mkfifo /tmp/data_pipe
$ producer &amp;gt; /tmp/data_pipe &amp;amp;
$ consumer &amp;lt; /tmp/data_pipe&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주의 사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. FIFO는 단방향&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양방향 통신이 필요하면 FIFO 2개를 만들어야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771748529160&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mkfifo(&quot;/tmp/fifo_to_server&quot;, 0666);
mkfifo(&quot;/tmp/fifo_to_client&quot;, 0666);

// Client
write(fd_to_server, request, len);
read(fd_from_server, response, len);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 정리 필수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 종료 시 FIFO 파일을 삭제해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771748552793&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;close(fd);
unlink(FIFO_PATH);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. SIGPIPE 조심&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reader가 먼저 종료하면 Writer가 SIGPIPE 시그널을 받아 종료될 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771748623527&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 해결책: SIGPIPE 무시
signal(SIGPIPE, SIG_IGN);

// 또는 에러 처리
if (write(fd, data, len) == -1) {
    if (errno == EPIPE) {
        printf(&quot;Reader closed\n&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIFO vs Shared Memory 벤치마크 (1MB 데이터 전송):&lt;/p&gt;
&lt;pre id=&quot;code_1771748743078&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FIFO:           5-10 ms
Shared Memory:  0.5-1 ms

&amp;rarr; FIFO는 SHM의 약 10배 느림&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 FIFO는 Kernel을 경유하기 때문입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771748763277&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FIFO:
Process A &amp;rarr; [User Space]
          &amp;darr;
        [Kernel Space]  (복사!)
          &amp;darr;
        [User Space] &amp;rarr; Process B

Shared Memory:
Process A &amp;harr; [Shared Memory] &amp;harr; Process B
           (복사 없음!)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속도가 중요하다면 Shared Memory를, 간단한 통신이면 FIFO를 사용하는 게 좋습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;디버깅 팁&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FIFO 상태 확인&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771748858262&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# FIFO 파일 확인
$ ls -l /tmp/myfifo
prw-rw-r-- 1 user user 0 Feb 21 10:00 /tmp/myfifo

# FIFO 내용 확인 (주의: 블로킹!)
$ cat /tmp/myfifo  # Reader처럼 동작

# 다른 터미널에서
$ echo &quot;test&quot; &amp;gt; /tmp/myfifo  # Writer처럼 동작&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;strace로 시스템 콜 추적&lt;/p&gt;
&lt;pre id=&quot;code_1771748873028&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ strace -e trace=open,read,write ./reader

open(&quot;/tmp/chat_fifo&quot;, O_RDONLY)       = 3
read(3, &quot;Hello\0&quot;, 256)                = 6
read(3, &quot;World\0&quot;, 256)                = 6&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIFO는 가장 기본적인 IPC 메커니즘 중 하나지만, 실제로 구현해 보면 생각보다 고려할 게 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배운 점:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Null terminator 처리의 중요성&lt;/li&gt;
&lt;li&gt;버퍼 초기화 필수&lt;/li&gt;
&lt;li&gt;길이 프로토콜로 견고하게&lt;/li&gt;
&lt;li&gt;Blocking 특성 이해&lt;/li&gt;
&lt;li&gt;EOF 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 공부는 Shared Memory를 공부해볼 예정입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man3/mkfifo.3.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://man7.org/linux/man-pages/man3/mkfifo.3.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1771748996386&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;mkfifo(3) - Linux manual page&quot; data-og-description=&quot;mkfifo(3) &amp;mdash; Linux manual page mkfifo(3) Library Functions Manual mkfifo(3) NAME &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; top mkfifo, mkfifoat - make a FIFO special file (a named pipe) LIBRARY &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; top Standard C library (libc, -lc) SYNOPSIS &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; top #include #include i&quot; data-og-host=&quot;man7.org&quot; data-og-source-url=&quot;https://man7.org/linux/man-pages/man3/mkfifo.3.html&quot; data-og-url=&quot;https://man7.org/linux/man-pages/man3/mkfifo.3.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man3/mkfifo.3.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://man7.org/linux/man-pages/man3/mkfifo.3.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;mkfifo(3) - Linux manual page&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;mkfifo(3) &amp;mdash; Linux manual page mkfifo(3) Library Functions Manual mkfifo(3) NAME &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; top mkfifo, mkfifoat - make a FIFO special file (a named pipe) LIBRARY &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; top Standard C library (libc, -lc) SYNOPSIS &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; top #include #include i&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;man7.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man7/fifo.7.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://man7.org/linux/man-pages/man7/fifo.7.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1771749017655&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;fifo(7) - Linux manual page&quot; data-og-description=&quot;fifo(7) &amp;mdash; Linux manual page fifo(7) Miscellaneous Information Manual fifo(7) NAME &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; top fifo - first-in first-out special file, named pipe DESCRIPTION &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; top A FIFO special file (a named pipe) is similar to a pipe, except that it is ac&quot; data-og-host=&quot;man7.org&quot; data-og-source-url=&quot;https://man7.org/linux/man-pages/man7/fifo.7.html&quot; data-og-url=&quot;https://man7.org/linux/man-pages/man7/fifo.7.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man7/fifo.7.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://man7.org/linux/man-pages/man7/fifo.7.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;fifo(7) - Linux manual page&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;fifo(7) &amp;mdash; Linux manual page fifo(7) Miscellaneous Information Manual fifo(7) NAME &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; top fifo - first-in first-out special file, named pipe DESCRIPTION &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; top A FIFO special file (a named pipe) is similar to a pipe, except that it is ac&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;man7.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dev/System Programming</category>
      <category>C언어</category>
      <category>IPC</category>
      <category>named pipe</category>
      <category>Pipe</category>
      <category>Process</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/13</guid>
      <comments>https://onepaperhoon.tistory.com/13#entry13comment</comments>
      <pubDate>Sun, 22 Feb 2026 17:35:22 +0900</pubDate>
    </item>
    <item>
      <title>싱글톤 패턴(singleton pattern) - C++</title>
      <link>https://onepaperhoon.tistory.com/12</link>
      <description>&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;1. 개념 이해&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;싱글톤 패턴&lt;/b&gt;이란?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;프로그램 전체에서 &lt;b&gt;인스턴스가 단 하나만 존재&lt;/b&gt;하도록 보장하는 디자인 패턴&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 클래스를 기반으로 여러 개의 개별적인 인스턴스를 만들 수 있지만, 그렇게 하지 않고 하나의 클래스를 기반으로 단 하나의 인스턴스를 만들어 이를 기반으로 로직을 만드는데 쓰이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 인스턴스를 만들어 놓고 해당 인스턴스를 다른 모듈들이 공유하며 사용하기 때문에 인스턴스를 생성할 때 드는 비용이 줄어드는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 보통 싱글톤 패턴이 적용된 객체가 필요한 경우는 그 객체가 &lt;b&gt;리소스를 많이 차지하는 역할&lt;/b&gt;을 하는&lt;br /&gt;무거운 클래스일 때 적합하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 보통 설정(Config) 관리자, 로그(Logger) 시스템, DB 커넥션 풀, 네트워크 소켓 매니저, 데이터 베이스 연결 모듈에 많이 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터 베이스 연결 모듈&lt;/b&gt;을 예로 들자면, 데이터 베이스에 접속하는 작업(I/O 바운드)은 무거운 작업에 속하며 또한 한 번만&lt;br /&gt;객체를 생성하고 돌려쓰면 되는 것을 굳이 여러 번 생성할 필요가 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 객체들은 재생성을 하여 사용될 일도 없으며 사용해도 리소스 낭비이다. 따라서 코드 내에서 유일해야 하는 것을 싱글톤 객체로 만들면 된다고 보면 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. C++ 에서의 싱글톤 구현&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본 구현 (Eager Initialization)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 단순한 형태의 싱글톤은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1771571062062&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Singleton {
private:
    static Singleton* instance;
    
    // 생성자를 private으로 &amp;rarr; 외부에서 new 불가
    Singleton() {}

public:
    // 복사 생성자, 대입 연산자 삭제
    Singleton(const Singleton&amp;amp;) = delete;
    Singleton&amp;amp; operator=(const Singleton&amp;amp;) = delete;

    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
};

// 정적 멤버 초기화
Singleton* Singleton::instance = nullptr;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 세 가지로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 생성자를 private으로 선언 = 외부에서 new Singleton()을 호출할 수 없게 막는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 복사 생성자와 대입 연산자를 delete - Singleton copy = *Singleton::getInstance(): 같은 복사를 원천 차단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. static 매서드로 접근 - 유일한 인스턴스에 대한 전역 접근점을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 방식에는 치명적인 문제가 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 문제점: 멀티스레드 환경에서의 Race Condition&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 기본 구현은&amp;nbsp;&lt;b&gt;멀티 스레드 환경에서 안전하지 않다.&lt;/b&gt; 두 스레드가 동시에 getInstance()를 호출하면 인스턴스가 두 번 생성될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1771572290754&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   Race Condition 시나리오
// Thread A: if (instance == nullptr)  &amp;rarr; true 진입
// Thread B: if (instance == nullptr)  &amp;rarr; true 진입 (A가 아직 생성 안 함)
// Thread A: instance = new Singleton()  &amp;rarr; 첫 번째 생성
// Thread B: instance = new Singleton()  &amp;rarr; 두 번째 생성! (메모리 누수)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 등장한 것이 Double-Checked Locking 패턴이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;보완 1: Double-Checked Locking&lt;/h4&gt;
&lt;pre id=&quot;code_1771572328486&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;mutex&amp;gt;

class Singleton {
private:
    static Singleton* instance;
    static std::mutex mtx;
    Singleton() {}

public:
    Singleton(const Singleton&amp;amp;) = delete;
    Singleton&amp;amp; operator=(const Singleton&amp;amp;) = delete;

    static Singleton* getInstance() {
        if (instance == nullptr) {              // 1차 검사 (lock 없이)
            std::lock_guard&amp;lt;std::mutex&amp;gt; lock(mtx);
            if (instance == nullptr) {          // 2차 검사 (lock 안에서)
                instance = new Singleton();
            }
        }
        return instance;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1차 검사는 이미 인스턴스가 생성된 이후에는 매번 lock을 걸지 않기 위한 성능 최적화이고, 2차 검사는 실제로 lock을 획득한 후 다른 스레드가 먼저 생성하지 않았는지 확인하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드 안전 문제는 해결했지만, 여전히 아쉬운 점이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;코드가 복잡하다&lt;/b&gt; &amp;mdash; mutex 관리, 이중 검사 로직이 필요하다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명령어 재배치(instruction reordering) 위험&lt;/b&gt; &amp;mdash; C++ 메모리 모델상 컴파일러나 CPU가 명령어 순서를 바꿀 수 있어 미묘한 버그가 발생할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리 관리가 수동&lt;/b&gt; &amp;mdash; new로 생성했으므로 어딘가에서 delete를 해줘야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 복잡성을 근본적으로 해결하는 방법이 C++11에서 등장했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;보완 2: Meyer's Singleton (C++11 이상) 권장&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++11 부터는 지역 정적 변수 (Local Static Variable)의 초기화가 스레드 안전하도록 표준에서 보장한다.&lt;br /&gt;이를 활용한 것이 Scott Meyers가 제안한 방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1771736365419&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Singleton {
private:
    Singleton() {}

public:
    Singleton(const Singleton&amp;amp;) = delete;
    Singleton&amp;amp; operator=(const Singleton&amp;amp;) = delete;

    static Singleton&amp;amp; getInstance() {
        static Singleton instance;  // C++11: 스레드 안전 보장
        return instance;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식의 장점은 명확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 스레드 안전: 컴파일러가 알아서 보장해 준다 (C++11)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Lazy Initialization: getInstance()가 처음 호출될 때 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 메모리&amp;nbsp; 누수 없음: new를 쓰지 않으므로 프로그램 종료 시 자동 소멸&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 코드가 간결: mutex나 포인터 관리가 필요 없다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 구현의 스레드 안전 문제, Double-Checked Locking의 복잡성과 명령어 재배치 위험을 모두 해결한 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++11 이상을 사용한다면 &lt;b&gt;사실상 정답&lt;/b&gt;이라고 볼 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 싱글톤 패턴 자체의 한계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현 방식을 아무리 잘 선택해도, 싱글톤 패턴 자체가 갖는 구조적인 문제들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;소멸 순서 문제 (Dead Reference Problem)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤이 하나만 존재할 때는 문제없지만, &lt;b&gt;여러 싱글톤이 서로 의존하는 경우&amp;nbsp;&lt;/b&gt;소멸 순서가 꼬일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1771736821699&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Logger {
public:
    static Logger&amp;amp; getInstance() {
        static Logger instance;
        return instance;
    }
    void log(const std::string&amp;amp; msg) { /* ... */ }
    ~Logger() { /* 파일 닫기 등 정리 작업 */ }
};

class Database {
public:
    static Database&amp;amp; getInstance() {
        static Database instance;
        return instance;
    }
    ~Database() {
        //   이 시점에 Logger가 이미 소멸되었을 수 있다!
        Logger::getInstance().log(&quot;Database 종료&quot;);
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 객체의 소멸 순서는 생성 순서의 역순이다. 만약 Logger가 Database 보다 먼저 생성되었다면 Logger가 나중에 소멸되므로 문제없지만, 그 반대라면 이미 소멸된 Logger에 접근하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 의존 관계가 있다면 초기화 시점에서 순서를 명시적으로 제어해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1771736932295&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Database {
public:
    static Database&amp;amp; getInstance() {
        // Logger를 먼저 초기화 &amp;rarr; 나중에 소멸되도록 보장
        Logger::getInstance();
        static Database instance;
        return instance;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;테스트가 어렵다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤은 전역 상태를 갖기 때문에 단위 테스트에서 독립적인 환경을 만들기 어렵다. 테스트마다 상태를 리섹해야 하고, mock 객체로 교체하기도 까다롭다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;의존성이 감춰진다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 시그니처만 봐서는 해당 함수가 싱글톤에 의존하는지 알 수 없다. 이는 코드의 결흡도를 높이고 유지보수를 어렵게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1771737065823&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 싱글톤 직접 접근 &amp;mdash; 의존성이 코드 내부에 숨어있다
class Service {
    void doWork() {
        Database::getInstance().query(&quot;SELECT ...&quot;);
    }
};

// 의존성 주입(DI) &amp;mdash; 의존성이 명시적으로 드러난다
class Service {
    Database&amp;amp; db;
public:
    Service(Database&amp;amp; db) : db(db) {}
    void doWork() {
        db.query(&quot;SELECT ...&quot;);
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때문에 현대 C++ 개발에서는 싱글톤 대신 의존성 주입(Dependency Injection) 패턴을 선호하는 추세다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤을 쓸지 판단할 때는 &quot;이 객체가 정말 프로그램 전체에서 단 하나여야 하는가?&quot;를 먼저 자문해 보자. 그렇다면 Meyer's Singleton으로 구현하되, 여러 싱글톤 간의 의존 관계와 테스트 용이성을 함께 고려하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dev/Design Pattern</category>
      <category>디자인 패턴</category>
      <category>싱글톤 패턴</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/12</guid>
      <comments>https://onepaperhoon.tistory.com/12#entry12comment</comments>
      <pubDate>Sun, 22 Feb 2026 14:15:17 +0900</pubDate>
    </item>
    <item>
      <title>Scop 구현기 5편: 행렬과 사용 점</title>
      <link>https://onepaperhoon.tistory.com/11</link>
      <description>&lt;p&gt;
이전 글에서는 Model 클래스에서 OBJ 파일을 파싱하고 정점 데이터를 처리하는 흐름을 살펴보았습니다.&lt;br&gt;
이번 글에서는 그 정점들이 실제로 화면에 어떻게 그려지는지를 결정짓는 핵심 요소, 바로 &quot;행렬(Matrix)&quot;에 대해 깊이 있게 분석합니다.&lt;br&gt;
3D 그래픽스에서 행렬은 단순한 계산 도구가 아니라, '시점', '위치', '존재'를 결정하는 철학적 도구입니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h3&gt;1. 왜 행렬이 필요한가?&lt;/h3&gt;
&lt;p&gt;
3D 그래픽스에서 모델을 불러온다고 해서 곧바로 화면에 보이는 건 아닙니다.&lt;br&gt;
그 이유는 모델의 정점(Vertex)들이 여전히 '자기 내부(Local Space)'에 존재하기 때문입니다. 예를 들어, 정점이 (0, 0, 0)이라고 하면, 이는 오브젝트 기준의 원점을 의미하지 화면 기준의 원점은 아닙니다.&lt;br&gt;&lt;br&gt;
우리는 화면(스크린)이라는 2차원 공간 위에 3차원 물체를 투영하여 보여주어야 합니다. 이를 위해선 여러 좌표계의 변환이 필요하며, 그 핵심이 바로 행렬입니다.&lt;br&gt;
3D 모델을 제대로 화면에 나타내기 위해선 다음과 같은 좌표계 변환이 반드시 필요합니다:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;로컬 공간 → 월드 공간&lt;/strong&gt; (Model 행렬)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;월드 공간 → 카메라(뷰) 공간&lt;/strong&gt; (View 행렬)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;카메라 공간 → 클립 공간 → NDC → 스크린 공간&lt;/strong&gt; (Projection 행렬)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
이 변환 과정이 없으면 아무리 모델을 잘 불러왔어도 화면에는 보이지 않습니다. 즉, 행렬은 화면에 &quot;존재하게 만드는&quot; 유일한 길입니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h3&gt;2. Scop에서 사용되는 3대 변환 행렬&lt;/h3&gt;
&lt;p&gt;
Scop의 렌더링 파이프라인에서는 아래 세 가지 행렬이 필수적으로 사용됩니다:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Model 행렬&lt;/strong&gt;: 오브젝트의 위치, 크기, 회전 상태를 결정합니다. 예를 들어 2배 확대하고 오른쪽으로 이동시키는 등의 작업은 이 단계에서 처리됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;View 행렬&lt;/strong&gt;: 카메라가 위치한 좌표와 그 시점을 기준으로 월드 공간의 모델들을 상대적으로 재배치합니다. 카메라가 이동하는 게 아니라 세상을 역으로 이동시키는 방식입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Projection 행렬&lt;/strong&gt;: 원근 투영 혹은 직교 투영 방식으로 3D 공간을 2D 화면에 투영합니다. 원근감을 표현하기 위해 필수적인 구성입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
이 행렬들은 최종적으로 셰이더에 전달되어 정점의 화면 좌표 &lt;code&gt;gl_Position&lt;/code&gt;을 결정합니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h3&gt;3. Model 행렬 구성 (Model.cpp)&lt;/h3&gt;
&lt;p&gt;
Scop에서는 &lt;code&gt;Model::Draw()&lt;/code&gt; 함수 내에서 다음과 같은 순서로 모델 변환 행렬을 구성합니다:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;multiplyMatrix(model, scaleMatrix, model);
rotateY(mRotY, model);
rotateX(mRotX, model);
multiplyMatrix(model, translate, model);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
- 먼저 정점의 크기를 조절하는 &lt;strong&gt;스케일 행렬&lt;/strong&gt;을 적용하고,&lt;br&gt;
- 이후 Y축과 X축에 대해 각각 회전(&lt;code&gt;rotateY&lt;/code&gt;, &lt;code&gt;rotateX&lt;/code&gt;)을 수행하며,&lt;br&gt;
- 마지막으로 위치를 이동시키는 &lt;strong&gt;이동 행렬&lt;/strong&gt;을 곱합니다.&lt;br&gt;&lt;br&gt;
OpenGL에서의 행렬 곱 연산은 오른쪽에서 왼쪽으로 적용되므로, 실제로는 &quot;스케일 → 회전 → 이동&quot; 순서로 변환됩니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h3&gt;4. View 행렬 구성 (카메라 시점 정의)&lt;/h3&gt;
&lt;p&gt;
View 행렬은 카메라가 어떤 위치에서 어떤 방향으로 세상을 보는지를 수학적으로 정의합니다. Scop에서는 이를 직접 &lt;code&gt;createLookAtMatrix()&lt;/code&gt;로 구현합니다:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;createLookAtMatrix({0, 0, 3}, {0, 0, 0}, {0, 1, 0}, view);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
위 코드는 &quot;카메라가 (0, 0, 3) 위치에서 (0, 0, 0)을 바라보며 위 방향은 Y축&quot;임을 의미합니다.&lt;br&gt;
LookAt 행렬의 구성은 다음과 같은 벡터 연산으로 이뤄집니다:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Forward = normalize(center - eye)&lt;/li&gt;
&lt;li&gt;Right = normalize(Forward × Up)&lt;/li&gt;
&lt;li&gt;Up' = Right × Forward&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
이 벡터들을 기반으로 한 4x4 행렬은 &quot;카메라 기준 좌표계&quot;를 만들어내며, 모델들은 이 좌표계 기준으로 재정렬됩니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h3&gt;5. Projection 행렬 구성 (원근 투영 정의)&lt;/h3&gt;
&lt;p&gt;
Scop에서는 원근 투영 행렬을 아래처럼 직접 계산합니다. 이 방식은 OpenGL의 &lt;code&gt;gluPerspective()&lt;/code&gt;와 동일한 역할을 수행합니다.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;inline void createPerspectiveMatrix(float fov, float aspect, float neer, float far, float* matrix) {
    float rad = fov * DEG2RAD;
    float tanHalfFov = tan(rad / 2.0f);

    for (int i = 0; i &lt; 16; ++i)
        matrix[i] = 0.0f;

    matrix[0] = 1.0f / (aspect * tanHalfFov);
    matrix[5] = 1.0f / tanHalfFov;
    matrix[10] = -(far + neer) / (far - neer);
    matrix[11] = -1.0f;
    matrix[14] = -(2.0f * far * neer) / (far - neer);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
수학적으로 위 함수는 아래와 같은 행렬을 의미합니다:
&lt;/p&gt;
&lt;p&gt;
$$
P = \begin{bmatrix}
\frac{1}{a \cdot \tan(\theta/2)} &amp; 0 &amp; 0 &amp; 0 \\\\
0 &amp; \frac{1}{\tan(\theta/2)} &amp; 0 &amp; 0 \\\\
0 &amp; 0 &amp; -\frac{f + n}{f - n} &amp; -1 \\\\
0 &amp; 0 &amp; -\frac{2fn}{f - n} &amp; 0
\end{bmatrix}
$$
&lt;/p&gt;
&lt;p&gt;
다만 이 행렬은 OpenGL이 사용하는 &lt;strong&gt;column-major&lt;/strong&gt; 메모리 구조에 맞춰 작성되어야 하므로,&lt;br&gt;
&lt;code&gt;matrix[11]&lt;/code&gt;과 &lt;code&gt;matrix[14]&lt;/code&gt;의 인덱스 위치가 수식과 달라 보일 수 있습니다. 이는 순서가 바뀐 것이 아니라 메모리 상 배치 기준이 다르기 때문입니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;h3&gt;6. 최종 정점 위치 계산&lt;/h3&gt;
&lt;p&gt;
위 세 행렬은 다음과 같은 순서로 곱해져서 셰이더에 전달되며, 최종적으로 정점의 위치가 결정됩니다:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gl_Position = projection * view * model * vec4(vertexPosition, 1.0);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
이 수식은 다음과 같은 의미를 담고 있습니다:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Model 행렬&lt;/strong&gt;: 오브젝트의 로컬 공간에서 월드 공간으로 이동&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;View 행렬&lt;/strong&gt;: 월드 공간에서 카메라 기준 공간으로 이동&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Projection 행렬&lt;/strong&gt;: 카메라 공간에서 정규화 장치 좌표계(NDC)로 투영&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;gl_Position&lt;/strong&gt;: GPU의 래스터라이저에 의해 화면 픽셀로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
즉, 우리가 OBJ 파일을 파싱하고 정점 정보를 넘기며 행렬을 구성하는 모든 과정은 &lt;strong&gt;이 한 줄의 수식&lt;/strong&gt;을 위해 존재하는 것입니다.
&lt;/p&gt;

&lt;hr&gt;

&lt;!-- MathJax --&gt;
&lt;script type=&quot;text/javascript&quot; async src=&quot;https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js&quot;&gt;&lt;/script&gt;</description>
      <category>Dev/Grapics</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/11</guid>
      <comments>https://onepaperhoon.tistory.com/11#entry11comment</comments>
      <pubDate>Thu, 17 Apr 2025 07:43:10 +0900</pubDate>
    </item>
    <item>
      <title>Scop 구현기 4편: Model 클래스</title>
      <link>https://onepaperhoon.tistory.com/10</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;전 편에서 Shader코드들을 분리해 클래스로 따로 만들었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 기본. obj 파일을 파싱 해서 VAO와 VBO 그리고 정점들에 대한 데이터 들을 클래스 단위로 분리해 보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scop에서 가장 중요한 클래스가 아닐까 싶다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 왜 Model 클래스를 도입했나?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삼각형 그리기를 생각해 본다면 VAO, VBO, 위치 데이터, 셰이더 정보 등이 전부 흩어져 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 하나로 묶지 않으면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 객체마다 VAO/VBO를 일일이 관리해야 하고&lt;/li&gt;
&lt;li&gt;draw 호출마다 셰이더 바인딩도 반복하고&lt;/li&gt;
&lt;li&gt;좌표, 색상, 메시 정보를 통합적으로 다루기 어렵다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 모든 &quot;하나의 그릴 대상&quot;을 감싸는 클래스를 만들게 된 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Model 클래스는 어떤 책임을 가져야 할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 단순히 &quot;정점 데이터를 갖는 그릇&quot; 정도로만 생각했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 직접 만들다 보니, 역할이 꽤 많았다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;역할&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;정점 데이터 보관&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;VBO를 생성하고 데이터를 업로드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;VAO 설정&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;버텍스 레이아웃을 GPU에 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;셰이더 연동&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Shader 클래스를 보관하고 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;변환 행렬 저장&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;위치/회전/크기를 위한 model matrix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;draw() 함수&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;화면에 실제로 그리는 로직&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;OBJ 파싱&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;.obj 파일을 파싱해서 정점 데이터, 메터리얼 등을 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 단순 그리기 이상으로, &lt;b&gt;렌더링 파이프라인을 포괄하는 최소 단위&lt;/b&gt;가 되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Model 클래스의 헤더부터 살펴보자&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Model.hpp (1).jpeg&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;2165&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ejFFYg/btsNjh1tHQW/I0o8DqTMLLLYlz3uEPhNR0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ejFFYg/btsNjh1tHQW/I0o8DqTMLLLYlz3uEPhNR0/img.jpg&quot; data-alt=&quot;Model.hpp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ejFFYg/btsNjh1tHQW/I0o8DqTMLLLYlz3uEPhNR0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FejFFYg%2FbtsNjh1tHQW%2FI0o8DqTMLLLYlz3uEPhNR0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1310&quot; height=&quot;2165&quot; data-filename=&quot;Model.hpp (1).jpeg&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;2165&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Model.hpp&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각 보다 Model 클래스의 크기가 커졌다....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;천천히 하나하나 설명을 작성해 보겠습니다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 생성자&lt;/h3&gt;
&lt;pre id=&quot;code_1744585995137&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Model::Model(const char* filePath, Shader* shader)
: mShaderProgram(shader)
, mScaleFactor(1.0f)
, mRotX(0.0f), mRotY(0.0f)
, mPosX(0.0f), mPosY(0.0f), mPosZ(0.0f)
, mbUseTexture(false)
, mBlendFactor(0.0f)
, mUVRotation(UVRotation::None)
{
	LoadOBJ(filePath);
	SetupMesh();
	LoadTexture(&quot;resources/sample.png&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자에서 간단하게 초기화해야 하는 멤버 변수들을 초기화하고 Model 클래스의 핵심이라고 할 수 있는 부분인 LoadOBJ로&lt;br /&gt;넘 거 가게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. LoadOBJ&lt;/h3&gt;
&lt;pre id=&quot;code_1744586265185&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void Model::LoadOBJ(const char* filePath)
{
	std::ifstream file(filePath);
	if (!file.is_open())
	{
		std::cerr &amp;lt;&amp;lt; &quot;Failed to open OBJ file: &quot; &amp;lt;&amp;lt; filePath &amp;lt;&amp;lt; std::endl;
		return;
	}
	std::vector&amp;lt;Vec3&amp;gt;	temp_V;
	std::vector&amp;lt;Vec2&amp;gt;	temp_Vt;
	std::vector&amp;lt;Vec3&amp;gt;	temp_Vn;
	std::vector&amp;lt;Vec3&amp;gt;	temp_color;
	bool				hasTexCoord;

	std::string line;
	while (std::getline(file, line))
	{
		std::istringstream iss(line);
		std::string prefix;
		iss &amp;gt;&amp;gt; prefix;
		// mtl 파일 파싱
		if (prefix == &quot;mtllib&quot;)
		{
			std::string mtlFilename;
			iss &amp;gt;&amp;gt; mtlFilename;
			mMaterials = MTLLoader::Load(&quot;resources/&quot; + mtlFilename);
			std::cout &amp;lt;&amp;lt; mtlFilename &amp;lt;&amp;lt; std::endl;
		}
		else if (prefix == &quot;v&quot;) // v 1.0123, 2.123123, -6.1235 -&amp;gt; 정점 좌표 정보일 경우
		{
			Vec3 v;

			iss &amp;gt;&amp;gt; v.x &amp;gt;&amp;gt; v.y &amp;gt;&amp;gt; v.z;
			temp_V.push_back(v);
		}
		else if (prefix == &quot;vt&quot;) // vt 0~1, 0~1 -&amp;gt; TextureCoord일 경우
		{
			Vec2 vt;
			iss &amp;gt;&amp;gt; vt.x &amp;gt;&amp;gt; vt.y;
			temp_Vt.push_back(vt);
			hasTexCoord = true;
		}
		else if (prefix == &quot;vn&quot;) // vn 1, 2, 5 -&amp;gt; Normal 정보일 경우
		{
			Vec3 vn;
			iss &amp;gt;&amp;gt; vn.x &amp;gt;&amp;gt; vn.y &amp;gt;&amp;gt; vn.z;
			temp_Vn.push_back(vn);
		}
		else if (prefix == &quot;usemtl&quot;) // v, vn, vt 정보가 모두 처리 완료 된 후 Usemtl -&amp;gt; 이후 f들에게 적용할 mtl 명
		{
			iss &amp;gt;&amp;gt; mCurrentMaterial;
		}
		else if (prefix == &quot;f&quot;) // f 정보가 나오면
		{
			Face face;
			face.mtlname = mCurrentMaterial;
			std::string vertexStr;
			while (iss &amp;gt;&amp;gt; vertexStr)
			{
				int vIdx = -1, vtIdx = -1, vnIdx = -1;
				size_t firstSlash = vertexStr.find('/'); // f 1/2/3 에서 첫번째 슬레쉬 찾기
				size_t secondSlash = vertexStr.find('/', firstSlash + 1); // 두번째 슬레쉬 찾기

				if (firstSlash == std::string::npos)
				{
					vIdx = std::stoi(vertexStr) - 1;
				}
				else if (secondSlash == std::string::npos)
				{
					vIdx = std::stoi(vertexStr.substr(0, firstSlash)) - 1;
					vtIdx = std::stoi(vertexStr.substr(firstSlash + 1)) - 1;
				}
				else if (secondSlash == firstSlash + 1)
				{
					vIdx = std::stoi(vertexStr.substr(0, firstSlash)) - 1;
					vnIdx = std::stoi(vertexStr.substr(secondSlash + 1)) - 1;
				}
				else
				{
					vIdx = std::stoi(vertexStr.substr(0, firstSlash)) - 1;
					vtIdx = std::stoi(vertexStr.substr(firstSlash + 1, secondSlash - firstSlash - 1)) - 1;
					vnIdx = std::stoi(vertexStr.substr(secondSlash + 1)) - 1;
				}

				Vertex v;
				v.position = temp_V[vIdx];
				if (vtIdx &amp;gt;= 0 &amp;amp;&amp;amp; temp_Vt.size() != 0)
					v.texCoord = temp_Vt[vtIdx];
				if (vnIdx &amp;gt;= 0 &amp;amp;&amp;amp; temp_Vn.size() != 0)
					v.normal = temp_Vn[vnIdx];

				mVertices.push_back(v);
				unsigned int index = mVertices.size() - 1;
				face.vertexIndices.push_back(index);
			}
			if (face.vertexIndices.size() == 3)
			{
				mFaces.push_back(face);
			}
			else if (face.vertexIndices.size() == 4)
			{
				Face f1, f2;
				f1.mtlname = face.mtlname;
				f1.vertexIndices = { face.vertexIndices[0], face.vertexIndices[1], face.vertexIndices[2] };

				f2.mtlname = face.mtlname;
				f2.vertexIndices = { face.vertexIndices[0], face.vertexIndices[2], face.vertexIndices[3] };

				mFaces.push_back(f1);
				mFaces.push_back(f2);
			}

		}
	}
	for (const auto&amp;amp; v : mVertices)
		mOriginalPositions.push_back(v.position);
	file.close();
	if (!hasTexCoord)
	{
		GenerateTexCoordsFromPosition();
	}
	NormalizeVertices(mVertices); // NDC 좌표계로
	ApplyFaceColorsFromIndex();
	PrintAllVertexInfo();
	// PrintAllFaceInfo();
	// 정점 및 인덱스 저장
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전체 흐름 요약&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;. obj 파일 열기&lt;/li&gt;
&lt;li&gt;v, vt, vb, f , usemtl, mtllib 등 접두어별로 처리&lt;/li&gt;
&lt;li&gt;각 줄을 파싱해 점검, 텍스쳐 좌표, 노멀 벡터 등 임시 저장&lt;/li&gt;
&lt;li&gt;f 라인에서 실제 정점 조합 생성 및 mVetices/mFaces 구성&lt;/li&gt;
&lt;li&gt;정점 정규화, 텍스쳐 좌표 자동 생성, 색상 정용 등 후처리&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;섹션 상세 설명&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 파일 열기&lt;/p&gt;
&lt;pre id=&quot;code_1744586623349&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::ifstream file(filePath);
if (!file.is_open())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ifstream으로 파일을 연뒤 열리지 않으면 경고 출력 후 리턴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 정점/텍스처/ 노멀 저장용 임시 버퍼&lt;/p&gt;
&lt;pre id=&quot;code_1744586707055&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::vector&amp;lt;Vec3&amp;gt;	temp_V;
std::vector&amp;lt;Vec2&amp;gt;	temp_Vt;
std::vector&amp;lt;Vec3&amp;gt;	temp_Vn;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;. obj는 f라인보다 v 라인이 먼저 오므로 일단 버퍼에 넣어둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정점마다 (v/vt/vn) 조합이 다르므로 나중에 매칭해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.mtlib &amp;amp; usemtl 처리&lt;/p&gt;
&lt;pre id=&quot;code_1744586607748&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (prefix == &quot;mtllib&quot;)
{
 // 재질 로딩
}
else if (prefix == &quot;usemtl&quot;)
{
 // 현재 페이스에 적용할 재질 이름 저장
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.obj 파일은. mtl 파일을 함께 사용하며, 재질 (Material) 이름은 Face에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 나중에 렌더링 할 때 해당 재질의 색상을 불러올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.f(face) 파싱&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 핵심 부분이라고 생각한다, 각 정점은 다음 4가지 방식으로 등장할 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;형식&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;v&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;정점 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;v/vt&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;정점 + 텍스처 좌표&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;v//vn&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;정점 + 노멀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;v/vt/vn&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;정점 + 텍스처 + 노멀&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를. find('/')로 판단하고 각 파트를 분리해 index로 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Vertex 객체 생성 및 mVertices 저장&lt;/p&gt;
&lt;pre id=&quot;code_1744586891635&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Vertex v;
v.position = temp_V[vIdx];
if (vtIdx &amp;gt;= 0) v.texCoord = temp_Vt[vtIdx];
if (vnIdx &amp;gt;= 0) v.normal = temp_Vn[vnIdx];
mVertices.push_back(v);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각각의 Face는 Vertex들의 index를 참조하여 만든다.&lt;/li&gt;
&lt;li&gt;. obj는 인덱스 기반 구조이기 때문에, Vertex 중복이 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;여기선 간단하게 mVertices.push_back()하며 인덱스를 추적해 넣는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 4 각형 face 삼각형 분해&lt;/p&gt;
&lt;pre id=&quot;code_1744586963065&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (face.vertexIndices.size() == 4) {
	// 0-1-2 / 0-2-3 으로 분해
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;. obj에서 사각형(f 1 2 3 4) 구조는 삼각형 두 개로 쪼개줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 구조에서는 그래야 OpenGL이 이해할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 텍스처 좌표 없을 경우 자동 생성&lt;/p&gt;
&lt;pre id=&quot;code_1744587014787&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (!hasTexCoord) {
	GenerateTexCoordsFromPosition();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스처 좌표가 없는. obj 파일도 많기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 경우 정점 위치 기반으로 UV를 강제로 생성해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 정규화 및 색상 적용&lt;/p&gt;
&lt;pre id=&quot;code_1744587069108&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;NormalizeVertices(mVertices);
ApplyFaceColorsFromIndex();&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정점 위치를 OpenGL의 -1.0 ~ 1.0 영역으로 스케일링 (NDC 대응)&lt;/li&gt;
&lt;li&gt;각 Face마다 색상을 지정해 색상을 가진 렌더링이 가능하도록 한다 (체크무늬)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.SetUpMesh&lt;/h3&gt;
&lt;pre id=&quot;code_1744587366872&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void Model::SetupMesh(void)
{
	glGenVertexArrays(1, &amp;amp;mVAO);
	glGenBuffers(1, &amp;amp;mVBO);
	glGenBuffers(1, &amp;amp;mEBO);

	glBindVertexArray(mVAO);

	glBindBuffer(GL_ARRAY_BUFFER, mVBO);
	glBufferData(GL_ARRAY_BUFFER, mVertices.size() * sizeof(Vertex), &amp;amp;mVertices[0], GL_STATIC_DRAW);
	/*
		Vertex
		-&amp;gt;	float * 3 : position
			float * 3 : normal
			float * 2 : texcoord
			flaot * 3 : color
	*/
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mEBO);
		// 각 face의 인덱스를 모아 하나의 인덱스 버퍼 생성
	mAllIndices.clear();
	for (const Face&amp;amp; face : mFaces) {
		mAllIndices.insert(mAllIndices.end(), face.vertexIndices.begin(), face.vertexIndices.end());
	}
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, mAllIndices.size() * sizeof(unsigned int), mAllIndices.data(), GL_STATIC_DRAW);

	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position));
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, texCoord));
	glEnableVertexAttribArray(2);
	glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, color));
	glEnableVertexAttribArray(3);
	glBindVertexArray(0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전체 흐름 요약&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;버퍼 객체 (VAO, VBO, EBO) 생성&lt;/li&gt;
&lt;li&gt;정점 데이터 GPU에 업로드 (glBufferData)&lt;/li&gt;
&lt;li&gt;인덱스 데이터 생성 및 업로드&lt;/li&gt;
&lt;li&gt;정점 속성 포인터 설정 (glVertexAttribPointer)&lt;/li&gt;
&lt;li&gt;마무리 정리 (glBindVertexArray(0))&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;섹션 상세 설명&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 버퍼 객체 생성&lt;/p&gt;
&lt;pre id=&quot;code_1744587470111&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;glGenVertexArrays(1, &amp;amp;mVAO);
glGenBuffers(1, &amp;amp;mVBO);
glGenBuffers(1, &amp;amp;mEBO);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VAO: 정점 배열 객체 (Vertex Array object)&lt;/li&gt;
&lt;li&gt;VBO: 정점 버퍼 객체 (Vertex Array object)&lt;/li&gt;
&lt;li&gt;EBO: 인덱스 버퍼 객체 (Element Buffer object)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. VAO 바인딩 시작&lt;/p&gt;
&lt;pre id=&quot;code_1744587530090&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;glBindVertexArray(mVAO);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제부터의 모든 설정은 VAO에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 한 번만 설정해 두면 glBindVertexArray(mVAO)만 해도 그 설정이 복구됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 정점 데이터 GPU에 업로드&lt;/p&gt;
&lt;pre id=&quot;code_1744587615818&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;glBindBuffer(GL_ARRAY_BUFFER, mVBO);
glBufferData(GL_ARRAY_BUFFER, mVertices.size() * sizeof(Vertex), &amp;amp;mVertices[0], GL_STATIC_DRAW);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GL_ARRAY_BUFFER는 정점 데이터를 위한 버퍼다.&lt;/li&gt;
&lt;li&gt;Vertex는 구조체로서 다음과 같은 레이아웃을 가진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744587659333&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Vertex {
	Vec3 position;   // 3 floats
	Vec3 normal;     // 3 floats
	Vec2 texCoord;   // 2 floats
	Vec3 color;      // 3 floats
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 11 floats = 44 bytes 짜리 구조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL에게 이 구조를 설명해야 하며, mVertices.size만큼 설정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 인덱스 버퍼 구성 (EBO)&lt;/p&gt;
&lt;pre id=&quot;code_1744587748613&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mAllIndices.clear();
for (const Face&amp;amp; face : mFaces) {
	mAllIndices.insert(mAllIndices.end(), face.vertexIndices.begin(), face.vertexIndices.end());
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mEBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, mAllIndices.size() * sizeof(unsigned int), mAllIndices.data(), GL_STATIC_DRAW);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 Face는 세 개의 정점 인덱스를 가진다 (삼각형)&lt;/li&gt;
&lt;li&gt;이 인덱스들을 하나의 벡터로 모아 glDrawElemets용으로 만든다.&lt;/li&gt;
&lt;li&gt;GL_ELEMENT_ARRAY_BUFFER는 EBO의 명칭이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 정점 속성 포인터 설정&lt;/p&gt;
&lt;pre id=&quot;code_1744587850659&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position));
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, texCoord));
glEnableVertexAttribArray(2);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, color));
glEnableVertexAttribArray(3);
glBindVertexArray(0);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;layout 0 -&amp;gt; position: 3개의 float (x, y, z)&lt;/li&gt;
&lt;li&gt;layout 1 -&amp;gt; normal: 3개의 float&lt;/li&gt;
&lt;li&gt;layout 2 -&amp;gt; texCoord: 2개의 float&lt;/li&gt;
&lt;li&gt;layout 3 -&amp;gt; color: 3개의 float&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744588749260&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;glVertexAttribPointer(index, size, type, normalize, stride, offset);&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;index&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;셰이더에서 layout(location = index)와 매칭됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;size&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;몇 개의 요소 (position은 3, texCoord는 2등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;type&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;GL_FLOAT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;normalize&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;정규화 여부 (일반적으로 false)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;stride&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;하나의 정점&amp;nbsp; 전체 크기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;offset&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;구조체 내 해당 필드까지의 거리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 마무리&lt;/p&gt;
&lt;pre id=&quot;code_1744588861395&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;glBindVertexArray(0);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VAO를 바인딩 해제함으로써 설정을 마감한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 나중에 Draw() 함수에서 glBindVertexArray(mVAO)만 하면 바로 사용 가능한 상태가 되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Draw() 함수&lt;/h3&gt;
&lt;pre id=&quot;code_1744589059930&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void Model::Draw(void)
{
	mShaderProgram-&amp;gt;Use();
	glBindVertexArray(mVAO);

	float view[16];
	createLookAtMatrix({0, 0, 3}, {0, 0, 0}, {0, 1, 0}, view);

	float projection[16];
	createPerspectiveMatrix(45.0f, 1000.0f/1000.0f, 0.1f, 100.0f, projection);

	float model[16];
	setIdentityMatrix(model);

	float scaleMatrix[16] = {
		mScaleFactor, 0.0f, 0.0f, 0.0f,
		0.0f, mScaleFactor, 0.0f, 0.0f,
		0.0f, 0.0f, mScaleFactor, 0.0f,
		0.0f, 0.0f, 0.0f, 1.0f
	};
	float translate[16];
	createTranslationMatrix(mPosX, mPosY, mPosZ, translate);

	multiplyMatrix(model, scaleMatrix, model);
	rotateY(mRotY, model);
	rotateX(mRotX, model);
	multiplyMatrix(model, translate, model);

	UpdateBlend(mbUseTexture, 0.016f);

	mShaderProgram-&amp;gt;SetMat4(&quot;model&quot;, model);
	// mShaderProgram-&amp;gt;SetBool(&quot;useTexture&quot;,mbUseTexture);
	mShaderProgram-&amp;gt;SetMat4(&quot;view&quot;, view);
	mShaderProgram-&amp;gt;SetMat4(&quot;projection&quot;, projection);
	mShaderProgram-&amp;gt;SetFloat(&quot;blendFactor&quot;, mBlendFactor);
	size_t indexOffset = 0;
	for (const Face&amp;amp; face : mFaces)
	{
		const auto it = mMaterials.find(face.mtlname);
		if (it != mMaterials.end())
		{
			const Material&amp;amp; mat = it-&amp;gt;second;
			mShaderProgram-&amp;gt;SetVec3(&quot;material.diffuse&quot;, mat.Kd.r, mat.Kd.g, mat.Kd.b);
			mShaderProgram-&amp;gt;SetVec3(&quot;material.ambient&quot;, mat.Ka.r, mat.Ka.g, mat.Ka.b);
			mShaderProgram-&amp;gt;SetVec3(&quot;material.specular&quot;, mat.Ks.r, mat.Ks.g, mat.Ks.b);
			mShaderProgram-&amp;gt;SetFloat(&quot;material.shininess&quot;, mat.Ns);
		}
		glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, (void*)(indexOffset * sizeof(unsigned int)));
		indexOffset += 3;
	}
	if (mAllIndices.size() % 3 != 0) std::cerr &amp;lt;&amp;lt; &quot;⚠️ Broken EBO!&quot; &amp;lt;&amp;lt; std::endl;
	glBindVertexArray(0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;전체 흐름 요약&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;셰이더 활성화&lt;/li&gt;
&lt;li&gt;View, Projection, Model 행렬 구성 및 전달&lt;/li&gt;
&lt;li&gt;재질 (Material) 속성 전달&lt;/li&gt;
&lt;li&gt;glDrawElements()로 실제 그리기&lt;/li&gt;
&lt;li&gt;VAO 해제&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;섹션 상세 설명&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 셰이더 프로그램 활성화&lt;/p&gt;
&lt;pre id=&quot;code_1744589138594&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mShaderProgram-&amp;gt;Use();
glBindVertexArray(mVAO);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VAO를 바인딩함으로써 버퍼 세팅 정보를 불러오고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰이더 프로그램을 GPU에서 사용하도록 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.View Projection, Model 행렬 구성&lt;/p&gt;
&lt;pre id=&quot;code_1744589179147&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;float view[16];
createLookAtMatrix({0, 0, 3}, {0, 0, 0}, {0, 1, 0}, view);

float projection[16];
createPerspectiveMatrix(45.0f, 1000.0f/1000.0f, 0.1f, 100.0f, projection);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;View: 카메라 위치를 기준으로 씬을 바라보는 시점 설정&lt;/li&gt;
&lt;li&gt;Projection: 원근 투영(FOV 45도, near/far 평면)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 모델 변환 적용&lt;/p&gt;
&lt;pre id=&quot;code_1744589225178&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;setIdentityMatrix(model);  // 초기화
multiplyMatrix(model, scaleMatrix, model); // 스케일
rotateY(mRotY, model);                     // 회전
rotateX(mRotX, model);
multiplyMatrix(model, translate, model);  // 이동&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정점 데이터를 모델 공간 -&amp;gt; 월드 공간으로 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정은 행렬 곱 순서가 매우 중요함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 유니폼 값 전달&lt;/p&gt;
&lt;pre id=&quot;code_1744589587513&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mShaderProgram-&amp;gt;SetMat4(&quot;model&quot;, model);
mShaderProgram-&amp;gt;SetMat4(&quot;view&quot;, view);
mShaderProgram-&amp;gt;SetMat4(&quot;projection&quot;, projection);
mShaderProgram-&amp;gt;SetFloat(&quot;blendFactor&quot;, mBlendFactor);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰이더에서 사용하는 매트릭스들은 모두 유니폼으로 전달되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰이더에서는 이걸 통해 gl_Position = projection * view * model * vec4(...) 형태로 변환하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 재질 정보 전달 및 드로우 호출&lt;/p&gt;
&lt;pre id=&quot;code_1744589729590&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (const Face&amp;amp; face : mFaces)
{
	const auto it = mMaterials.find(face.mtlname);
	if (it != mMaterials.end())
	{
		const Material&amp;amp; mat = it-&amp;gt;second;
		mShaderProgram-&amp;gt;SetVec3(&quot;material.diffuse&quot;, mat.Kd.r, mat.Kd.g, mat.Kd.b);
		mShaderProgram-&amp;gt;SetVec3(&quot;material.ambient&quot;, mat.Ka.r, mat.Ka.g, mat.Ka.b);
		mShaderProgram-&amp;gt;SetVec3(&quot;material.specular&quot;, mat.Ks.r, mat.Ks.g, mat.Ks.b);
		mShaderProgram-&amp;gt;SetFloat(&quot;material.shininess&quot;, mat.Ns);
	}
	glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, (void*)(indexOffset * sizeof(unsigned int)));
	indexOffset += 3;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;material.diffuse, specular, ambient 등은 프래그먼트 셰이더에서 조명 연산에 사용된다.&lt;/li&gt;
&lt;li&gt;glDrawElements는 EBO에 있는 인덱스를 3개씩 참조하여 하나의 삼각형을 그림.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. VAO 해제&lt;/p&gt;
&lt;pre id=&quot;code_1744589820305&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;glBindVertexArray(0);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VAO 바인딩 해제!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 모델을 그릴 수 있도록 준비해 주는 단계.&lt;/p&gt;</description>
      <category>Dev/Grapics</category>
      <category>3d</category>
      <category>Game</category>
      <category>graphics</category>
      <category>OpenGL</category>
      <category>그래픽스</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/10</guid>
      <comments>https://onepaperhoon.tistory.com/10#entry10comment</comments>
      <pubDate>Mon, 14 Apr 2025 09:19:48 +0900</pubDate>
    </item>
    <item>
      <title>Scop 구현기 3편: 셰이더 클래스를 만들기로 했다</title>
      <link>https://onepaperhoon.tistory.com/9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL 셰이더를 매번 glCreateShader &amp;rarr; glCompileShader &amp;rarr; glLinkProgram 반복하는 게&lt;br /&gt;너무 지저분하고 비효율적이라는 생각이 들었다.&lt;br /&gt;심지어 하나만 있을 땐 모르겠는데, 앞으로 이게 10개, 20개 넘어가면&amp;hellip; 말 다했지 뭐.&lt;/p&gt;
&lt;p data-end=&quot;350&quot; data-start=&quot;314&quot; data-ke-size=&quot;size16&quot;&gt;그래서 아예 &lt;b&gt;셰이더 클래스를 하나 짜기로 했다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;헤더부터 깔끔하게 작성했다.&lt;/h3&gt;
&lt;pre id=&quot;code_1744238119613&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Shader {
public:
	Shader(const char* vsPath, const char* fsPath);
	~Shader();

	void Use();
	GLuint GetId();

	void SetBool(const std::string&amp;amp; name, bool value) const;
	void SetInt(const std::string&amp;amp; name, int value) const;
	void SetFloat(const std::string&amp;amp; name, float value) const;
	void SetMat4(const std::string&amp;amp; name, const float* value) const;

private:
	GLuint mProgramId;
	std::unordered_map&amp;lt;std::string, GLint&amp;gt; mUniformLocations;

	GLuint CompileShader(const char* vsPath, const char* fsPath);
	GLuint AddShader(const char* shaderCode, GLenum shaderType);
	std::string ReadFile(const char* filePath);
	void LoadActiveUniforms();
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 목표는 심플했다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.gsls 파일을 외부에서 읽어오고&lt;/li&gt;
&lt;li&gt;컴파일/링크하고&lt;/li&gt;
&lt;li&gt;Use()로 Activate&lt;/li&gt;
&lt;li&gt;Uniform도 Set 가능하게&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 무엇보다 main에서 glsl의 코드가 안 보이게!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Shader 클래스 구조 요약&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 생성자 &amp;amp; 소멸자&lt;/h4&gt;
&lt;pre id=&quot;code_1744352504263&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Shader::Shader(const char* vsPath, const char* fsPath)
{
	mProgramId = CompileShader(vsPath, fsPath);
	LoadActiveUniforms();
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일을 읽고&lt;/li&gt;
&lt;li&gt;CompileShader()를 호출해 프로그램 생성&lt;/li&gt;
&lt;li&gt;LoadAcriveUniforms()로 유니폼 목록 을 미리 로딩&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744352556582&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Shader::~Shader()
{
	if (mProgramId != 0)
		glDeleteProgram(mProgramId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그램 ID가 유효하면 glDeleteProgram 으로 정리&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 셰이더 컴파일 및 추가&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CompileShader&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ReadFile에서 읽어온 소스 파일 내용을 토대로 ShaderProgram을 만든뒤 ShaderProgram Id를 리턴하는 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744359022729&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GLuint	Shader::CompileShader(const char* vsPath, const char* fsPath)
{
	std::string vsCode = ReadFile(vsPath);
	std::string fsCode = ReadFile(fsPath);

	if (vsCode.empty() || fsCode.empty())
	{
		std::cerr &amp;lt;&amp;lt; &quot;Error: Failed to load shader file code.&quot; &amp;lt;&amp;lt; std::endl;
		return (0);
	}

	GLuint vs = AddShader(vsCode.c_str(), GL_VERTEX_SHADER);
	GLuint fs = AddShader(fsCode.c_str(), GL_FRAGMENT_SHADER);
	if (!vs || !fs)
	{
		return (0);
	}
	GLuint ShaderProgram = glCreateProgram();
	glAttachShader(ShaderProgram, vs);
	glAttachShader(ShaderProgram, fs);
	glLinkProgram(ShaderProgram);

	GLint success;
	GLchar infoLog[1024];
	glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &amp;amp;success);
	if (!success)
	{
		glGetProgramInfoLog(ShaderProgram, sizeof(infoLog), NULL, infoLog);
		std::cerr &amp;lt;&amp;lt; &quot;Error linking shader program: &quot; &amp;lt;&amp;lt; infoLog &amp;lt;&amp;lt; std::endl;
		return (0);
	}

	glDeleteShader(vs);
	glDeleteShader(fs);

	return (ShaderProgram);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 셰이더 사용&lt;/h4&gt;
&lt;pre id=&quot;code_1744353107491&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void Shader::Use()
{
	if (mProgramId != 0)
		glUseProgram(mProgramId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL은 명시적으로 glUseProgram()을 호출해야 현재 셰이더가 GPU 파이프라인에서 활성화된다.&lt;br /&gt;이 함수를 안 호출하면 아무것도 그려지지 않는다. 그런데 이걸 매번 glUseProgram(shaderProgram);으로 쓰면 헷갈린다.&lt;br /&gt;클래스 내부에 넣어두면 깔끔하게 shader.Use(); 한 줄로 끝난다. 의외로 이 한 줄이 코드 가독성을 굉장히 올려준다.&lt;br /&gt;게다가 &lt;b&gt;유지보수 시에 실수도 줄일 수 있다&lt;/b&gt;.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. Uniform 자동 로딩&lt;/h4&gt;
&lt;pre id=&quot;code_1744353552468&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void Shader::LoadActiveUniforms()
{
	GLint count;
	glGetProgramiv(mProgramId, GL_ACTIVE_UNIFORMS, &amp;amp;count);

	for (int i = 0; i &amp;lt; count; ++i)
	{
		GLchar	name[256];
		GLsizei	length;
		GLint	size;
		GLenum	type;

		glGetActiveUniform(mProgramId, i, sizeof(name), &amp;amp;length, &amp;amp;size, &amp;amp;type, name);

		GLint loaction = glGetUniformLocation(mProgramId, name);
		mUniformLocations[name] = loaction;
		std::cout &amp;lt;&amp;lt; &quot;Uniforms Name: &quot; &amp;lt;&amp;lt; name &amp;lt;&amp;lt; std::endl;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OpenGL에서 GL_ACTIVE_UNIFORMS 값을 받아서&lt;/li&gt;
&lt;li&gt;glGetActiveUniform()으로 현재 셰이더에 등록된 유니폼 이름 및 사이즈를 전부 가져온다.&lt;br /&gt;그리고 mUniformLocations 맵에 전부 캐싱해둔다!&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;OpenGL은 현재 프로그램에 등록된 유니폼들을 조회할 수 있는 API를 제공한다.&lt;br /&gt;&lt;/span&gt;그걸 활용해서 glGetActiveUniform()으로 유니폼의 이름, 타입, 크기를 받아오고&lt;br /&gt;해당 이름의 glGetUniformLocation()도 미리 호출해서 캐시한다.&lt;br /&gt;이 과정을 통하면 런타임 중에 &quot;유니폼 이름이 틀렸습니다&quot; 같은 에러 없이&lt;br /&gt;&lt;b&gt;처음 실행 시점에 모든 유니폼 존재 여부를 체크할 수 있게 된다.&lt;br /&gt;&lt;/b&gt;개발자 입장에선 이게 정말 심리적으로 안정감을 준다.&lt;br /&gt;어디서 안 보이면, 일단 이 함수 로그만 보면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 유니폼 전송 함수들 (SetX 계열)&lt;/h4&gt;
&lt;pre id=&quot;code_1744358188662&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;void	Shader::SetBool(const std::string&amp;amp; name, bool value) const
{
	GLint location = GetLocation(name);
	if (location != -1)
		glUniform1i(location, (int)value);
	else
		std::cerr &amp;lt;&amp;lt; &quot;Not Found : &quot; &amp;lt;&amp;lt; name &amp;lt;&amp;lt; &quot; Uniform&quot; &amp;lt;&amp;lt; std::endl;
}

void	Shader::SetInt(const std::string&amp;amp; name, int value) const
{
	GLint location = GetLocation(name);
	if (location != -1)
		glUniform1i(location, value);
	else
		std::cerr &amp;lt;&amp;lt; &quot;Not Found : &quot; &amp;lt;&amp;lt; name &amp;lt;&amp;lt; &quot; Uniform&quot; &amp;lt;&amp;lt; std::endl;
}

void	Shader::SetFloat(const std::string&amp;amp; name, float value) const
{
	GLint location = GetLocation(name);
	if (location != -1)
		glUniform1f(location, value);
	else
		std::cerr &amp;lt;&amp;lt; &quot;Not Found : &quot; &amp;lt;&amp;lt; name &amp;lt;&amp;lt; &quot; Uniform&quot; &amp;lt;&amp;lt; std::endl;
}

void	Shader::SetVec2(const std::string&amp;amp; name, float x, float y) const
{
	GLint location = GetLocation(name);
	if (location != -1)
		glUniform2f(location, x, y);
	else
		std::cerr &amp;lt;&amp;lt; &quot;Not Found : &quot; &amp;lt;&amp;lt; name &amp;lt;&amp;lt; &quot; Uniform&quot; &amp;lt;&amp;lt; std::endl;
}

void	Shader::SetVec3(const std::string&amp;amp; name, float x, float y, float z) const
{
	GLint location = GetLocation(name);
	if (location != -1)
		glUniform3f(location, x, y, z);
	else
		std::cerr &amp;lt;&amp;lt; &quot;Not Found : &quot; &amp;lt;&amp;lt; name &amp;lt;&amp;lt; &quot; Uniform&quot; &amp;lt;&amp;lt; std::endl;
}

void	Shader::SetVec4(const std::string&amp;amp; name, float x, float y, float z, float w) const
{
	GLint location = GetLocation(name);
	if (location != -1)
		glUniform4f(location, x, y, z, w);
	else
		std::cerr &amp;lt;&amp;lt; &quot;Not Found : &quot; &amp;lt;&amp;lt; name &amp;lt;&amp;lt; &quot; Uniform&quot; &amp;lt;&amp;lt; std::endl;
}

void	Shader::SetMat3(const std::string&amp;amp; name, const float* value) const
{
	GLint location = GetLocation(name);
	if (location != -1)
		glUniformMatrix3fv(location, 1, GL_FALSE, value);
	else
		std::cerr &amp;lt;&amp;lt; &quot;Not Found : &quot; &amp;lt;&amp;lt; name &amp;lt;&amp;lt; &quot; Uniform&quot; &amp;lt;&amp;lt; std::endl;
}

void	Shader::SetMat4(const std::string&amp;amp; name, const float* value) const
{
	GLint location = GetLocation(name);
	if (location != -1)
		glUniformMatrix4fv(location, 1, GL_FALSE, value);
	else
		std::cerr &amp;lt;&amp;lt; &quot;Not Found : &quot; &amp;lt;&amp;lt; name &amp;lt;&amp;lt; &quot; Uniform&quot; &amp;lt;&amp;lt; std::endl;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;2309&quot; data-start=&quot;2186&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;OpenGL은 유니폼을 보낼 때마다 glGetUniformLocation()을 호출해야 한다.&lt;br /&gt;이건 비용이 크고, 런타임 중 호출 시 실수도 많고 성능에도 좋지 않다.&lt;/p&gt;
&lt;p data-end=&quot;2390&quot; data-start=&quot;2311&quot; data-ke-size=&quot;size16&quot;&gt;그래서 나는 mUniformLocations라는 맵을 하나 만들고,&lt;br /&gt;셰이더 초기화 시에 모든 유니폼 이름과 위치를 미리 다 저장해놨다.&lt;/p&gt;
&lt;p data-end=&quot;2524&quot; data-start=&quot;2392&quot; data-ke-size=&quot;size16&quot;&gt;그 덕분에 지금은 그냥 shader.SetVec3(&quot;uColor&quot;, 1.0f, 0.0f, 0.0f); 한 줄이면 끝난다.&lt;br /&gt;이 구조가 되니까 uniform 작업이 너무 편해졌고,&lt;br /&gt;&lt;b&gt;코드도 안정적이고 예측 가능하게 바뀌었다&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. GLSL 타입 출력 함수&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1744358242270&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static const char* GetGLTypeString(GLenum type)
{
	switch (type)
    {
		case GL_FLOAT: return &quot;float&quot;;
		case GL_FLOAT_VEC2: return &quot;vec2&quot;;
		case GL_FLOAT_VEC3: return &quot;vec3&quot;;
		case GL_FLOAT_VEC4: return &quot;vec4&quot;;
		case GL_INT: return &quot;int&quot;;
		case GL_INT_VEC2: return &quot;ivec2&quot;;
		case GL_INT_VEC3: return &quot;ivec3&quot;;
		case GL_INT_VEC4: return &quot;ivec4&quot;;
		case GL_BOOL: return &quot;bool&quot;;
		case GL_BOOL_VEC2: return &quot;bvec2&quot;;
		case GL_BOOL_VEC3: return &quot;bvec3&quot;;
		case GL_BOOL_VEC4: return &quot;bvec4&quot;;
		case GL_FLOAT_MAT2: return &quot;mat2&quot;;
		case GL_FLOAT_MAT3: return &quot;mat3&quot;;
		case GL_FLOAT_MAT4: return &quot;mat4&quot;;
		case GL_SAMPLER_2D: return &quot;sampler2D&quot;;
		case GL_SAMPLER_CUBE: return &quot;samplerCube&quot;;
		default: return &quot;unknown&quot;;
	}
}

void Shader::PrintActiveUniforms() const
{
	GLint count;
	glGetProgramiv(mProgramId, GL_ACTIVE_UNIFORMS, &amp;amp;count);

	std::cout &amp;lt;&amp;lt; &quot;Active Uniforms in Shader Program &quot; &amp;lt;&amp;lt; &quot;: &quot; &amp;lt;&amp;lt; mProgramId &amp;lt;&amp;lt;  std::endl;

	for (int i = 0; i &amp;lt; count; ++i) {
		GLchar name[256];
		GLsizei length;
		GLint size;
		GLenum type;
		glGetActiveUniform(mProgramId, i, sizeof(name), &amp;amp;length, &amp;amp;size, &amp;amp;type, name);
		std::cout &amp;lt;&amp;lt; &quot; - &quot; &amp;lt;&amp;lt; name &amp;lt;&amp;lt; &quot; (Type: &quot; &amp;lt;&amp;lt; GetGLTypeString(type) &amp;lt;&amp;lt; &quot;, Size: &quot; &amp;lt;&amp;lt; size &amp;lt;&amp;lt; &quot;)&quot; &amp;lt;&amp;lt; std::endl;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2138&quot; data-start=&quot;2098&quot;&gt;프린트할 때 vec3, mat4 같은 문자열로 출력해주는 유틸&lt;/li&gt;
&lt;li data-end=&quot;2175&quot; data-start=&quot;2139&quot;&gt;디버깅용 PrintActiveUniforms()에서 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2198&quot; data-start=&quot;2182&quot; data-ke-size=&quot;size26&quot;&gt;구현하면서 느낀 점&lt;/h2&gt;
&lt;h3 data-end=&quot;2225&quot; data-start=&quot;2200&quot; data-ke-size=&quot;size23&quot;&gt;✔️ 만들고 나니까 이런 점이 좋았다:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2353&quot; data-start=&quot;2227&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2264&quot; data-start=&quot;2227&quot;&gt;&lt;b&gt;GLSL 파일만 바꿔도 바로 적용됨&lt;/b&gt; &amp;rarr; 실시간 개발 느낌&lt;/li&gt;
&lt;li data-end=&quot;2292&quot; data-start=&quot;2265&quot;&gt;uniform 명칭 실수 줄어듦&lt;/li&gt;
&lt;li data-end=&quot;2317&quot; data-start=&quot;2293&quot;&gt;셰이더가 많아질수록 이 구조가 빛을 발함&lt;/li&gt;
&lt;li data-end=&quot;2353&quot; data-start=&quot;2318&quot;&gt;PrintActiveUniforms()로 디버깅도 쉬워짐&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;2372&quot; data-start=&quot;2355&quot; data-ke-size=&quot;size23&quot;&gt;❌ 만들면서 애먹은 점:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2462&quot; data-start=&quot;2374&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2411&quot; data-start=&quot;2374&quot;&gt;GLSL 읽기 실패했을 때 에러 로그 안 뜨는 것 &amp;rarr; 로그 추가&lt;/li&gt;
&lt;li data-end=&quot;2462&quot; data-start=&quot;2412&quot;&gt;uniform 이름이 바뀌었는데 캐시만 믿었다가 값이 안 들어감 &amp;rarr; 리로딩 필요성 느낌&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;  최종 사용 예시&lt;/h4&gt;
&lt;pre id=&quot;code_1744358809454&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Shader shader(&quot;shaders/basic.vert&quot;, &quot;shaders/basic.frag&quot;);
shader.Use();
shader.SetVec3(&quot;uColor&quot;, 1.0f, 0.2f, 0.5f);
shader.SetMat4(&quot;uMVP&quot;, glm::value_ptr(mvp));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dev/Grapics</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/9</guid>
      <comments>https://onepaperhoon.tistory.com/9#entry9comment</comments>
      <pubDate>Fri, 11 Apr 2025 17:51:43 +0900</pubDate>
    </item>
    <item>
      <title>Scop 구현기 2편: VAO와 VBO의 세계</title>
      <link>https://onepaperhoon.tistory.com/8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우를 띄우는 것까지 했으니 이제 삼각형을 그려보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;삼각형 그리기 전체 코드&lt;/h3&gt;
&lt;pre id=&quot;code_1744182269055&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;GL/glew.h&amp;gt;
#include &amp;lt;GLFW/glfw3.h&amp;gt;
#include &amp;lt;OpenGL/gl.h&amp;gt;
#include &amp;lt;iostream&amp;gt;

const char* vertexShaderSource = R&quot;(
#version 330 core
layout (location = 0) in vec3 aPos;

void main() {
	gl_Position = vec4(aPos, 1.0);
}
)&quot;;

const char* fragmentShaderSource = R&quot;(
#version 330 core
out vec4 FragColor;

void main() {
	FragColor = vec4(1.0, 0.5, 0.2, 1.0); // 주황색
}
)&quot;;

void processInput(GLFWwindow* window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}

int main(void)
{
	if (!glfwInit())
	{
		std::cerr &amp;lt;&amp;lt; &quot;GFLW Init Failed&quot; &amp;lt;&amp;lt; std::endl;
		return (-1);
	}

	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

	GLFWwindow* window = glfwCreateWindow(500, 500, &quot;scop&quot;, NULL, NULL);

	glfwMakeContextCurrent(window);
	int framebuffer_w, framebuffer_h;
	glfwGetFramebufferSize(window, &amp;amp;framebuffer_w, &amp;amp;framebuffer_h);
	glViewport(0, 0, framebuffer_w , framebuffer_h);

	if (glewInit() != GLEW_OK)
	{
		std::cerr &amp;lt;&amp;lt; &quot;GLEW Init Failed&quot; &amp;lt;&amp;lt; std::endl;
		glfwTerminate();
		return (-1);
	}

	// 정점 데이터
	float vertices[] = {
		-0.5f, -0.5f, 0.0f,
		0.5f, -0.5f, 0.0f,
		0.0f, 0.5f, 0.0f
	};

	GLuint VAO, VBO;
	// VAO &amp;amp; VBO 생성
	glGenVertexArrays(1, &amp;amp;VAO);
	glGenBuffers(1, &amp;amp;VBO);

	// VAO 바인딩
	glBindVertexArray(VAO);

	// VBO 바인딩 + 데이터 전달
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	// 정점 속성 지정 (position);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

	// 바인딩 해제
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);

	// --- 셰이더 컴파일 및 프로그램 생성 ---
	GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &amp;amp;vertexShaderSource, NULL);
	glCompileShader(vertexShader);

	GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &amp;amp;fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);

	GLuint shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);

	// 세이더는 프로그램에 연결 후 삭제 가능
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	while (!glfwWindowShouldClose(window))
	{
		processInput(window);
		glClearColor(0, 0, 0, 1);
		glClear(GL_COLOR_BUFFER_BIT);

		glUseProgram(shaderProgram);
		glBindVertexArray(VAO);
		glDrawArrays(GL_TRIANGLES, 0, 3);

		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	// --- 종료 처리 ---
	glDeleteVertexArrays(1, &amp;amp;VAO);
	glDeleteBuffers(1, &amp;amp;VBO);
	glDeleteProgram(shaderProgram);
	glfwTerminate();
	return (0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 삼각형은 그래픽스의 최소 단위&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래픽스에서 화면에 뭔가를 그릴 때, 그 기본은 항상&amp;nbsp;&lt;b&gt;삼각형&lt;/b&gt;인데요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 삼각형인지 찾아 보았다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;평면 상의&amp;nbsp;&lt;b&gt;가장 단순한 다각형&amp;nbsp;&lt;/b&gt;(모든 다각형은 삼각형으로 나눌 수 있음)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GPU&amp;nbsp;&lt;/b&gt;하드웨어 친화적 (세 점이면 면이 정의됨)&lt;/li&gt;
&lt;li&gt;렌더링 파이프 라인 구조상, 삼각형을 기준으로 rasterization이 이루어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-09 오후 4.22.28.png&quot; data-origin-width=&quot;278&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ce7hBc/btsNdo8hxx4/FV7ARvykUrYMWKQLzxMia0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ce7hBc/btsNdo8hxx4/FV7ARvykUrYMWKQLzxMia0/img.png&quot; data-alt=&quot;rasterization&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ce7hBc/btsNdo8hxx4/FV7ARvykUrYMWKQLzxMia0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fce7hBc%2FbtsNdo8hxx4%2FFV7ARvykUrYMWKQLzxMia0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;98&quot; height=&quot;233&quot; data-filename=&quot;스크린샷 2025-04-09 오후 4.22.28.png&quot; data-origin-width=&quot;278&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;rasterization&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 오늘 이 삼각형 하나를 그리기 위해 필요한 OpenGL파이프라인 구성을 배워볼 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. VAO와 VBO는 왜 필요한가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL의 Core Profile에서는 삼각형을 그리려면 정점 데이터 (Vertex Data)를 GPU에게 보내줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 데이터는 버퍼에 담겨야 하, 그 버퍼를 설명하는 정보도 있어야 한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;요소&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;VBO (Vertex Buffer Object)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;정점 데이터를 GPU에 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;VAO (Vertex Array Object)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;정점 버퍼(VBO)를 어떻게 해석할지 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VBO는&amp;nbsp;&lt;b&gt;데이터 저장소,&amp;nbsp;&lt;/b&gt;VAO는&amp;nbsp;&lt;b&gt;데이터 해석 방법&lt;/b&gt;이라고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말해, VBO가 책이라면, VAO는 그 책의 목차다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;참고: VAO는 OpenGL 3.0 이상 Core Profile에선 필수. 없으면 에러!&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 셰이더 프로그램 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL은 셰이더 없이 그릴 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰이더란 GPU에게&amp;nbsp;&lt;b&gt;어떻게 그릴 지를 알려주는 프로그램&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Vertex Shader&lt;/h4&gt;
&lt;pre id=&quot;code_1744185749456&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#version 330 core
layout (location = 0) in vec3 aPos;

void main() {
    gl_Position = vec4(aPos, 1.0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Vertex Shader는 아주 기본적인 형태이지만, OpenGL에서 렌더링 파이프라인의 첫 번째 관문이자 정점 처리의 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 줄에 대해서 설명&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;#version 330 core&lt;br /&gt;-&amp;gt; 이 셰이더는 OpenGL 3.3 Core Profile에 맞는 GLSL(OpenGL Shading Language) 문법을 사용하겠다는 뜻.&lt;/li&gt;
&lt;li&gt;layout (location = 0) in vec3 aPos;&lt;br /&gt;-&amp;gt; 이 부분은 GPU로부터 입력받을 정점 속성(Attribute)을 선언.&lt;br /&gt;Location = 0은 C++ 코드에서 glVertexAttribPointer(0, ...)로 연결해 준 버퍼 데이터의 위치와 매핑된다.&lt;br /&gt;Loaction = 1일 경우 C++코드에서 glVertexAttribPointer(1, ....)로 연결한 것&lt;/li&gt;
&lt;li&gt;void main()&lt;br /&gt;-&amp;gt; 셰이더 프로그램은 항상 main() 함수부터 실행된다, GPU에서 수천 개의 정점을 동시에 처리할 때 이 main()이&lt;br /&gt;병렬 실행된다.&lt;/li&gt;
&lt;li&gt;gl_Position = vec4(aPos, 1.0);&lt;br /&gt;-&amp;gt; 핵심 부분! OpenGL은 모든 정점 위치를 동차 좌표(clip space)로 변환해야만 그릴 수 있다.&lt;br /&gt;그래서 vec3인 정점 위치에 w = 1.0을 붙여서 vec4로 만들어 gl_Position에 넣는다.&lt;br /&gt;&lt;br /&gt;여기서 gl_Position은 OpenGL 내부 파이프라인에서&amp;nbsp;&lt;b&gt;꼭 필요한 예약 변수&lt;/b&gt;로,&lt;br /&gt;이 값이 나중에 resterization 단계를 거쳐 화면 좌표로 바뀐다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;요약하면:&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정점 하나하나를 처리해서 화면 좌표계로 넘기는 작업.&lt;/li&gt;
&lt;li&gt;나중에 카메라, 변환 행렬 등이 추가되면 이 부분이 훨씬 복잡해짐.&lt;/li&gt;
&lt;li&gt;지금은 아주 기본적인 통과 셰이더(pass-throgh shader) 역할.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Fragment Shader&lt;/h4&gt;
&lt;pre id=&quot;code_1744185773127&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#version 330 core
out vec4 FragColor;

void main() {
	FragColor = vec4(1.0, 0.5, 0.2, 1.0); // 주황색
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 셰이더는 삼각형이 rasterization 과정을 거친 후,&amp;nbsp;&lt;b&gt;화면의 픽셀을 어떤 색으로 칠할지 결정&lt;/b&gt;하는 역할.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 줄에 대해서 설명&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;#version 330&lt;br /&gt;-&amp;gt; Vertex Shader와 동일하게, OpenGL 3.3 Core Profile에 맞춰 작성한 셰이더 버전.&lt;/li&gt;
&lt;li&gt;out vec4 FragColor;&lt;br /&gt;-&amp;gt; 이건 출력 변수. vec4는 RGBA로 구성된&amp;nbsp;&lt;b&gt;최종 색상&lt;/b&gt;을 의미.&lt;br /&gt;이 변수가 GPU 파이프라인을 통해 화면에 실제로 출력되는 색상.&lt;/li&gt;
&lt;li&gt;void main()&lt;br /&gt;-&amp;gt; Fragment Shader도 각 픽셀 단위(Fragment)로 실행된다.&lt;br /&gt;삼각형 내부 픽셀마다 이 함수가 호출되며, 하나하나 색을 지정한다.&lt;/li&gt;
&lt;li&gt;FragColor = vec4(1.0, 0.5, 0.2, 1.0);&lt;br /&gt;-&amp;gt; 픽셀을 주황색(빨간색 100%, 초록색 50%, 파란색 20%, 알파 100%)으로 칠한다는 뜻.&lt;br /&gt;이걸 gl_FragColor로 쓰던 시절도 있었지만, 지금은 명시적으로 out 변수로 정의해야 함.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;요약하면:&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정점 처리 후 생긴 픽셀마다 실행돼서, 그 픽셀의 최종 색상을 결정.&lt;/li&gt;
&lt;li&gt;텍스처, 조명, 음영, 하이라이트 등 모두 이 단계에서 구현된다.&lt;/li&gt;
&lt;li&gt;지금은 단순히 &quot;색만 고정 출력&quot;하는 기본 셰이더지만, 응용하면 거의 모든 렌더링 효과를 만들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 렌더링 루프 안에 드로우 코드 추가&lt;/h3&gt;
&lt;pre id=&quot;code_1744188013765&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while (!glfwWindowShouldClose(window))
{
	processInput(window);
	glClearColor(0, 0, 0, 1);
	glClear(GL_COLOR_BUFFER_BIT);

	glUseProgram(shaderProgram);       // 셰이더 사용
	glBindVertexArray(VAO);            // VAO 바인딩
	glDrawArrays(GL_TRIANGLES, 0, 3);  // 삼각형 그리기

	glfwSwapBuffers(window);
	glfwPollEvents();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 렌더링 루프 안에서 드로우 코드를 넣고 실행해 보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-09 오후 5.42.28.png&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;1058&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZAHK7/btsNdVScwsV/18nJme6QxhOvV3ZKlNKRb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZAHK7/btsNdVScwsV/18nJme6QxhOvV3ZKlNKRb0/img.png&quot; data-alt=&quot;simple Triangles&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZAHK7/btsNdVScwsV/18nJme6QxhOvV3ZKlNKRb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZAHK7%2FbtsNdVScwsV%2F18nJme6QxhOvV3ZKlNKRb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;1058&quot; data-filename=&quot;스크린샷 2025-04-09 오후 5.42.28.png&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;1058&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;simple Triangles&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 주황색 삼각형이 보이면 성공한 것이다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;만들며 궁금했던 점들&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. gl_Position은 정의돼 있는 변수인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GLSL의 내장(내부) 변수가 맞다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GLSL은 OpenGL 파이프라인과 맞물리는 구조로 설계되어 있어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중에서도 gl_Position은 Vertex Shader에서 반드시 지정해야 하는 출력 변수로 설정되어 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;정확히 말하자면 gl_Position은 VertexShader가 GPU에 넘기는 최종 정점 위치를 나타내는 내장 변수다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 값은 클립 공간 (Clip Space) 좌표다.&lt;/li&gt;
&lt;li&gt;이후 자동으로 NDC(Normalized Device Coordinates)로 변환되고,&lt;/li&gt;
&lt;li&gt;다시 Viewport 변환을 거쳐 스크린 좌표로 찍히게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, gl_Position에 우리가 지정한 vec4(x, y, z, w)가 그래픽스 파이프 라인에 쭉 따라가서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면에 삼각형이 보이게 되는 원인이 되는 것.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2. gl_Position과 Fragment Shader는 연결되어 있나?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vertex Shader와 Fragment Shader는 OpenGL 파이프라인 내에서 다단계 릴레이 시스템처럼 동작한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vertex Shader가 정점들을 처리해서 삼각형을 정의하고,&lt;/li&gt;
&lt;li&gt;Rasterizer가 그 삼각형을 픽셀 단위(fragment)로 나눔&lt;/li&gt;
&lt;li&gt;그 픽셀마다 프래그먼트 셰이더가 색상을 결정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, gl_Position은 삼각형이 어디에 그려질지를 결정하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 삼각형 안의 픽셀들에 대해서 Fragment Shader가 색을 채우는 역할을 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL이 굉장히&amp;nbsp;&lt;b&gt;저수준 API&lt;/b&gt;이기 때문에 명확한 지시 없이는 어떤 일도 하지 않는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DirectX도 Vulkan도 마찬기지로. GPU는 매우 병렬적이고 강력한 하드웨어지만, 그만큼&amp;nbsp;&lt;b&gt;명확한 지시&lt;/b&gt; 없이는 어떤 일도 하지 않는다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dev/Grapics</category>
      <category>c++</category>
      <category>cpp</category>
      <category>glfw</category>
      <category>graphicapi</category>
      <category>graphics</category>
      <category>OpenGL</category>
      <category>shader</category>
      <category>Vertex Shader</category>
      <category>그래픽스</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/8</guid>
      <comments>https://onepaperhoon.tistory.com/8#entry8comment</comments>
      <pubDate>Wed, 9 Apr 2025 19:04:49 +0900</pubDate>
    </item>
    <item>
      <title>Scop 구현기 1편: 윈도우 하나 띄우는 게 뭐라고 (Object Viewer)</title>
      <link>https://onepaperhoon.tistory.com/7</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;앞선 지식들을 구현하며 공부하느라 많이 정리를 하지 못했습니다.&lt;br /&gt;구현했던 순서대로 작성을 해보겠습니다!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;윈도우를 만들기 위한 전체 코드&lt;/h3&gt;
&lt;pre id=&quot;code_1744173165328&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;GL/glew.h&amp;gt;
#include &amp;lt;GLFW/glfw3.h&amp;gt;
#include &amp;lt;OpenGL/gl.h&amp;gt;
#include &amp;lt;iostream&amp;gt;

int main(void)
{
	if (!glfwInit())
	{
		std::cerr &amp;lt;&amp;lt; &quot;GFLW Init Failed&quot; &amp;lt;&amp;lt; std::endl;
		return (-1);
	}

	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

	GLFWwindow* window = glfwCreateWindow(500, 500, &quot;scop&quot;, NULL, NULL);

	glfwMakeContextCurrent(window);
	int framebuffer_w, framebuffer_h;
	glfwGetFramebufferSize(window, &amp;amp;framebuffer_w, &amp;amp;framebuffer_h);
	glViewport(0, 0, framebuffer_w , framebuffer_h);

	if (glewInit() != GLEW_OK)
	{
		std::cerr &amp;lt;&amp;lt; &quot;GLEW Init Failed&quot; &amp;lt;&amp;lt; std::endl;
		glfwTerminate();
		return (-1);
	}

	while (!glfwWindowShouldClose(window))
	{
		glClearColor(0, 0, 0, 1);
		glClear(GL_COLOR_BUFFER_BIT);

		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	glfwTerminate();
	return (0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. OpenGL 프로젝트의 시작은 항상 창 만들기부터!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL은 &quot;그래픽&quot;만 담당합니다. 운영체제 창은 만들어주지 않죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리는 GLFW 같은 라이브러리를 써야 해요. GLFW는 &quot;운영체제에 독립적인 윈도우 생성 및 입력 처리&quot;를 담당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;#include &amp;lt;GLFW/glfw3.h&amp;gt;&lt;/span&gt;헤더를 포함한 뒤 glfw 함수를 사용하기 위해 초기화해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1744172546269&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (!glfwInit()) {
	std::cerr &amp;lt;&amp;lt; &quot;GFLW Init Failed&quot; &amp;lt;&amp;lt; std::endl;
	return (-1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;glfwInit()&amp;nbsp;&lt;/span&gt;은 GLFW를 사용하기 위해 라이브러리 초기화 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기화가 성공하면 GLFW_TRUE(1)를 오류가 발생하면 GLFW_FALSE(0)가 반환됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패하면 OpenGL을 시작도 못 하게 됩니다. 그래서 항상 체크해 주는 게 기본 중 기본입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  참고로 glfwInit은 내부적으로 OS에 맞는 초기화 루틴을 실행합니다.&lt;br /&gt;예: 윈도우에서는 WinAPI 초기화, 맥에서는 Cocoa 세팅 등!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GLFW 사용이 끝나면, 일반적으로 프로그램이 종료되기 직전에 GLFW를 종료해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1744173006905&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;glfwTerminate ();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 남아 있는 모든 윈도우가 삭제되고 GLFW가 할당한 다른 리소스도 해제됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수를 호출한 후에는 GLFW를 다시 초기화해야 해당 리소스를 필요로 하는 GLFW함수를 사용할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. OpenGL 3.3 Core Profile로 세팅하기&lt;/h3&gt;
&lt;pre id=&quot;code_1744173130850&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 뭐냐고요? 간단히 말하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;나는 2000년대 OpenGL 말고, 최신 방식만 쓸 거야!&quot; 선언입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GLFW_CONTEXT_VERSION_MAJOR, MINOR -&amp;gt; OpenGL 버전 지정(3.3은 안정적 + 최소 셰이더 기반 버전)&lt;/li&gt;
&lt;li&gt;GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE -&amp;gt; 구형 API 제거, VAO/VBO 안 쓰면 에러 발생!&lt;/li&gt;
&lt;li&gt;GLFW_OPENGL_FORWARD_COMPAT -&amp;gt; 더 이상 안 쓰는 기능 무시 (Mac에선 필수!)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이걸 안 하면 VAO 없이도 그려지는 이상한(?) 상태가 발생될 수 있어요.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;170&quot; data-start=&quot;130&quot; data-ke-size=&quot;size26&quot;&gt;왜 GLFW_OPENGL_CORE_PROFILE이 중요한가?&lt;/h2&gt;
&lt;p data-end=&quot;197&quot; data-start=&quot;171&quot; data-ke-size=&quot;size16&quot;&gt;VAO 없이도 그려지는 이상한(?) 상태의 정체&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1744178397645&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-end=&quot;480&quot; data-start=&quot;274&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;480&quot; data-start=&quot;274&quot; data-ke-size=&quot;size16&quot;&gt;위 설정을 하지 않으면 기본적으로 &lt;b&gt;Compatibility Profile&lt;/b&gt;로 실행될 수 있습니다.&lt;br /&gt;그러면 OpenGL의 예전 방식, 예를 들면 glBegin, glVertex, glEnd와 같은 함수들이 여전히 살아있죠. 그뿐만 아니라, VAO (Vertex Array Object) 없이도 버퍼를 쓰는 게 &amp;ldquo;되는 것처럼 보이는&amp;rdquo; 상황이 발생합니다.&lt;/p&gt;
&lt;p data-end=&quot;493&quot; data-start=&quot;482&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;493&quot; data-start=&quot;482&quot; data-ke-size=&quot;size16&quot;&gt;이게 왜 문제냐고요?&lt;/p&gt;
&lt;p data-end=&quot;608&quot; data-start=&quot;495&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;608&quot; data-start=&quot;495&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;\Core Profile에서는 VAO 없이 그리면 명백하게 에러&lt;/b&gt;가 납니다. VAO가 없으면 GPU는 &amp;ldquo;어떤 버퍼에서, 어떤 방식으로 정점 데이터를 읽을지&amp;rdquo;를 알 수 없기 때문이죠.&lt;/p&gt;
&lt;p data-end=&quot;727&quot; data-start=&quot;610&quot; data-ke-size=&quot;size16&quot;&gt;하지만 Compatibility Profile에선 &lt;b&gt;자동으로 기본 VAO 상태가 세팅되어&lt;/b&gt; 있는 것처럼 작동합니다. 그래서 실수로 VAO를 안 만들었는데도 삼각형이 그려지거나, 버퍼가 먹히는 일이 생깁니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;815&quot; data-start=&quot;729&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;815&quot; data-start=&quot;731&quot; data-ke-size=&quot;size16&quot;&gt;이 상태가 바로 &quot;이상한(?) 상태&quot;입니다. 코드가 돌아가긴 하는데, 실제로는 명확하지도, 이식성도 없고, 최신 OpenGL 문법에도 어긋납니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. GLFW 윈도우 생성과 컨텍스트 설정&lt;/h3&gt;
&lt;pre id=&quot;code_1744173559194&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GLFWwindow* window = glfwCreateWindow(500, 500, &quot;scop&quot;, NULL, NULL);
glfwMakeContextCurrent(window);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 창을 생성했습니다!  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;glfwCreateWindow()&lt;/span&gt;는 너비, 높이, 창 이름을 받고 윈도우를 만들어줘요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 만든다고 바로 쓰는 건 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL은&amp;nbsp;&lt;b&gt;렌더링 컨텍스트&lt;/b&gt;라는 걸 만들어야만 함수들이 제대로 작동해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;glfwMakeContextCurrent(window)&lt;/span&gt;로 이 창을 &quot;현재 쓰는 컨텍스트&quot;로 등록합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;참고: &quot;컨텍스트&quot;는 OpenGL 상태 저장소라고 보면 됩니다, OpenGL 함수들은 모두 이 상태에 종속되며.&lt;br /&gt;프레임버퍼 객체에 그리지 않을 때 렌더링 명령이 그릴 (잠재적으로 보이는) 기본 프레임 버퍼를 나타냅니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Viewport 설정 - 너의 픽셀 범위는 어디니?&lt;/h3&gt;
&lt;pre id=&quot;code_1744174315740&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int framebuffer_w, framebuffer_h;
glfwGetFramebufferSize(window, &amp;amp;framebuffer_w, &amp;amp;framebuffer_h);
glViewport(0, 0, framebuffer_w , framebuffer_h);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 창이 생겼다고 GPU가 그릴 공간이 자동으로 지정되는 건 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;glViewport는 말 그래도 이 화면에서 실제로 그릴 픽셀 영역을 정해줘요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(0, 0)은 좌측 하단의 시작점&lt;/li&gt;
&lt;li&gt;(w, h)는 그릴 영역 크기&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  고해상도 모니터 (예: Retina)에서는 실제 프레임버퍼 크기가 창 사이즈보다 클 수 있어서 &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;glfwGetFramebufferSize로 정확하게 받아와야 합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. GLEW로 OpenGL 함수 포인터 초기화&lt;/h3&gt;
&lt;pre id=&quot;code_1744175488365&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (glewInit() != GLEW_OK) {
	std::cerr &amp;lt;&amp;lt; &quot;GLEW Init Failed&quot; &amp;lt;&amp;lt; std::endl;
	glfwTerminate();
	return (-1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL 함수는 마법처럼 다 쓸 수 있는 게 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS는 기본적으로 &lt;b&gt;OpenGL의 정의&lt;/b&gt;만 알려주고, 실제 포인터 주소는 우리가 직접 가져와야 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 GLEW를 써서 모든 OpenGL 확장 함수 포인터를 초기화합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GLEW는 Extension Wrangler의 약자&lt;/li&gt;
&lt;li&gt;glGenBuffers, glCreateShader 등등.. 다 여기서 초기화됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패했을 때 위에서 glfw를 생성했기 때문에 glfwTerminate를 호출한 뒤 나가는 게 중요 포인트!&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;GLEW를 쓰지 않는다면? glX, wgl처럼 OS별 API로 직접 불러와야 하는 복잡한 세상에 빠집니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 루프: 그리기, 버퍼 교체, 이벤트 처리&lt;/h3&gt;
&lt;pre id=&quot;code_1744177230777&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while (!glfwWindowShouldClose(window)) {
	glClearColor(0, 0, 0, 1);
	glClear(GL_COLOR_BUFFER_BIT);

	glfwSwapBuffers(window);
	glfwPollEvents();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 메인 루프!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;창을 열고 계속해서 프레임을 갱신합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;glClearColor와 glClear로 매 프레임 배경색을 초기화&lt;/li&gt;
&lt;li&gt;glfwSwapBuffers: 백 버퍼를 프론트로 교체 (더블 버퍼링)&lt;/li&gt;
&lt;li&gt;glfwPollEvents: 마우스, 키도브, 창 크기 등 이벤트 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 루프 안에서 드로잉, 물리 연산, 애니메이션 등 모든 게 돌아갑니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-09 오후 2.57.25.png&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;1292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HnLUW/btsNfpcUd18/JTcRck3qbO1AyF7UpnfSQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HnLUW/btsNfpcUd18/JTcRck3qbO1AyF7UpnfSQ1/img.png&quot; data-alt=&quot;OpenGL Window&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HnLUW/btsNfpcUd18/JTcRck3qbO1AyF7UpnfSQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHnLUW%2FbtsNfpcUd18%2FJTcRck3qbO1AyF7UpnfSQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;381&quot; data-filename=&quot;스크린샷 2025-04-09 오후 2.57.25.png&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;1292&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;OpenGL Window&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 종료 처리 - 메모리는 우리가 책임져야죠&lt;/h3&gt;
&lt;pre id=&quot;code_1744177388593&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;glfwTerminate();
return (0);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막엔 GLFW가 사용한 자원을 해제해줘야 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안 그러면 OS에 리소스가 남아 이상한 버그의 원인이 되기도 하죠.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다음 글 예고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 정말 &quot;창만 띄운&quot; 코드였죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여기서부터가 진짜 시작입니다. 앞으로 우리는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VAO, VBO를 만들고&lt;/li&gt;
&lt;li&gt;셰이더를 로딩하고&lt;/li&gt;
&lt;li&gt;3D 모델을 로딩하고&lt;/li&gt;
&lt;li&gt;마우스로 회전까지!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Scop 구현기 2편: VAO와 VBO의 세계&lt;/b&gt;&lt;/p&gt;</description>
      <category>Dev/Grapics</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/7</guid>
      <comments>https://onepaperhoon.tistory.com/7#entry7comment</comments>
      <pubDate>Wed, 9 Apr 2025 15:10:35 +0900</pubDate>
    </item>
    <item>
      <title>Rasterization vs Ray Tracing</title>
      <link>https://onepaperhoon.tistory.com/6</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;렌더링 기술의 두 갈래, 무엇이 다르고 왜 중요한가?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 그래픽을 담당하는 개발자 A는 GPU 성능을 쥐어짜며 실시간 렌더링을 구현하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 어느 날, 팀의 아티스트가 무심코 묻습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;레이 트레이싱 쓰면 더 예쁘지 않아요? 왜 안 써요?&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 A는 한숨을 쉬며 이렇게 답합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;아름다움은 계산량과 반비례하거든요....&quot;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 오늘의 질문은 이것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 어떤 렌더링은&lt;b&gt; Rasterization&lt;/b&gt;을 쓰고, 어떤 렌더링은 &lt;b&gt;Ray Tracing&lt;/b&gt;을 쓸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘은 어떻게 다르고, 각각 어떤 장단점이 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 두 기술의 핵심 원리, 차이점, 그리고 실제 적용 사례까지 알아보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Rasterization - 화면에 삼각형을 빠르게 찍는 기술&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rasterization은 3D 공간의 정점을 화면 좌표계로 투영한 뒤, 삼각형 단위로 색상을 채워나가는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, '모양'을 기준으로 빠르게 픽셀을 칠하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Rasterization.jpg&quot; data-origin-width=&quot;1728&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWNIXz/btsNcpsjW7f/5HBKTZC60DXKBE17r8mjvK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWNIXz/btsNcpsjW7f/5HBKTZC60DXKBE17r8mjvK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWNIXz/btsNcpsjW7f/5HBKTZC60DXKBE17r8mjvK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWNIXz%2FbtsNcpsjW7f%2F5HBKTZC60DXKBE17r8mjvK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;250&quot; data-filename=&quot;Rasterization.jpg&quot; data-origin-width=&quot;1728&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 프로세스&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모델 정점(Vertex)을 가져온다&lt;/li&gt;
&lt;li&gt;뷰 변환 및 투영(Projection)을 적용한다&lt;/li&gt;
&lt;li&gt;삼각형 구성 및 화면 공간으로 변환한다&lt;/li&gt;
&lt;li&gt;삼각형 내부 픽셀 색칠 (Fragment Shader)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1744116930760&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// OpenGL 기본 예시
glUseProgram(shaderProgram);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES, 0, 3); // 삼각형을 그린다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점과 단점&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;매우 빠름 (GPU 최적화 완료)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;광학 효과 표현이 제한됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;코드가 단순하고 예측 가능함&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;복잡한 조명은 트릭으로 구현해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Ray Tracing - 픽셀마다 광선을 쏘는 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ray Tracing은 카메라에서 출발한 광선을 따라가며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라에서 쏜 광선은 장면 속 물체에 닿거나, 계속 진행해 화면 밖으로 사라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;광선이 어떤 물체에 닿는다면, 그 표면에서 &lt;span&gt;&lt;b&gt;반사되거나 굴절&lt;/b&gt;&lt;/span&gt;되어 다시 다른 경로로 나아가게 되죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 반사되거나 굴절된 광선은 또 다른 물체에 닿거나, 결국 &lt;span&gt;&lt;b&gt;광원&lt;/b&gt;&lt;/span&gt;에 도달하거나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;아니면 그냥 &lt;/span&gt;&lt;b&gt;사라질 수도 있습니다&lt;/b&gt;&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGZiDk/btsNdBL4Q2J/bYXBDbPTlOpF5a9KQQrZSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGZiDk/btsNdBL4Q2J/bYXBDbPTlOpF5a9KQQrZSk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;252&quot; data-filename=&quot;Reflectec Light2.png&quot; style=&quot;width: 34.9602%; margin-right: 10px;&quot; data-widthpercent=&quot;35.37&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGZiDk/btsNdBL4Q2J/bYXBDbPTlOpF5a9KQQrZSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGZiDk%2FbtsNdBL4Q2J%2FbYXBDbPTlOpF5a9KQQrZSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4BMFv/btsNerooHKt/kUQPWo4lUn3DVznW8jehv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4BMFv/btsNerooHKt/kUQPWo4lUn3DVznW8jehv0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;683&quot; data-origin-height=&quot;314&quot; data-filename=&quot;Ray Tracing Image.png&quot; style=&quot;width: 63.877%;&quot; data-widthpercent=&quot;64.63&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4BMFv/btsNerooHKt/kUQPWo4lUn3DVznW8jehv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4BMFv%2FbtsNerooHKt%2FkUQPWo4lUn3DVznW8jehv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;683&quot; height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 &lt;span&gt;&lt;b&gt;재귀적으로 반복&lt;/b&gt;&lt;/span&gt;하면서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빛의 흐름이 어디에서 어떻게 시작되어 카메라까지 도달하는지를 역추적합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 광원이 닿는 경로를 찾게 되면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 경로를 따라 들어오는 빛의 기여도(조명, 색상, 반사율 등)를 계산해 해당 픽셀의 최종 색을 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 &amp;ldquo;빛이 어떻게 반사되어 이곳까지 왔는가?&amp;rdquo;를 거슬러 추적하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지를 만들어내는 기법이 바로 &lt;span&gt;&lt;b&gt;Ray Tracing&lt;/b&gt;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Ray Tracing.gif&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwFpOH/btsNeqC1cmY/y8WLqId6ssQo1PPxdFVzj1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwFpOH/btsNeqC1cmY/y8WLqId6ssQo1PPxdFVzj1/img.gif&quot; data-alt=&quot;Ray Tracing&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwFpOH/btsNeqC1cmY/y8WLqId6ssQo1PPxdFVzj1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bwFpOH/btsNeqC1cmY/y8WLqId6ssQo1PPxdFVzj1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;594&quot; height=&quot;289&quot; data-filename=&quot;Ray Tracing.gif&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Ray Tracing&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점과 단점&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 57px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;자연스러운 그림자, 반사, 굴절 표현 가능&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;매우 높은 연산량(느림)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;물리 기반 조명 계산 (PBR에 최적)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;실시간 처리를 위해선 RTX 등 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 요즘 게임은 왜 Ray Tracing을 쓰기 시작했을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전엔 Ray Tracing을 '쓸 수 없었던 기술'이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 느렸거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 2018년, NVIDIA가&amp;nbsp;&lt;b&gt;RTX 시리즈 + 하드웨어 레이 트레이싱&lt;/b&gt;을 도입하면서 판이 바뀌었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 등장한 기술이 바로 &lt;b&gt;Hybrid Rendering&amp;nbsp;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Hybrid 방식: Raster + Ray의 콜라보&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본적인 장면 그리기: Rasterization&lt;/li&gt;
&lt;li&gt;반사, 그림자, 글로벌 일루미네이션(GI): Ray Tracing&lt;/li&gt;
&lt;li&gt;-&amp;gt; 빠른 속도는 유지하고, 빛의 퀄리티는 리얼하게 가져가는 전략&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Hybrid Rendering Img.jpg&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;655&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x0YSy/btsNb9cbaW7/S93GOKOmaTmqEav7NV4ZKk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x0YSy/btsNb9cbaW7/S93GOKOmaTmqEav7NV4ZKk/img.jpg&quot; data-alt=&quot;Ndivia Hybrid Rendering Pipeline&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x0YSy/btsNb9cbaW7/S93GOKOmaTmqEav7NV4ZKk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx0YSy%2FbtsNb9cbaW7%2FS93GOKOmaTmqEav7NV4ZKk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1270&quot; height=&quot;655&quot; data-filename=&quot;Hybrid Rendering Img.jpg&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;655&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Ndivia Hybrid Rendering Pipeline&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대표 게임&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cyberpunk 2077&lt;/li&gt;
&lt;li&gt;Minecraft RTX&lt;/li&gt;
&lt;li&gt;Battlefield V&lt;/li&gt;
&lt;li&gt;Hogwarts Legacy 등&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 비교 요약: 언제 어떤 방식을 써야 할까?&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Rasterization&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Ray Tracing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;작동 방식&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;정점 -&amp;gt; 삼각형 -&amp;gt; 픽셀&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;카메라에서 광원 발사 -&amp;gt; 교차 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;렌더링 속도&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;매우 빠름&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;상대적으로 느림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;광확 효과 구현&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;쉐이더 기반 트릭 사용&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;자연스러운 표현 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;주 사용 분야&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;게임, 실시간 그래픽스&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;영화, 시각화, 오프라인 렌더링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;하드웨어 요구&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;GPU 기본 지원&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;RTX 등 특수 하드웨어 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 왜 둘은 철학이 다른가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rasterization은 &lt;b&gt;빠른 속도와 예측 가능성&lt;/b&gt;이 우선인 철학입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;현실을 모방한다&quot;는 것보다는, &quot;현실처럼 보이게 하는 트릭&quot;에 집중하며 발전해 왔습니다.&lt;/li&gt;
&lt;li&gt;성능을 위해 비현실적 계산도 과감히 생략합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ray Tracing은&amp;nbsp;&lt;b&gt;현실을 물리 기반으로 시뮬레이션&lt;/b&gt;하는 철학입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 빛의 경로처럼 반사, 굴절, 그림자를 추적합니다.&lt;/li&gt;
&lt;li&gt;결과는 자연스럽지만, 계산량은 필연적으로 많습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;현실과 비슷하게 보이게 할 것이냐?&lt;br /&gt;아니면, 현실처럼 &quot;계산&quot;할 것이냐?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 철학적 질문이 두 렌더링 방식의 차이입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dev/Grapics</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/6</guid>
      <comments>https://onepaperhoon.tistory.com/6#entry6comment</comments>
      <pubDate>Wed, 9 Apr 2025 06:30:13 +0900</pubDate>
    </item>
    <item>
      <title>셰이더는 그낭 함수일까?</title>
      <link>https://onepaperhoon.tistory.com/5</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;GPU 프로그래밍의 착각과 진실&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 셰이더는 정말 함수일까?&lt;/h3&gt;
&lt;pre id=&quot;code_1744069994872&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main()
{
    gl_Position = ...;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 GLSL 셰이더 코드를 보면 진짜 함수처럼 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C 언어와 똑같은 void main()에, 변수 선언, 수식, return 없이도 끝나는 심플한 구조까지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 흔히 이렇게 착각하곤 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;아하, 셰이더도 함수니까 그냥 내가 부르면 되겠네?&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 막상 실행해 보면 이상하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main()을 딱 한 번 쓴 것 같은데, 화면 전체에 셰이더가 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어라? 내가 호출한 적 없는데?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그럼 셰이더는 대체 뭐지?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 함수처럼 보이는데.... 진짜 함수는 아닌 걸까?&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 셰이더란 무엇인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰이더는&amp;nbsp;&lt;b&gt;GPU&lt;/b&gt;에서 실행되는 &lt;b&gt;작은 프로그램&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로그램은 정점(Vertex), 픽셀(Fragment), 도형(geometry) 같은 그래픽 데이터를 처리하는&amp;nbsp;&lt;b&gt;전용 처리 유닛&lt;/b&gt;으로 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 셰이더 종류는 다음과 같다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Vertex Shader&lt;/b&gt;: 정점 하나하나의 위치와 속성 처리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Fragment&lt;/b&gt; &lt;b&gt;Shader&lt;/b&gt;: 픽셀 하나하나의 색상 결정&lt;/li&gt;
&lt;li&gt;(선택적)&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;b&gt;Gemetry / Tessellation / Compute&amp;nbsp;&lt;/b&gt;등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 셰이더들은 모두 특정&amp;nbsp;&lt;b&gt;그래픽 파이프라인의 단계에 '자동'으로 삽입되어 실행&lt;/b&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 우리가 직접 호출하는 것이 아니라, GPU가 데이터를 처리할 때마다 스스로 실행된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 셰이더 구조는 함수처럼 생겼다&lt;/h3&gt;
&lt;pre id=&quot;code_1744075657614&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// vertex shader
#version 330 core
layout (location = 0) in vec3 aPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1744075693265&quot; class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// fragment shader
#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0, 0.5, 0.2, 1.0); // 주황색
}​&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 보면 정말 함수처럼 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어 void main() 도 똑같고, 변수도 선언하고, 곱셈 연산도 하고, 값을 대입하고...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&amp;nbsp;&lt;b&gt;이 셰이더는 우리가 실행하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 OpenGL이 glDraw*()를 호출하는 순간&amp;nbsp;&lt;b&gt;수천 번 자동 실행&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 1000개의 정점이 있다면, vertex shader의 main()은 1000번 실행된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. CPU함수 vs GPU 셰이더의 결정적 차이점&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 89.4186%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.5193%;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 29.3799%;&quot;&gt;CPU함수&lt;/td&gt;
&lt;td style=&quot;width: 44.1858%;&quot;&gt;GPU 셰이더&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.5193%;&quot;&gt;실행 주체&lt;/td&gt;
&lt;td style=&quot;width: 29.3799%;&quot;&gt;내가 호출함&lt;/td&gt;
&lt;td style=&quot;width: 44.1858%;&quot;&gt;GPU가 자동 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.5193%;&quot;&gt;실행 횟수&lt;/td&gt;
&lt;td style=&quot;width: 29.3799%;&quot;&gt;호출한 만큼&lt;/td&gt;
&lt;td style=&quot;width: 44.1858%;&quot;&gt;정점/픽셀 개수 만큼 반복 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.5193%;&quot;&gt;입출력 구조&lt;/td&gt;
&lt;td style=&quot;width: 29.3799%;&quot;&gt;인자, 반환값&lt;/td&gt;
&lt;td style=&quot;width: 44.1858%;&quot;&gt;in/out, uniform, gl_Position등 특수 변수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.5193%;&quot;&gt;실행 위치&lt;/td&gt;
&lt;td style=&quot;width: 29.3799%;&quot;&gt;CPU의 한 쓰레드&lt;/td&gt;
&lt;td style=&quot;width: 44.1858%;&quot;&gt;GPU의 수천 개 코어에서 동시에 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.5193%;&quot;&gt;목적&lt;/td&gt;
&lt;td style=&quot;width: 29.3799%;&quot;&gt;논리 처리, 계산&lt;/td&gt;
&lt;td style=&quot;width: 44.1858%;&quot;&gt;그래픽 처리 전용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 데이터가 흐르면 실행되는 파이프라인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰이더는 함수처럼 생겼지만,&amp;nbsp;&lt;b&gt;실행 흐름이 함수가 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 vertexShader.main() 같은 걸 호출하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 우리가 하는 일은 단 하나다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;glDrawArrays(...);&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 GPU는 내부적으로 다음과 같은 일을 한다&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;버퍼에 담긴 정점 데이터를 꺼냄&lt;/li&gt;
&lt;li&gt;정점 개수만큼 &lt;b&gt;vertex shader의 main()을 병렬 실행&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;삼각형 조립 -&amp;gt; rasterization -&amp;gt;&amp;nbsp;&lt;b&gt;각 픽셀마다 fragment shader의 main() 병렬 실행&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;셰이더는 데이터 흐름에 붙은 작은 프로그램&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마치 공장 컨베이어 벨트 위의 자동화 로봇팔처럼,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 지나갈 때 알아서 일하는 프로그램이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 함수처럼 짜긴 짤 수 있다&lt;/h3&gt;
&lt;pre id=&quot;code_1744076148803&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vec3 brighten(vec3 color, float power) {
    return clamp(color + vec3(power), 0.0, 1.0);
}

void main()
{
    FragColor = vec4(brighten(vec3(1.0, 0.3, 0.1), 0.2), 1.0);
}​&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GLSL은 일반적인 함수도 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 셰이더 안에서도 보조 함수를 정의해서 재사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 코드도&amp;nbsp;&lt;b&gt;픽셀 수만큼 main()이 실행되며,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 안에서 각각 brighten()이 호출되는 것일 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 구조는 함수 같지만,&amp;nbsp;&lt;b&gt;전체 흐름은 병렬 파이프라인에 속해 있다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. &quot;프로그래머가 직접 호출하지 않는 함수&quot;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍의 본질은 &quot;함수를 정의하고 호출&quot;하는 데 있다고 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 셰이더는 다르다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우리는 직접 호출하지 않는다.&lt;/li&gt;
&lt;li&gt;호출 순서를 정하지도 않는다.&lt;/li&gt;
&lt;li&gt;return 값도 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 불구하고,&amp;nbsp;&lt;b&gt;정의된 코드는 자동으로 동작하며 세상을 바꾼다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 마치 JavaScript의 이벤트 리스너 같기도 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수형 프로그래밍의 map/filter처럼&amp;nbsp;&lt;b&gt;데이터에 따라 자동 호출되는 콜백 구조&lt;/b&gt;와도 닮았다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;셰이더는 &quot;나를 불러줘&quot;가 아니라&lt;br /&gt;&quot;내가 그 자리에 있으면 알아서 불러주겠지&quot;에 가까운 존재다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. 요약 - 셰이더는 GPU의 함수 같지만, '그런 함수'는 아니다&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;셰이더는 함수처럼 보이지만,&lt;/li&gt;
&lt;li&gt;파이프라인에서 자동으로 실행되며,&lt;/li&gt;
&lt;li&gt;GPU의 수많은 코어에서 병렬적으로 작동하는 그래픽 전용 프로그램이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 점에서 셰이더는 일반적인 프로그래밍 함수보다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GPU 병렬 처리에 특화된 작은 데이터 기반 스크립트&lt;/b&gt;라고 보는 게 더 정확하다.&lt;/p&gt;</description>
      <category>Dev/Grapics</category>
      <category>graphics</category>
      <category>OpenGL</category>
      <category>그래픽스</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/5</guid>
      <comments>https://onepaperhoon.tistory.com/5#entry5comment</comments>
      <pubDate>Tue, 8 Apr 2025 17:18:13 +0900</pubDate>
    </item>
    <item>
      <title>OpenGL의 좌표계, 진짜로 이해하기</title>
      <link>https://onepaperhoon.tistory.com/4</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;왜 0, 0, 0에 두었는데 화면에 안 보일까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.&amp;nbsp; 좌표계가 헷갈리는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL에서 처음 물체를 렌더링 할 때 이런 경험이 있다면 당신만 그런 게 아니다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분명히 (0, 0, 0)에 모델을 뒀는데 화면에 아무것도 안 보인다.&lt;/li&gt;
&lt;li&gt;카메라는 설정했는데 방향이 어디를 보는지 감이 없다.&lt;/li&gt;
&lt;li&gt;gl_Position 계산했는데 화면이 시커멓다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 혼란의 원인은 대부분&amp;nbsp;&lt;b&gt;좌표계와 변환에 대한 이해 부족&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL에서의 렌더링은 단순한 그리기가 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;모델의 좌표를 변환해 최종 스크린까지 도달시키는 여정&quot;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 좌표계란 무엇인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;좌표계(Coordinate System)는&lt;/b&gt;&lt;b&gt; 공간의 기준을 정의하는 약속&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어디를 기준으로 (0, 0, 0)인지, 각 축이 어느 방향을 향하는지를 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL은 총&amp;nbsp;&lt;b&gt;6단계의 좌표계&lt;/b&gt;를 거치며 위치를 변환한다&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Object&amp;nbsp;-&amp;gt; World -&amp;gt; View -&amp;gt; Clip -&amp;gt; NDC -&amp;gt; Screen&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단계는 행렬(Matrix) 연산으로 이어지며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 다음과 같은 연산으로 요약된다&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;gl_Position = Projection * View * Model * vec4(position, 1.0);&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;즉, 하나의 정점(Vertex)을 최종 위치로 변환하는 데는 &lt;b&gt;3개의 행렬과 6개의 좌표계&lt;/b&gt;가 필요하다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작하기 앞서 OpenGL에서 기본적으로 사용하는 좌표계 방향을 알아보자&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Right handed system.png&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEzr06/btsNbgoBYzS/fwRODMfnQSZ7old9PUkmH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEzr06/btsNbgoBYzS/fwRODMfnQSZ7old9PUkmH0/img.png&quot; data-alt=&quot;Right Handed system&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEzr06/btsNbgoBYzS/fwRODMfnQSZ7old9PUkmH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEzr06%2FbtsNbgoBYzS%2FfwRODMfnQSZ7old9PUkmH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;474&quot; height=&quot;366&quot; data-filename=&quot;Right handed system.png&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Right Handed system&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL에서는 기본적으로 오른손 좌표계를 사용하고 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 뷰 카메라는 -Z방향을 보고 있기 때문에 흔히 하는 실수는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물체를 +Z 축 방향으로 이동시키면 카메라의&amp;nbsp;&lt;b&gt;뒤쪽으로 물체를 보내버리는 셈이라 화면에 보이지 않게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 좌표계 흐름 완전 정복&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각 좌표계가&amp;nbsp;&lt;b&gt;무엇을 의미하고, 왜 존재하는지&lt;/b&gt;를 하나씩 세세히 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;coordinate_systems.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A7xwp/btsNclWKVMU/Iy2UWNt2TyRpDwpALHKe9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A7xwp/btsNclWKVMU/Iy2UWNt2TyRpDwpALHKe9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A7xwp/btsNclWKVMU/Iy2UWNt2TyRpDwpALHKe9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA7xwp%2FbtsNclWKVMU%2FIy2UWNt2TyRpDwpALHKe9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;394&quot; data-filename=&quot;coordinate_systems.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 모델 좌표계&amp;nbsp;&lt;b&gt;(Model Space, 또는 Local/Object Space)&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;이 물체 자체 안에서 좌표가 어떻게 생겼나?&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 정점은 오브젝트 기준으로 표현됨&lt;/li&gt;
&lt;li&gt;예: 정육면체는 중심을 (0, 0, 0)으로 하고, 꼭짓점은 (-1, -1, -1) 같은 값&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Model행렬&lt;/b&gt;은 이 모델을 &quot;어디에&quot;, &quot;얼마나 회전시켜&quot;, &quot;얼마나 확대/축소할지&quot; 결정한다&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;model = Translate * Rotate * Scale;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;world_space.png&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TwmoK/btsNb1j61FN/5tgKes9ek4vjKm6VmM3jwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TwmoK/btsNb1j61FN/5tgKes9ek4vjKm6VmM3jwk/img.png&quot; data-alt=&quot;Model -&amp;amp;gt; World&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TwmoK/btsNb1j61FN/5tgKes9ek4vjKm6VmM3jwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTwmoK%2FbtsNb1j61FN%2F5tgKes9ek4vjKm6VmM3jwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;571&quot; height=&quot;240&quot; data-filename=&quot;world_space.png&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Model -&amp;gt; World&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 월드 좌표계 (World Space)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;이 오브젝트를 세상 안에 어떻게 배치할까?&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모델이 속해있는&amp;nbsp;&lt;b&gt;월드 좌표 기준&lt;/b&gt;으로 변환.&lt;/li&gt;
&lt;li&gt;모든 물체들이 공유하는 절대적 위치 기준&lt;/li&gt;
&lt;li&gt;서로 다른 모델들이 이 좌표계에서&amp;nbsp;&lt;b&gt;충돌 검사, 카메라 연산&amp;nbsp;&lt;/b&gt;등을 할 수 있게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 뷰 좌표계 (View Space, Camera Space)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;카메라의 시점에서 이 물체는 어떻게 보일까?&quot;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카메라를 원점 (0, 0, 0)으로 이동시키는 것처럼 동작&lt;/li&gt;
&lt;li&gt;월드 공간의 모든 물체들을&amp;nbsp;&lt;b&gt;카메라 기준으로 뒤로 이동시키는 연산&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;nbsp;View행렬&lt;/b&gt;은 LookAt() 같은 방식으로 만든다&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;view = glm::lookAt(cameraPos, targetPos, upVector);&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 클립 좌표계 (Clip Space)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;투영(projection)을 거쳐 계산된 공간. 아직 정규화는 안 됨&quot;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Perspective(원근) 또는 Orthographic(직교) 방식으로 변환됨&lt;/li&gt;
&lt;li&gt;Z 축 깊이 정보가 보존되지만, 아직 w를 나누기 전이라&amp;nbsp;&lt;b&gt;왜곡되지 않음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;모든 좌표계 중 가장 수학적인 단계&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;perspect ortho.jpeg&quot; data-origin-width=&quot;321&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sLmsg/btsNdikHIWG/AyjxbfkDKLWEKCBdmOK881/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sLmsg/btsNdikHIWG/AyjxbfkDKLWEKCBdmOK881/img.jpg&quot; data-alt=&quot;Perspective orthographic&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sLmsg/btsNdikHIWG/AyjxbfkDKLWEKCBdmOK881/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsLmsg%2FbtsNdikHIWG%2FAyjxbfkDKLWEKCBdmOK881%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;462&quot; height=&quot;226&quot; data-filename=&quot;perspect ortho.jpeg&quot; data-origin-width=&quot;321&quot; data-origin-height=&quot;157&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Perspective orthographic&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. NDC (Normalized Device Coordinates)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;좌표계를 -1 ~ 1 사이로 정규화&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;클립 공간 좌표를 w로 나누어 정규화한 좌표계&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL에서 그릴 수 있는 범위는 반드시 -1 &amp;le; x, y, z &amp;le; 1 이내여야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 범위를 넘으면&amp;nbsp;&lt;b&gt;자동으로 잘려(Clipping) 버려짐&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;clipping.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mks49/btsNdhzkUl7/mg2BgmajChQFvkWI5Dl5Ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mks49/btsNdhzkUl7/mg2BgmajChQFvkWI5Dl5Ik/img.png&quot; data-alt=&quot;Cplipping&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mks49/btsNdhzkUl7/mg2BgmajChQFvkWI5Dl5Ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMks49%2FbtsNdhzkUl7%2Fmg2BgmajChQFvkWI5Dl5Ik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;196&quot; height=&quot;348&quot; data-filename=&quot;clipping.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Cplipping&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 비유하면, 클립공간에서 NDC좌표계로 변환되며 -1~1의 범위를 넘어가는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라로 촬영할 때 카메라의 뷰파인더와 같이 뷰파인더 안에 들어온 장면만 필름에 담기고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바깥은 잘려나가는 것과 같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;NDC Coordinate.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;197&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Sm2lP/btsNcFOngw1/jlL5UOPFMKCcEN3CUTspQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Sm2lP/btsNcFOngw1/jlL5UOPFMKCcEN3CUTspQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Sm2lP/btsNcFOngw1/jlL5UOPFMKCcEN3CUTspQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSm2lP%2FbtsNcFOngw1%2FjlL5UOPFMKCcEN3CUTspQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;197&quot; data-filename=&quot;NDC Coordinate.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;197&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NDC는 해상도에 상관없는 '가상 화면' 개념&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 1920x1080이든, 300x300이든 NDC의 좌표값은 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 스크린 좌표계 (Screen Space)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;정말로 픽셀이 존재하는 화면&quot;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;viewports_gl_vk.png&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMor0d/btsNbekaIVc/FvjhRGFKHToRzvu7k7kD2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMor0d/btsNbekaIVc/FvjhRGFKHToRzvu7k7kD2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMor0d/btsNbekaIVc/FvjhRGFKHToRzvu7k7kD2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMor0d%2FbtsNbekaIVc%2FFvjhRGFKHToRzvu7k7kD2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;464&quot; height=&quot;223&quot; data-filename=&quot;viewports_gl_vk.png&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NDC의 -1~1 범위를 실제 해상도 (예: 800x600)에 매핑&lt;/li&gt;
&lt;li&gt;Viewport 설정에 따라 달라짐&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;giViewport(0, 0, screenWidth, screenHeight);&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. w로 나누는 이유 - 원근 투영의 핵심&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL은 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;gl_Position = vec4(x, y, z, w);&lt;/span&gt;&amp;nbsp;로 셰이더에서 좌표를 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 렌더링 전에 GPU는 자동으로 다음과 같은 연산을 수행한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;x' = x / w&lt;br /&gt;y' = y / w&lt;br /&gt;z' = z / w&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 바로 원근(Perspective) 투영의 핵심이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;w 값은 보통 카메라로부터의 거리와 관련된 값이다.&lt;br /&gt;멀리 있는 물체일수록 w값이 커지고, 그 결과 x/w, y/w는 작아져서 &lt;b&gt;더 작게 보인다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&amp;nbsp;&lt;b&gt;원근 왜곡 효과&lt;/b&gt;는 이 w 나눗셈에서 시작된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 자주 생기는 좌표계 관련 실수&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 97.4407%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.3044%;&quot;&gt;상황&lt;/td&gt;
&lt;td style=&quot;width: 35.6093%;&quot;&gt;원인&lt;/td&gt;
&lt;td style=&quot;width: 49.725%;&quot;&gt;해결 방법&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.3044%;&quot;&gt;물체가 안 보인다&lt;/td&gt;
&lt;td style=&quot;width: 35.6093%;&quot;&gt;NDC 범위 밖에 있음&lt;/td&gt;
&lt;td style=&quot;width: 49.725%;&quot;&gt;View/Projection 설정 점검&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.3044%;&quot;&gt;화면에 물체가 너무 큼&lt;/td&gt;
&lt;td style=&quot;width: 35.6093%;&quot;&gt;FOV나 Neer/Far Plane 이상함&lt;/td&gt;
&lt;td style=&quot;width: 49.725%;&quot;&gt;Projection 행렬 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.3044%;&quot;&gt;텍스처가 뒤집혀 보임&lt;/td&gt;
&lt;td style=&quot;width: 35.6093%;&quot;&gt;UV좌표계와 스크린 좌표계 차이&lt;/td&gt;
&lt;td style=&quot;width: 49.725%;&quot;&gt;텍스처 flip 설정 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 좌표계 흐름 요약표&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 98.024%; height: 74px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 29.6871%; height: 19px;&quot;&gt;좌표계&lt;/td&gt;
&lt;td style=&quot;width: 32.7475%; height: 19px;&quot;&gt;설명&lt;/td&gt;
&lt;td style=&quot;width: 102.084%; height: 19px;&quot;&gt;변환 행렬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 29.6871%; height: 19px;&quot;&gt;모델&lt;/td&gt;
&lt;td style=&quot;width: 32.7475%; height: 19px;&quot;&gt;오브젝트 내부 기준&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 102.084%; height: 19px;&quot;&gt;Model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 29.6871%; height: 19px;&quot;&gt;월드&lt;/td&gt;
&lt;td style=&quot;width: 32.7475%; height: 19px;&quot;&gt;전역기준 좌표&lt;/td&gt;
&lt;td style=&quot;width: 102.084%; height: 19px;&quot;&gt;Model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 29.6871%; height: 17px;&quot;&gt;뷰&lt;/td&gt;
&lt;td style=&quot;width: 32.7475%; height: 17px;&quot;&gt;카메라 기준&lt;/td&gt;
&lt;td style=&quot;width: 102.084%; height: 17px;&quot;&gt;View&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.6871%;&quot;&gt;클립&lt;/td&gt;
&lt;td style=&quot;width: 32.7475%;&quot;&gt;투영된 좌표&lt;/td&gt;
&lt;td style=&quot;width: 102.084%;&quot;&gt;Projection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.6871%;&quot;&gt;NDC&lt;/td&gt;
&lt;td style=&quot;width: 32.7475%;&quot;&gt;정규화된 -1~1 공간&lt;/td&gt;
&lt;td style=&quot;width: 102.084%;&quot;&gt;w 나눗셈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.6871%;&quot;&gt;스크린&lt;/td&gt;
&lt;td style=&quot;width: 32.7475%;&quot;&gt;실제 해상도 픽셀 기준&lt;/td&gt;
&lt;td style=&quot;width: 102.084%;&quot;&gt;Viewport 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://learnopengl.com/Getting-started/Coordinate-Systems&quot;&gt;LearnOpenGL - Coordinate Systems&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://www.khronos.org/opengl/wiki/Vertex_Transformation&quot;&gt;OpenGL Wiki - Vertex Transformation&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix&quot;&gt;Scratchapixel - 3D Perspective Projection&lt;/a&gt;&lt;/p&gt;</description>
      <category>Dev/Grapics</category>
      <category>graphics</category>
      <category>OpenGL</category>
      <category>그래픽스</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/4</guid>
      <comments>https://onepaperhoon.tistory.com/4#entry4comment</comments>
      <pubDate>Tue, 8 Apr 2025 11:00:38 +0900</pubDate>
    </item>
    <item>
      <title>정점(Vertex)이란 무엇인가? - 그래픽 세계의 가장 작은 시작점</title>
      <link>https://onepaperhoon.tistory.com/3</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 정점이라는 단어, 너무 자주 보인다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 그래픽스를 막 시작하면 다음과 같은 단어들을 자주 보게 된다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vertex Buffer&lt;/li&gt;
&lt;li&gt;Vertex Shader&lt;/li&gt;
&lt;li&gt;gl_Position&lt;/li&gt;
&lt;li&gt;layout (location = x)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단어들은 모두&amp;nbsp;&lt;b&gt;정점(Vertex)&lt;/b&gt;이라는 개념과 밀접하게 연결되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 처음엔 너무 당연하다는 듯 쓰이기 때문에&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;그래서... 정점이라는 게 도대체 뭐야?&quot;&lt;br /&gt;라는 질문이 생기기 마련이다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Vertex.webp&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjB5Ci/btsNb1xwLCL/jFm9F6sHjffvA0QpWZyIfK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjB5Ci/btsNb1xwLCL/jFm9F6sHjffvA0QpWZyIfK/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjB5Ci/btsNb1xwLCL/jFm9F6sHjffvA0QpWZyIfK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjB5Ci%2FbtsNb1xwLCL%2FjFm9F6sHjffvA0QpWZyIfK%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;316&quot; height=&quot;162&quot; data-filename=&quot;Vertex.webp&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 정점은 '점'이 아니다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단어 뜻 그대로만 보면, 정점(Vertex)은&amp;nbsp;&lt;b&gt;공간 위의 점 하나&lt;/b&gt;로 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&amp;nbsp;&lt;b&gt;그래픽에서의 정점은 단순한 위치 정보만 담고 있는 것이 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;정확히 말하자면,&amp;nbsp;&lt;b&gt;정점은 '위치 + 속성 정보들'&lt;/b&gt;의&amp;nbsp;&lt;b&gt;묶음&lt;/b&gt;이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 게임 캐릭터의 얼굴 한 지점은 단순히 (x,y,z) 위치 외에도&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;거기에 어떤 색상이 칠해져야 하는지&lt;/li&gt;
&lt;li&gt;어떤 텍스처 좌표(UV)를 갖는지&lt;/li&gt;
&lt;li&gt;빛에 어떻게 반응해야 하는지(Normal)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등의 정보가&amp;nbsp;&lt;b&gt;모두 하나의 정점 데이터에 포함되어 있어야&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 하나의 정점은 다음 정보를 포함하고 있다&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 41.279%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.1708%;&quot;&gt;구성 요소&lt;/td&gt;
&lt;td style=&quot;width: 17.671%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.1708%;&quot;&gt;위치(Position)&lt;/td&gt;
&lt;td style=&quot;width: 17.671%;&quot;&gt;3D 공간상의 좌표 (x,y,z)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.1708%;&quot;&gt;색상(Color)&lt;/td&gt;
&lt;td style=&quot;width: 17.671%;&quot;&gt;RGB 또는 RGBA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.1708%;&quot;&gt;텍스쳐 좌표(UV)&lt;/td&gt;
&lt;td style=&quot;width: 17.671%;&quot;&gt;이미지 매핑 좌표&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.1708%;&quot;&gt;노멀 벡터(Normal)&lt;/td&gt;
&lt;td style=&quot;width: 17.671%;&quot;&gt;조명 계산용 방향 백터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.1708%;&quot;&gt;접선(Tangent)&lt;/td&gt;
&lt;td style=&quot;width: 17.671%;&quot;&gt;(선택적) 고급 조명 계산용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 정점 -&amp;gt; 삼각형 -&amp;gt; 메시(mesh)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래픽에서 도형은&amp;nbsp;&lt;b&gt;정점&lt;/b&gt;으로부터 만들어진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정점 3개를 연결하면 삼각형&lt;/li&gt;
&lt;li&gt;삼각형 수십 개가 모이면 메시(Mesh)&lt;/li&gt;
&lt;li&gt;메시 수백 개가 모이면 모델(Model)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;sphere_surface.png&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUKez1/btsNdlhmysZ/etWUEgPJiSr4gqibCjW9s1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUKez1/btsNdlhmysZ/etWUEgPJiSr4gqibCjW9s1/img.png&quot; data-alt=&quot;PolyGon Mesh&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUKez1/btsNdlhmysZ/etWUEgPJiSr4gqibCjW9s1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUKez1%2FbtsNdlhmysZ%2FetWUEgPJiSr4gqibCjW9s1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;322&quot; height=&quot;282&quot; data-filename=&quot;sphere_surface.png&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PolyGon Mesh&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정점만 있어서는 아무것도 보이지 않지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정점을 조합하면 도형이 되고, 도형은 결국 화면에 보이는 모든 3D 그래픽의 기반이 된다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 렌더링 파이프라인에서의 정점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정점은 GPU의 렌더링 파이프라인에서 가장 먼저 처리되는 단위다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 정점이 없으면 렌더링 자체가 시작될 수 없다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 97.5589%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 48.9151%;&quot;&gt;단계&lt;/td&gt;
&lt;td style=&quot;width: 94.0303%;&quot;&gt;정점의 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 48.9151%;&quot;&gt;Vertex Specification&lt;/td&gt;
&lt;td style=&quot;width: 94.0303%;&quot;&gt;정점 데이터를 CPU -&amp;gt; GPU로 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 48.9151%;&quot;&gt;Vertex Shader&lt;/td&gt;
&lt;td style=&quot;width: 94.0303%;&quot;&gt;좌표 변환 수행 (모델 좌표 -&amp;gt; 클립 공간)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 48.9151%;&quot;&gt;Primitive Assembly&lt;/td&gt;
&lt;td style=&quot;width: 94.0303%;&quot;&gt;정점 3개를 삼각형으로 묶음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 48.9151%;&quot;&gt;Rasterization&lt;/td&gt;
&lt;td style=&quot;width: 94.0303%;&quot;&gt;정점 간의 값 (색상, UV 등)을 보간하여 픽셀 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 괴정을 통해 정점은&amp;nbsp;&lt;b&gt;위치 정보&lt;/b&gt;만 전달하는 것이 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그래픽 전체에 영향을 주는 주요 정보 전달자&amp;nbsp;&lt;/b&gt;역할을 하게 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 실용적인 예: 정점 구조체&lt;/h3&gt;
&lt;pre id=&quot;code_1744037394674&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Vec3{
    float x;
    float y;
    float z;
};

struct Vec2{
    float x;
    float y;
};

struct Vertex{
    Vec3 position;	// 위치
    Vec3 normal;	// 조명 계산용 방향 벡터
    Vec2 texCoord;	// 텍스처 좌표
    Vec3 color;		// 정점 색상
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조체를 std::vector &amp;lt;Vertex&amp;gt; 등에 담아 VBO(Vertex Buffer Object)로 넘기고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰이더에서는 각 속성을 layout(location = x)로 받아 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 정점 데이터가 그래픽 파이프라인에 연결되면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 정점이 단순한 점을 넘어&amp;nbsp;&lt;b&gt;화면 위 픽셀 색상까지 영향을 주는 데이터 단위&lt;/b&gt;가 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 왜 정점이 중요한가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정점은 모든 그래픽 정보의 시작이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정점이 없다면 도형이 만들어질 수 없다.&lt;/li&gt;
&lt;li&gt;정점이 많을수록&amp;nbsp;&lt;b&gt;더 정밀한 메시와 부드러운 곡선 표현&lt;/b&gt;이 가능하다.&lt;/li&gt;
&lt;li&gt;조명 효과, 그림자, 텍스처 표현 등도 &lt;b&gt;정점 간 정보 보간&lt;/b&gt;을 통해 자연스럽게 처리된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 너무 많은 정점은 GPU에 부담을 주기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정점 데이터 최적화(예: LOD, 정점 캐시 등)&lt;/b&gt;는 실전 렌더링에서 굉장히 중요한 요소다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 요약&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 99.6517%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 49.2553%;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 114.689%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 49.2553%;&quot;&gt;정점이란?&lt;/td&gt;
&lt;td style=&quot;width: 114.689%;&quot;&gt;위치 + 다양한 그래픽 속성 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 49.2553%;&quot;&gt;어디 쓰이나?&lt;/td&gt;
&lt;td style=&quot;width: 114.689%;&quot;&gt;모든 그래픽 렌더링의 시작점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 49.2553%;&quot;&gt;어떻게 구성?&lt;/td&gt;
&lt;td style=&quot;width: 114.689%;&quot;&gt;구조체 -&amp;gt; VBO -&amp;gt; VAO -&amp;gt; 셰이더&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 49.2553%;&quot;&gt;왜 중요?&lt;/td&gt;
&lt;td style=&quot;width: 114.689%;&quot;&gt;그래픽 품질, 최적화, 보간 전부에 영향&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;9. 참고 자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://learnopengl.com/Getting-started/Hello-Triangle&quot;&gt;LearnOpenGL - Vertex Specification&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://www.khronos.org/opengl/wiki/Vertex_Specification&quot;&gt;OpenGL Wiki - Vertex Specification&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.nvidia.com/gpugems/gpugems3/chapter-2-efficient-vertex-data&quot;&gt;GPU Gems 3 - Efficient Vertex Data&lt;/a&gt;&lt;/p&gt;</description>
      <category>Dev/Grapics</category>
      <category>graphics</category>
      <category>OpenGL</category>
      <category>그래픽스</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/3</guid>
      <comments>https://onepaperhoon.tistory.com/3#entry3comment</comments>
      <pubDate>Tue, 8 Apr 2025 07:00:46 +0900</pubDate>
    </item>
    <item>
      <title>렌더링 파이프라인: 그래픽이 만들어지는 과정</title>
      <link>https://onepaperhoon.tistory.com/2</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. glDrawElements() 또는 glDrawArrays() 줄 뒤에 무슨 일이?&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;우리가 흔히 쓰는 glDrawElements()나 glDrawArrays() 같은 함수.&lt;br /&gt;이 한 줄의 호출 뒤에, 정확히 무슨 일이 벌어지고,&lt;br /&gt;어떻게 해서 한 줄의 코드가 화면에 그림을 그릴 수 있을까?&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 렌더링 파이프라인 전체 그림&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;정점 데이터가 픽셀이 되는 과정&quot;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Graphic Pipe Line.png&quot; data-origin-width=&quot;271&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W7HX5/btsNcsVrKUV/nbC4UziOelKzAjbXdS4AP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W7HX5/btsNcsVrKUV/nbC4UziOelKzAjbXdS4AP0/img.png&quot; data-alt=&quot;OpenGL Grphics Pipe Line&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W7HX5/btsNcsVrKUV/nbC4UziOelKzAjbXdS4AP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW7HX5%2FbtsNcsVrKUV%2FnbC4UziOelKzAjbXdS4AP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;271&quot; height=&quot;602&quot; data-filename=&quot;Graphic Pipe Line.png&quot; data-origin-width=&quot;271&quot; data-origin-height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;OpenGL Grphics Pipe Line&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Vertex Specification (정점 입력)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU가 넘겨준 정점 데이터를 GPU가 이해하도록 연결&lt;/li&gt;
&lt;li&gt;glVertexAttribPointer() 등을 통해 설정&lt;/li&gt;
&lt;li&gt;위치, 색상, 텍스처 좌표 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Vertex Specification.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQdzse/btsNco6Jz0a/nTytj1NV6JalGFtzKTSA41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQdzse/btsNco6Jz0a/nTytj1NV6JalGFtzKTSA41/img.png&quot; data-alt=&quot;Vertex Specification&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQdzse/btsNco6Jz0a/nTytj1NV6JalGFtzKTSA41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQdzse%2FbtsNco6Jz0a%2FnTytj1NV6JalGFtzKTSA41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;300&quot; data-filename=&quot;Vertex Specification.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Vertex Specification&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1744007872881&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position));
glEnableVertexAttribArray(0);

glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
glEnableVertexAttribArray(1);

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, texCoord));
glEnableVertexAttribArray(2);

glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, color));
glEnableVertexAttribArray(3);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.Vertex Shader (정점 변환)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각각의 정점에 대해 실행됨&lt;/li&gt;
&lt;li&gt;주 역할:&amp;nbsp;&lt;b&gt;모델 좌표 -&amp;gt; 클립 공간으로 변환&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;출력: gl_Position&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;vertex-shader-anim.gif&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tni4u/btsNdjjqejw/SZFGw84kX2KNssKLj1LWhk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tni4u/btsNdjjqejw/SZFGw84kX2KNssKLj1LWhk/img.gif&quot; data-alt=&quot;Vertex Shader 원본 https://webglfundamentals.org/webgl/lessons/webgl-how-it-works.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tni4u/btsNdjjqejw/SZFGw84kX2KNssKLj1LWhk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/tni4u/btsNdjjqejw/SZFGw84kX2KNssKLj1LWhk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;405&quot; height=&quot;245&quot; data-filename=&quot;vertex-shader-anim.gif&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Vertex Shader 원본 https://webglfundamentals.org/webgl/lessons/webgl-how-it-works.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1744007972294&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;gl_Position = projection * view * model * vec4(position, 1.0);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.Tessellation (정점 세분화) - 선택적&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정점 간격을 기준으로 더 촘촘한 삼각형을 만들어 고품질 메시 생성&lt;/li&gt;
&lt;li&gt;프래그먼트 셰이더에서&amp;nbsp;&lt;b&gt;보간 정확도 증가&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;픽셀당 연산의 의미가 커짐 (특히 PBR, Normal Mapping 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Tessellation.png&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kUVEW/btsNcL8uSqq/mDqWl9wN8cOo8YXruB7BwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kUVEW/btsNcL8uSqq/mDqWl9wN8cOo8YXruB7BwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kUVEW/btsNcL8uSqq/mDqWl9wN8cOo8YXruB7BwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkUVEW%2FbtsNcL8uSqq%2FmDqWl9wN8cOo8YXruB7BwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;565&quot; height=&quot;390&quot; data-filename=&quot;Tessellation.png&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Geometry Shader (도형 단위 변형) - 선택적&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;삼각형 전체(3개의 정점 단위)를 입력으로 받아 새로운 정점을 추가하거나 제거 가능&lt;/li&gt;
&lt;li&gt;예: 실시간 실루엣 생성, 그림자용 볼륨 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Geometry Shader.png&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/32x6z/btsNc9HXLGn/sYItYqYNd1HqluNN0Qy8Y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/32x6z/btsNc9HXLGn/sYItYqYNd1HqluNN0Qy8Y1/img.png&quot; data-alt=&quot;Geometry Shader&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/32x6z/btsNc9HXLGn/sYItYqYNd1HqluNN0Qy8Y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F32x6z%2FbtsNc9HXLGn%2FsYItYqYNd1HqluNN0Qy8Y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;556&quot; height=&quot;482&quot; data-filename=&quot;Geometry Shader.png&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Geometry Shader&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Vertex Post-Processing (좌표계 보정 &amp;amp; 클리핑)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클립 공간에서 NDC로 변환&lt;/li&gt;
&lt;li&gt;화면 밖 정점 제거(Clipping)&lt;/li&gt;
&lt;li&gt;보이지 않는 Primitive들을 삭제해 버림.&lt;/li&gt;
&lt;li&gt;vertex의 좌표계가 clipspace에서 window space로 변환됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Primitive Assembly (도형 조립)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정점들을 삼각형, 선, 점 등&amp;nbsp;&lt;b&gt;기하 도형(Primitive)으로&lt;/b&gt; 묶음&lt;/li&gt;
&lt;li&gt;예: 3개의 정점 -&amp;gt; 하나의 삼각형&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;primitiveassemblygraph1.webp&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8NbAZ/btsNc40ZnE1/OJeNCVioCB8OrK7xWsBWTk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8NbAZ/btsNc40ZnE1/OJeNCVioCB8OrK7xWsBWTk/img.webp&quot; data-alt=&quot;Primitive Assembly&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8NbAZ/btsNc40ZnE1/OJeNCVioCB8OrK7xWsBWTk/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8NbAZ%2FbtsNc40ZnE1%2FOJeNCVioCB8OrK7xWsBWTk%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;362&quot; height=&quot;275&quot; data-filename=&quot;primitiveassemblygraph1.webp&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Primitive Assembly&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. Rasterization (픽셀로 분할)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;삼각형 내부를 스캔하여&amp;nbsp;&lt;b&gt;화면상의 픽셀 좌표(Fragment)&amp;nbsp;&lt;/b&gt;생성&lt;/li&gt;
&lt;li&gt;이때 보간(interpolation)도 같이 수행됨 (예: UV, 색상)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cehzXj/btsNcewwsho/wbP3Vt4AJkwZMm1u8ifXUK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cehzXj/btsNcewwsho/wbP3Vt4AJkwZMm1u8ifXUK/img.webp&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;175&quot; data-origin-height=&quot;206&quot; data-filename=&quot;rasterization.webp&quot; data-widthpercent=&quot;50.09&quot; style=&quot;width: 49.5054%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cehzXj/btsNcewwsho/wbP3Vt4AJkwZMm1u8ifXUK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcehzXj%2FbtsNcewwsho%2FwbP3Vt4AJkwZMm1u8ifXUK%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;175&quot; height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cP2zGQ/btsNc92h8TW/ltDJvGhR6uracVzZK31kL1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cP2zGQ/btsNc92h8TW/ltDJvGhR6uracVzZK31kL1/img.webp&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;171&quot; data-origin-height=&quot;202&quot; data-filename=&quot;interpolation.webp&quot; style=&quot;width: 49.3318%;&quot; data-widthpercent=&quot;49.91&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cP2zGQ/btsNc92h8TW/ltDJvGhR6uracVzZK31kL1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcP2zGQ%2FbtsNc92h8TW%2FltDJvGhR6uracVzZK31kL1%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;171&quot; height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Fragment &amp;amp; interpolation&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. Fragment Shader ( 픽셀 색상 계산)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 프래그먼트에 대해 실행됨&lt;/li&gt;
&lt;li&gt;텍스처, 조명, 그림자 계산 등 모두 여기서 처리됨&lt;/li&gt;
&lt;li&gt;출력: 최종 색상(out vec4 FragColor)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Fragment Shader anim.gif&quot; data-origin-width=&quot;597&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHqOP1/btsNbqxHAbs/ALszmeSIEevAadkT9FQ0M1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHqOP1/btsNbqxHAbs/ALszmeSIEevAadkT9FQ0M1/img.gif&quot; data-alt=&quot;원본 https://webglfundamentals.org/webgl/lessons/webgl-how-it-works.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHqOP1/btsNbqxHAbs/ALszmeSIEevAadkT9FQ0M1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bHqOP1/btsNbqxHAbs/ALszmeSIEevAadkT9FQ0M1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;597&quot; height=&quot;398&quot; data-filename=&quot;Fragment Shader anim.gif&quot; data-origin-width=&quot;597&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;원본 https://webglfundamentals.org/webgl/lessons/webgl-how-it-works.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. Per-Sample Operations (테스트 &amp;amp; 블렌딩 후처리)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Depth Test: 더 가까운 픽셀만 화면에 남김&lt;/li&gt;
&lt;li&gt;Stencil, Alpha, Blending 등 후처리&lt;/li&gt;
&lt;li&gt;통과한 픽셀만 프레임 버퍼에 기록됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stencil Testing&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Stencil-testing.png&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdIcfE/btsNbRuY3zg/UGeAp4PlS8kqYbSF9tTNvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdIcfE/btsNbRuY3zg/UGeAp4PlS8kqYbSF9tTNvk/img.png&quot; data-alt=&quot;Stencil Testing&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdIcfE/btsNbRuY3zg/UGeAp4PlS8kqYbSF9tTNvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdIcfE%2FbtsNbRuY3zg%2FUGeAp4PlS8kqYbSF9tTNvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;595&quot; height=&quot;223&quot; data-filename=&quot;Stencil-testing.png&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Stencil Testing&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이 모든 과정을 거쳐&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;GPU는 수천 개의 정점 데이터를 병렬로 처리해&lt;br /&gt;매 프레임마다&amp;nbsp;&lt;b&gt;수백만 개의 픽셀&lt;/b&gt;을 그린다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요약 표&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 97.673%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.8032%;&quot;&gt;단계&lt;/td&gt;
&lt;td style=&quot;width: 168.019%;&quot;&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.8032%;&quot;&gt;Vertex Spec&lt;/td&gt;
&lt;td style=&quot;width: 168.019%;&quot;&gt;정점 입력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.8032%;&quot;&gt;Vertex Shader&lt;/td&gt;
&lt;td style=&quot;width: 168.019%;&quot;&gt;좌표 변환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.8032%;&quot;&gt;Tessellation&lt;/td&gt;
&lt;td style=&quot;width: 168.019%;&quot;&gt;고해상도 분할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.8032%;&quot;&gt;Geometry Shader&lt;/td&gt;
&lt;td style=&quot;width: 168.019%;&quot;&gt;도형 조작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.8032%;&quot;&gt;Post-Process&lt;/td&gt;
&lt;td style=&quot;width: 168.019%;&quot;&gt;클리핑, 변환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.8032%;&quot;&gt;Assembly&lt;/td&gt;
&lt;td style=&quot;width: 168.019%;&quot;&gt;도형 만들기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.8032%;&quot;&gt;&lt;span&gt;Rasterization&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 168.019%;&quot;&gt;픽셀 분할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.8032%;&quot;&gt;Fragment Shader&lt;/td&gt;
&lt;td style=&quot;width: 168.019%;&quot;&gt;색 계산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.8032%;&quot;&gt;Per-Sample&lt;/td&gt;
&lt;td style=&quot;width: 168.019%;&quot;&gt;테스트 &amp;amp; 블렌딩&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dev/Grapics</category>
      <category>OpenGL</category>
      <category>그래픽스</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/2</guid>
      <comments>https://onepaperhoon.tistory.com/2#entry2comment</comments>
      <pubDate>Mon, 7 Apr 2025 23:05:58 +0900</pubDate>
    </item>
    <item>
      <title>컴퓨터 그래픽스에서 말하는 '그래픽'이란 무엇인가?</title>
      <link>https://onepaperhoon.tistory.com/1</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Graphics.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPqgQ8/btsNcfaAm6m/ZKsxlxUqCk0BQ0kxh8kp90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPqgQ8/btsNcfaAm6m/ZKsxlxUqCk0BQ0kxh8kp90/img.png&quot; data-alt=&quot;Graphic&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPqgQ8/btsNcfaAm6m/ZKsxlxUqCk0BQ0kxh8kp90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPqgQ8%2FbtsNcfaAm6m%2FZKsxlxUqCk0BQ0kxh8kp90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;340&quot; height=&quot;510&quot; data-filename=&quot;Graphics.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Graphic&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 그냥 &quot;그림&quot;이 아니다&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 &quot;컴퓨터 그래픽스&quot;를 배운다고 할 때, &quot;그래픽&quot;은 도대체 뭘 말하는 걸까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터로 무언가를 그리고, 표현하고, 만들어내는 일을 통틀어 우리는 &quot;그래픽스(Graphics)&quot;라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 막상 그 그래픽이 구체적으로 뭘 말하는지 명확히 설명되지 않는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;그래픽이 중요해&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;GPU는 그래픽을 위해 존재해&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;셰이더는 그래픽을 조정하는 코드야&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 말하긴 쉬운데, 정작 &quot;그래픽&quot;이 뭔지를 정확하게 묻는다면 우리는 뭐라고 대답할 수 있을까?&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 그래픽은 '시각적 정보'다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 그래픽스에서 &lt;b&gt;그래픽(Graphics)&lt;/b&gt; 이란&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링(rendering) 과정을 통해 화면에 나타나는 모든 시각적 결과물을 의미한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 구성하는 대표적인 요소들은 다음과 같다&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;요소&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;정점 (Vertex)&lt;/td&gt;
&lt;td&gt;점 하나하나의 위치 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;도형&lt;/td&gt;
&lt;td&gt;선(Line), 삼각형(Triangle), 사각형 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;색상(Color)&lt;/td&gt;
&lt;td&gt;픽셀 단위의 색 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;텍스처(Texture)&lt;/td&gt;
&lt;td&gt;이미지로 표현되는 표면 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;조명(Light)&lt;/td&gt;
&lt;td&gt;빛의 방향, 강도, 색 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 것은 '그래픽'이라는 이름으로 묶인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래픽은 단순히 &quot;보이는 것&quot;을 넘어, &lt;b&gt;디지털 세계와 우리 눈 사이의 인터페이스&lt;/b&gt;다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 왜 '그래픽'이 그렇게 중요할까?&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 현실과의 연결고리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실에서는 모든 것이 시각적으로 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 화면을 통해 디지털 세계를 보고, 상호작용하고, 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 모든 것은 결국 &quot;그래픽&quot;을 통해 표현된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게임에서 캐릭터가 움직인다 -&amp;gt; 그래픽&lt;/li&gt;
&lt;li&gt;웹에서 버튼이 빛난다 -&amp;gt; 그래픽&lt;/li&gt;
&lt;li&gt;CAD에서 설계 도면을 그린다 -&amp;gt;그래픽&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시각적으로 표현되지 않으면, 그것은 존재하지 않는 것이나 다름없다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 하드웨어도 그래픽을 중심으로 발전해 왔다&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;GPU(Graphics Processing Unit)는&lt;/b&gt; 이름 그대로 그래픽 연산을 위한 전용 장치다.&lt;/li&gt;
&lt;li&gt;OpenGL, Vulkan, DirectX 등 모든 그래픽스 API는&amp;nbsp;&lt;b&gt;그래픽 데이터를 다루기 위한 표준&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;심지어 AI 연산도 GPU에서 빠르게 처리할 수 있는 이유는, &lt;b&gt;그래픽이 고도로 병렬적인 데이터 처리&lt;/b&gt;를 필요로 하기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 비유로 이해해 보자&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 63.8372%; height: 174px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.965%;&quot;&gt;분야&lt;/td&gt;
&lt;td style=&quot;width: 133.779%;&quot;&gt;그래픽에 해당하는 것&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.965%;&quot;&gt;음악&lt;/td&gt;
&lt;td style=&quot;width: 133.779%;&quot;&gt;소리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.965%;&quot;&gt;요리&lt;/td&gt;
&lt;td style=&quot;width: 133.779%;&quot;&gt;맛과 비주얼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.965%;&quot;&gt;수학&lt;/td&gt;
&lt;td style=&quot;width: 133.779%;&quot;&gt;그래프&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 47.965%;&quot;&gt;연극&lt;/td&gt;
&lt;td style=&quot;width: 133.779%;&quot;&gt;무대와 조명&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  컴퓨터 세계에선?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;rarr; 그래픽은 &lt;/span&gt;&lt;b&gt;화면에 보이는 모든 것&lt;/b&gt;&lt;span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 그것이 없다면 사용자는 아무것도 볼 수 없다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 요약&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그래픽은 단순한 그림이 아니다.&lt;/li&gt;
&lt;li&gt;컴퓨터 그래픽스에서 말하는 그래픽은 정점, 선, 색, 텍스처, 조명 등 모든 시각정 요소의 총합이다.&lt;/li&gt;
&lt;li&gt;우리는 그것을 만들기 위해 GPU를 쓰고, API를 공부하고, 셰이더를 작성한다.&lt;/li&gt;
&lt;li&gt;결국 그래픽은 보이기 위해 존재하며, 보여주기 위해 존재한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Dev/Grapics</category>
      <author>onepaperhoon</author>
      <guid isPermaLink="true">https://onepaperhoon.tistory.com/1</guid>
      <comments>https://onepaperhoon.tistory.com/1#entry1comment</comments>
      <pubDate>Mon, 7 Apr 2025 14:45:07 +0900</pubDate>
    </item>
  </channel>
</rss>