LearnOpenGL(8) - Light

흑빡·2026년 6월 8일

그래픽스

목록 보기
29/40
post-thumbnail

LearnOpenGL - Colors

여기 이걸보고 코드를 수정하면 되는데...
일단 빛반사와 관련된 내용이라

빛 반사는
1. 태양 백색광에서 빛이 나온다
2. 특정 물체의 표면의 색상에 따라 일부 파장은 흡수되고, 일부 파장은 반사된다

  • 빨간색 표면이면 빨간색 계열을 만드는데 필요한 파장이 반사됨
  1. 반사된 빛이 우리 눈에 보인다

이게 전부임

그냥 코드를 좀 고침

중요!!!

내가 코드를 직접 주석달고, 동작원리를 주석으로 설명해놓음
그러니 이해가 안되더라도 한번 보고 넘어가자

main.cpp

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <iostream>
#include "Shader.h"
#include "Camera.h"

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);

// 윈도우 사이즈
const uint32_t SCR_WIDTH = 1280;
const uint32_t SCR_HEIGHT = 720;

// 카메라
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = SCR_WIDTH / 2.0f;
float lastY = SCR_HEIGHT / 2.0f;
bool firstMouse = true;

// 프레임 보정
float deltaTime = 0.0f;
float lastFrame = 0.0f;

// 라이팅
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);

int main()
{
    // glfw 초기화, 설정
    // ----------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // glfw 창 생성
    // ------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "흑45", nullptr, nullptr);
    if (window == nullptr)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    // glfw 인풋 콜백 처리
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // glfw 사용자 마우스 설정
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // glad로 openGL모든 함수 포인터 로딩
    // -------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // openGL 설정
    // -----------
    glEnable(GL_DEPTH_TEST);

    // 쉐이더 프로그램 만들기
    // -------------------
    Shader normalCubeShader("VertexShaders/ColorShader.vsh", "FragmentShaders/ColorShader.fsh");
    Shader lightCubeShader("VertexShaders/LightShader.vsh", "FragmentShaders/LightShader.fsh");

    // 정점 데이터
    // ----------
    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, 
    };
    
    // VBO, cubeVAO를 만들고 생성
    uint32_t VBO, cubeVAO;
    glGenVertexArrays(1, &cubeVAO);
    glGenBuffers(1, &VBO);

    //VBO를 GL_ARRAY_BUFFER에 바인딩(상태등록)
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    //현재 gl GL_ARRAY_BUFFER에 등록된 상태인 VBO버퍼에게 사용할 정점 데이터 전달
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    //VAO를 VertexArray에 바인딩(상태등록)
    glBindVertexArray(cubeVAO);
    //현재 gl vertexArray에 등록된 상태인 cubeVAO에게
    //VBO에 전달된 정점 데이터를 어떤식으로 사용해야하는지 vertexAttribute 처리
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //현재 gl vertexArray에 등록된 상태인 cubeVAO를 사용하는 쉐이더의 0번 location으로 데이터 전달 준비
    glEnableVertexAttribArray(0);

    //lightCubeVAO를 만들고 VertexArray생성
    //VAO를 VertexArray에 바인딩(상태등록) -> cubeVAO에서 lightCubeVAO로 상태바뀜
    uint32_t lightCubeVAO;
    glGenVertexArrays(1, &lightCubeVAO);
    glBindVertexArray(lightCubeVAO);

    //이미 GL_ARRAY_BUFFER에 VBO가 바인딩 되어있긴하지만,
    //명시적으로 바인딩 체킹 하는 습관을 가집시다
    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    ////현재 gl vertexArray에 등록된 상태인 lightCubeVAO에게
    //VBO에 전달된 정점 데이터를 어떤식으로 사용해야하는지 vertexAttribute 처리
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //현재 gl vertexArray에 등록된 상태인 lightCubeVAO를 사용하는 쉐이더의 0번 location으로 데이터 전달 준비
    glEnableVertexAttribArray(0);


    // 렌더링 루프(매 프레임 실행)
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // 프레임 보정
        // ----------
        float currentFrame = static_cast<float>(glfwGetTime());
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // 인풋
        // ----
        processInput(window);

        // 드로우 시작!
        // ----------
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        
        //cubeVAO에 사용할 쉐이더인 normalCubeShader프로그램을 GL에게 쉐이더 상태 등록
        normalCubeShader.use();
        normalCubeShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
        normalCubeShader.setVec3("lightColor",  1.0f, 1.0f, 1.0f);

        // world->view로 가는 view변환행렬/view->screen으로 가는 projection 변환 행렬 만들기
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        glm::mat4 view = camera.GetViewMatrix();
        normalCubeShader.setMat4("projection", projection);
        normalCubeShader.setMat4("view", view);

        // local->world로 가는 model변환행렬 만들기
        glm::mat4 model = glm::mat4(1.0f);
        normalCubeShader.setMat4("model", model);

        //cubeVAO를 GL에게 VertexArray로 바인딩(상태등록)
        //normalCubeShader가 현재 shaderProgram등록되어 있으므로,
        //Draw를 할때, 
        //cubeVAO에 저장한 vertex Attribute를 
        //enableVertexAttribArray의 0번 등록해놨으니
        //normalCubeShader의 location = 0으로 해당 정점 속성 데이터 전달
        glBindVertexArray(cubeVAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);


        //lightCubeVAO에 사용할 쉐이더인 lightCubeShader프로그램을 GL에게 쉐이더 상태 등록
        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);

        //lightCubeVAO를 GL에게 VertexArray로 바인딩(상태등록)
        //lightCubeShader가 현재 shaderProgram등록되어 있으므로,
        //Draw를 할때, 
        //lightCubeVAO에 저장한 vertex Attribute를 
        //enableVertexAttribArray의 0번 등록해놨으니
        //lightCubeShader의 location = 0으로 해당 정점 속성 데이터 전달
        glBindVertexArray(lightCubeVAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);


        // glfw 버퍼 스와핑
        // ---------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    //안해도 되긴하는데, 명시적으로 프로그램 종료될때
    //Buffer, VertexArray를 메모리에서 삭제
    // ---------------------------------------
    glDeleteVertexArrays(1, &cubeVAO);
    glDeleteVertexArrays(1, &lightCubeVAO);
    glDeleteBuffers(1, &VBO);

    //프로그램 종료시 glfw도 끝내기!
    // ----------
    glfwTerminate();
    return 0;
}

// 키 인풋 콜백 이벤트
// -----------------
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);

    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        camera.ProcessKeyboard(LEFT, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        camera.ProcessKeyboard(RIGHT, deltaTime);
}

// glfw를 이용해 gl에게 사용할 창의 viewport크기 지정
// ---------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}


// 마우스 이동 콜백 이벤트
// --------------------
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
    float xpos = static_cast<float>(xposIn);
    float ypos = static_cast<float>(yposIn);

    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top

    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}

// 마우스 스크롤 콜백 이벤트
// ---------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    camera.ProcessMouseScroll(static_cast<float>(yoffset));
}

shader.h
ColorShader.vsh
ColorShader.fsh
LightShader.vsh
LightShader.fsh

그 외에는 위 코드 참고해서 수정!

퐁 조명

그냥 항상 퐁조명부터 시작함
PBR은 나중에 할거

ColorShader.fsv를 수정해줄거임

왜냐면 색상은 fragment에서 결정되거든ㅇㅇ

차례대로 만들어보자

ambient

아무리 어두워도 어디선가 나오는 빛 덕분에
물체는 살짝이라도 빛이남!
그걸 표현하는거임

Abient = Ambient 세기 * 입사광 색
Abient 색 = 물체 색 * Abient

이거임

그럼 glsl을 수정해보자

#version 330 core
out vec4 FragColor;
  
uniform vec3 objectColor; //물체색
uniform vec3 lightColor; //입사광

void main()
{
    float ambientStrength = 0.1f;
    vec3 ambient = ambientStrength * lightColor;
    
    vec3 ambientColor = objectColor * ambient;
    FragColor = vec4(ambientColor, 1.0);
}

EASY~~

diffuse

공식은 간단함

Diffuse=NormallightDirlightColorDiffuse = |Normal| \cdot |lightDir| * lightColor

표면 법선 벡터

diffuse는 오브젝트 전체의 기본 색상을 결정하는 거임

이 사진을 설명해봄

  1. 입사광이 있음
  2. 법선(normal)이 있음
  3. 입사광 벡터와 법선 벡터의 내적이 (90°\leq 90\degree) 1에 가까울수록 반사되는 빛의 양이 많아지고, 방향이 달라질때는 0에 더 가까워짐
    • 이때 두 벡터는 모두 normalized된 상태여야함!!

이때 법선벡터는 표면(fragment)에 대한 벡터지, 정점에 대한 normal이 아님

평면 법선벡터 구하는법

삼각형은 3개의 정점이 있음
이 중 순서에 맞춰 ab\overrightarrow{ab}ac\overrightarrow{ac}를 구해서
두 벡터의 외적을 하면 법선벡터가 나옴

근데 귀찮으니
어짜피 평면 큐브니까 간단하기도 하고 해서...

float vertices[] = 
{
    //정점 좌표               //normal벡터
    -0.5f, -0.5f, -0.5f,    0.0f,  0.0f, -1.0f,
     0.5f, -0.5f, -0.5f,    0.0f,  0.0f, -1.0f, 
     0.5f,  0.5f, -0.5f,    0.0f,  0.0f, -1.0f, 
     0.5f,  0.5f, -0.5f,    0.0f,  0.0f, -1.0f, 
    -0.5f,  0.5f, -0.5f,    0.0f,  0.0f, -1.0f, 
    -0.5f, -0.5f, -0.5f,    0.0f,  0.0f, -1.0f, 

    -0.5f, -0.5f,  0.5f,    0.0f,  0.0f, 1.0f,
     0.5f, -0.5f,  0.5f,    0.0f,  0.0f, 1.0f,
     0.5f,  0.5f,  0.5f,    0.0f,  0.0f, 1.0f,
     0.5f,  0.5f,  0.5f,    0.0f,  0.0f, 1.0f,
    -0.5f,  0.5f,  0.5f,    0.0f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,    0.0f,  0.0f, 1.0f,

    -0.5f,  0.5f,  0.5f,   -1.0f,  0.0f,  0.0f,
    -0.5f,  0.5f, -0.5f,   -1.0f,  0.0f,  0.0f,
    -0.5f, -0.5f, -0.5f,   -1.0f,  0.0f,  0.0f,
    -0.5f, -0.5f, -0.5f,   -1.0f,  0.0f,  0.0f,
    -0.5f, -0.5f,  0.5f,   -1.0f,  0.0f,  0.0f,
    -0.5f,  0.5f,  0.5f,   -1.0f,  0.0f,  0.0f,

     0.5f,  0.5f,  0.5f,    1.0f,  0.0f,  0.0f,
     0.5f,  0.5f, -0.5f,    1.0f,  0.0f,  0.0f,
     0.5f, -0.5f, -0.5f,    1.0f,  0.0f,  0.0f,
     0.5f, -0.5f, -0.5f,    1.0f,  0.0f,  0.0f,
     0.5f, -0.5f,  0.5f,    1.0f,  0.0f,  0.0f,
     0.5f,  0.5f,  0.5f,    1.0f,  0.0f,  0.0f,

    -0.5f, -0.5f, -0.5f,    0.0f, -1.0f,  0.0f,
     0.5f, -0.5f, -0.5f,    0.0f, -1.0f,  0.0f,
     0.5f, -0.5f,  0.5f,    0.0f, -1.0f,  0.0f,
     0.5f, -0.5f,  0.5f,    0.0f, -1.0f,  0.0f,
    -0.5f, -0.5f,  0.5f,    0.0f, -1.0f,  0.0f,
    -0.5f, -0.5f, -0.5f,    0.0f, -1.0f,  0.0f,
  
    -0.5f,  0.5f, -0.5f,    0.0f,  1.0f,  0.0f,
     0.5f,  0.5f, -0.5f,    0.0f,  1.0f,  0.0f,
     0.5f,  0.5f,  0.5f,    0.0f,  1.0f,  0.0f,
     0.5f,  0.5f,  0.5f,    0.0f,  1.0f,  0.0f,
    -0.5f,  0.5f,  0.5f,    0.0f,  1.0f,  0.0f,
    -0.5f,  0.5f, -0.5f,    0.0f,  1.0f,  0.0f
};

그냥 이렇게 정점을 정의하고

// VBO, cubeVAO를 만들고 생성
uint32_t VBO, cubeVAO;
glGenVertexArrays(1, &cubeVAO);
glGenBuffers(1, &VBO);

//VBO에 vertices 데이터 넘기기
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

//color cube VAO 정점 속성 및 인덱스 매핑
glBindVertexArray(cubeVAO);
//정점 속성 매핑
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//법선 속성 매핑
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);

//light cube VAO 만들기
uint32_t lightCubeVAO;
glGenVertexArrays(1, &lightCubeVAO);
glBindVertexArray(lightCubeVAO);
    
glBindBuffer(GL_ARRAY_BUFFER, VBO);

//light cube VAO 정점 속성 및 인덱스 매핑
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

그리고 color.vsh를 수정할 차례!!

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;


out vec3 Normal;

void main()
{
	Normal = aNormal;
	
	gl_Position = projection * view * model * vec4(aPos, 1.0);
}

diffuse계산

이제 vertex shader에서는 normal을 받았고 out을 하고있으니
fragment shader에서 색을 입힐 차례!!

그전에!!!

현재 fragment shader에서 fragment의 위치를 알아야
lightDir을 구할 수 있음!

lightPos - fragmentPos = lightDir

그래서 먼저 vertex shader에 out으로 fragPos를 넘겨주도록 하자

  • color.vsh
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;


out vec3 FragPos;
out vec3 Normal;

void main()
{
	FragPos = vec3(model * vec4(aPos, 1.0));
	Normal = aNormal;
	
	gl_Position = projection * view * model * vec4(aPos, 1.0);
}

사실 fragment shader로 넘겨서 처리해도 되는데
model변환을 해야해서 vertex shader에서 처리후 넘겨줌

model변환을 해주는 이유는

  • 모든 조명은 world좌표계에서 계산됨

때문임
생각해보면 local에서 어떻게 표면을 계산할지도 의문이기도 함ㅋㅋ

이제 fragment shader로 넘어가자~

먼저 out으로 Normal, FragPos를 넘기고 있으니
in으로 받아주자~

그리고 diffuse공식을 사용하자

diffuse공식은 다시한번 말하지만

  • 입사광 방향 벡터 \cdot 법선벡터
    • 모든 벡터는 정규화된 단위 벡터

이를 위해서 lightPos를 가져와야됨
따라서 uniform으로 lightPos를 가져오고, 계산~

  • main.cpp 렌더링 함수 내부
normalCubeShader.setVec3("lightPos", lightPos);
  • color.fsh
#version 330 core
out vec4 FragColor;

in vec3 Normal;
in vec3 FragPos;
  
uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 lightPos;

void main()
{
    //ambient
    float ambientStrength = 0.1f;
    vec3 ambient = ambientStrength * lightColor;
    
    //diffuse
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;
    
    //result
    vec3 color = objectColor * (ambient + diffuse);
    FragColor = vec4(color, 1.0);
}

이런 diffuse가 만들어짐 구웃~

여기서 중요한점 하나

optional. 법선 행렬(법선벡터 변환)

법선벡터는 현재 표면에 대해서만 유효함

뭔소린가 하믄...

이 사진을 봐보자

변환행렬을 통해 scale을 바꿀 수 있다는걸 지난번에 알아봤음

법선 벡터는 scale이 일정하게 늘거나 줄어들을 때,
즉 1대1 비율일때, 법선은 일정하게 유지되지만,
표면의 비율이 1대1로 scale이 변환되지 않으면 법선은 90°\degree를 유지하지 않음

이때 법선벡터를 world좌표로 변환시켜주는 함수(행렬)을
법선행렬이라고 함

이분의 수식 설명을 보고 이해해보도록 하자

식은 간단함

model행렬이 local좌표에서 world좌표로 변환시키는 행렬이라는걸 알고있을거임

그럼 model행렬의 역행렬의 전치 행렬이 법선 행렬(MN)이 되어서,
MN * Normal을 하면 어떠한 scale변화에도 normal이 유지됨

  • 역행렬 : 행렬 MM *역행렬 M1M^{-1}의 곱은 단위행렬 II
  • 전치 행렬 : 행렬 MM의 행과 열을 바꾼 행렬

이거는 쉽게 shader에서 사용할 수 있는데,,,
문제는 역행렬은 약 O(N3)O(N^3)의 시간복잡도를 가진다는거임

그래서 되도록이면 쉐이더에서 매 프레임 계산하기보다는...
가능하다면 CPU에서...

  1. model좌표가 변하지 않으면 역행렬도 유지
  2. model좌표가 변할때만 역행렬 재연산

와 같이 최적화하자~~

귀찮으니 쉐이더에서 일단 함..

  • color.vsh
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;


out vec3 FragPos;
out vec3 Normal;

void main()
{
	FragPos = vec3(model * vec4(aPos, 1.0));
	Normal = mat3(transpose(inverse(model))) * aNormal;
	
	gl_Position = projection * view * model * vec4(aPos, 1.0);
}

Normal이 바뀜~~

specular

이제 정점인
하이라이트 반사광을 추가하는거임

입사광이 표면에서 반사되었을때의 벡터와
시점(카메라) 사이의 각도를 계산해서
1과 가까울때(닮을때) 가장 많은 빛을 반사하고,
그게 아니라면 서서히 감소(falloff)를 시키면 됨

공식도 개간단함

SpecularStrength = SsS_s
view Direction = VV
reflected vector = RR
fall off = FF

(VR)FSslightColor(|V| \cdot |R|)^F * S_s * lightColor

임ㅋㅋ

world변환 VS view변환

위에서 diffuse를 우리는 world에서 수행했음
근데 view에서 하는게 더 간편함

view에서는 시점이 무조건 0,0,0으로 고정임

따라서 model + view 변환 행렬까지 사용하여
FragmentPosNormal을 계산하여 사용하는게
더 적은 자원으로 같은 효과를 낼 수 있는거지비

먼저
viewPos를 uniform을 통해 입력받음

  • color.fsv
uniform vec3 viewPos;
  • main.cpp
normalCubeShader.setVec3("viewPos", camera.Position);

그리고 이걸 통해서 specular를 계산하면됨

specular는 간단함

//specular
float specularStrength = 0.8f;
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm); 
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 256);
vec3 specular = specularStrength * spec * lightColor;

이렇게 계산이 됨

pow를 통해 지수를 늘려주면
시점을 제외한 부분에서 급격한 빛 반사 감소가 일어나서 부자연스러워 질수도 있음

적당히 알아서 원하는 값으로 고고

난 확실한 차이를 보고싶어서 256처럼 큰값으로 사용함

  • color.fsv
#version 330 core
out vec4 FragColor;

in vec3 Normal;
in vec3 FragPos;
  
uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 viewPos;

void main()
{
    //ambient
    float ambientStrength = 0.1f;
    vec3 ambient = ambientStrength * lightColor;
    
    //diffuse
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;
    
    //specular
    float specularStrength = 0.8f;
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm); 
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 256);
    vec3 specular = specularStrength * spec * lightColor;

    //result
    vec3 color = objectColor * (ambient + diffuse + specular);
    FragColor = vec4(color, 1.0);
}

개간단!!

view변환을 통해 쉐이딩

  • color.vsh
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

out vec3 FragPos;
out vec3 Normal;
out vec3 LightPos;

uniform vec3 lightPos; //view변환

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
	gl_Position = projection * view * model * vec4(aPos, 1.0);
	FragPos = vec3(view * model * vec4(aPos, 1.0));
	Normal = mat3(transpose(inverse(view * model))) * aNormal;
	LightPos = vec3(view * vec4(lightPos, 1.0)); 
}
  • color.fsh
#version 330 core
out vec4 FragColor;

in vec3 FragPos;
in vec3 Normal;
in vec3 LightPos; 

uniform vec3 lightColor;
uniform vec3 objectColor;

void main()
{
    // ambient
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;

    // diffuse 
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(LightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;

    // specular
    float specularStrength = 0.8;
    vec3 viewDir = normalize(-FragPos); //원점 - FragPos
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 256);
    vec3 specular = specularStrength * spec * lightColor;

    vec3 result = (ambient + diffuse + specular) * objectColor;
    FragColor = vec4(result, 1.0);
}

플랫 쉐이딩 vs 구로 쉐이딩 vs 퐁 쉐이딩

학부에서 그래픽스를 공부할때 플랫 쉐이딩 & 구로 쉐이딩을 공부했음

flat이나 gouraud나 phong이나 똑같음

flat shading

flat은 면 단위로 쉐이딩을 함
즉 같은 면이면 같은 색상을 칠하는거임

이렇게 로우폴리 느낌이 날때 사용하면 좋음
혹은 각진 사물이라던지 ㅇㅇ

gouraud shading

구로 쉐이딩이라고 하는데
위 플랫 쉐이딩은 면마다 색상이 결정되므로 부자연스럽다는 문제가 있음

그래서 각 메쉬의 정점단위로 색상을 결정짓기 시작함

기본적으로 메쉬는 삼각형으로 이루어져 있음

이때 각 정점을 둘러싸고 있는 면의 normal의 합의 평균의 단위벡터를 구해서 그걸로 shading하는거임

이거임

근데 정점인데 어떻게 표면에서 색상이 결정되는거지?

이게 핵심인데
geometry shader에서 자동적으로 정점 사이를 보간처리함

그래서 정점이 만든 삼각형 메쉬 사이를
vertex shader에서 정점에 쌓인 색상 데이터를 이용해
보간을 하여 모든 평면의 픽셀에 대해 색상을 결정지음

phong shading

구로 쉐이딩은 문제가 있음

  1. 마하 밴딩
    메쉬를 기본적인 단위는 삼각형임
    따라서 삼각형의 크기가 커지면, 선형보간을 통해 점, 선, 면의 색상이 결정되고,
    이로 인해 조명의 강도가 변하는 지점에서 메쉬 단위가 눈에 보이게 되는 문제
  2. 하이라이트
    위에서 살펴봤듯이 specular로 하이라이트를 표현함
    근데 정점 단위로 계산을 하게 되면,
    하이라이트가 필요한 표면에 대해서는 계산이 이뤄지지 않고, 단순히 보간을 통해서 색상만 결정되므로, 하이라이트 표현이 힘들어짐

뭐 이런저런 문제가 있어서 등장한게
phong shading임

다 똑같은데 fragment shader로 넘어온 원시 삼각형의 표면에 대해서
light shading을 하는거임

표면은 보간을 통해 자연스럽게 픽셀의 좌표가 결정된 상태이므로
하이라이트도 표면단위로 되고, 색상 계산은 표면단위로 계산되므로 조명이 어떻게 변화하든 메쉬단위가 보이지 않게 됨

profile
그래픽스 하는 퍼그

0개의 댓글