OpenGL

[OpenGL] ※그래픽 파이프라인과 GLSL(ShaderProgram)

usingsystem 2024. 4. 3. 09:59
728x90

전통적인 그래픽스 파이프라인

예전에는 모두 fixed vlsi칩으로 구현했기 때문에 수정할 수 없다.

Vertex input Vertex
Processing
primitive
assembly
rasterization fragment
processing
blend framebuffer
    fixed hardware      

Vertex Processing

  • 입력받은 Vertex의 좌표 변환(transform)
  • 필요하다면 위치를 조금씩 수정한다.

Primitive assembly

  • vertex(꼭지점)를 결합한다. (graphics primitive)
  • 1vertex(점), 2vertex(선), 3vertex(삼각형)
  • 컴퓨터 테크픽스 관점에서는 다른 다각형들을 모두 삼각형으로 분해할 수 있다. 그래서 삼각형으로 최적화하려는 목적이 있다.

Rasterization(래서터화)

  • Primitive에 그려진 삼격형을 pixel로 선정한다. 즉 픽셀의 집합으로 만든다.
  • primitive에서 만든 기하학적인 삼각형을 pixel로 변형한다.

Fragment processing 

  • Rasterization에서 변형한 pixel에 관련자료(색상, 깊이, 위치....)를 합한다.
  • 해당 픽셀이 하나하나 색상이 입혀져 화면에 켜지며 어떤 형태로 자리 잡게 된다.

blend(후처리 단계)

  • 후처리 단계로 fragment 단위 처리로 최종 나온 삼각형에 다양한 효과를 준다 ex) 안개효과 등.

현재 프로그래머블 파이프라인(Shader 프로그램)

vertex processing vlsi를 shader로 대체

fragment processing vlsi를 shader로 대체

Vertex input Vertex
Shader
primitive
assembly
rasterization fragment
Shader
blend framebuffer

 

전통적인 그래픽스 파이프라인의 속도를 계선하기 위해 병렬 처리를 도입하였는데 이때 vertex processing과 fragment processing에서 속도가 느려지는 현상이 발생하였다. 이때 쉐이더를 사용하여 프로그램을 개발하는 파이프라인을 프로그래머블 파이프라인이라고 한다.

 

Shader program 

  • GPU에서 돌아가는 small size 프로그램으로 병렬 처리된다.

Shader Language 

  • 쉐이더를 만들기 위한 프로그래밍 언어

Shader  compiler

  • 쉐이더 전용 컴파일러

 

shader Programming 종류

DirectX HLSL - PC와 XBox에서 사용가능

Cg(c for Graphics) - nvidia 그래픽카드에서만 작동

OpenGL SL - 거의 모든 GPU에서 사용가능(GLSL)

GLSL(OpenGL Shader Language)

GLSL은 C 프로그래밍 언어를 기반으로 하며, 다음과 같은 주요 특징들을 가지고 있습니다.

  • 벡터(vector) 및 행렬(matrix) 기본 자료형 지원: GLSL은 벡터 및 행렬 연산을 지원하여 3D 그래픽 처리에 편리합니다.
  • 내장 함수: 삼각 함수, 로그 함수, 행렬 연산 등과 같은 다양한 내장 함수를 제공합니다.
  • Pointer 자료형이 없음. (gpu 병렬처리를 위해서는 pointer는 필요 없음)
  • float의 정밀도 조정 : highp, mediump, lowp의 precision qualifier를 사용하여 속도를 조절할 수 있다.(ex precision lowp float;)
  • register에 대한 qualifier : shader 별로 input, ouput에 따라 사용

1. 버텍스 셰이더(Vertex Shader): 입력 정점 데이터를 받아들여 정점의 위치, 색상, 텍스처 좌표 등을 변형하는 셰이더입니다. 버텍스 셰이더는 정점의 기하학적 변형을 담당하며, 이러한 변형은 보통 모델 변환, 카메라 변환 및 투영 변환과 같은 단계에서 이루어집니다.

attribute0, 1, 2, 3  -> vertex shader processor -> varying0, 1, 2, 3

 

In 메모리 - attribute register에 저장된다.

Out 메모리 - varying register에 출력된다.

in vec4 vertexPos;
void main(void)
{
 gl_Position = vertexPos;
}

vertexPos

in : attribute register

out : varying register

gl_Position : vertex position을 저장

#version 330 core //3.3버전의 core를 사용하겠다 익스트렉터사용x
GLflot vertPos[] ={
-0.5f, -0.5f, 0.0f, 1.0f,
+0.5f, -0.5f, 0.0f, 1.0f
-0.5f, +0.5f, 0.0f, 1.0f
}

위에 선언한 GLflot의 vertexPos 3개는 각각 하나의 프로세서가 병렬로 처리하게 된다.

 

2. 프래그먼트 셰이더(Fragment Shader): 렌더링 된 버텍스들 사이의 픽셀을 채우는 역할을 합니다. 픽셀의 최종 색상, 광원 모델, 그림자 효과 및 텍스처 매핑 등을 계산하여 픽셀의 색상을 결정합니다.

varying0, 1, 2, 3 -> fragment shader processor -> output

In 메모리 - varying register 저장

Out - framebuffer update 출력

#version 330 core //3.3버전의 core를 사용하겠다 익스트렉터사용x
out vec4 FragColor;
void main(void){
 FragColor = vec4(1.0, 0.0, 0.0, 1.0);//color red
}

vertexPos

in : varying register

out : framebuffer update

forColor당 하나의 프로세서가 병렬로 처리하게 된다.

 

vs(Vertex Shader) 소스코드와 fs(Fragment Shader) 소스코드 사용법

vs소스코드와 fs소스코드 쉐이더 변환 과정 

  1. glCreateShader
  2. glShaderSource
  3. glCompileShader
  4. 결괏값 각각의 shader
const char* vertSource =
"#version 330 core \n\
in vec4 vertexPos; \n\
void main(void) { \n\
	gl_Position = vertexPos; \n\
}";

const char* fragSource =
"#version 330 core \n\
out vec4 FragColor; \n\
void main(void) { \n\
	FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n\
}";

GLuint vert = 0; // vertex shader ID number
GLuint frag = 0; // fragment shader ID number
GLuint prog = 0; // shader program ID number

void initFunc(void) {
	// vert: vertex shader
	vert = glCreateShader(GL_VERTEX_SHADER);//쉐이더 오브젝트 생성
	glShaderSource(vert, 1, &vertSource, NULL);//쉐이더 소스코드를 준다
	glCompileShader(vert); // compile to get .OBJ
	// frag: fragment shader
	frag = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(frag, 1, &fragSource, NULL);
	glCompileShader(frag); // compile to get .OBJ
	// prog: program
	prog = glCreateProgram();//프로그램 id번호 출력
	glAttachShader(prog, vert);
	glAttachShader(prog, frag);
	glLinkProgram(prog); // link to get .EXE
	// execute it!
	glUseProgram(prog);//그래픽카드 실행
}

glUseProgram에서 만든 쉐이더를 이용하여 exe를 만들어 그래픽카드에서 동작한다.

 

생성한 쉐이더 gpu와 c언어 cpu와 연결

vertPos에서 선언한 3개의 포지션은 각각 위에서 만든 vertSource로 연결되어 gpu에서 병렬로 처리된다. 

ex) in vect vertexPos = {-0.5F, -0.5F, 0.0F, 1.0F} - 1번 프로세싱

ex) in vect vertexPos = {+0.5F, -0.5F, 0.0F, 1.0F} - 2번 프로세싱

ex) in vect vertexPos = {-0.5F, +0.5F, 0.0F, 1.0F} - 3번 프로세싱

GLfloat vertPos[] = {
	-0.5F, -0.5F, 0.0F, 1.0F,
	+0.5F, -0.5F, 0.0F, 1.0F,
	-0.5F, +0.5F, 0.0F, 1.0F,
};

void drawFunc(void) {
	// clear in gray color
	glClear(GL_COLOR_BUFFER_BIT);
	// provide the vertex attributes
	GLuint loc = glGetAttribLocation(prog, "vertexPos");//vertexPos이 할당된 에트리뷰트의 위치를 찾는다.
	glEnableVertexAttribArray(loc);//찾은 에트리뷰트 실행 c언어 프로그램과 연결
	glVertexAttribPointer(loc, 4, GL_FLOAT, GL_FALSE, 0, vertPos);//미리 선언한 carray를 보낸다 vertPos를 보내게 된다. c언어의 cpu와 gpu가 연결된상황
	// draw a triangle
	glDrawArrays(GL_TRIANGLES, 0, 3);//실제로 그리는 부분
	// done
	glFinish();//opengl의 모든 실행을 완료시킨다.
}

1.2 버전 옛날 vs(Vertex Shader) 소스코드와 fs(Fragment Shader) 소스코드

#version 120

attribute vec4 vertexPos;

void main(void) {
	gl_Position = vertexPos;
}
#version 120

void main(void) {
	gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

 layout을 사용하여 vs와 fs 메모리 직접지정

vs는 attribute에 저장되며 fs는 varying에 저장된다. 이를 컴파일에서 자동저장이 아닌 직접저장을 할 수 있다.

#version 330 core

layout (location = 5) in vec4 vertexPos; // attribute

void main(void) {
	gl_Position = vertexPos;
}
#version 330 core

layout (location = 0) out vec4 FragColor; // fragment color: framebuffer

void main(void) {
	FragColor = vec4(1.0, 0.0, 0.0, 1.0); // red color
}

Uniforms

uniform Global Variable 이다. Global은 uniform은 shader program 객체에서 고유한 변수로 shader program의 모든 단계의 모든 shader에서 접근 가능합니다.

uniform 변수에 어떤 값을 설정하든 리셋을 하거나 업데이트를 하기 전까지 그 값을 계속 유지하고 있습니다.

 

자세히 설명하면, Uniform은 OpenGL에서 셰이더 프로그램에 값을 전달하는 메커니즘입니다. 이것은 셰이더 프로그램 내에서 상수값이나 변하지 않는 데이터를 전달하는 데 사용됩니다. 예를 들어, 변환 행렬, 빛의 위치, 재질 속성 등의 값들이 이에 해당합니다.

Uniform은 다음과 같은 특징을 갖습니다:

  1. 값이 변하지 않음(Global): Uniform은 한 번 설정되면 해당 프레임의 렌더링 동안 고정됩니다. 이는 셰이더 프로그램이 동일한 값을 모든 정점 또는 프래그먼트에 적용할 수 있도록 해줍니다.
  2. 셰이더 프로그램 내에서 선언: Uniform은 셰이더 프로그램의 코드 내에서 uniform 키워드를 사용하여 선언됩니다. 예를 들어, uniform mat4 modelViewProjectionMatrix;와 같이 사용됩니다.
  3. 값 설정: Uniform의 값은 OpenGL API를 사용하여 설정됩니다. 이를 통해 CPU에서 값을 변경하고 셰이더 프로그램에 전달할 수 있습니다. glUniform 시리즈의 함수를 사용하여 값을 설정할 수 있습니다.
  4. 변수 타입: Uniform은 다양한 타입을 가질 수 있습니다. 예를 들어, 행렬, 벡터, 스칼라 등이 될 수 있습니다.
#version 330 core

in vec4 aPos; // vertex position: attribute
in vec4 aColor; // vertex color: attribute
out vec4 vColor; // varying color: varying
uniform vec4 uMove; // movement vector: uniform

void main(void) {
	gl_Position = aPos + uMove;
	vColor = aColor;
}

aPos와 uMove를 더하여 새로운 포지션인 gl_Position을 만들어 낸다.

	// draw the first triangle
	GLuint locMove = glGetUniformLocation(prog, "uMove");//uniform위치 얻기
	glUniform4f(locMove, -0.5F, -0.5F, 0.0F, 0.0F);//uniform 값 적용
	glDrawArrays(GL_TRIANGLES, 0, 3);//pos + uniform 위치로 삼각형 생성
	// draw the second triangle
	glUniform4f(locMove, 0.0F, 0.0F, 0.0F, 0.0F);
	glDrawArrays(GL_TRIANGLES, 0, 3);

GLSL(쉐이더프로그램) 디버그

glGetShaderiv 

이 함수를 사용하면 셰이더의 컴파일 상태, 유형 및 정보 로그 길이와 같은 여러 속성을 쿼리할 수 있습니다. 함수 시그니처는 일반적으로 다음과 같습니다.

void glGetShaderiv(GLuint shader, GLenum pname, GLint *params);
  • shader: 셰이더 객체의 이름(식별자).
  • pname: 조회할 매개변수를 지정합니다. 이는 GL_SHADER_TYPE, GL_COMPILE_STATUS, GL_INFO_LOG_LENGTH 등과 같은 사전 정의된 상수 중 하나일 수 있습니다.
  • params: 검색된 값이 저장될 변수를 가리킵니다.

예를 들어, 셰이더가 성공적으로 컴파일되었는지 여부를 확인하려면 pname으로 GL_COMPILE_STATUS를 사용합니다. 그런 다음 값은 params 변수에 저장되며, 컴파일이 성공했는지 여부를 결정하기 위해 해당 값을 확인할 수 있습니다.

 
glGetShaderInfoLog
셰이더 객체에 대한 컴파일 또는 링크 과정에서 발생한 오류 및 경고 메시지를 가져오는 데 사용됩니다. 이 함수를 사용하면 셰이더 컴파일 또는 링크 과정에서 발생한 오류 및 경고 메시지를 확인할 수 있습니다.
 
void glGetShaderInfoLog(GLuint shader, GLsizei maxLength, GLsizei *length, GLchar *infoLog);
  • shader: 정보를 가져올 셰이더 객체의 이름(식별자).
  • maxLength: 가져올 정보 로그의 최대 길이.
  • length: 실제로 가져온 정보 로그의 길이를 저장하는 변수.
  • infoLog: 정보 로그가 저장될 문자열 버퍼.

예를 들어, 셰이더 컴파일 과정에서 발생한 오류 및 경고 메시지를 확인하려면 이 함수를 사용하여 해당 정보를 가져올 수 있습니다. 가져온 정보는 infoLog 문자열에 저장되며, 실제로 가져온 문자열의 길이는 length 변수에 저장됩니다.

 
glGetProgramiv 
이 함수를 사용하면 프로그램의 링크 상태, 연결된 셰이더의 수, 정보 로그의 길이 등과 같은 여러 속성을 쿼리할 수 있습니다. link status, validate status 확인
void glGetProgramiv(GLuint program, GLenum pname, GLint *params);
  • program: 매개변수를 검색할 프로그램 객체의 이름(식별자).
  • pname: 조회할 매개변수를 지정하는 상수입니다. 예를 들어, GL_LINK_STATUS, GL_ATTACHED_SHADERS, GL_INFO_LOG_LENGTH 등이 있습니다.
  • params: 검색된 값이 저장될 변수를 가리킵니다.

예를 들어, 프로그램의 링크 상태를 확인하려면 GL_LINK_STATUS를 pname으로 사용하여 해당 값을 가져올 수 있습니다. 가져온 값은 params 변수에 저장되며, 프로그램의 링크 상태를 판단하기 위해 해당 값을 확인할 수 있습니다.

 

glGetProgramInfoLog 

이 함수를 사용하면 프로그램 링크 과정에서 발생한 오류 및 경고 메시지를 확인할 수 있습니다.

link와 validation 로그 정보 메세지로 받아옴

void glGetProgramInfoLog(GLuint program, GLsizei maxLength, GLsizei *length, GLchar *infoLog);

 

  • program: 정보를 가져올 프로그램 객체의 이름(식별자).
  • maxLength: 가져올 정보 로그의 최대 길이.
  • length: 실제로 가져온 정보 로그의 길이를 저장하는 변수.
  • infoLog: 정보 로그가 저장될 문자열 버퍼.

예를 들어, 프로그램 링크 과정에서 발생한 오류 및 경고 메시지를 확인하려면 이 함수를 사용하여 해당 정보를 가져올 수 있습니다. 가져온 정보는 infoLog 문자열에 저장되며, 실제로 가져온 문자열의 길이는 length 변수에 저장됩니다.

 

glValidateProgram

OpenGL에서 프로그램 객체를 유효성 검사하는 데 사용되는 함수입니다. 이 함수를 호출하면 OpenGL은 현재 프로그램 객체를 검사하여 현재 OpenGL 컨텍스트의 상태와 일치하는지 확인합니다.

void glValidateProgram(GLuint program);
  • program: 유효성을 검사할 프로그램 객체의 이름(식별자).

glValidateProgram을 호출하면 OpenGL은 프로그램 객체의 유효성을 검사하고, 유효성 검사 결과는 프로그램 객체의 상태에 반영됩니다. 유효성 검사가 성공적으로 완료되면 GL_TRUE를 반환하고, 그렇지 않으면 GL_FALSE를 반환합니다.

프로그램 객체를 사용하기 전에 유효성 검사를 수행하는 것은 권장되는 방법 중 하나입니다. 유효성 검사를 통해 프로그램이 현재 OpenGL 컨텍스트와 호환되는지 확인할 수 있습니다.

 
 
 

 

 

 

728x90