📝 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)
📝 코드 흐름
- Assimp를 사용해 받아온 정점데이터를 VAO에 저장하여
shader.vert
의 입력 값으로 전달
- 정점셰이더의 출력 값인 정점 위치를 rasterize하여 픽셀 위치로 변환
- 변환된 픽셀 위치가
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.h
의 Program
구조체를 사용하여 셰이더 코드에 필요한 입력 값을 전달
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;
cameraPhi -= dy / h * 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;
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);
GLuint modelMatLocation = glGetUniformLocation(program.programID, "modelMat");
glUniformMatrix4fv(modelMatLocation, 1, 0, value_ptr(mat4(1)));
GLuint viewMatLocation = glGetUniformLocation(program.programID, "viewMat");
glUniformMatrix4fv(viewMatLocation, 1, 0, value_ptr(viewMat));
GLuint projMatLocation = glGetUniformLocation(program.programID, "projMat");
glUniformMatrix4fv(projMatLocation, 1, 0, value_ptr(projMat));
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);
p = projMat * viewMat * modelMat * p;
gl_Position= p;
worldPosition = vec3(modelMat * vec4(in_Position, 1));
}
3. 텍스처 적용
✔️ main.cpp
- 텍스처 이미지 읽어오기
- 텍스처 이미지 fragment shader의 입력 값으로 전달
void init() {
. . .
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);
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);
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);
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에서 출력 값
normal
과 texCoords
으로 설정
#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;
}