[2023 동계 모각소] 알구자구 2주차

jungizz_·2024년 1월 21일
0

모각소

목록 보기
8/12
post-thumbnail

📝 2주차

  • shader 코드 이해
  • 마우스로 카메라 이동 적용
  • obj모델에 텍스쳐 적용

1. Shader 코드

◾셰이더

  • 화면에 출력할 픽셀의 위치와 색상을 계산하는 함수
  • 픽셀의 농담, 색조, 명암을 조합한 RGBA 색상 값 하나를 출력한다.

◾3D 그래픽 파이프라인

  • 3차원 공간에 존재하는 물체를 컴퓨터 모니터라는 2차원 평면 위에 보여주기 위해 존재한다.

1. 정점셰이더 (vertex shader)

  • 정점데이터는 3D모델(정점들의 집합)
  • 정점셰이더는 3D물체를 구성하는 정점들의 위치를 화면 좌표로 변환하는 Space Transformation(공간변환) 과정을 한다.
  • 각 정점의 공간을 변환하기 때문에 3D물체를 구성하는 정점의 수만큼 호출된다.

2. 래스터라이저

  • 정점셰이더가 출력하는 정점의 위치를 3개씩 모아 삼각형을 만들고, 그 안에 들어갈 픽셀들을 찾아낸다.

3. 픽셀셰이더 (fragment shader)

  • 화면에 출력할 최종 색상을 계산한다.
  • 래스터라이저가 찾아내는 픽셀 수 만큼 호출된다.

◾ 셰이더에서 사용할 수 있는 입력 값

  • 전역변수: 한 물체를 구성하는 모든 정점이 동일한 값을 사용할 때 전역변수가 될 수 있다 (월드행렬, 카메라의 위치 등)
  • 정점데이터: 한 물체를 구성하는 모든 정점이 동일한 값을 사용하지 않으면 정점데이터의 일부로 이 값을 받아들여야 한다. (정점의 위치, UV좌표 등)
📖 (셰이더 프로그래밍 입문-Pope kim)

📝 코드 흐름

  1. Assimp를 사용해 받아온 정점데이터를 VAO에 저장하여 shader.vert의 입력 값으로 전달
  2. 정점셰이더의 출력 값인 정점 위치를 rasterize하여 픽셀 위치로 변환
  3. 변환된 픽셀 위치가 shader.frag의 입력 값으로 사용 됨
  • +) 추가적으로 필요한 정보는 render() 과정에서 입력 값으로 전달

✔️ main.cpp

  • 버텍스 어레이 오브젝트(VAO)에 버퍼들을 저장 (0번 공간에 vertices정보를 가진 버퍼를 저장한 상태)
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, 0, 0, 0);
  • render() 함수에서 셰이더 코드를 파싱해주는 toys.hProgram 구조체를 사용하여 셰이더 코드에 필요한 입력 값을 전달
glUseProgram(program.programID);
GLuint colorLocation = glGetUniformLocation(program.programID, "color");
glUniform4fv(colorLocation, 1, value_ptr(vec4(1, 1, 1, 1)));

✔️ shader.vert

  • VAO 0번 공간에 저장된 정보를 입력 값으로 사용
#version 410 core

layout(location=0) in vec3 in_Position;

void main(void)
{
	gl_Position= vec4(in_Position, 1.0);
}

✔️ shader.frag

  • 입력 값 color를 사용해 화면에 출력할 최종 색상 out_Color 설정
#version 150 core

uniform vec4 color;

out vec4 out_Color;

void main(void)
{
	out_Color = color;
}

2. 마우스로 카메라 이동

  • 마우스를 드래그했을 때, 마우스 이동량을 사용하여 마우스 움직임에 따라 카메라 회전 각도 설정
  • 휠을 스크롤했을 때, 스크롤 정도에 따라 시야각을 조절하여 줌인/줌아웃 설정

✔️ cameraMove.hpp

#pragma once

#ifndef moveCam_h
#define moveCam_h

#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>

using namespace glm;

const float PI = 3.14159265358979f;

float cameraTheta = 0;      // 수평 회전 각도
float cameraPhi = 0;        // 수직 회전 각도
float fovy = 80 * PI / 180; // 시야각
float cameraDistance = 0.5;


void cursorPosCallback(GLFWwindow* win, double xpos, double ypos) {
    static double lastX = 0;
    static double lastY = 0;
    if (glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_1)) {
        double dx = xpos - lastX;
        double dy = ypos - lastY;
        int w, h;
        glfwGetWindowSize(win, &w, &h);
        cameraTheta -= dx / w * PI; // 마우스 x축 이동량에 PI 곱하여 라디안 단위로 변환
        cameraPhi -= dy / h * PI;   // 마우스 y축 이동량에 PI 곱하여 라디안 단위로 변환
        if (cameraPhi < -PI / 2 + 0.01) {
            cameraPhi = -PI / 2 + 0.01;
        }
        else if (cameraPhi > PI / 2 - 0.01) {
            cameraPhi = PI / 2 - 0.01;
        }
    }
    lastX = xpos;
    lastY = ypos;
}

void scrollCallback(GLFWwindow* win, double xoffset, double yoffset) {
    fovy -= yoffset / 30; // yoffset은 휠을 위로 올릴 때 양수, 아래로 내릴 때 음수
    if (fovy < 0) fovy = 0;
    else if (fovy > PI) fovy = PI;
}

#endif

✔️ main.cpp

int main(void) 
{
    if (!glfwInit()) exit(EXIT_FAILURE);
    glfwWindowHint(GLFW_SAMPLES, 8); 
    GLFWwindow* window = glfwCreateWindow(640, 480, "Hello", NULL, NULL); 
    
    // 마우스 콜백 함수
    glfwSetCursorPosCallback(window, cursorPosCallback);
    glfwSetScrollCallback(window, scrollCallback);
	
    . . .
}

void render(GLFWwindow* window) {
    int width, height;
    glfwGetFramebufferSize(window, &width, &height);
    glViewport(0, 0, width, height);
    glClearColor(0.1, 0.1, 0.1, 0);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glUseProgram(program.programID);

    vec3 initialCameraPosition = vec3(0, 0, cameraDistance);         // 초기 카메라 위치
    mat4 cameraRotationMatrix1 = rotate(cameraPhi, vec3(1, 0, 0));   // 수평 회전 행렬
    mat4 cameraRotationMatrix2 = rotate(cameraTheta, vec3(0, 1, 0)); // 수직 회전 행렬

    vec3 cameraPosition = cameraRotationMatrix2 * cameraRotationMatrix1 * vec4(initialCameraPosition, 1);

    mat4 viewMat = glm::lookAt(cameraPosition, vec3(0, 0, 0), vec3(0, 1, 0));    // 뷰행렬
    mat4 projMat = glm::perspective(fovy, width / (float)height, 0.01f, 1000.f); // 투영행렬

	// modelMat을 vertex shader의 입력 데이터로 전달
    GLuint modelMatLocation = glGetUniformLocation(program.programID, "modelMat");
    glUniformMatrix4fv(modelMatLocation, 1, 0, value_ptr(mat4(1)));

	// viweMat을 vertex shader의 입력 데이터로 전달
    GLuint viewMatLocation = glGetUniformLocation(program.programID, "viewMat");
    glUniformMatrix4fv(viewMatLocation, 1, 0, value_ptr(viewMat));

	// projMat을 vertex shader의 입력 데이터로 전달
    GLuint projMatLocation = glGetUniformLocation(program.programID, "projMat");
    glUniformMatrix4fv(projMatLocation, 1, 0, value_ptr(projMat));

	// cameraPosition을 vertex shader의 입력 데이터로 전달
    GLuint cameraPositionLocation = glGetUniformLocation(program.programID, "cameraPosition");
    glUniform3fv(cameraPositionLocation, 1, value_ptr(cameraPosition));

✔️ shader.vert

  • vertex의 position 정보를 homogeneous coordinate로 변환한 뒤, 모델/뷰/투영 행렬을 곱하여 ...~
#version 410 core

layout(location=0) in vec3 in_Position;

uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;

out vec3 worldPosition;

void main(void)
{
	vec4 p = vec4(in_Position.xyz, 1); // homogenous coord
	p = projMat * viewMat * modelMat * p;
	gl_Position= p;
    
    worldPosition = vec3(modelMat * vec4(in_Position, 1)); // modelMat을 사용해 월드좌표 계산
}


3. 텍스처 적용

✔️ main.cpp

  • 텍스처 이미지 읽어오기
  • 텍스처 이미지 fragment shader의 입력 값으로 전달
void init() {

    . . .

    // Texture
    // load diffuse map
    int w, h, n;
    void* buf = stbi_load("LPS_lambertian.jpg", &w, &h, &n, 4);
    glGenTextures(1, &diffTex);
    glBindTexture(GL_TEXTURE_2D, diffTex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf);
    stbi_image_free(buf);

    // load normal map
    buf = stbi_load("LPS_NormalMap.png", &w, &h, &n, 4);
    glGenTextures(1, &normTex);
    glBindTexture(GL_TEXTURE_2D, normTex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf);
    stbi_image_free(buf);

    // load roughness map
    buf = stbi_load("LPS_Roughness.png", &w, &h, &n, 4);
    glGenTextures(1, &roughTex);
    glBindTexture(GL_TEXTURE_2D, roughTex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf);
    stbi_image_free(buf);

    // load specularAO map
    buf = stbi_load("LPS+SpecularAO.png", &w, &h, &n, 4);
    glGenTextures(1, &specTex);
    glBindTexture(GL_TEXTURE_2D, specTex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf);
    stbi_image_free(buf);
}

void render(GLFWwindow* window) {

	. . .

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, diffTex);
    GLuint diffTexLocation = glGetUniformLocation(program.programID, "diffTex");
    glUniform1i(diffTexLocation, 0);

    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, normTex);
    GLuint normTexLocation = glGetUniformLocation(program.programID, "normTex");
    glUniform1i(normTexLocation, 1);

    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, roughTex);
    GLuint roughTexLocation = glGetUniformLocation(program.programID, "roughTex");
    glUniform1i(roughTexLocation, 2);

    glActiveTexture(GL_TEXTURE3);
    glBindTexture(GL_TEXTURE_2D, specTex);
    GLuint specTexLocation = glGetUniformLocation(program.programID, "specTex");
    glUniform1i(specTexLocation, 3);


    glBindVertexArray(vertexArray);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
    glDrawElements(GL_TRIANGLES, triangles.size()*3, GL_UNSIGNED_INT, 0);
}

✔️ shader.vert

  • Assimp를 사용해 normal과 texcoords 정보도 읽어온 뒤, vertex shader에서 출력 값normaltexCoords으로 설정
#version 410 core

layout(location=0) in vec3 in_Position;
layout(location=1) in vec3 in_Normal;
layout(location=2) in vec2 in_TexCoords;

uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;
out vec3 normal;
out vec3 worldPosition;
out vec2 texCoords;

void main(void)
{
	vec4 p = vec4(in_Position.xyz, 1);
	p = projMat * viewMat * modelMat * p;
	gl_Position= p;
    
    worldPosition = vec3(modelMat * vec4(in_Position, 1)); 
    
    normal = normalize((modelMat * vec4(in_Normal, 0)).xyz);
    texCoords = in_TexCoords;
}

✔️ shader.frag

  • vertex shader의 출력 값 = fragment shader의 입력 값
  • texCoords와 텍스쳐맵을 사용해 텍스처 적용
#version 150 core

out vec4 out_Color;
uniform sampler2D diffTex;
uniform sampler2D normTex;
uniform sampler2D roughTex;
uniform sampler2D specTex;

in vec3 normal;
in vec3 worldPosition;
in vec2 texCoords;

const float PI = 3.14159265358979f;

void main(void)
{
	vec4 c4 = texture(diffTex, texCoords);
	out_Color = c4;
}

profile
( •̀ .̫ •́ )✧

0개의 댓글