[카테고리:] 미분류

  • OpenGL에서 FBX 모델 렌더링 문제 해결하기

    OpenGL을 사용하여 3D 모델링 어플리케이션을 개발할 때 가장 흔히 마주치는 문제 중 하나는 모델이 화면에 제대로 표시되지 않는 현상입니다. 특히 FBX 포맷의 모델을 로드하고 렌더링하는 과정에서 “모델이 성공적으로 로드되었습니다”라는 메시지는 표시되지만 실제로 화면에는 아무것도 보이지 않는 상황이 발생할 수 있습니다. 이번 포스트에서는 이러한 문제의 원인과 해결 방법에 대해 심층적으로 알아보겠습니다.

    문제 상황 이해하기

    보통 다음과 같은 코드 구조를 가진 애플리케이션에서 문제가 발생합니다:

    1. FbxModelLoader 클래스: Assimp 라이브러리를 사용하여 FBX 파일을 로드
    2. OpenGLModelRenderer 클래스: OpenGL을 통해 로드된 모델을 렌더링
    3. 메인 폼에서 이 두 클래스 인스턴스를 생성하고 관리

    모델 로드 후 디버그 로그에는 메시 정보가 표시되지만, OpenGL 창에는 아무것도 표시되지 않는 현상이 발생합니다.

    원인 분석

    여러 로그를 분석해 본 결과 다음과 같은 원인이 확인되었습니다:

    모델 크기: X(-76.54645 ~ 76.54895), Y(-29.784735 ~ 78.81894), Z(-0.10244751 ~ 30.674017)
    첫 번째 메시 정점 수: 3312
    정점 0: (0.7970118, -16.076624, 26.799282)
    정점 1: (0.8244844, -16.079245, 26.821259)
    ...
    

    이 로그에서 확인할 수 있듯이, 문제의 핵심은 다음과 같습니다:

    1. 모델 크기 문제: 모델의 크기가 매우 크며, 축에 따라 다양한 범위를 가집니다.
    2. 카메라 위치와 시야각: 기본 설정된 카메라 위치와 시야각이 큰 모델을 표시하기에 적합하지 않습니다.
    3. 정점 위치 오프셋: 정점들이 원점(0,0,0)으로부터 상당히 떨어진 위치에 있습니다.
    4. 조명 설정: 부적절한 조명 설정으로 모델이 화면에 보이지 않을 수 있습니다.

    해결 방법

    이러한 문제를 해결하기 위한 주요 접근법은 다음과 같습니다:

    1. 모델 데이터 분석 및 로깅

    문제 해결의 첫 단계는 로드된 모델의 데이터를 정확히 파악하는 것입니다. 다음과 같은 코드를 추가하여 모델의 크기와 정점 위치를 확인할 수 있습니다:

    // 모델 크기 정보 가져오기 및 로깅
    if (IsModelLoaded)
    {
        // 모델의 바운딩 박스 크기 확인
        Vector3D min = new Vector3D(float.MaxValue);
        Vector3D max = new Vector3D(float.MinValue);
        
        foreach (var mesh in fbxScene.Meshes)
        {
            foreach (var vertex in mesh.Vertices)
            {
                min.X = Math.Min(min.X, vertex.X);
                min.Y = Math.Min(min.Y, vertex.Y);
                min.Z = Math.Min(min.Z, vertex.Z);
                
                max.X = Math.Max(max.X, vertex.X);
                max.Y = Math.Max(max.Y, vertex.Y);
                max.Z = Math.Max(max.Z, vertex.Z);
            }
        }
        
        // 디버그 정보 출력
        Debug.WriteLine($"모델 크기: X({min.X} ~ {max.X}), Y({min.Y} ~ {max.Y}), Z({min.Z} ~ {max.Z})");
    }
    

    정점 데이터를 추가로 확인하기 위해 다음 코드를 사용할 수 있습니다:

    // 첫 번째 메시의 첫 몇 개 정점을 로그로 출력
    if (scene.MeshCount > 0)
    {
        Mesh firstMesh = scene.Meshes[0];
        Debug.WriteLine($"첫 번째 메시 정점 수: {firstMesh.VertexCount}");
        for (int i = 0; i < Math.Min(5, firstMesh.VertexCount); i++)
        {
            Debug.WriteLine($"정점 {i}: ({firstMesh.Vertices[i].X}, {firstMesh.Vertices[i].Y}, {firstMesh.Vertices[i].Z})");
        }
    }
    

    2. 모델 크기 및 위치 조정

    모델의 크기가 너무 크거나 위치가 시야에서 벗어난 경우, 다음과 같이 스케일과 위치를 조정해야 합니다:

    private void RenderFbxMeshes(Scene scene)
    {
        // 모델 크기에 따라 스케일 조정
        float scale = 1.0f;
        Vector3D sceneMin = new Vector3D(float.MaxValue);
        Vector3D sceneMax = new Vector3D(float.MinValue);
        
        // 모델의 전체 크기 계산
        foreach (Mesh mesh in scene.Meshes)
        {
            foreach (Vector3D vertex in mesh.Vertices)
            {
                sceneMin.X = Math.Min(sceneMin.X, vertex.X);
                sceneMin.Y = Math.Min(sceneMin.Y, vertex.Y);
                sceneMin.Z = Math.Min(sceneMin.Z, vertex.Z);
                
                sceneMax.X = Math.Max(sceneMax.X, vertex.X);
                sceneMax.Y = Math.Max(sceneMax.Y, vertex.Y);
                sceneMax.Z = Math.Max(sceneMax.Z, vertex.Z);
            }
        }
        
        // 모델 크기에 따라 스케일 계산
        Vector3D size = new Vector3D(
            sceneMax.X - sceneMin.X,
            sceneMax.Y - sceneMin.Y,
            sceneMax.Z - sceneMin.Z
        );
        
        float maxDim = Math.Max(Math.Max(size.X, size.Y), size.Z);
        if (maxDim > 0)
        {
            scale = 40.0f / maxDim; // 화면에 맞게 스케일 조정
        }
        
        // 중심 위치 계산
        Vector3D center = new Vector3D(
            (sceneMin.X + sceneMax.X) / 2,
            (sceneMin.Y + sceneMax.Y) / 2,
            (sceneMin.Z + sceneMax.Z) / 2
        );
        
        // 렌더링 시 스케일 및 위치 조정 적용
        GL.PushMatrix();
        GL.Scale(scale, scale, scale);
        GL.Translate(-center.X, -center.Y, -center.Z);
        
        // 메시 렌더링 코드...
        
        GL.PopMatrix();
    }
    

    3. 카메라 위치 및 시야각 조정

    카메라 위치와 시야각도 모델 크기에 맞게 조정해야 합니다:

    private void OpenGLModelRenderer_Paint(object sender, PaintEventArgs e)
    {
        glControl.MakeCurrent();
    
        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
    
        GL.MatrixMode(MatrixMode.Projection);
        GL.LoadIdentity();
    
        // 투영 설정 - 더 넓은 시야각과 더 먼 평면 거리
        float aspectRatio = (float)glControl.Width / glControl.Height;
        Matrix4 perspective = Matrix4.CreatePerspectiveFieldOfView(
            MathHelper.DegreesToRadians(70), aspectRatio, 0.1f, 1000.0f);
        GL.LoadMatrix(ref perspective);
    
        GL.MatrixMode(MatrixMode.Modelview);
        GL.LoadIdentity();
    
        // 카메라 위치를 더 멀리 설정
        GL.Translate(0, 0, -150);
    
        // 나머지 렌더링 코드...
    }
    

    4. 조명 및 재질 설정 개선

    적절한 조명과 재질 설정은 모델이 보이는 데 중요합니다:

    private void SetupOpenGLRendering()
    {
        glControl.MakeCurrent();
        GL.ClearColor(0.2f, 0.2f, 0.2f, 1.0f); // 배경색 어둡게 변경
        GL.Enable(EnableCap.DepthTest);
        GL.Enable(EnableCap.CullFace);
        GL.CullFace(CullFaceMode.Back);
    
        // 조명 활성화
        GL.Enable(EnableCap.Lighting);
        GL.Enable(EnableCap.Light0);
        GL.ShadeModel(ShadingModel.Smooth);
    
        // 기본 조명 설정 강화
        float[] lightPosition = { 5.0f, 5.0f, 5.0f, 0.0f };
        float[] lightAmbient = { 0.4f, 0.4f, 0.4f, 1.0f };
        float[] lightDiffuse = { 1.0f, 1.0f, 1.0f, 1.0f };
        float[] lightSpecular = { 1.0f, 1.0f, 1.0f, 1.0f };
    
        GL.Light(LightName.Light0, LightParameter.Position, lightPosition);
        GL.Light(LightName.Light0, LightParameter.Ambient, lightAmbient);
        GL.Light(LightName.Light0, LightParameter.Diffuse, lightDiffuse);
        GL.Light(LightName.Light0, LightParameter.Specular, lightSpecular);
    
        // 재질 설정
        float[] materialAmbient = { 0.2f, 0.2f, 0.2f, 1.0f };
        float[] materialDiffuse = { 0.8f, 0.8f, 0.8f, 1.0f };
        float[] materialSpecular = { 1.0f, 1.0f, 1.0f, 1.0f };
        float materialShininess = 32.0f;
    
        GL.Material(MaterialFace.Front, MaterialParameter.Ambient, materialAmbient);
        GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, materialDiffuse);
        GL.Material(MaterialFace.Front, MaterialParameter.Specular, materialSpecular);
        GL.Material(MaterialFace.Front, MaterialParameter.Shininess, materialShininess);
    }
    

    5. 와이어프레임 모드로 테스트

    초기 디버깅 단계에서는 와이어프레임 모드를 사용하여 모델의 기본 구조가 제대로 로드되고 있는지 확인하는 것이 유용합니다:

    private void RenderWireframeMeshes(Scene scene)
    {
        GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
        GL.Disable(EnableCap.Lighting);  // 와이어프레임에서는 조명 비활성화
        GL.Color3(1.0f, 1.0f, 1.0f);     // 흰색 선
        
        // 스케일 및 위치 조정 코드...
        
        foreach (Mesh mesh in scene.Meshes)
        {
            GL.Begin(OpenTK.Graphics.OpenGL.PrimitiveType.Triangles);
            
            for (int i = 0; i < mesh.FaceCount; i++)
            {
                Face face = mesh.Faces[i];
                for (int j = 0; j < face.IndexCount; j++)
                {
                    int index = face.Indices[j];
                    Vector3D vertex = mesh.Vertices[index];
                    GL.Vertex3(vertex.X, vertex.Y, vertex.Z);
                }
            }
            
            GL.End();
        }
        
        GL.PopMatrix();
        GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
        GL.Enable(EnableCap.Lighting);
    }
    

    모델 변경 감지 및 동기화

    모델이 로드된 후 렌더러에 변경 사항을 알리는 메커니즘도 중요합니다:

    // 모델 변경 체크 타이머 추가
    Timer modelCheckTimer = new Timer();
    modelCheckTimer.Interval = 500; // 500ms마다 확인
    modelCheckTimer.Tick += (sender, e) => {
        if (modelLoader.IsModelLoaded != isModelLoaded)
        {
            isModelLoaded = modelLoader.IsModelLoaded;
            Debug.WriteLine($"모델 상태 변경 감지: {isModelLoaded}");
            glControl.Invalidate();
        }
    };
    modelCheckTimer.Start();
    

    또한 모델을 로드한 후 즉시 화면을 갱신하도록 하는 것도 중요합니다:

    if (_modelLoader.LoadModel(selectedFilePath))
    {
        MessageBox.Show("모델이 성공적으로 로드되었습니다.", "성공", MessageBoxButtons.OK, MessageBoxIcon.Information);
        
        // 모델 로드 후 렌더러 갱신
        _modelRenderer.Invalidate();
    }
    

    결론

    OpenGL에서 FBX 모델을 로드하고 렌더링할 때 발생하는 “모델이 보이지 않는” 문제는 대부분 다음과 같은 이유로 발생합니다:

    1. 모델의 크기와 위치가 시야 영역을 벗어나는 경우
    2. 카메라 설정(위치, 시야각, 클리핑 평면 등)이 부적절한 경우
    3. 조명 설정이 모델을 보이지 않게 만드는 경우
    4. 모델 로드 후 렌더러 갱신이 제대로 이루어지지 않는 경우

    이러한 문제를 해결하기 위해서는 모델 데이터를 철저히 분석하고, 적절한 스케일과 위치 조정, 카메라 설정, 조명 설정을 적용해야 합니다. 또한 초기 디버깅 단계에서는 와이어프레임 모드를 사용하여 모델의 기본 구조가 제대로 로드되고 있는지 확인하는 것이 유용합니다.

    FBX 렌더링에 문제가 있다면 이 포스트에서 설명한 방법들을 단계적으로 적용해 보세요. 대부분의 경우 모델이 화면에 올바르게 표시될 것입니다.