태그

레이블이 OpenGL 정리인 게시물을 표시합니다. 모든 게시물 표시
레이블이 OpenGL 정리인 게시물을 표시합니다. 모든 게시물 표시

2019년 5월 6일 월요일

3D Model Loading

버텍스, UV, 인덱스 정보를 담고있는 3D Model을 로드하여 화면에 그리고자 한다.
OBJ파일을 읽고 분류하여 데이터화 한다.

먼저, OBJ 파일을 자바의 FileReader 객체를 이용하여 Load하고, Line by Line으로 처리한다.


OBJ 파일로 3D Model Loading을 하기 위해서, Vertex, Texture(UV), Normal, Index 배열을 만든다. List를 사용하여 데이터를 구성하고 추후 Array 형태로 리턴한다.


OBJ 파일은 데이터가 Line 별로 구분되어 있으며, 각 라인의 Header에 따라 데이터를 분류할 수 있다. 각 라인의 데이터는 공백 문자로 구분되어 있으므로, 공백 문자로 Split하여 배열로 만든다.

v로 시작하는 라인은 Vertex 데이터로써, 3개의 좌표값으로 구성되어 있고 각각 x, y, z를 나타낸다.
vt는 Texture(UV)로써, 2개의 좌표값으로 구성되어 있고 각각 텍스쳐의 x, y 좌표를 나타낸다.
vn으로 시작하는 라인은 Normal 데이터로써, 3개의 좌표값으로 구성되어 있고 각각 x, y, z를 나타낸다. 각 버텍스에서의 법선 벡터를 나타낸다. 법선 데이터가 없어도 렌더링이 가능하지만, Light를 사용하는 경우 빛의 자연스러운 반사를 위해 법선 데이터가 필요하다.
f는 Face로, (v/vt/vn)의 순으로 위의 데이터의 Index를 나타낸다. Face 데이터의 경우 아래에서 분리하여 처리하기 위해 배열을 만들고 다음으로 넘긴다.



Face 데이터 또한 Space를 구분자로 사용하여 배열로 만든다.
각각의 배열은 면을 구성하는 3개의 버텍스에 대한 v, vt, vn의 인덱스를 담고 있으며, (v/vt/vn) 형태로 이루어져 있으므로 추가적으로 Slash를 구분자로 사용하여 다시 한번 배열로 분리한다.
분리한 버텍스 데이터는 processVertex() 함수를 호출하여 처리한다.



processVertex() 함수에서는 파싱한 버텍스 인덱스 데이터와 List, Array의 레퍼런스를 파라미터로 받는다.
버텍스의 인덱스 데이터는 (버텍스 인덱스, 텍스쳐 인덱스, 법선 인덱스) 순으로 전달된다.



처리할 버텍스 인덱스를 currentVertexPointer 변수에 저장한다. OBJ 파일에서의 인덱스는 1에서부터 시작하므로, 0부터 시작하는 기존 시스템에 적용하기 위해 인덱스 데이터에서 1을 뺀다. 버텍스 인덱스는 Indices List에 넣는다.


Texture(UV)를 처리할 차례이다. OBJ 파일에서 UV 값들을 파싱하여 추가했던 Textures List에서 인덱스 값을 활용하여 UV 데이터를 읽고 Vector2f 변수로 저장한다.
저장한 변수는 텍스쳐 배열에 저장하며, Serialize하게 저장하기 위해 버텍스 인덱스 포인터에 2를 곱한 위치에 x 값을, 이로부터 1 증가한 위치에 y값을 저장한다.

이 때, 1에서 y 값을 뺀 수를 배열에 저장하는데, 이는 3D Object를 만든 Blender에서 사용하는 좌표계와 OpenGL에서 사용하는 좌표계가 다르기에 이를 보정하기 위해 필요한 과정이다.
Blender는 일반적인 좌표계인 Bottom-Left 부터 시작하며 1사분면 영역으로 생각할 수 있다. OpenGL은 Top-Left 부터 시작하며, 4사분면 영역으로 생각할 수 있다. 위 좌표계는 x축 대칭이다.


위와 동일하게, 법선 데이터를 처리한다.


다시 loadObjModel() 함수로 돌아와서, 남은 Vertex, Index 배열에 데이터를 담는 작업을 진행한다. 아래와 같이 배열을 선언하고, List를 읽어 배열로 전환하여 저장한다.
배열로 전환이 완료되면 Loader의 loadToVao() 함수를 호출하여 VAO에 저장한다.

기존의 Static 데이터를 제거하고, 위에서 만든 loadObjMode()함수를 호출하여, OBJ 파일을 RawModel 객체로 변환한다. 텍스쳐를 로딩하고 Entity 객체로 만든다. Entity의 위치를 조정하고, 매 Loop 마다 Rotation의 y 값을 증가하여 y축 회전하도록 한다.

2019년 4월 29일 월요일

Obj export 및 파일 분석

이전 예제(http://blog.daechan.net/2019/04/blender-textured-cube.html) 에서 만든 Cube를 Obj 형식으로 Export 하고, Obj 파일 형식을 분석하고자 한다.

Blender로 Cube 프로젝트를 열고, 아래와 같이 File - Export - Obj 를 선택한다.

저장한 경로로 이동하여 Obj 파일을 선택하고, 텍스트 에디터로 열면 아래 그림과 같다.
본 예제에서는 Visual Studio Code를 사용하였다.

위의 Obj 파일은 아래와 같이 구성되어 있다.

  1. 주석 : Blender 버전 및 Original 파일 정보.
  2. mtllib : Material 파일 이름 (Texture)
  3. o : Object 이름
  4. v : Vertex 좌표값. (x, y, z)가 각각 space로 구분됨
  5. vt : Vertex Texture Coordinate. 0과 1 사이로 UV 좌표값을 나타낸다.
  6. vn : Vertex Normal. Vertex에 대한 Normal 좌표
  7. usemtl : Material 이름
  8. s : Smooth Shading 활성화 여부
  9. f : Face. 각 면의 정보. (v/vt/vn)의 순으로 위의 데이터의 Index를 나타낸다.

위의 Obj 구성 정보를 가지고, Cube 데이터에 적용하면 다음과 같다.

<V>
각 Vertex 좌표값을 나타낸다. -1 또는 +1 의 값으로 이루어지며 Cube의 각각의 Vertex를 의미하므로 총 8개로 구성되어 있다.

<VT>
X 좌표는 (0, 0.25, 0.5, 0.75, 1)로 나눌 수 있다. (소수점 오차가 있으나 무시한다.)
UV의 X좌표는 0에서 1 사이에서 Texture의 위치를 나타내며,
아래와 같이 가로 영역을 5개의 점으로 구분 한 만큼, 5가지의 X 좌표값이 존재하게 된다.

Y 좌표는 (0, 0.33, 0.66, 1)로 나눌 수 있고, 아래와 같이 세로 영역을 4개의 점으로 구분하였기에 4가지의 Y 좌표값이 존재하게 된다.

X 축으로는 5개의 점, Y 축으로는 4개의 점으로 나뉘어진다.
<VN>
Normal 좌표에 대한 정보로써, 각 Vertex에서의 법선 Vector 값을 가지고 있다.

<F>
각 면에 대한 정보를 가지고 있다.
정 육면체로써 6개의 Face로 구성되어 있다.
각 Face는 4개의 데이터셋으로 이루어져 있으며, v, vt, vn 의 Index를 나타낸다.
각 Index는 1부터 시작한다.
마지막 줄의 첫번째 데이터셋인 (8/13/6)은 각각 8번째의 v값, 13번째의 vt값, 6번째의 vn값을 나타내며 이를 변환하면 다음과 같다.
array의 index가 0부터 시작한다고 가정하여 각 Index에서 1을 빼주었다.

v[8 - 1] = (1.000000 1.000000 -1.000000)
vt[13 - 1] = (0.500000 0.995752)
vn[6 - 1] = (0.0000 1.0000 0.0000)

Index로 나타냄으로써 데이터의 중복을 막아 데이터 크기 감소의 효과가 있다.

예제에서 사용한 obj 파일 링크

Blender로 Textured Cube 만들기

1. Blender.org에서 Blender 다운로드 및 설치

Blender 실행 모습

블렌더를 시작하면 아래와 같이 초기 프로젝트가 로딩된다.
초기 프로젝트

모든 객체를 선택하기 위해 화면 클릭 후 A 키를 두번 누른다.
전체 선택 후 X키를 눌러 삭제 팝업을 띄우고 Delete를 선택하면 모든 객체가 삭제된다.


삭제 후 좌측 Create 탭에서 Cube를 눌러 Cube를 생성한다.


Cube가 생성되었다.

Cube의 위치를 (0, 0, 0)으로 이동하기 위해, N키를 눌러 우측 Transform 패널을 연 후 x, y, z 좌표를 모두 0으로 변경한다.

아래와 같이 좌표가 변경되었다.
이후 우측 Hierarchy 메뉴에서 Cube 옆 뒤집어진 삼각형을 눌러 버텍스 편집모드로 이동한다.

하단의 Edge select 를 눌러 Edge 선택 모드로 전환한다.

Cube를 십자가 형태로 펴기 위해 재봉선을 넣는 과정이다.
아래와 같이 Shift + 우 클릭으로 Edge를 선택한다.
잘 못 선택한 경우 A를 두번 눌러 선택을 초기화한다.

마우스 가운데 버튼을 클릭하고 드래그하면 Cube의 다른 면으로 이동할 수 있다.
하단의 Edge도 아래와 같이 선택한다.

재봉선(Seam)을 넣는 과정이다.
하단의 Mesh - Edges - Mark Seam 을 눌러 선택한 Edge에 Seam을 넣는다.

Seam은 붉은 색으로 표시된다.

이제 만든 Cube에 텍스쳐를 입히는 과정이다.
A를 두 번 눌러 모든 버텍스를 선택하고, Mesh - UV Unwrap - Unwrap을 눌러 Mesh를 UV Unwrap 한다.

상단의 Default view를 UV Editing 뷰로 전환한다.

아래와 같이 UV Unwrap 됨을 확인할 수 있다.

하단의 Image - Open Image 메뉴로 이동하여 Cube에 입힐 텍스쳐 이미지를 로드한다.
이미지 로드 후 Default 뷰에서 Mesh - UV Unwrap - Unwrap 과정을 반복하여, 버텍스들이 텍스쳐 위치를 이미지 크기에 맞게 이동하도록 한다.

아래와 같이 이미지가 로드 되었다.
하지만, 아래와 같이 UV 좌표가 잘 못 위치되어 있다.

UVs - Mirror - X Axis 메뉴를 눌러 모든 UV 좌표들에 대해 X축으로 Flip 시킨다.

결과물을 확인하기 위해 Default 뷰로 이동하여 하단의 Viewport Shading - Texture 를 선택한다.

아래와 같이 텍스쳐가 Cube에 입혀졌다.

Scroll로 Cube 내로 이동한 모습은 다음과 같다.

아래는 위의 예제에서 사용한 Cube 이미지이다.
https://en.wikipedia.org/wiki/Cube_mapping
예제에서 사용한 Blender 파일은 다음과 같다.

2019년 4월 8일 월요일

객체 정리

Vertices, Texture Coords(UV), Indices로 만든 VAO는 RawModel에 아래와 같이 저장한다.


또한, Texture는 Loader의 loadToTexture() 함수를 사용하여 ModelTexture 객체에 저장하며, ModelTexture는 int형식의 texture id를 저장한다.


ModelTexture 객체는 int형식의 texture id를 저장한다.

Object의 구성정보를 담은 RawModel과 Texture를 담은 ModelTexture를 묶어 TexturedModel 객체로 관리한다.



객체들간의 관계를 정리하면 다음과 같이 나타낼 수 있다.



Entity 객체는 Object의 구성정보 및 Texture 데이터를 담고 있는 TexturedModel과 3D 공간에서의 객체의 위치, 회전정보 및 Scale값을 담고있는 개체이다.



위의 관계를 정리하면 다음과 같이 나타낼 수 있다.


2019년 3월 25일 월요일

VAO, VBO 만들기 정리

Vertex 좌표, Texture coordinates (UV), Index를 가지고 VAO를 만들 수 있다.
VAO는 Vertex Array Object의 약자로써 Object 속성을 바인딩하여 쉽게 사용할 수 있도록 한다.
본 페이지에서는 정육면체를 기준으로 설명한다.

Vertex 좌표(position), Texture coordinates (UV), Index 데이터를 이용한 VAO의 생성은 아래 순서와 같다.

  1. VAO 생성
  2. Attribute에 Vertex 좌표, Texture coordinates 전달
  3. 버퍼에 Index 저장
  4. VAO 바인딩 해제


1. VAO 생성



GL30.glGenVertexArrays() 함수를 호출하여 VAO ID를 생성한다.
생성한 VAO는 GL30.glBindVertexArray() 함수를 호출하여 바인딩 한다.


2. Attribute에 Vertex 좌표, Texture coordinates 전달



Vertex Shader에 선언한 변수(vec3 position, vec2 textureCoords)에 데이터를 전달하기 위해 storeDataInAttributeList() 함수를 사용한다.
storeDataInAttributeList() 함수 내에서는 크게 아래와 같은 순서로 진행된다.

  1. VBO (Vertex Buffer Object) 생성
  2. VBO 바인딩
  3. 데이터 행렬을 Buffer로 변환
  4. VBO에 데이터 입력
  5. Shader의 Attribute 와 연결
  6. VBO 언바인딩


[1-2]. VBO 생성 및 바인딩
GL15.glGenBuffers() 함수를 호출하여 VBO ID를 생성한다.
GL15.glBindBuffer() 함수를 호출하여 VBO를 바인딩 할 수 있으며,
Attribute를 넣기 위해 파라미터로 GL15.GL_ARRAY_BUFFER와 VBO ID를 넣는다.

[3]. 데이터 행렬을 Buffer로 변환
데이터를 전달하기 전에 Java에서는 Buffer로 데이터를 전환하며,
storeDataInFloatBuffer() 함수를 사용한다.


[4-5]. VBO 데이터 입력 및 Attribute와 연결



GL15.glBufferData() 함수를 사용하여 생성한 데이터 버퍼를 바인딩한다.
파라미터로는 GL15.GL_ARRAY_BUFFER, Buffer, GL15.GL_STATIC_DRAW를 넣는다.
GL_ARRAY_BUFFER 는 버퍼의 타겟이며, GL_STATIC_DRAW는 버퍼의 데이터가 변하는 것인지(Dynamic) 아니면 정적인 데이터인지(Static)를 나타낸다.

버퍼 바인딩 후, GL20.glVertexAttribPointer() 함수를 사용하여 Shader의 Attribute에 연결시킨다. 파라미터로는 Shader 생성 시 Attribute name과 Bind한 Location handler, 데이터 셋의 크기, 데이터 형식, 정규화 여부, stride, offset을 입력하며 자세한 설명은 http://blog.daechan.net/2018/03/513.html 를 참조한다.

위의 과정을 통해 VBO를 생성하고 데이터를 저장한 후 Shader의 Attribute로 전달할 수 있게 되었다.

[6]. VBO 언바인딩
GL15.glBindBuffer(0)함수를 호출하여 0번 VBO를 바인딩 함으로써, 설정을 완료한 VBO를 Unbind 한다.


3. 버퍼에 Index 저장


bindIndicesBuffer() 함수를 사용하여 Index 데이터를 Buffer에 바인딩한다.
순서는 위의 storeDataInAttributeList() 함수와 동일하며,
차이점으로는 버퍼의 타겟을 GL15.GL_ELEMENT_ARRAY_BUFFER 로 설정한다.
glBufferData() 함수에 대한 Khronos 문서를 참조하면 아래와 같다. 자세한 설명은 http://blog.daechan.net/2018/03/chapter-51.html를 참조한다.

https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBufferData.xhtml

4. VAO 바인딩 해제

GL30.glBindVertexArray(0) 함수를 호출하여 0번 VAO를 Bind 함으로써 VAO 바인딩을 해제한다.
0번 VAO를 Bind할 때의 동작에 대한 설명은 Khronos 문서에 아래와 같이 설명되어 있다.

"glBindVertexArray binds the vertex array object with name arrayarray is the name of a vertex array object previously returned from a call to glGenVertexArrays, or zero to break the existing vertex array object binding."
(https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBindVertexArray.xhtml)


2019년 3월 20일 수요일

Vertex, Texture Coordinate(UV), Index 정리

Vertex 좌표, Texture Coordinates (UV), Index를 가지고 VAO를 만들 수 있다.
VAO는 Vertex Array Object의 약자로써 Object 속성을 바인딩하여 쉽게 사용할 수 있도록 한다.
본 페이지에서는 정육면체를 기준으로 설명한다.

1. Vertex


정육면체의 각 면을 구성하는 좌표들로 구성한다.
한 면의 vertex 수 (4) * 면의 수(6) = 24개의 (x, y, z) 좌표로 이루어져 있다.
처음 4개의 정점을 보면 z좌표가 -0.5로써 정육면체의 바닥을 나타냄을 알 수 있다.

바닥 면의 정점 좌표 그래프

2. Texture Coordinate (UV)


위의 각 Vertex 마다 Texture의 어느 부분에 해당하는지 나타낸다.
UV 범위는 0~1로써, U는 Texture의 X 좌표이고 V는 Texture의 Y 좌표이다.
데이터는 위의 Vertex 데이터 수와 동일하게 24개의 (u, v) 좌표로 이루어져 있다.
처음 4개의 정점은 (0, 0) - (0, 1) - (1, 1) - (1, 0)으로 바닥 면에 Texture 전체를 매핑한다.

3. Index


Index는 정육면체를 어떻게 그릴 것인지를 나타낸다.
정육면체의 각 면은 삼각형 2개로 분할하여 그리므로
면의 수(6) * 면 당 삼각형의 수(2) = 12개의 데이터셋으로 구성되며
각 데이터셋은 vertex index 3개로 구성된다.
위에서의 Vertex 수는 24개 이므로 index의 범위는 0 부터 23 까지이다.