
Initialize
//! ObjLoader 클래스 생성자
ObjLoader::ObjLoader(Renderer* parent)
{
// 부모 렌더러 객체가 없는 경우, 함수 종료
if (!parent)
return;
// 1. 렌더러 관련 객체 설정
RendererHandler = parent;
ProgramManagerObj = parent->RendererProgramManager();
TransformObj = parent->RendererTransform();
modelType = ObjFileType;
// 2. 깊이 테스트 활성화
glEnable(GL_DEPTH_TEST);
// 3. 텍스처 이미지 로드 (컬러 텍스처)
char fname[MAX_PATH] = {""};
strcpy(fname, "/storage/emulated/0/Android/data/cookbook.opengles_7_5/Images/");
strcat(fname, "earthcolor.png"); // 사용할 텍스처 파일명 설정
image = new PngImage();
image->loadImage(fname);
// 4. 텍스처 이미지 로드 (노멀 맵 텍스처)
memset(fname, 0, MAX_PATH);
strcpy(fname, "/storage/emulated/0/Android/data/cookbook.opengles_7_5/Images/");
strcat(fname, "earthnormal.png"); // 사용할 노멀 맵 파일명 설정
imageNormal = new PngImage();
imageNormal->loadImage(fname);
// 5. OBJ 모델 로드
LoadMesh();
}
earthcolor.png 와 erathnormal.png 두개의 텍스처를 생성한다.
loadImage 함수는 아래 챕터 참고
https://graphicsimon.tistory.com/117
//! OBJ 파일에서 메쉬 데이터를 로드하는 함수
void ObjLoader::LoadMesh()
{
// 1. OBJ 모델 파일 경로 설정
char fname[MAX_PATH] = {""};
strcpy(fname, "/storage/emulated/0/Android/data/cookbook.opengles_7_5/files/");
strcat(fname, ModelNames[ModelNumber]); // 모델 이름 추가
// 2. WaveFront OBJ 모델 파싱 및 메쉬 데이터 로드
objMeshModel = waveFrontObjectModel.ParseObjModel(fname);
IndexCount = waveFrontObjectModel.IndexTotal();
// 3. 정점 속성의 메모리 오프셋 및 스트라이드 설정
stride = (2 * sizeof(vec3)) + sizeof(vec2) + sizeof(vec4);
offsetNormal = (GLvoid*)(sizeof(glm::vec3) + sizeof(vec2)); // 노멀 데이터 오프셋
offsetTexCoord = (GLvoid*)(sizeof(glm::vec3)); // 텍스처 좌표 오프셋
offsetTangent = (GLvoid*)(sizeof(glm::vec3) + sizeof(vec2) + sizeof(vec3)); // 탄젠트 오프셋
// 4. 정점 버퍼(VBO) 생성 및 데이터 업로드
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, objMeshModel->vertices.size() * sizeof(objMeshModel->vertices[0]),
&objMeshModel->vertices[0], GL_STATIC_DRAW);
// 5. 정점 배열 객체(VAO) 생성 및 설정
glGenVertexArrays(1, &OBJ_VAO_Id);
glBindVertexArray(OBJ_VAO_Id);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
// 6. 정점 속성 활성화 및 속성 포인터 설정
glEnableVertexAttribArray(VERTEX_POSITION);
glEnableVertexAttribArray(TEX_COORD);
glEnableVertexAttribArray(NORMAL_POSITION);
glEnableVertexAttribArray(TANGENTS);
glVertexAttribPointer(VERTEX_POSITION, 3, GL_FLOAT, GL_FALSE, stride, 0); // 정점 위치
glVertexAttribPointer(TEX_COORD, 2, GL_FLOAT, GL_FALSE, stride, offsetTexCoord); // 텍스처 좌표
glVertexAttribPointer(NORMAL_POSITION, 3, GL_FLOAT, GL_FALSE, stride, offsetNormal); // 노멀 벡터
glVertexAttribPointer(TANGENTS, 4, GL_FLOAT, GL_FALSE, stride, offsetTangent); // 탄젠트 벡터
// 7. VAO 바인딩 해제
glBindVertexArray(0);
// 8. 메쉬 데이터 메모리 해제
objMeshModel->vertices.clear();
}
버텍스 구성 요소

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)0); // 정점 위치
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, offsetTexCoord); // 텍스처 좌표
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, stride, offsetNormal); // 노멀
glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, stride, offsetTangent); // 탄젠트
- **스트라이드(Stride)**는 하나의 정점 데이터의 전체 크기입니다.
- **오프셋(Offset)**은 각 속성의 메모리 시작 위치입니다.
- 올바른 오프셋과 스트라이드를 설정하면, OpenGL이 정점 데이터를 올바르게 해석할 수 있습니다.
//! OBJ 모델 초기화 함수
void ObjLoader::InitModel()
{
// 1. 렌더링 관련 객체 가져오기
ProgramManager* ProgramManagerObj = RendererHandler->RendererProgramManager();
Transform* TransformObj = RendererHandler->RendererTransform();
// 2. 셰이더 프로그램 초기화
if (!(program = ProgramManagerObj->Program((char*)"Grid"))) {
program = ProgramManagerObj->ProgramInit((char*)"Grid");
ProgramManagerObj->AddProgram(program);
}
// 3. 셰이더 로드 및 컴파일
program->VertexShader = ShaderManager::ShaderInit(VERTEX_SHADER_PRG, GL_VERTEX_SHADER);
program->FragmentShader = ShaderManager::ShaderInit(FRAGMENT_SHADER_PRG, GL_FRAGMENT_SHADER);
// 4. 정점 셰이더 컴파일
CACHE* m = reserveCache(VERTEX_SHADER_PRG, true);
if (m) {
if (!ShaderManager::ShaderCompile(program->VertexShader, (char*)m->buffer, 1)) exit(1);
freeCache(m);
}
// 5. 프래그먼트 셰이더 컴파일
m = reserveCache(FRAGMENT_SHADER_PRG, true);
if (m) {
if (!ShaderManager::ShaderCompile(program->FragmentShader, (char*)m->buffer, 1)) exit(2);
freeCache(m);
}
// 6. 셰이더 프로그램 링크
if (!ProgramManagerObj->ProgramLink(program, 1)) exit(3);
// 7. 셰이더 프로그램 사용
glUseProgram(program->ProgramID);
// 8. 머티리얼(Material) 유니폼 설정
char MaterialAmbient = ProgramManagerObj->ProgramGetUniformLocation(program, (char*)"MaterialAmbient");
char MaterialSpecular = ProgramManagerObj->ProgramGetUniformLocation(program, (char*)"MaterialSpecular");
char MaterialDiffuse = ProgramManagerObj->ProgramGetUniformLocation(program, (char*)"MaterialDiffuse");
if (MaterialAmbient >= 0) {
glUniform3f(MaterialAmbient, 0.10f, 0.10f, 0.10f);
}
if (MaterialSpecular >= 0) {
glUniform3f(MaterialSpecular, 0.3f, 0.3f, 0.3f);
}
glm::vec3 color = glm::vec3(1.0, 1.0, 1.0);
if (MaterialDiffuse >= 0) {
glUniform3f(MaterialDiffuse, color.r, color.g, color.b);
}
// 9. 조명(Light) 유니폼 설정
char LightAmbient = ProgramManagerObj->ProgramGetUniformLocation(program, (char*)"LightAmbient");
char LightSpecular = ProgramManagerObj->ProgramGetUniformLocation(program, (char*)"LightSpecular");
char LightDiffuse = ProgramManagerObj->ProgramGetUniformLocation(program, (char*)"LightDiffuse");
char LightPosition = ProgramManagerObj->ProgramGetUniformLocation(program, (char*)"LightPosition");
if (LightAmbient >= 0) {
glUniform3f(LightAmbient, 1.0f, 1.0f, 1.0f);
}
if (LightSpecular >= 0) {
glUniform3f(LightSpecular, 1.0f, 1.0f, 1.0f);
}
if (LightDiffuse >= 0) {
glUniform3f(LightDiffuse, 1.0f, 1.0f, 1.0f);
}
if (LightPosition >= 0) {
glm::vec3 lightPosition(0.0, 10.0, 10.0);
glUniform3fv(LightPosition, 1, (float*)&lightPosition);
}
// 10. 머티리얼의 반짝임(Shininess) 설정
char ShininessFactor = ProgramManagerObj->ProgramGetUniformLocation(program, (char*)"ShininessFactor");
if (ShininessFactor >= 0) {
glUniform1f(ShininessFactor, 2.0);
}
// 11. 행렬 유니폼 설정
MVP = ProgramManagerObj->ProgramGetUniformLocation(program, (char*)"ModelViewProjectionMatrix");
MV = ProgramManagerObj->ProgramGetUniformLocation(program, (char*)"ModelViewMatrix");
NormalMatrix = ProgramManagerObj->ProgramGetUniformLocation(program, (char*)"NormalMatrix");
// 12. 컬러 텍스처 설정
TEX = ProgramManagerObj->ProgramGetUniformLocation(program, (char*)"ImageTexture");
glActiveTexture(GL_TEXTURE0);
if (image) {
glBindTexture(GL_TEXTURE_2D, image->getTextureID());
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
// 13. 노멀 맵 텍스처 설정
TEX_NORMAL = ProgramManagerObj->ProgramGetUniformLocation(program, (char*)"ImageTextureNormal");
glActiveTexture(GL_TEXTURE1);
if (imageNormal) {
glBindTexture(GL_TEXTURE_2D, imageNormal->getTextureID());
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
}
모델 뷰, 모델 뷰 프로젝션, 퐁라이팅 설정, 컬러 텍스처, 노멀맵 텍스처 설정
Renderer
//! OBJ 모델을 렌더링하는 함수
void ObjLoader::Render()
{
// 1. 백페이스 컬링 비활성화 및 블렌딩 활성화
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // 알파 블렌딩 (투명도 적용)
// 2. 셰이더 프로그램 사용
glUseProgram(program->ProgramID);
// 3. 텍스처 유닛 설정
glUniform1i(TEX, 0); // 컬러 텍스처를 GL_TEXTURE0에 바인드
glUniform1i(TEX_NORMAL, 1); // 노멀 맵 텍스처를 GL_TEXTURE1에 바인드
// 4. 모델 변환 적용 (회전 변환)
TransformObj->TransformPushMatrix(); // 현재 변환 행렬 저장
static float rot = 0.0;
TransformObj->TransformRotate(rot++, 0.0, 1.0, 0.0); // Y축 회전 (애니메이션)
TransformObj->TransformRotate(180.0, 0.0, 0.0, 1.0); // Z축 회전 (모델 뒤집기)
// 5. MVP 행렬 설정 (모델-뷰-프로젝션 행렬)
glUniformMatrix4fv(MVP, 1, GL_FALSE, (float*)TransformObj->TransformGetModelViewProjectionMatrix());
// 6. MV 행렬 설정 (모델-뷰 행렬)
glUniformMatrix4fv(MV, 1, GL_FALSE, (float*)TransformObj->TransformGetModelViewMatrix());
// 7. 노멀 행렬 계산 및 설정
glm::mat4 matrix = *(TransformObj->TransformGetModelViewMatrix());
glm::mat3 normalMat = glm::mat3(glm::vec3(matrix[0]), glm::vec3(matrix[1]), glm::vec3(matrix[2]));
glUniformMatrix3fv(NormalMatrix, 1, GL_FALSE, (float*)&normalMat);
// 8. 변환 행렬 복원
TransformObj->TransformPopMatrix();
// 9. VAO 바인딩 및 모델 렌더링
glBindVertexArray(OBJ_VAO_Id);
glDrawArrays(GL_TRIANGLES, 0, IndexCount);
// 10. VAO 바인딩 해제
glBindVertexArray(0);
}
VertexShader
#version 300 es
precision mediump float;
// 1. 정점 정보 입력
layout(location = 0) in vec4 VertexPosition; // 정점 위치
layout(location = 1) in vec3 Normal; // 노멀 벡터
layout(location = 2) in vec2 TexCoords; // 텍스처 좌표
layout(location = 3) in vec4 VertexTangent; // 탄젠트 벡터
// 2. 유니폼 변수 (행렬 및 조명 정보)
uniform mat4 ModelViewProjectionMatrix; // 모델-뷰-프로젝션 행렬
uniform mat4 ModelViewMatrix; // 모델-뷰 행렬
uniform mat3 NormalMatrix; // 노멀 행렬
uniform mediump vec3 LightPosition; // 조명 위치
// 3. 출력 변수 (프래그먼트 셰이더로 전달)
out vec2 textureCoord; // 텍스처 좌표
out vec3 eyeCoord; // 카메라에서 정점까지의 벡터 (Tangent Space)
out mat3 tangentSpace; // Tangent Space 변환 행렬
void main()
{
// 4. 노멀 벡터와 탄젠트 벡터를 Eye Space로 변환
vec3 norm = normalize(NormalMatrix * Normal); // 노멀 벡터 변환 및 정규화
vec3 tang = normalize(NormalMatrix * vec3(VertexTangent)); // 탄젠트 벡터 변환 및 정규화
// 5. Binormal(비노멀) 벡터 계산
vec3 binormal = cross(norm, tang); // 비노멀 벡터는 노멀과 탄젠트의 외적
// 6. Tangent Space 변환 행렬 계산
tangentSpace = mat3(
tang.x, binormal.x, norm.x,
tang.y, binormal.y, norm.y,
tang.z, binormal.z, norm.z
);
// 7. 카메라에서 정점까지의 벡터(Eye Space) 계산 및 Tangent Space로 변환
eyeCoord = vec3(ModelViewMatrix * VertexPosition) * tangentSpace;
// 8. 텍스처 좌표 전달
textureCoord = TexCoords;
// 9. 정점 위치 변환 (클립 공간)
gl_Position = ModelViewProjectionMatrix * VertexPosition;
}
NormalMatrix 는 ViewMatrix라고 보면된다.
퐁라이팅의 Reflect를 구하기 위해서 View Space 전환된 벡터들이 필요하기 때문에 NormalMatrix를 곱해서
View Space의 벡터들로 만들어준다.
탄젠트 스페이스도 TBN 행렬을 곱해주면 World Space로 변환되고, NormalMatrix를 곱해서
View Space의 벡터로 만들어준다.
- Tangent Space에서 정의된 노멀 벡터를 World Space로 변환하려면,
- TBN 행렬을 사용하는 이유는 기저 벡터(Base Vectors) 변환 개념 때문입니다.
- Tangent Space는 표면의 로컬 좌표계이고, World Space는 전역적인 3D 공간의 좌표계입니다.
- TBN 행렬을 곱하는 것은 **로컬 좌표계(Tangent Space)**의 벡터를 **월드 좌표계(World Space)**로 변환하는 것입니다.
FragmentShader
#version 300 es
precision mediump float;
// 1. 머티리얼(Material) 속성
uniform vec3 MaterialAmbient; // 머티리얼의 주변광 반사 색상
uniform vec3 MaterialSpecular; // 머티리얼의 반사광 색상
uniform vec3 MaterialDiffuse; // 머티리얼의 확산광 색상
// 2. 조명(Light) 속성
uniform vec3 LightAmbient; // 조명의 주변광 색상
uniform vec3 LightSpecular; // 조명의 반사광 색상
uniform vec3 LightDiffuse; // 조명의 확산광 색상
uniform float ShininessFactor; // 머티리얼의 반짝임(Shininess) 계수
uniform vec3 LightPosition; // 조명 위치
// 3. 입력 변수 (정점 셰이더에서 전달)
in vec2 textureCoord; // 텍스처 좌표
in vec3 eyeCoord; // 카메라에서 정점까지의 벡터 (Tangent Space)
in mat3 tangentSpace; // Tangent Space 변환 행렬
// 4. 출력 색상
layout(location = 0) out vec4 FinalColor; // 최종 출력 색상
// 5. 쉐이딩 계산에 필요한 변수들
vec3 normalizeNormal, normalizeEyeCoord, normalizeLightVec, V, R;
vec3 ambient, diffuse, specular;
float sIntensity, cosAngle;
// 6. Gouraud 쉐이딩 함수
vec3 GouraudShading(vec3 Normal, vec3 MaterialDiffuse) {
// 6.1. 정규화된 노멀, EyeCoord, LightVec 계산
normalizeNormal = normalize(Normal);
normalizeEyeCoord = normalize(eyeCoord);
normalizeLightVec = normalize((LightPosition - eyeCoord) * tangentSpace);
// 6.2. 확산광 계산 (Diffuse Intensity)
cosAngle = max(0.0, dot(normalizeNormal, normalizeLightVec));
// 6.3. 반사광 계산 (Specular Intensity)
V = -normalizeEyeCoord; // 카메라 방향 벡터
R = reflect(-normalizeLightVec, normalizeNormal); // 반사 벡터
sIntensity = pow(max(0.0, dot(R, V)), ShininessFactor);
// 6.4. ADS (Ambient, Diffuse, Specular) 색상 계산
ambient = MaterialAmbient * LightAmbient;
diffuse = MaterialDiffuse * LightDiffuse * cosAngle;
specular = MaterialSpecular * LightSpecular * sIntensity;
// 6.5. 최종 색상 반환
return ambient + diffuse + specular;
}
// 7. 텍스처 샘플러
uniform sampler2D ImageTexture; // 컬러 텍스처
uniform sampler2D ImageTextureNormal; // 노멀 맵 텍스처
void main() {
// 8. 노멀 맵에서 노멀 벡터를 조회
vec4 normalMap = texture(ImageTextureNormal, vec2(1.0 - textureCoord.x, textureCoord.y));
// 9. 노멀 맵 좌표를 [0,1]에서 [-1,1]로 변환
normalMap = (2.0 * normalMap - 1.0);
// 10. 컬러 텍스처에서 색상 조회
vec4 texColor = texture(ImageTexture, vec2(1.0 - textureCoord.x, textureCoord.y));
// 11. Gouraud 쉐이딩 함수 호출 및 최종 색상 설정
FinalColor = vec4(GouraudShading(normalMap.xyz, texColor.rgb), 1.0);
return;
}
vec2(1.0 - textureCoord.x, textureCoord.y)
- X축 텍스처 좌표를 뒤집는 작업입니다.
- X축 반전은 텍스처의 좌우가 뒤집혀 있을 때 이를 보정하기 위함입니다.
normalMap = (2.0 * normalMap - 1.0);
노멀 맵의 RGB 값은 [0, 1] 범위이지만, 실제 노멀 벡터는 [-1, 1] 범위로 표현됩니다.
이 변환은 노멀 맵의 벡터 값을 Tangent Space 기준의 노멀 벡터로 변환하는 작업입니다.
Gouraud vs Phong 코드 차이점 요약
코드에서는 GoraundShading 이라고 나와있지만, PhongShading 이라고 보는게 맞음.
조명 계산 위치 | 정점 셰이더에서 수행 | 프래그먼트 셰이더에서 수행 |
정점 셰이더 | 조명 계산을 포함하고, 보간된 색상 전달 | 위치와 노멀 벡터만 전달 |
프래그먼트 셰이더 | 보간된 색상 값만 출력 | 각 픽셀에서 조명 계산 수행 |
성능 | 빠름 | 느림 |
조명 정확도 | 낮음 | 높음 |
'Opengles 3.0 with Android' 카테고리의 다른 글
10.1 Scene Graph (Transformation Graph) (0) | 2025.01.30 |
---|---|
9_4 엠보싱 효과 (0) | 2024.12.29 |
9.3 툰 셰이딩: 엣지 디텍팅과 카툰화 (0) | 2024.12.25 |
Chapter 9.2 Gawsian Blur ( 2 step ) (0) | 2024.12.01 |
Chapter 7.4 FBO (0) | 2024.11.17 |