카테고리 없음

쉐이더 심화학습 06-3D 프로젝트

mazayong 2024. 3. 12. 23:56

2D가 아닌 3D 프로젝트를 구성할 때 차이를 알아보고, 기본 3D 프로젝트 세팅을 수행한다.

 

 

 

0. 2D 프로젝트와의 차이

1. 3D 프로젝트 구성

2. 카메라와 원근 투영

 

 

 

 

 

 

 

 

 

 

0. 2D 프로젝트와의 차이

= quad에 제한되는 sprite가 아닌 많은 형태 정보를 담고 있다.

= 실시간으로 빛(light)의 영향을 받는 모습을 계산해야 하기 때문에 shader이 mesh 형태 정보를 사용한다.

(라이팅 연산은 추후에 실습할 것이다.)

 

 

 

 

 

 

 

 

 

 

1. 3D 프로젝트 구성

= 처음으로 UV 좌표를 색상 데이터로 사용하여 mesh를 렌더링하는 shader를 제작할 것이다.

 

1.1. mesh 불러오기

전체적으로 프로젝트의 구성 요소들에 대해 배울 것이기 때문에 이미 구성된 mesh 파일의 데이터를 불러온다.

= 깃허브 링크의 torus.ply를 가져온다.

(복잡한 형태의 mesh를 코드에서 생성하려면 수많은 quad들로 구성되게 만들어야 하기 때문.)

 

그래서 cpp에서 아래와 같이 불러오는 함수를 작성한다.

 

 

ofApp.cpp

void ofApp::setup()
{
	ofDisableArbTex();
	ofEnableDepthTest();
	torusMesh.load("torus.ply");
	uvShader.load("passthrough.vert", "uv_vis.frag");
}

= 헤더파일에 torusMesh, uvShader를 선언한다.

= load를 통해 mesh값을 불러온다.

 

 

1.2. vertex shader 작성

= 이전에 사용한 passthrough.vert와 유사하게 만든다.

= model, view, projection 행렬들이 연산된 vertex 위치를 가져오기 때문에 mvp uniform 변수를 만들어서 선언한다.

 

++ 대부분의 게임은 cpp에서 vertex 관련 연산해야 할 행렬들을 하나로 합친 뒤 전달한다.

(그래픽 파이프라인에서 vertex마다 행렬의 곱셈을 처리하는 것이 아닌, 전체 mesh에서 한 번만 처리하기 위함.)

 

passthrough.vert

#version 410

layout (location=0) in vec3 pos;
layout (location=3) in vec2 uv;

uniform mat4 mvp;

out vec2 fragUV;

void main()
{
    gl_Position=mvp*vec4(pos, 1.0);
    fragUV=uv;
}

 

 

1.3. fragment shader 작성

= 이전에 사용한 UV 좌표를 색상값으로 출력한 shader과 같다.

 

uv_vis.frag

#version 410

in vec2 fragUV;

out vec4 outCol;

void main()
{
	outCol = vec4(fragUV, 0.0, 1.0);
}

 

 

 

1.4. draw() 작성

= 우선 다운로드한 mesh가 정상적으로 잘 작동하는지 확인하기 위해 변환 행렬을 단위행렬로 선언한 뒤 진행한다.

 

ofApp.cpp

void ofApp::draw() {
	using namespace glm;
	uvShader.begin();
	uvShader.setUniformMatrix4f("mvp", mat4());
	torusMesh.draw();
	uvShader.end();
}

 

 

 

결과는 아래와 같다.

= 이전 quad와 달리 3d mesh UV는 부드럽게 펼쳐져 있지 않다.

= 적절한 뷰, 투영 행렬이 없어서 3D처럼 보이지 않는다.

 

 

 

 

 

 

 

 

 

 

 

2. 카메라와 원근 투영

렌더링한 mesh가 3D처럼 보이게 처리를 할 것이다.

 

2.1. 카메라 생성

헤더파일에 아래와 같이 생성한다.

 

ofApp.h

CameraData cam;

 

 

 

2.2. 직교 투영으로 렌더링하기

 

2.2.1. 카메라 구조체 선언

= 헤더파일에서 카메라 구조체 데이터를 아래와 같이 선언한다.

 

ofApp.h

struct CameraData {
	glm::vec3 position;
	float rotation;
};

 

 

2.2.2. draw() 수정

= 이전 챕터에서 직교 투영 코드를 가져와서 반영했다.

 

ofApp.cpp

void ofApp::draw(){
	using namespace glm;

	cam.position = vec3(0, 0, 1);
	mat4 proj = glm::ortho(-1.33f, 1.33f, -1.0f, 1.0f, 0.0f, 10.0f);
	float aspect = 1024.0f / 768.0f;

	mat4 model = rotate(1.0f, vec3(1, 1, 1)) * scale(vec3(0.5, 0.5, 0.5));
	mat4 view = inverse(translate(cam.position));

	mat4 mvp = proj * view * model;

	uvShader.begin();
	uvShader.setUniformMatrix4f("mvp", mvp);
	torusMesh.draw();
	uvShader.end();
}

 

 

 

결과는 아래와 같다.

= 원근 효과가 없어서 카메라에 가까운 왼쪽 부분과 오른쪽 부분의 두께가 같다.

= 2D, 3D 모두에서 사용 가능하다.

= 특수한 경우일 때를 제외하고 3D 게임에서는 깊이 조건이 적용되지 않아 mesh가 어색하게 보여 잘 사용하지 않는다.

 

 

 

 

2.3. 원근 투영으로 렌더링하기

 

2.3.1. 원근 투영(perspective projection)

= 대부분 3D 게임에서 차용하는 투영 방식.

= 카메라의 프러스텀이 원평면과 근평면에 의해 잘린 피라미드 형태이다.

= 원근법이 적용되어 거리에 따른 대상의 크기가 다르게 렌더링된다.

= 카메라가 바라보는 방향으로 평행하게 뻗어 나가는 두 직선은 카메라로부터 멀어질수록 서로 가까워진다.

 

2.3.2. 구현 방법

= glm::perspective()를 사용한다.

= 인자는 시야각(field of view),  스크린 종횡비, 근평면 위치, 원평면 위치이다.

 

mat4 perspective(float fov, float aspect, float zNear, float zFar);

 

++ 시야각 : 피라미드 형태의 카메라 프러스텀 왼쪽과 오른쪽 면 간의 각도.

= 클수록 게임의 더 많은 영역을 화면에 그린다.

(pc게임에서는 일반적으로 90도 전후의 값을 차용한다.)

 

 

2.3.2.1. Camera 구조체 변경

= 시야각 옵션이 있으므로 아래와 같이 값을 변경한다.

 

ofApp.h

struct CameraData
{
	glm::vec3 pos;
	float fov;
};

 

 

2.3.2.2. draw()에서 원근 투영하기

= 카메라의 위치, 시야각을 설정한다.

= 모델, 뷰, 투영 행렬을 원하는 임의의 위치대로 설정한 뒤 변환 행렬을 만든다.

(++ 종횡비는 1024/768로 진행했다.)

 

ofApp.cpp

void ofApp::draw(){
	using namespace glm;

	cam.pos = vec3(0, 0, 1);
	cam.fov = radians(90.0f);
	float aspect = 1024.0f / 768.0f;

	mat4 model = rotate(1.0f, vec3(1, 1, 1)) * scale(vec3(0.5, 0.5, 0.5));
	mat4 view = inverse(translate(cam.pos));
	mat4 proj = perspective(cam.fov, aspect, 0.01f, 10.0f);

	mat4 mvp = proj * view * model;

	uvShader.begin();
	uvShader.setUniformMatrix4f("mvp", mvp);
	torusMesh.draw();
	uvShader.end();
}

 

 

결과는 아래와 같다.

= 카메라에 가까운 왼쪽 부분이 오른쪽 부분보다 두꺼워 보인다.

 

 

위와 같이 3D 프로젝트를 렌더링해봄으로써, 원근 투영을 적용하고 mesh를 불러와서 uv를 색상으로 출력해보았다.