본문 바로가기

카테고리 없음

[OpenGL 공부] Colors

학습을 위해 참조한 사이트

https://learnopengl.com/Lighting/Colors

 

LearnOpenGL - Colors

Colors Lighting/Colors We briefly used and manipulated colors in the previous chapters, but never defined them properly. Here we'll discuss what colors are and start building the scene for the upcoming Lighting chapters. In the real world, colors can take

learnopengl.com

 

 

빛과 색상

반사되는 가시광 영역에 의해 물체의 색을 인식

 

현실에서 물체는 실제로 해당 색상을 가지고 있는 것이 아니라, 빛을 얼마나 흡수하고 반사할 것인지에 따라 결정됨을 나타낸다.

 

사람의 눈은 가시광선을 파장에 따라 붉은 계열, 푸른 계열, 녹색 계열로 빛을 받아들이는데, 이를 분석하고 혼합하는 방식으로 색을 인식하게 된다.


RGB

빛의 삼원색

 

빛의 삼원색을 이용하여 색을 표현하는 방식으로 빨강, 파랑, 초록 세 종류의 광원을 사용하여 나타내는 체계이다.

가산 혼합 방식을 통해 색을 섞을수록 흰색에 가까워지고(모든 빛을 반사), 혼합하는 색의 양이 적을수록 검은색에 가까워지도록 하여(모든 빛을 흡수) 색상 뿐만 아니라 밝기도 표현할 수 있다.

R,G,B 세가지의 축으로 좌표화

 

이를 3차원 좌표계로 옮겨 표현하면 빛의 물리적인 양을 수치화하여 나타낼 수 있고, 벡터로 나타냄으로써 조절에도 용이함을 알 수 있다. 또한 벡터 연산도 그대로 적용할 수 있다.

 

컬러값의 범위는 정수형 또는 부동소수점형으로 나타낼 수 있다.

정수형의 경우 각 색상마다 8bit를 사용하여 0~255의 범위를 가지며,

256 * 256 * 256 = 16,777,216 개의 색을 나타낼 수 있다.

부동 소수점을 사용할 경우 0.0 ~ 1.0으로 표현이 가능하고 색을 좀 더 세밀하게 표현할 수 있으며,

소수의 특성 상 무수히 많은 색을 나타낼 수 있다.

 


광원에 따른 물체의 색상

빛의 반사율은 곧 나타낼 색상의 비율을 나타내므로, OpenGL에서 어떤 물체의 색상 속성을 지정할 땐, 해당 색상을 이루는 비율을 벡터로 나타내면 된다. 산호색의 경우 아래 코드와 같이 지정할 수 있다.

glm::vec3 lightColor(1.0f, 1.0f, 1.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);		// Color : coral

lightColor는 광원의 색상으로, 광원을 흰색으로 가정한 경우 모든 가시광선을 합치면 흰색이 되므로, R, G, B의 값이 모두 1.0으로 설정되었다.

toyColor는 색상의 요소들을 어느 비율만큼 반사할 것인지 나타내었으며, 산호색을 띄도록 반사 수치가 설정되어있다.

따라서 두 값을 곱하는 것으로, 물체의 색상을 구할 수가 있게 된다.

 

 

만약 광원이 다음과 같은 수치를 가질 경우,

glm::vec3 lightColor(0.0f, 1.0f, 0.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);

광원은 강한 초록색의 빛을 가지게 된다.

 

물체의 색상을 결정하기 위해서 lightColor와 toyColor를 곱해보면, 다음과 같은 수치를 얻을 수 있다.

glm::vec3 result = lightColor * toyColor; // = (0.0f, 0.5f, 0.0f);

이는 toy 물체가 빨간색의 빛을 모두 반사하지만, 광원은 초록색의 빛만 가지기 때문에 반사할 빛이 없기 때문에 해당 색상을 갖지 못하게 된다. 동일한 이유로 파란색 또한 가지지 못하며, 유일하게 초록색의 빛만 반사하게 되어 물체는 짙은 녹색을 띄게 된다.

 

아래 실행 결과는 위의 수치를 직접 반영하여 나타낸 것이다.

조명 색상에 따른 물체의 색상 변화


조명 추가하기

위 결과와 같이 조명과 조명을 받는 물체를 나타내기 위해선 서로 독립적인 객체가 영향을 주고 받아야 한다.

어떤 객체를 그리기 위해 정점 정보와 속성 정보를 저장할 수 있는 VAO 객체를 생성하였고, 이와 마찬가지로 조명 객체를 이루는 정점과 속성을 저장하기 위해 조명만의 VAO를 생성한다. 조명의 VAO를 따로 생성하는 이유는 Cube의 VAO의 정점 데이터와 속성 포인터가 변경될 때, 조명 객체인 Light cube로 전파되는 것을 원하지 않기 때문이다.

 

OpenGL 그래픽스 파이프라인, VAO와 VBO

 

[OpenGL 공부] Hello Triangle

학습을 위해 참고한 사이트 https://learnopengl.com/Getting-started/Hello-Triangle Vertex Shader : 3D 좌표를 다른 3D 좌표로 변환 Geometry Shader(선택적) : 도형을 이루는 정점 컬렉션에서 새로운 정점을 형성하여 다

cotten-candev.tistory.com

파이프라인의 첫번째 단계인 정점 셰이더를 위해, 입력으로 들어갈 정점 데이터가 어떻게 해석되는지 명시해주어야 할 필요가 있다.

아래의 코드의 역할은 크게 세 가지로 다음과 같다.

1. GPU에 메모리 생성

2. OpenGL이 메모리를 해석하는 방법 구성

3. 데이터를 GPU로 보내는 방법 지정

이 과정을 거친 후 GPU는 정점 셰이더에 따라 정점을 처리하게 된다.

더보기

사용된 정점 데이터

float vertices[] = {
    -0.5f, -0.5f, -0.5f,
     0.5f, -0.5f, -0.5f,
     0.5f,  0.5f, -0.5f,
     0.5f,  0.5f, -0.5f,
    -0.5f,  0.5f, -0.5f,
    -0.5f, -0.5f, -0.5f,

    -0.5f, -0.5f,  0.5f,
     0.5f, -0.5f,  0.5f,
     0.5f,  0.5f,  0.5f,
     0.5f,  0.5f,  0.5f,
    -0.5f,  0.5f,  0.5f,
    -0.5f, -0.5f,  0.5f,

    -0.5f,  0.5f,  0.5f,
    -0.5f,  0.5f, -0.5f,
    -0.5f, -0.5f, -0.5f,
    -0.5f, -0.5f, -0.5f,
    -0.5f, -0.5f,  0.5f,
    -0.5f,  0.5f,  0.5f,

     0.5f,  0.5f,  0.5f,
     0.5f,  0.5f, -0.5f,
     0.5f, -0.5f, -0.5f,
     0.5f, -0.5f, -0.5f,
     0.5f, -0.5f,  0.5f,
     0.5f,  0.5f,  0.5f,

    -0.5f, -0.5f, -0.5f,
     0.5f, -0.5f, -0.5f,
     0.5f, -0.5f,  0.5f,
     0.5f, -0.5f,  0.5f,
    -0.5f, -0.5f,  0.5f,
    -0.5f, -0.5f, -0.5f,

    -0.5f,  0.5f, -0.5f,
     0.5f,  0.5f, -0.5f,
     0.5f,  0.5f,  0.5f,
     0.5f,  0.5f,  0.5f,
    -0.5f,  0.5f,  0.5f,
    -0.5f,  0.5f, -0.5f,
};
// 1. 빛을 받는 객체 cube
unsigned int VBO, cubeVAO;		
glGenVertexArrays(1, &cubeVAO);
glGenBuffers(1, &VBO);

glBindBuffer(GL_ARRAY_BUFFER, VBO);
// VBO에 저장된 정점 속성을 메모리에 복사
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glBindVertexArray(cubeVAO);

// 정점 속성(position) 처리 방법 명시
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);	// 속성 활성화

// 2. 조명 객체 lightCube
unsigned int lightCubeVAO;			// 조명에 사용될 VAO
glGenVertexArrays(1, &lightCubeVAO);
glBindVertexArray(lightCubeVAO);

glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 조명 역시 cube와 같은 정점을(정육면체) 사용할 것이므로, 동일한 VBO를 바인딩
// 앞선 코드에서 VBO의 정점 데이터를 복사하였으므로, glBufferData를 호출할 필요 x
//glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

 

셰이더

Shader 클래스에는 정점 셰이더, 프래그먼트 셰이더 객체를 생성하고 컴파일하여 셰이더 프로그램을 만드는 과정이 포함되어 있으며, 이후 렌더링 과정에서 glUseProgram 함수를 호출하여 셰이더 프로그램을 설치한다.

lightingShader와 lightCubeShader 객체는 각각 물체(Cube)와 광원 물체(Light cube)에 적용될 셰이더 코드를 읽어들인다.

Shader lightingShader("shaders/vertShader.glsl", "shaders/fragShader.glsl");
Shader lightCubeShader("shaders/lightCubevShader.glsl", "shaders/lightCubefShader.glsl");

 

 

 

 

 

정점 셰이더

아직 어떠한 lighting 효과도 적용하지 않았으므로, Cube와 Light cube 모두 동일한 정점 셰이더가 사용된다.

#version 330 core
// 지정자 키워드 layout은 0번에 해당하는 속성(정점 위치)을 가져와 aPos 변수에 저장하도록 한다.
layout (location = 0) in vec3 aPos;

// 정점들을 클립 공간으로 옮기기 위해 model, view, projection 행렬 변수를 선언한다.
// 이때 각 변수는 uniform으로 선언되어 있어, 셰이더와 연결된 프로그램에서 데이터를 전달받는다.
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
	// 정점 셰이더가 실행됨에 따라 각 정점들에 변환 행렬이 가해져 클립 좌표로 변환된다.
	gl_Position = projection * view * model * vec4(aPos, 1.0);
}

 

 

프래그먼트 셰이더

최종 출력 색상을 결정하는 프래그먼트 셰이더는 다음과 같다.

/* Cube */
#version 330 core
out vec4 FragColor;
  
uniform vec3 objectColor;
uniform vec3 lightColor;

void main()
{
    FragColor = vec4(lightColor * objectColor, 1.0);
}

빛을 받는 물체인 Cube의 경우, 애플리케이션으로부터 물체의 색상과 빛의 색상 정보를 넘겨받아 곱하는 것으로 결정된다.

 

/* Light cube */
#version 330 core
out vec4 FragColor;

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

광원인 Light cube는 발광체로 주위 물체의 영향을 받지 않는다. 따라서 상수로 흰색을 띄는 값으로 결정된다.

 

렌더링

glUseProgram()이 호출되면 현재 렌더링 상태에서(매 프레임 마다) 컴파일과 링크 과정을 마친 셰이더 프로그램의 실행 파일이 메인 프로그램 객체에 추가되고, 정점 처리 시 실행되어 렌더링된다.

렌더링은 매 프레임마다 반복되므로, glUseProgram() 또한 렌더링 과정에서 수행되어야 한다.

또한 셰이더에 정의된 모든 Uniform 변수는 glUseProgram()이 실행되어 셰이더 프로그램을 사용할 수 있을 때에만 값을 지정해줄 수 있다.

while (!glfwWindowShouldClose(window)) {

    float currentFrame = static_cast<float>(glfwGetTime());
    deltaTime = currentFrame - lastFrame;		// frame 당 간격
    lastFrame = currentFrame;

    // 입력 감지
    processInput(window);

    // 렌더링 명령 ...
    glEnable(GL_DEPTH_TEST);
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    // GL_DEPTH_BUFFER_BIT -> 렌더링이 반복될 때마다 이전 깊이 버퍼의 값을 제거
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    lightingShader.use();		// glUseProgram() 실행
    lightingShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
    lightingShader.setVec3("lightColor", 0.0f, 1.0f, 0.0f);

    // view/projection transformations
    glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
    glm::mat4 view = camera.GetViewMatrix();
    lightingShader.setMat4("projection", projection);
    lightingShader.setMat4("view", view);

    // world transformation
    glm::mat4 model = glm::mat4(1.0f);
    lightingShader.setMat4("model", model);

    // render the cube
    glBindVertexArray(cubeVAO);
    glDrawArrays(GL_TRIANGLES, 0, 36);

    // also draw the lamp object
    lightCubeShader.use();
    lightCubeShader.setMat4("projection", projection);
    lightCubeShader.setMat4("view", view);
    model = glm::mat4(1.0f);
    model = glm::translate(model, lightPos);
    model = glm::scale(model, glm::vec3(0.2f)); // a smaller cube
    lightCubeShader.setMat4("model", model);

    glBindVertexArray(lightCubeVAO);
    glDrawArrays(GL_TRIANGLES, 0, 36);