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 까지이다.


2019년 3월 19일 화요일

셰이더 프로그램 만들기 정리

셰이더 프로그램을 만드는 방법은 크게 아래와 같은 순서로 진행된다.

  1. Create Vertex Shader
  2. Create Fragment Shader
  3. Create Program
  4. Attach Shaders to Program
  5. Link Program
  6. Validate Program
  7. Bind Attributes
  8. Get Uniform Locations

1. Create Vertex and Fragment Shader [1-2]


loadShader() 함수를 통해 Handler 성격의 각 Shader id 가 생성된다.
loadShader() 함수 내부의 순서는 다음과 같다.
  1. Shader가 저장 된 txt 파일을 읽어 String 형식으로 변환.
  2. GL20.glCreateShader() 함수를 호출하여 Shader 생성.
    Shader type를 파라미터로 넣는다. (GL20.GL_VERTEX_SHADER, GL20.GL_FRAGMENT_SHADER)
  3. Shader ID에 위에서 불러온 Shader string을 설정(Shader source).
  4. GL20.glCompileShader() 함수로 Shader ID를 전달하여 Compile.


2. Create Program [3, 4, 5, 6]

생성한 Shader를 바탕으로 Program을 생성한다.
프로그램의 생성 순서는 다음과 같다.
  1. GL20.glCreateProgram() 함수를 호출하여, Program 생성.
  2. GL20.glAttachShader() 함수를 호출하여, Program과 생성한 Shader들을 Attach 시킨다.
  3. GL20.glLinkProgram() 함수를 호출하여, Program과 Shader들을 Link 시킨다.
  4. GL20.glValidateProgram() 함수로 Program을 검증한다.



3. Bind Attributes [7]

Shader 내의 변수를 Attribute ID로 Bind 시킨다.
예를들어, Vertex Shader 내의 "vec3 position" 변수나 "vec2 textureCoords" 변수 등에 접근하기 위해 매번 찾지 않고 Attribute ID로 빠르게 접근할 수 있다.


위와 같이 attribute id와 변수명을 bind 한다. super.bindAttribute() 함수 내에서는


GL20.glBindAttribLocation() 함수를 사용하여 Program에서 attribute id와 변수명을 연결시킨다.

4. Get Uniform Locations [8]

Shader 내의 Uniform 변수에 접근하기 위해 Handler 성격의 Integer id를 불러온다.
위와 동일하게 handler로 접근함으로써 빠른 데이터의 적재가 가능하다.


위의 그림에서와 같이 Program에서 Uniform 변수 명으로 변수 위치를 불러와 전역변수로 저장한다.


getUniformLocation() 함수 내에서는 GL20.glGetUniformLocation() 함수로 Program에서 Uniform 변수 명으로 변수 위치를 불러와 리턴한다.