uniform은 cpu side 데이터들을 gpu 상으로 옮길 수 있도록 양쪽 모두에서 액세스가 가능한 일종의 변수 역할을 해준다.
uniform은 set-per-draw 인데, 즉 Draw call 동안에는(즉 화면에 실제로 렌더링하는 도중에는) uniform을 수정할 수 없음을 의미한다.
<Basic.shader>
#shader vertex
#version 330 core
layout(location = 0) in vec4 position;
void main()
{
gl_Position = position;
}
#shader fragment
#version 330 core
layout(location = 0) out vec4 color;
// uniform 선언
uniform vec4 u_Color;
void main()
{
color = u_Color;
/*
기존에 color = vec4( 0.1f, 0.8f, ... )으로 하드코딩한 것과는 다르게, uniform을 만들어
rgba값을 셰이더 바깥, 즉 cpu side에서 입력 및 변경할 수 있도록 했다.
(참고: color는 렌더링 되는 모든 폴리곤의 색상을 한 번에 바꿔버리기 때문에
여러가지 색상을 표현할 수 없지만 여기서는 어디까지나 학습을 위해 임시로 사용중이다.)
*/
}
<cpp>
// Include GLEW
#include <GL/glew.h>
// Include GLFW
#include <GLFW/glfw3.h>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <assert.h>
#define ASSERT(x) if (!(x)) assert(false)
#define GLCall(x) GLClearError();\
x;\
ASSERT(GLCheckError())
static void GLClearError()
{
while (glGetError() != GL_NO_ERROR);
}
static bool GLCheckError()
{
while (GLenum error = glGetError())
{
std::cout << "[OpenGL Error] ";
switch(error) {
case GL_INVALID_ENUM :
std::cout << "GL_INVALID_ENUM : An unacceptable value is specified for an enumerated argument.";
break;
case GL_INVALID_VALUE :
std::cout << "GL_INVALID_OPERATION : A numeric argument is out of range.";
break;
case GL_INVALID_OPERATION :
std::cout << "GL_INVALID_OPERATION : The specified operation is not allowed in the current state.";
break;
case GL_INVALID_FRAMEBUFFER_OPERATION :
std::cout << "GL_INVALID_FRAMEBUFFER_OPERATION : The framebuffer object is not complete.";
break;
case GL_OUT_OF_MEMORY :
std::cout << "GL_OUT_OF_MEMORY : There is not enough memory left to execute the command.";
break;
case GL_STACK_UNDERFLOW :
std::cout << "GL_STACK_UNDERFLOW : An attempt has been made to perform an operation that would cause an internal stack to underflow.";
break;
case GL_STACK_OVERFLOW :
std::cout << "GL_STACK_OVERFLOW : An attempt has been made to perform an operation that would cause an internal stack to overflow.";
break;
default :
std::cout << "Unrecognized error" << error;
}
std::cout << std::endl;
return false;
}
return true;
}
struct ShaderProgramSource
{
std::string VertexSource;
std::string FragmentSource;
};
static struct ShaderProgramSource ParseShader(const std::string& filepath)
{
enum class ShaderType
{
NONE = -1, VERTEX = 0, FRAGMENT = 1
};
std::ifstream stream(filepath);
std::string line;
std::stringstream ss[2];
ShaderType type = ShaderType::NONE;
while (getline(stream, line))
{
if (line.find("#shader") != std::string::npos)
{
if (line.find("vertex") != std::string::npos)
type = ShaderType::VERTEX;
else if (line.find("fragment") != std::string::npos)
type = ShaderType::FRAGMENT;
}
else
{
ss[(int)type] << line << '\n';
}
}
return { ss[0].str(), ss[1].str() };
}
static unsigned int CompileShader(unsigned int type, const std::string& source)
{
GLCall( unsigned int id = glCreateShader(type) );
const char* src = source.c_str();
GLCall( glShaderSource(id, 1, &src, nullptr) );
GLCall( glCompileShader(id) );
// Error handling
int result;
GLCall( glGetShaderiv(id, GL_COMPILE_STATUS, &result) );
std::cout << (type == GL_VERTEX_SHADER ? "vertex" : "fragment") << " shader compile status: " << result << std::endl;
if ( result == GL_FALSE )
{
int length;
GLCall( glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length) );
char* message = (char*) alloca(length * sizeof(char));
GLCall( glGetShaderInfoLog(id, length, &length, message) );
std::cout
<< "Failed to compile "
<< (type == GL_VERTEX_SHADER ? "vertex" : "fragment")
<< "shader"
<< std::endl;
std::cout << message << std::endl;
GLCall( glDeleteShader(id) );
return 0;
}
return id;
}
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{
// create a shader program
unsigned int program = glCreateProgram();
unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertexShader);
unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);
GLCall( glAttachShader(program, vs) );
GLCall( glAttachShader(program, fs) );
GLCall( glLinkProgram(program) );
GLint program_linked;
GLCall( glGetProgramiv(program, GL_LINK_STATUS, &program_linked) );
std::cout << "Program link status: " << program_linked << std::endl;
if (program_linked != GL_TRUE)
{
GLsizei log_length = 0;
GLchar message[1024];
GLCall( glGetProgramInfoLog(program, 1024, &log_length, message) );
std::cout << "Failed to link program" << std::endl;
std::cout << message << std::endl;
}
GLCall( glValidateProgram(program) );
GLCall( glDeleteShader(vs) );
GLCall( glDeleteShader(fs) );
return program;
}
int main( void )
{
// Initialise GLFW
if( !glfwInit() )
{
fprintf( stderr, "Failed to initialize GLFW\n" );
getchar();
return -1;
}
glfwWindowHint(GLFW_SAMPLES, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Open a window and create its OpenGL context
GLFWwindow* window = glfwCreateWindow( 1024, 768, "Tutorial 02 - Red triangle", NULL, NULL);
if( window == NULL ){
fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" );
getchar();
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// Initialize GLEW
glewExperimental = true; // Needed for core profile
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to initialize GLEW\n");
getchar();
glfwTerminate();
return -1;
}
std::cout << "Using GL Version: " << glGetString(GL_VERSION) << std::endl;
// Ensure we can capture the escape key being pressed below
glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);
// Dark blue background
glClearColor(0.0f, 0.0f, 0.4f, 0.0f);
// Have to set VAO, don't know why yet
GLuint VertexArrayID;
GLCall( glGenVertexArrays(1, &VertexArrayID) );
GLCall( glBindVertexArray(VertexArrayID) );
float positions[] = {
-0.5f, -0.5f, // 0
0.5f, -0.5f, // 1
0.5f, 0.5f, // 2
-0.5f, 0.5f // 3
};
unsigned int indices[] = {
0, 1, 2,
2, 3, 0
};
// Create buffer and copy data
unsigned int buffer;
GLCall( glGenBuffers(1, &buffer) );
GLCall( glBindBuffer(GL_ARRAY_BUFFER, buffer) );
GLCall( glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(float), positions, GL_STATIC_DRAW) );
// define vertex layout
GLCall( glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0) );
GLCall( glEnableVertexAttribArray(0) );
// Create index buffer
unsigned int ibo;
GLCall( glGenBuffers(1, &ibo) );
GLCall( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo) );
GLCall( glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW) );
ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");
std::cout << "VERTEX" << std::endl << source.VertexSource << std::endl;
std::cout << "FRAGMENT" << std::endl << source.FragmentSource << std::endl;
unsigned int shader = CreateShader(source.VertexSource, source.FragmentSource);
GLCall( glUseProgram(shader) );
/*
OpenGL에서 셰이더 내에서 uniform을 선언하면 그 uniform의 id가 자동으로 할당된다.
최신 버전의 OpenGL에서는 이 id를 특정 값으로 지정해주는 것도 가능하다.
셰이더 바깥에서, 즉 cpu side 코드에서 이 uniform에 접근하려면 이 id값을 사용해야 하는데,
어떤 unform의 id는 glGetUniformLocation(셰이더, uniform 변수명)으로 얻을 수 있다.
*/
GLCall( unsigned int u_Color = glGetUniformLocation(shader, "u_Color") );
ASSERT(u_Color != -1); // 위에서 uniform을 셰이더에서 찾지 못했을 때 -1을 반환한다.
// 주의: uniform이 셰이더에서 선언되었다고 하더라도 이 uniform을 셰이더 내에서 사용하지 않는다면
// 컴파일러가 알아서 그 uniform을 없애버릴 수 있고, 그렇게 없어지면 찾지 못해 -1을 반환할 수 있다.
/*
아래 코드 덩어리는 이 uniform의 Red값을 매 draw call 마다 변화시켜주어
화면 상의 모든 폴리곤의 색상이 프레임마다 변하는 효과를 주게 만드는 부분이다.
여기에 편의를 위해 ESC키를 눌렀을 경우 작동을 중지하는 기능을 추가했다.
여기서 주의할 점은 우리는 지금 모든 폴리곤의 색을 한번에 바꾸고 있으므로(fragment 셰이더의 color)
각각의 폴리곤의 색상을 현재로써는 개별적으로 지정할 수 없다는 점이다.
그러나 vertex attributes를 사용하면 개별적으로 값을 지정하는게 가능한데, 이에 대해서는 나중에 다뤄보겠다.
*/
float red = 0.0f;
float step = 0.05f;
do{
// Clear the screen
GLCall( glClear( GL_COLOR_BUFFER_BIT );
// uniform 색상 변경
GLCall( glUniform4f(u_Color, red, 0.3, 0.8, 1.0) );
// Draw the triangle !
GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr)) );
// Swap buffers
glfwSwapBuffers(window);
glfwPollEvents();
// Red 값 증가 감소 반복
if (red < 0.0f || red > 1.0f)
step *= -1.0;
red += step;
} // Check if the ESC key was pressed or the window was closed
while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS &&
glfwWindowShouldClose(window) == 0 );
// Cleanup VBO
GLCall( glDeleteBuffers(1, &buffer) );
GLCall( glDeleteVertexArrays(1, &VertexArrayID) );
GLCall( glDeleteProgram(shader) );
// Close OpenGL window and terminate GLFW
glfwTerminate();
return 0;
}
'computer graphics > OpenGL' 카테고리의 다른 글
[OpenGL] 8. 추상화(Abstraction)와 렌더러 (0) | 2022.04.02 |
---|---|
[OpenGL] 7. Vertex Array Object (VAO) (0) | 2022.03.01 |
[OpenGL] 5. Error Handling (1) (0) | 2022.02.24 |
[OpenGL] 4. Index Buffer (0) | 2022.02.19 |
[OpenGL] 3. Shader (0) | 2022.02.15 |