본문 바로가기

프로그래밍/OpenGL

[OpenGL] 오픈지엘 GLSL 쉐이더 적용

이번 시간에는 GLSL에 대해 알아보고 프로젝트를 통해 실제 적용해보는 시간을 가지겠다.

 

01. GSLS 생성

 

GLSL(OpenGL Shader Language)이란 c언어 기반의 상위 레벨 쉐이딩 언어이다.

기본적으로 정점의 처리를 위해 벡터(vectors)와 행렬(Matrices) 자료구조를 사용하며

c언어에서 사용하는 대부분의 연산자를 지원한다.

 

 

 

우리가 GLSL을 이용하여 생성하려는 데이터는 다음과 같다.

 

● vertex.glsl: 정점처리 단계에서 사용할 세이더 / 객체의 정점 처리, 위치 설정

● fragment.glsl: 프래그먼트 단계에서 사용할 세이더 / 객체의 픽셀 색상 설정

 

그리려고 하는 객체가 삼각형이라고 할때

vertex.glsl은 삼각형의 세 꼭짓점의 위치를 결정하고

frament.glsl은 삼각형의 색상을 결정한다.

 

glsl 파일 2개 생성

 

| vertex.glsl

#version 330 core

layout (location = 0) in vec3 in_Position; 
layout (location = 1) in vec3 in_Color; 

out vec3 out_Color; 

void main(void) 
{
	gl_Position = vec4 (in_Position.x, in_Position.y, in_Position.z, 1.0);
	out_Color = in_Color;
}

 

다음 코드에서 in/out은 입력/출력변수로 CPU로 부터 데이터를 전달 받고 각 세이더의 연산결과를 전달할 때 사용한다. vertex.glsl에서 객체의 위치와 색상을 입력 값으로 받고 위치를 지정한 뒤 fragment.glsl에 색상 값을 전달한다.

 

| fragment.glsl

#version 330 core

in vec3 out_Color; 
out vec4 FragColor;

void main(void) 
{
	FragColor = vec4 (out_Color, 1.0);
}

 

fragment.glsl에서 전달받은 색상 값을 FragColor(색상+투명도) 값에 저장한 후 다음 단계로 전달한다.

 

 

 

02. 세이더 프로그램 생성

 

glsl을 생성하였으니, 세이더 프로그램을 생성하여 응용 프로그램과 세이더를 연결해야한다.

 

세이더 프로그램을 생성하는 과정은 다음과 같다.

 

세이더 프로그램 생성 과정

 

 

먼저 세이더 객체를 생성해보자.

 

char* GetBuf(const char* file)
{
	FILE* fptr;
	long length;
	char* buf;

	// 파일 읽기 형식으로 열기
	fopen_s(&fptr, file, "rb"); 

	// 예외처리
	if (!fptr) return NULL;

	// 파일 buf로 변환
	fseek(fptr, 0, SEEK_END); 
	length = ftell(fptr); 
	buf = (char*)malloc(length + 1); 
	fseek(fptr, 0, SEEK_SET);
	fread(buf, length, 1, fptr); 

	// 파일 종료
	fclose(fptr); 

	buf[length] = 0; 
	return buf; 
}

GLint CreateShader(const char* file, int type)
{
	// glsl 파일 읽기
	GLchar* source = GetBuf(file);

	// 객체 생성
	GLint shader = glCreateShader(type);
	glShaderSource(shader, 1, (const GLchar**)&source, 0);
	glCompileShader(shader);

	// 컴파일 에러 체크
	GLint result;
	GLchar errorLog[512];
	glGetShaderiv(shader, GL_COMPILE_STATUS, &result);

	if (!result)
	{
		glGetShaderInfoLog(shader, 512, NULL, errorLog);
		std::cerr << "ERROR: 컴파일 실패\n" << errorLog << std::endl;
		return 0;
	}
	
	return shader;
}

 

 

그런 다음 세이더 프로그램을 생성하면 된다.

void CreateShaderProgram()
{
	// 세이더 객체 생성
	vertexShader = CreateShader("vertex.glsl", GL_VERTEX_SHADER);
	fragmentShader = CreateShader("fragment.glsl", GL_FRAGMENT_SHADER);

	// 세이더 프로그램 생성
	shaderProgramID = glCreateProgram();
	glAttachShader(shaderProgramID, vertexShader);
	glAttachShader(shaderProgramID, fragmentShader);
	glLinkProgram(shaderProgramID);

	// 세이더 삭제
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	// 세이더 프로그램 사용
	glUseProgram(shaderProgramID);
}

 

 

| Debug

 

01. 'version' 전처리기 명령이 잘못되었습니다.

 

 

.glsl의 #version 330 core가 안 먹히는 에러이다. 이 경우 .glsl 파일을 삭제 후 [프로젝트]->[추가]->[새 항목]를 누른 뒤 .glsl 파일을 새로 생성해 주고 다시 쓰면 해결된다. 요점은 복붙해서 사용하면 안되며 새로 생성해야 오류가 나지 않는다.

 

 

02. glCreateShader 액세스 위반 

 

 

이전 프로젝트에서 세이더를 컴파일 하려고 하면 디버깅 시 액세스 위반 에러가 발생한다.

해결방법은 glewInit() 함수를 사용해 glew를 초기화 해주면 해결된다.

 

void main(int argc, char** argv)
{
	...
   
	//GLEW 초기화
	glewExperimental = GL_TRUE;
	glewInit();

	...
}

 

 

전체 코드

#include <iostream>
#include <gl/glew.h> 
#include <gl/freeglut.h>
#include <gl/freeglut_ext.h>

#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "freeglut.lib")

#define _CRT_SECURE_NO_WARNINGS

#define WIDTH 800
#define HEIGHT 600

using namespace std;

// 콜벡 함수
GLvoid Render(GLvoid);
GLvoid Reshape(int w, int h);

GLchar* vertexSource, * fragmentSource; // 소스코드 저장 변수
GLuint vertexShader, fragmentShader; // 세이더 객체
GLuint shaderProgramID; // 세이더 프로그램

GLint CreateShader(const char* file, int type);
GLvoid CreateShaderProgram();

void main(int argc, char** argv)
{
	// 윈도우 생성
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(0, 0);
	glutInitWindowSize(WIDTH, HEIGHT);
	glutCreateWindow("Shader GLSL");

	// GLEW 초기화하기
	glewExperimental = GL_TRUE;
	glewInit();
	
	// 세이더 프로그램 생성
	CreateShaderProgram();

	glutDisplayFunc(Render);
    
	glutMainLoop();
}

GLvoid Render()
{
	glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);
    
    	// 세이더 프로그램 사용
	glUseProgram(shaderProgramID);

	glutSwapBuffers();
}

GLvoid Reshape(int w, int h)
{
	// 뷰포트 기본 WIDTH HEIGHT로 설정
	glViewport(0, 0, w, h);
}

char* GetBuf(const char* file)
{
	FILE* fptr;
	long length;
	char* buf;

	// 파일 읽기 형식으로 열기
	fopen_s(&fptr, file, "rb"); 

	// 예외처리
	if (!fptr) return NULL;

	// 파일 buf로 변환
	fseek(fptr, 0, SEEK_END); 
	length = ftell(fptr); 
	buf = (char*)malloc(length + 1); 
	fseek(fptr, 0, SEEK_SET);
	fread(buf, length, 1, fptr); 

	// 파일 종료
	fclose(fptr); 

	buf[length] = 0; 
	return buf; 
}

GLint CreateShader(const char* file, int type)
{
	// glsl 파일 읽기
	GLchar* source = GetBuf(file);

	// 객체 생성
	GLint shader = glCreateShader(type);
	glShaderSource(shader, 1, (const GLchar**)&source, 0);
	glCompileShader(shader);

	// 컴파일 에러 체크
	GLint result;
	GLchar errorLog[512];
	glGetShaderiv(shader, GL_COMPILE_STATUS, &result);

	if (!result)
	{
		glGetShaderInfoLog(shader, 512, NULL, errorLog);
		std::cerr << "ERROR: 컴파일 실패\n" << errorLog << std::endl;
		return 0;
	}
	
	return shader;
}

GLvoid CreateShaderProgram()
{
	vertexShader = CreateShader("vertex.glsl", GL_VERTEX_SHADER);
	fragmentShader = CreateShader("fragment.glsl", GL_FRAGMENT_SHADER);

	// 세이더 프로그램 생성
	shaderProgramID = glCreateProgram();
	glAttachShader(shaderProgramID, vertexShader);
	glAttachShader(shaderProgramID, fragmentShader);
	glLinkProgram(shaderProgramID);

	// 세이더 삭제
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	// 세이더 프로그램 사용
	glUseProgram(shaderProgramID);
}

 

 

성공적으로 실행 성공

 

glsl 세이더 연결에 성공했으니 이제 도형을 그릴 준비가 되었다.

다음 시간에는 여러가지 도형을 실제로 그려보는 시간을 가지도록 하겠다.