Scientia Conditorium

[OpenGL 007] 쉐이더 파일 생성 및 프로그램 수정 본문

프로그래밍/컴퓨터 그래픽스

[OpenGL 007] 쉐이더 파일 생성 및 프로그램 수정

크썸 2025. 2. 15. 02:35

이전글 : https://molonlabe.tistory.com/124

 

[OpenGL 006] 그래픽스 파이프 라인 및 간단한 쉐이더 프로그램

이전글 : https://molonlabe.tistory.com/123 [OpenGL 005] Keyboard Callback 배경 색상 바꾸기이전글 : https://molonlabe.tistory.com/122 [OpenGL 004] Keyboard Callback 등록이전글 : [OpenGL003] Refresh Callback 함수 등록 [OpenGL003] Ref

molonlabe.tistory.com


 

쉐이더 프로그램 수정

이전 글에서 쉐이더 프로그램을 작성했지만 C스타일에 역슬래시까지 입력하면서 번거로운 작업을 진행했습니다. 앞으로쉐이더 프로그램을 종종 수정해야 하는데 매번 이런 식이면 불편한게 한두가지가 아닙니다. 따라서 쉐이더 전용 파일을 따로 만들어서 읽는 방식을 사용하고 나아가 쉐이더 파일을 디버깅할 수 있도록 변경해보겠습니다. 쉐이더 파일 읽어오는 코드는 가장 유명한 OpenGL 학습 사이트인 learnopengl.com 에서 가져왔습니다.

 

Shader 클래스

Shader 클래스를 따로 만들어서 쉐이더 전용 파일들을 불러와 적용하는 코드를 만들겠습니다. 앞으로 vertex shader 파일은 .vert 라 하고 fragment shader 파일은 .frag 라 칭할 것 입니다. 먼저 이전 포스팅에서 작성했던 쉐이더 소스 코드들을 파일로 따로 빼겠습니다. 그러면 아래처럼 2개의 쉐이더 파일을 생성하고 내용은 다음과 같습니다.

// Vertex Shader
#version 330 core

in vec4 vertexPos;

void main(void)
{
	gl_Position = vertexPos;
}

 

// Fragment Shader
#version 330 core

out vec4 FragColor;

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

 

앞으로 쉐이더 파일이 여러개 늘어갈 것으로 예상되니 ShaderSrc 폴더를 하나 만들고 그 안에 다시 Triangle 폴더로 하나 더 만들어놓겠습니다. 그리고 각각의 파일 이름은 Triangle.vert 와 Triangle.frag 로 지었습니다. 다음으로 Shader.h 와 Shader.cpp를 만들고 Shader 클래스를 구현하겠습니다.

 

// Shader.h

#pragma once

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

#include "GL/glew.h"

class Shader
{
public:
	Shader(const char* vertexPath, const char* fragmentPath);
	~Shader() = default;

	const void use() const;
	const GLuint getmID() const;
	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;
	
private:
	void checkCompileErrors(unsigned int shader, const std::string& type);

	GLuint mID = 0;
};

 

// Shader.cpp

#include "Shader.h"

Shader::Shader(const char* vertexPath, const char* fragmentPath)
{
	std::ifstream vShaderFile;
	std::ifstream fShaderFile;

	vShaderFile.open(vertexPath);
	fShaderFile.open(fragmentPath);
	std::stringstream vShaderStream, fShaderStream;

	vShaderStream << vShaderFile.rdbuf();
	fShaderStream << fShaderFile.rdbuf();

	vShaderFile.close();
	fShaderFile.close();

	std::string vertexCode = vShaderStream.str();
	std::string fragmentCode = fShaderStream.str();

	const char* vertesSource = vertexCode.c_str();
	const char* fragSource = fragmentCode.c_str();

	GLuint vertex = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertex, 1, &vertesSource, nullptr);
	glCompileShader(vertex);
	checkCompileErrors(vertex, "VERTEX");

	GLuint fragment = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragment, 1, &fragSource, nullptr);
	glCompileShader(fragment);
	checkCompileErrors(fragment, "FRAGMENT");

	mID = glCreateProgram();
	glAttachShader(mID, vertex);
	glAttachShader(mID, fragment);
	glLinkProgram(mID);
	checkCompileErrors(mID, "PROGRAM");

	glDeleteShader(vertex);
	glDeleteShader(fragment);
}

const void Shader::use() const
{
	glUseProgram(mID);
}

const GLuint Shader::getmID() const
{
	return mID;
}

void Shader::setBool(const std::string& name, bool value) const
{
	glUniform1d(glGetUniformLocation(mID, name.c_str()), static_cast<int>(value));
}

void Shader::setInt(const std::string& name, int value) const
{
	glUniform1i(glGetUniformLocation(mID, name.c_str()), value);
}

void Shader::setFloat(const std::string& name, float value) const
{
	glUniform1f(glGetUniformLocation(mID, name.c_str()), value);
}

void Shader::checkCompileErrors(unsigned int shader,const std::string& type)
{
	int success = 0;
	char infoLog[1024]{};
	if (type != "PROGRAM")
	{
		glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
		if (!success)
		{
			glGetShaderInfoLog(shader, 1024, nullptr, infoLog);
			std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n";
		}
	}
	else
	{
		glGetProgramiv(shader, GL_LINK_STATUS, &success);
		if (!success)
		{
			glGetProgramInfoLog(shader, 1024, nullptr, infoLog);
			std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n";
		}
	}
}

 

Shader.cpp 에 구현된 코드는 learnopengl.com의 Shaders 챕터에서 가져왔습니다. C++ filestreams 를 통해 파일을 읽어서 string 문자열로 기록한 다음 이전 장에서 만들어두었던 shader compile을 통하도록 했습니다. 우리는 이제 GLSL 파일만 별도로 작성할 수 있고 GLSL 파일만 별도로 컴파일 할 수 있습니다. 만약 GLSL 파일이 잘못되었을 경우 오류 메시지를 확인하기 위해 checkCompileErrors 함수도 필요합니다.

 

 

Triangle 클래스 수정

기존 Triangle 클래스에서 가지고 있었던 GLSL 코드가 없어졌기 때문에 일부 수정이 필요합니다. 우선 Triangle 클래스가 삼각형 객체를 그리기 위해 필요한 GLSL 파일을 호출해야 합니다. 여기서 어떤 파일을 호출할지 명시해주어야 하기 때문에 일단은 파일 이름을 멤버 변수로 넣어두겠습니다.

// 수정한 Triangle.h

#pragma once
#include "GL/glew.h"
#include "GLFW/glfw3.h"

#include "RenderObject.h"

class Triangle : public RenderObject
{
public:
	Triangle();
	~Triangle() = default;

	void update();

private:
	Shader mShader{ "./ShaderSrc/Triangle/Triangle.vert", "./ShaderSrc/Triangle/Triangle.frag" };

	GLuint mProgram = 0;
	GLfloat mVertPos[16] = {
	-0.5F, -0.5F, 0.0F, 1.0F,
	+0.5F, -0.5F, 0.0F, 1.0F,
	-0.5F, +0.5F, 0.0F, 1.0F,
	};
};

 

현재 제 코드에서 실행할 때 필요한 vertex shader와 fragment shader 파일 경로를 넣어주었습니다. 이 파일 경로는 본인이 원하는 경로로 바꾸셔도 상관없습니다. Triangle 클래스에서 별도 쉐이더 컴파일을 하지 않기 때문에 불필요한 함수들은 전부 지웠습니다. 다만 update 함수에서 일부 수정이 필요합니다.

 

// Triangle.cpp

#include "Triangle.h"

Triangle::Triangle()
{
	mShader.use();
}

void Triangle::update()
{
	GLuint loc = glGetAttribLocation(mShader.getmID(), "vertexPos");
	glEnableVertexAttribArray(loc);
	glVertexAttribPointer(loc, 4, GL_FLOAT, GL_FALSE, 0, mVertPos);

	glDrawArrays(GL_TRIANGLES, 0, 3);
	
	glFinish();
}

 

기존에는 glGetAttribLocation 함수에서 쉐이더 프로그램 id를 멤버변수로 가지고 있었습니다. 그러나 지금은 별도의 클래스에서 생성해주고 있으니 이에 맞는 프로그램 id를 가져와야 합니다. 이제 몇 가지 컴파일 오류를 수정하고 실행하면 기존과 마찬가지로 삼각형이 정상 출력되는 것을 확인할 수 있습니다.