일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- vertexattributeobject
- unity
- callbacks
- 쿼터니온
- bufferswap
- VRC
- vao
- 회전행렬
- VRChat
- BeatSaber
- 수학
- NVM
- MongoDB
- ibo
- liltoon
- bufferobjects
- Directx12
- react
- indexbufferobject
- vbo
- Drawcall
- 3d 기하학
- shaders
- 수학 #기하학 #벡터 #벡터연산 #선형대수학
- kubernetes
- OpenGL
- initialization
- LiV
- ReactJS
- vertexbufferobject
- Today
- Total
To Paint a World
OpenGL - 4. Shaders 본문
Writing Shaders
쉐이더는 정점 데이터를 입력받아 GPU 에서 병렬 연산을 처리하는 프로그램이다.
쉐이더의 종류로는 정점 데이터를 변환해주는 Vertex Shader 와, 픽셀 데이터를 가공하여 픽셀 색상을 출력하는 Fragment Shader 가 있다.
- Vertex Shader
- Fragment Shader
렌더링 파이프라인은 정점 데이터를 모으고, 쉐이더를 거쳐 데이터를 가공하고, 기하 연산을 처리하여 어떤 도형이 최종적으로는 스크린에 출력될지 결정하는 역할을 한다.
Vertex Shader
Vertex Shader 는 한 개의 정점 데이터 (position, color, ... 등의 attribute들) 를 입력받고, 일괄적인 연산을 수행하여 출력하는 쉐이더이다.
Vertex Shader 의 가장 흔한 목표는 각 정점에 트랜스폼 (로컬 -> 월드 좌표계 변환 등) 을 적용하는 것이다. 가공된 좌표는 gl_Position 이란 built-in 변수 값에 배정하면 된다.
#version 330
layout (location = 0) in vec3 position;
void main()
{
gl_Position = vec4(position, 1.0);
}
Fragment Shader
Vertex Shader 를 거친 정점 데이터는 Rasterization 이란 과정을 통해 화면 내 픽셀로 변환된다.
이러한 픽셀은 Fragment Shader 로 보내진다. Fragment Shader 는 픽셀의 위치 값, attribute 등을 처리하여 화면에 출력할 색상을 반환한다.
#version 330 core
out vec4 color;
void main()
{
color = vec4(0.8F, 0.5f, 0.2f, 0.8F);
}
Shader Class
쉐이더 코드를 작성했으면, 이를 불러오고 컴파일하여 OpenGL 프로그램에 부착해서 사용할 수 있다.
아래 Shader 클래스는 그러한 기능을 수행하기 위한 함수들을 정의한다.
#pragma once
#include <GL/glew.h>
#include <GLFW/glfw3.h>
class Shader {
private:
unsigned int _id;
bool deleted = 0;
public:
Shader(GLenum shaderType, const GLchar* source);
~Shader();
void Attach(unsigned int programId);
void Delete();
};
Creating a Shader
쉐이더를 생성하여 그 이름을 _id 변수에 저장하고, 경로로부터 쉐이더 코드를 불러오고 컴파일한다.
Shader::Shader(GLenum shaderType, const GLchar* source) {
_id = glCreateShader(shaderType);
glShaderSource(_id , 1, &source, NULL);
glCompileShader(_id);
// Check for compile time errors
GLint success;
GLchar infoLog[512];
glGetShaderiv(_id, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(_id, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::COMPILATION_FAILED\n" << infoLog << std::endl;
}
}
Attach
쉐이더를 OpenGL 프로그램에 부착한다.
void Shader::Attach(unsigned int programId) {
glAttachShader(programId, _id);
}
Delete
쉐이더를 삭제한다.
void Shader::Delete() {
if (deleted)
return;
glDeleteShader(_id);
deleted = true;
}
Shader Initialization
GLApp 에서 쉐이더를 불러오는 InitShaders 함수와, 쉐이더 프로그램의 이름인 shaderProgram 변수, 각각 vertex shader 와 fragment shader 에 해당하는 Shader 오브젝트를 선언한다.
class GLApp {
// ...
protected:
bool InitShaders(const char* vertexPath, const char* fragPath);
protected:
unsigned int shaderProgram;
Shader* vertexShader;
Shader* fragmentShader;
};
쉐이더를 불러오기에 앞서, 쉐이더를 사용하려면 OpenGL 3+ 버전을 지정해야 한다.
bool GLApp::InitOpenGL() {
if (!glfwInit())
return false;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
// glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
// ...
}
InitShaders 함수에서 vertex shader 와 fragment shader 의 경로를 받아와서, 프로그램에 부착한다.
bool GLApp::InitShaders(const char* vertexPath, const char* fragPath) {
shaderProgram = glCreateProgram();
std::string vertexShaderSource = load_text(vertexPath);
if (!vertexShaderSource.empty()) {
vertexShader = new Shader(GL_VERTEX_SHADER, vertexShaderSource.c_str());
vertexShader->Attach(shaderProgram);
vertexShader->Delete();
}
std::string fragmentShaderSource = load_text(fragPath);
if (!fragmentShaderSource.empty()) {
fragmentShader = new Shader(GL_FRAGMENT_SHADER, fragmentShaderSource.c_str());
fragmentShader->Attach(shaderProgram);
fragmentShader->Delete();
}
glLinkProgram(shaderProgram);
return true;
}
Using Shaders
MainApp 의 Initialize 함수에서 쉐이더를 불러온다.
bool MainApp::Initialize() {
// init GLApp functionalities
GLApp::InitOpenGL();
GLApp::InitShaders("../src/Scenes/tutorials/shaders/vert.vert",
"../src/Scenes/tutorials/shaders/frag.frag");
GLApp::InitCallbacks();
// ...
}
드로우 콜에서 도형에 대한 명령어를 호출하기에 앞서, glUseProgram 함수를 호출하여 쉐이더를 사용하도록 설정한다.
void MainApp::Draw() {
// ...
glUseProgram(shaderProgram);
// ...
}
모든 구현이 성공적으로 이뤄졌으면, fragment shader 에서 지정한 색상으로 도형이 색칠된 것을 볼 수 있다.
'3D Engine > OpenGL' 카테고리의 다른 글
OpenGL - 5. Uniforms (0) | 2025.02.18 |
---|---|
OpenGL - 3. Buffer Objects (1) | 2025.01.02 |
OpenGL - 2. Input Callbacks (0) | 2024.12.26 |
OpenGL - 1. Initialization (3) | 2024.12.26 |