GLSL
GLSL은 GL Shader Language 의 약자로, 셰이더 프로그래밍을 위한 C 기반의 언어이다.
대표적인 사용 방법은 아래와 같다.
#version version_number // Open GL 버전
in type in_variable_name;
in type in_variable_name2; // 셰이더에 들어오는 입력 변수
out type out_variable_name; // 셰이더가 처리한 뒤 내보내는 출력 변수
uniform type uniform_name; // 모든 정점에 대해 동일하게 사용되는 전역 변수
void main() {
out_variable_name = weird_stuff_we_processed;
}
GLSL 데이터 타입
GLSL은 대부분의 C 데이터 타입을 지원한다. (int, float, double, uint, bool, ...)
이 외에도 vector, matrix 데이터 타입도 지원한다.
특히 벡터는 아래와 2, 3, 4 개 구성요소를 지니는 컨테이너로서, 아래 종류가 있다.
벡터의 구성 요소 개수를 n이라고 할 때
vecn : n개의 실수 성분을 지니는 기본 벡터
bvecn : n개의 bool 성분을 지니는 벡터
ivecn : n개의 정수 성분을 지니는 벡터
uvecn : n개의 unsigned int 성분을 지니는 벡터
dvecn : n개의 double 성분을 지니는 벡터
Swizzling
스위즐링은 벡터의 각 성분(component)을 조합하는 것을 말한다.
말보다 직접 코드를 보는 것이 빠르다.
vec2 someVec;
예를 들어, vec2 타입의 someVec 데이터가 들어왔다고 하자.
이 2개 성분을 가진 벡터를 이용해서 3개 성분을 가진 vec3 타입의 데이터를 아래와 같이 만들 수 있다.
vec2 someVec;
vec3 = someVec.xyx;
someVec 은 2개 성분 (x, y) 를 가지고 있다. 이 2개 성분을 사용해서 3개 성분의 벡터 (x, y, x) 를 만든 것이다.
이는 vec4 를 만들때도 그대로 적용할 수 있다.
Shader Inputs and Outputs
먼저 셰이더의 input 에 대해 더 자세히 알아보자.
기본적으로 셰이더의 출력값은 그 다음 스테이지 셰이더의 입력값으로 들어가게 된다.
하지만 vertex shader 는 초기 데이터를 외부에서 입력받아야 한다.
그 데이터는 우리가 VBO 데이터 풀에서 VAO에 저장된 attribute 정보를 이용해 파싱하여 얻는다.
그렇다면 이 데이터를 어떻게 셰이더로 넘길 수 있을까?
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
해당하는 정보는 위와 같이 layout 을 이용하여 넘길 수 있다.
layout() 안에는 location 값이 들어간다.
이 값은 우리가 VAO에 연결한 attribute의 구분 번호를 쓰면 된다.
glVertexAttribPointer(0, ..... );
glEnableVertexAttribArray(0); // 이 구분번호 0을 layout에 넘긴다.
그리고 vertex shader처럼 초기 데이터를 외부로부터 받는게 아니라면, 이전 셰이더의 출력 변수가 그 다음 스테이지 셰이더의 입력 변수로 그대로 들어간다.
따라서 vertex shader 에서 입력한 출력변수의 이름은, fragment shader 의 입력변수와 타입, 이름이 같아야 한다.
Uniform 변수
기본적으로 in 으로 받는 데이터는 각 정점마다 다르게 들어온다.
각 정점마다 차례대로 끊어낸 데이터 묶음 조각을 받기 때문이다.
하지만 모든 정점에 대해 동일한 값을 받아 쓰고 싶을 때 uniform 변수를 쓸 수 있다.
구체적으로 uniform 변수는 CPU에서 모든 vertex에 동일한 변수값을 전달할 때 사용한다.
uniform 변수는 일종의 전역변수 속성을 지니며, 모든 스테이지의 shader에서 접근할 수 있다.
셰이더에서 uniform 변수에 접근하는 방법은 아래와 같다.
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor;
void main() {
FragColor = ourColor;
}
위 셰이더 프로그램은 프로그래먼트 셰이더 프로그램이다.
출력으로 RGB 색상값을 내보내는데, 이 값을 ourColor 라고 하는 uniform 전역 변수로부터 얻어 내보낸다.
Uniform 변수값 설정
그렇다면 셰이더 외부에서 uniform 변수값은 어떻게 설정할까?
다음 함수를 사용하여 셰이더 프로그램에 uniform 변수를 설정할 수 있다.
(셰이더가 아니라 여러 셰이더가 링크된 셰이더 프로그램에 설정한다. 그래서 모든 셰이더에서 접근할 수 있는 것이다.)
glGetUniformLocation(shaderProgram, uniform 변수 이름)
이렇게 하면 셰이더 프로그램에 문자열로 전달된 이름을 가진 uniform 변수가 생성된다.
그리고 이 함수는 그 변수의 위치값을 int 형으로 돌려준다.
하지만 아직 이 변수가 어떤 타입인지는 정해져 있지 않다.
이를 설정하기 위해 다음과 같은 함수들을 사용할 수 있다.
glUniformf() // 실수값 설정
glUniformi() // 정수값 설정
glUniformui() // unsigned 정수값 설정
glUniform3f() // 실수 3개 값 설정
glUniform4f() // 실수 4개 값 설정
glUniformfv() // 실수로 구성된 벡터 값 설정
각 함수들은 공통적으로 아래 인자를 갖는다.
glUniform4f( uniform변수 위치값, 실수값1, 실수값2, 실수값3, 실수값4);
// 실수값 4개로 구성된 4차원 float 벡터로 셰이더에 전달한다.
그렇다면 glUniformfv() 와 glUniform4f 의 차이점은 뭘까...
이를 통해 셰이더에서 사용할 값을 렌더링 루프에서 동적으로 바꿔서 전달해줄 수도 있다.
이를 응용해 시간이 지날 때마다 색상이 변하는 삼각형을 그릴 수도 있다.
해당 예제는 다음 글에서 정리해본다.
셰이더 프로그램을 외부 소스코드에서 참조하기
지금은 셰이더 프로그램을 main.cpp 안에 문자열로 하드코딩해서 사용했었다.
이 프로그램을 외부 소스코드에서 코딩하고, 그 소스코드를 가져올 수 있다.
#include <Shader.h>
// #include <Shader_s.h>
Shader shader(vertexShaderSourceCodeFileName, fragmentShaderSourceCodeFileName);
// rendering loop
while (..) {
...
// glUserProgram(shaderProgram);
shader.use();
glBindVertexArray(VAO);
glDrawArrays(...);
}
Shader 클래스를 사용하여 직접 여러 셰이더가 링크된 shaderProgram을 만들 수 있다.
Shader.h (내가 갖고있는 예제 코드에서는 Shader_s.h) 를 include 하여 Shader 클래스 정보를 가져온다.
셰이더 프로그램 객체를 만들면서, 생성자에 vertex shader, fragment shader 소스코드 파일을 넘긴다.
이걸로 셰이더 프로그램은 준비가 끝이다!
이제 렌더링 루프에서 셰이더 프로그램을 사용할 때, glUseProgram() 함수 대신에, 생성한 셰이더 객체의 use 메소드를 사용하면 된다.
다음 글에서는 위 내용을 토대로 시간이 지날 때마다 색상이 바뀌는 사각형을 그려보고자 한다.
'CS > HCI 윈도우즈프로그래밍' 카테고리의 다른 글
[OpenGL] 10. Shader (6) - Texture (0) | 2024.04.11 |
---|---|
[OpenGL] 9. Shader (5) - 직사각형에 실시간으로 변하는 색상 입히기 (GLFW, GLAD) (0) | 2024.04.09 |
[OpenGL] 7. Shader (3) - EBO (Element Buffer Object) (0) | 2024.04.08 |
[OpenGL] 6. Shader (2) - VAO (1) | 2024.04.07 |
[OpenGL] 5. Shader (1) - Shader, VBO, Shader Program (0) | 2024.04.05 |