눈팅하는 게임개발자 블로그
OpenGL Getting Started 2-5 Shaders 본문
원본 사이트
learnopengl.com/Getting-started/Shaders
LearnOpenGL - Shaders
Shaders Getting-started/Shaders As mentioned in the Hello Triangle chapter, shaders are little programs that rest on the GPU. These programs are run for each specific section of the graphics pipeline. In a basic sense, shaders are nothing more than program
learnopengl.com
번역 사이트
heinleinsgame.tistory.com/8?category=757483
[Learn OpenGL 번역] 2-5. 시작하기 - Shaders
Shaders 시작하기/Shaders Hello Triangle 강좌에서 언급했듯이 shader는 GPU에서 동작하는 작은 프로그램입니다. 이 프로그램들은 그래픽 파이프라인의 특정 부분을 각자 맡아서 실행합니다. 기본적인 의�
heinleinsgame.tistory.com
Shaders
Shader는 GPU에서 동작하며 그래픽 파이프라인의 특정 부분을 각자 맡아서 실행하는 작은 프로그램이다.
기본적으로 shader는 받은 입력 값을 출력 값으로 변환시키는 프로그램이며
아주 독립적인 프로그램이기 때문에 서로 의사소통 할 수 없다.
유일한 인터페이스는 그들의 입력 값과 출력 값을 이용하는 것 뿐이다.
GLSL(OpenGL Shading Language)
Shader는 C언어와 비슷하게 생긴 GLSL으로 작성된다.
GLSL은 그래픽과 함께 쓰일 수 있도록 만들어졌고 특히 vertor와 matrix를 조작하는 데 유용한 기능을 가지고 있다.
구조는 Version 선언 -> 입력 변수, 출력 변수들 -> uniform -> main 함수로 이루어져 있다.
Main 함수에서 모든 입력 변수를 처리하고 출력 변수로 결과를 출력한다.
#version version_number
In type in_var_name;
In type in_var_name;
Out type out_var_name;
Uniform type uniform_name;
Void main()
{
//입력 값을 처리하고 그래픽 작업 수행
Out_var_name = weird_stuff_processed; // 처리된 결과를 출력 변수로 출력
}
Vertex Shader의 각각의 입력 변수는 vertex attribute라고 한다.
이는 하드웨어에 의해 제한되어 vertex attribute를 선언할 수 있는 최대 개수가 정해져 있다.
OpenGL은 4가지 요소를 가진 vertex attribute를 최소 16개까지 보장한다.
하지만 하드웨어에 따라 더 많이 허용될 수 있다.
이에 대한 정보를 GL_MAX_VERTEX_ATTRIBS를 통해 알 수 있다.
Int nrAttributes;
glGenIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std :: cout << “Maximum nr of vertex attributes supported: “ << nrAttributes << std::endl;
보통 최소값인 16을 리턴한다. 이는 대부분의 작업에서 충분한 숫자다.
Types
GLSL에서 사용하는 데이터 타입.
C언어와 같은 언어에서 볼 수 있는 기본적인 데이터 타입을 대부분 가지고 있다.
int, float, double, uint, bool 등
또한 GLSL은 Vector, matrices라는 두 가지의 컨테이너 타입이 있다.
Vectors
GLSL의 Vector는 1~4가지의 요소(언급한 모든 기본적인 타입의 요소들)을 가진 컨테이너이며
다음과 같은 형식을 취한다.
vecn : n개의 float 타입 요소를 가지는 기본적인 vector
bvecn : n개의 Boolean 타입 요소를 가지는 vector
ivecn : n개의 integer 타입 요소를 가지는 vector
uvecn : n개의 unsigned integer 타입 요소를 가지는 vector
dvecn : n개의 double 타입 요소를 가지는 vector
float 타입이 대부분의 작업에서 사용되기 때문에 대부분 기본적인 vecn을 사용한다.
Vector의 요소들은 vec.x, vec.y, vec.z, vec.w를 통해 접근할 수 있다.
또한 컬러는 rgba를, 텍스쳐 좌표는 stpq를 사용하여 나타낼 수 있다.
Vector 데이터 타입으로 swizzling이라고 불리는 유연한 요소 선택을 할 수 있다.
예를 들면
Vec2 someVec;
Vec4 differentVec = someVec.xyxx;
Vec3 anotherVec = differentVec.zyw;
Vec4 otherVec = someVec.xxxx + anothervec.yxzy;
위의 예시와 같이 새로운 vector를 원래의 vector가 가지고 있는 요소들로
최대 4의 문자 조합을 사용하여 생성할 수 있다.
입력과 출력 데이터 in, out
vertex shader는 vertex 데이터를 곧바로 입력으로 받는다.
vertex 데이터가 어떻게 구성되어 있는지 정의하기 위해
location 메타 데이터와 함께 입력 변수를 지정함으로써 CPU에 vertex attribute를 구성할 수 있다.
이를 위해 layout (location = 0)코드를 사용했었는데,
이처럼 vertex shader는 입력에 관한 별도의 layout 명시를 요구하므로 이를 통해 vertex 데이터와 연결할 수 있다.
또한 layout 명시자를 사용하지 않고 glGetAttribLocation함수를 통해 attribute의 location을
요청할 수도 있지만 vertex shader에 직접 설정하는 것이 권장된다.
또 다른 예외로서 fragment shader는 최종 출력 컬러를 생성해야 하기 때문에 vec4타입의
컬러 출력 변수를 필요로 한다는 점이다.
fragments shader에서 출력 컬러를 지정하지 않는다면 OpenGL은 오브젝트를 검정색 또는 하얀색으로 렌더링한다.
데이터를 한 shader에서 다른 shader로 넘기고 싶다면 보내는 shader에서
출력을 선언해야 하고 마찬가지로 받는 shader에서 입력을 선언해야 한다.
양쪽의 타입과 이름이 같다면 OpenGL은 그 변수들을 연결시켜 shader 간의 데이터 입출력이 가능하도록 만든다.
Uniforms
Uniform은 CPU위의 응용 프로그램에서 GPU 위의 shader로 데이터를 전달하는 방법이다.
하지만 이는 vertex attribute와 비교하면 약간 복잡하다.
우선 uniform global.
(uniform 변수인)global은 shader program 객체에서 고유한 변수이고 shader program의 모든 단계의 모든 shader에서 접근이 가능하다.
uniform 변수에 어떤 값을 설정하든 리셋 또는 업데이트 하기 전까지 그 값을 유지한다.
GLSL에서 uniform을 선언하기 위해서는 shader에 타입, 이름과 함께 uniform 키워드를 추가하기만 하면 된다.
그 후부터 새로 선언된 uniform 변수를 사용할 수 있다.
아래는 uniform을 통해 삼각형의 컬러를 설정하는 코드이다.
#version 330 core
Out vec4 FragColor;
Uniform vec4 ourColor;
Void main()
{
FragColor = ourColor;
}
이제 uniform에 데이터를 삽입하기 위해서
shader에서 uniform attribute의 index/location을 찾아야 한다.
이를 알아내기만 하면 값을 수정할 수 있다.
fragment shader에 하나의 컬러를 전달하는 대신 시간에 따라 점점 변하는 컬러를 추가해보자.
Float timeValue = glfwGetTime(); // 실행 시간을 초단위로 검색
Float greenValue = (sin(timeValue) / 2.0f) + 0.5f; // sin 함수로 컬러의 값을 0.0~1.0사이의 값으로 다르게 설정 후 greenValue에 저장.
Int vertexColorLocation = glGetUniformLocation(shaderProgram, “ourColor”); // outColor uniform 변수의 location을 확인. 이 함수에 shader Program과 uniform의 변수명을 매개변수로 호출. 찾지 못한다면 -1을 리턴한다.
glUseProgram(shaderProgram);
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f); //마지막으로 gUniform함수로 unform 변수의 값을 설정, 값을 변경하기 위해서는 glUseProgram함수가 선행되어 실행될 필요가 있다.
이렇게 uniform은 루프가 돌 때마다 변할 수 있는 attribute들을 세팅하는 데에나
응용 프로그램과 shader 사이에서 데이터를 상호 교환하는 데 유용한 도구가 된다.
만약 각 vertex마다 색을 설정하고 싶은 경우
가진 vertex의 수만큼 많은 uniform을 선언해야 한다.
더 좋은 해법은 vertex attribute에 더 많은 데이터를 추가하는 것이다.
float vertices[] = {
// 위치 // 컬러
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 우측 하단
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 좌측 하단
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 위
};
vertex sahder에 보낼 추가 데이터가 있는 경우
vertex attribute 입력으로 컬러 값도 받을 수 있도록 vertex shader를 조정해야 한다.
aColor attribute의 location을 layout 명시자를 이용하여 1로 설정한다.
#version 330 core
Layout (location = 0) in vec3 aPos; // attribute position 0
Layout (location = 1) in vec3 aColor; // attribute position 1
Out vec3 ourColor; // 컬러를 fragment shader로 출력
Void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor;
}
fragment 컬러값으로 ourColor 입력변수를 fragment shader 코드에 추가.
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
void main()
{
FragColor = vec4(ourColor, 1.0);
}
이제 layout을 새로변경했으므로
glVertexAttribPointer 함수를 사용하여 vertex 형식을 수정.
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
attribute location 1의 attribute를 구성, 컬러 값은 float 타입 3개의 크기, 정규화 하지 않는다.
이제 stride 값을 다시 계산해야 한다. 데이터의 배열의 다음 attribute 값은 float * 6을 더한 주소.
해당 데이터가 처음 시작하는 위치가 3번째 위치이므로 offset을 3으로 지정.
결과
각 vertex에서 멀어질수록 색이 변하는 것을 확인할 수 있는데.
이는 Rasterizer가 각 vertex의 위치들을 기반으로 하여
Fragment shader의 모든 입력 변수를 보간(interpolate)하여 나타나는 현상이다.
파란색과 초록색의 중간 지점은 두 색이 50%씩 섞인 색이 되는 식이다.
이 fragment interpolation은 fragment shader의 모든 입력 attribute에 적용된다.
Shader 클래스
Shader를 작성하고 컴파일하고 관리하는 것을 쉽게 만들기 위하여
shader 클래스를 생성하여 작업을 쉽게 만들기로 한다.
이는 지금까지 학습한 내용들을 어떻게 유용한 추상 객체에 캡슐화할 것인지에 대한 아이디어를 제공한다.
#ifndef SHADER_H
#define SHADER_H
#include <glad/glad.h> // 필요한 모든 OpenGL의 헤더파일을 가져오기 위해 glad를 포함합니다.
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
class Shader
{
public:
// program ID
unsigned int ID;
// 생성자는 shader를 읽고 생성합니다.
Shader(const GLchar* vertexPath, const GLchar* fragmentPath);
// shader를 활성화하고 사용합니다.
void use();
// Uniform 유틸리티 함수들
void setBool(const std::string &name, bool value) const;
void setInt(const std::string &name, int value) const;
void setFloat(const std::string &name, float value) const;
};
#endif
#include "Shader.h"
Shader::Shader(const GLchar* vertexPath, const GLchar* fragmentPath)
{
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try
{
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
vShaderFile.close();
fShaderFile.close();
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
}
catch (std::ifstream::failure e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
}
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
// 가져온 코드들로 shader 컴파일
unsigned int vertex, fragment;
int success;
char infoLog[512];
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
// 오류 체크
glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertex, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
//오류 체크
glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragment, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
//오류 체크
glGetProgramiv(ID, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(ID, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
glDeleteShader(vertex);
glDeleteShader(fragment);
}
void Shader::use()
{
glUseProgram(ID);
}
void Shader::setBool(const std::string& name, bool value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}
void Shader::setInt(const std::string& name, int value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}
void Shader::setFloat(const std::string& name, float value) const
{
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
'공부한거 > OpenGL' 카테고리의 다른 글
OpenGL Getting Started 2-9 Camera (0) | 2020.09.29 |
---|---|
OpenGL Getting Started 2-8 Coordinate System (0) | 2020.09.29 |
OpenGL Getting Started 2-7 Translation (0) | 2020.09.27 |
OpenGL Getting Started 2-6 Texture (0) | 2020.09.24 |
OpenGL Getting Started 2-4 Hello Triangle (0) | 2020.09.24 |