Computer Graphics

[Computer Graphics] 3D 게임 프로그래밍 후기 with TheCherno

Razelo 2023. 2. 7. 14:50

2022년에는 특히나 그래픽스 프로그래밍에 대해 관심갖고 지냈다. 특히 언리얼5 출시와 함께 많은 프로젝트가 발표되었는데 상당히 인상깊은 프로젝트들이 많았다. 

 

국내에서 가장 유명했던 프로젝트를 꼽으라면 당연히 러셀 님이 진행하고 계신 Project RYU가 될 수 있겠다. 그 영상을 보고 정말 감명받았고 배우고 싶다는 생각이 들었다. 아래 프로젝트 RYU 영상을 링크로 걸어뒀다.

 

꽤나 긴 영상이지만 시간이 남는다면 꼭 보기 바란다. 언리얼로 이정도 수준까지 구현할 수 있다는 점이 정말 놀랍다. 

 

Project RYU

 

개인적으로 아쉬웠던 점은 우리 컴퓨터공학 학부는 그래픽스 강좌가 현재는 열리지 않는 상태이기 때문에 어떻게든 혼자서 공부하는 수밖에 없었다. 아마 강좌가 있었다면 해당 강좌를 수강하고 어떤 식으로 그래픽 프로그래밍이 진행되는 지 알 수 있었을 텐데 그럴 기회가 없었다. 그게 너무 안타까웠다. 너무 공부해보고 싶었는데 강의가 안열려서 못한다는게 슬펐다. 그래서 이럴 바에는 어떻게든 혼자서 공부해보기로 했다. 

 

처음 시도했던 것은 "이득우의 게임수학"이라는 책이었는데 초반 CMAKE 설정에서 뭔가 잘못건드려서 오류가 많이 났다. 키보드 샷건을 치려다 한번 참았고 설정 이후에 나오는 수식 설명 과정에서 싸인, 코싸인을 만나고 결국 무너지고 책을 덮었다. 수학 공식을 안본지가 정말 오래되었기 때문에 수학 실력이 많이 부족하다는 생각이 들었다. 

 

이후 개인적으로 존경하시는 교수님께서 쓰신 "OpenGL로 배우는 3차원 컴퓨터 그래픽스"를 통해 공부하려 했는데 이 역시도 시간 탓에 1장까지만 진행하고 더 이상 진행하지 못했다. 당시 다른 분들과 프로젝트를 진행 중이기도 했고 취업 준비를 위해서 자소서 데드라인이 얼마 남지 않은 상황이었다. 또한 여러모로 다른 백엔드 프로젝트에 참여하느라 바빴던 탓도 있었던 것 같다. 

 

그러던 와중에 예전에 Java로 3D 게임 프로그래밍을 진행하는 강좌를 저장했던 것이 기억나서 부랴부랴 첫 강좌를 듣게 되었다. 그리고 나서 거의 며칠을 이 강좌에 몰두해서 지냈던 것 같다. 너무 재밌어서 하루종일 이 강좌만 수강하고 강의를 따라하면서 프로젝트를 진행한 것 같다. 아침부터 저녁까지 이것만 했다. 

 

참고로 게임 프로젝트를 진행하면서 도저히 이해가지 않는 수식들이 있었다. cos와 sin을 합치면 원이 된다는데 도저히 무슨 말인지는 아직도 이해가 가지 않는다. 그 외에도 여러 가지 수식이 많다. 대충은 이해하고 넘어가려해도 겉핥기 수준 이상으로는 공식을 제대로 접하지 않는 이상 코드 수준에서 이해하기에는 원리를 깨우치기엔 제한이 있다는 생각이 들었다. 

 

다만 어떤 식으로 렌더링 과정이 일어나는지, 3D 게임 프로그래밍에서 어떤 요소를 고려해야하는 지에 대해서는 제대로 알게 되었다. 

 

그리고 무엇보다 재밌었던 점은 게임 프로그래밍에서의 디버깅 과정에서는 전혀 다른걸 잡는다는 점이다. 일반적인 백엔드 프로그래밍을 예로 들어본다면 DB에 조회할때 특정 조건을 통해 가져온 엔티티를 다루는 이후 애플리케이션 로직 단에서 계속해서 에러가 난다면 DB에 해당 조건에 해당되는 값이 없을 수도 있다고 여기고 디버깅을 진행할 수 있다. 하지만 게임 프로그래밍에서는 예를 들면 픽셀이 몇칸씩 밀린다던지, 이미지를 텍스쳐로 사용하기 위해 로드했는데 이미지의 끝 테두리 부분이 이상하게 잘려나간다던지 하는 부분을 디버깅하기 위해서 수식에서 사용하는 상수 값을 변경시키는 등의 시도를 했다. 디버깅이라는 행위는 당연히 같았지만 어떻게 디버깅할지, 어떤 방식으로 추리할지 등이 미세하게 달랐따. 

 

이런 점에서 게임 프로그래밍이 꽤나 스트레스를 동반할 수 있는 작업임을 알게 되었다. 또한 내가 지금까지 접했던 다른 프로그래밍과는 확실히 난이도가 있었다. 꽤 어려웠다. 고려해야할 사항이 생각보다 굉장히 많다는 점에 놀랐다. 

 

결과물로 완성한 게임의 링크는 아래와 같다. 

 

아니면 내가 만들어놓은 Github 리포지토리에 가서 관련 커밋과 스터디 로그를 보실 수도 있다. 

 

게임 다운로드 링크: https://spongeb0b.tistory.com/520

깃허브 리포지토리 링크: https://github.com/yuny0623/Java-3D-Game-Programming

 

게임 다운로드 링크에서 다운받은 zip을 해제한 뒤 해당 디렉토리로 접근해서 gradlew build를 통해 빌드를 한 뒤 jar파일을 실행시키면 된다. 

(참고로 이미지 파일은 절대 삭제하지 말길 바란다. zip파일에 있는 디렉토리 그대로 존재해야만 애플리케이션이 이미지를 정상적으로 로드할 수 있다.)

 

아래에 강의를 보고 프로젝트를 진행하면서 작성한 스터디 로그를 첨부한다. 깃허브 리포지토리에서 마크다운 형식으로 작성해두었는데 다른 분들이 강의에서 어떤 내용을 다루는지에 관한 정보를 얻으실 수 있을 것 같아 첨부한다.

 

마찬가지로 시간이 남으시는 분이 계시거나 3D 게임 프로그래밍에 조금은 관심이 있어서 맛보기로 접하고 계신 분이 계시다면 아래 링크에서 TheCherno의 Java 3D Game Programming 강좌를 보는 걸 권한다. 

 

https://www.youtube.com/watch?v=iH1xpfOBN6M&list=PL656DADE0DA25ADBB&index=1 


🎮 [Graphics Study] 3D Game Programming

📅 Date

    📆 2023.01.19 ~ 2023.01.28 

🕹️ How to play

    ⬆️ w: 전진
    ⬅️ A: 좌로 이동
    ⬇️ S: 후진  
    ➡️ D: 우로 이동 
    🖱️  Mouse: 시점 변환 
    ⌨️ Keyboard 방향키 좌우: 시점 변환 

❓ Why

    학부 강좌에 그래픽스 강좌가 없어서 강의를 따라하며 간략한 게임을 만들어보고 
    3D 프로그래밍에 대한 기초적인 개념을 파악한다. 

🥅 Study Goal

    1️⃣ 3D Game을 Java로 만들어본다. 
    2️⃣ 3D Programming에 대한 기본적인 개념을 알아보도록 한다. 
    3️⃣ 그래픽 렌더링에 대한 대략적인 이해를 할 수 있도록 한다. 

✒️ How to study

    1️⃣  강의를 듣고 예제를 구현한다. 
    2️⃣  주차별 강의에 대한 스터디 내용 정리와 후기를 남긴다. 

Download Link

📜 Study Result

26 floor7 floor8 floor18202527

📚 Study Log

    ✅ 3D Game Programming - Episode 1 - Window
        Java Swing을 사용하기에 꽤나 익숙하게 시작할 수 있다. 
        (이전에 Swing을 써봐서 꽤 익숙한 내용이다.)

    ✅ 3D Game Programming - Episode 2 - Game Loop 
        게임 루프를 만들기 위해 thread를 응용한다. 별로 어려운 내용은 없다.
        그냥 thread start하는 내용들이 있다.  

    ✅ 3D Game Programming - Episode 3 - Arrays
        pixel value를 Array안에 저장하겠다는 내용이다. 

    ✅ 3D Game Programming - Episode 4 - Drawing Pixels!
        랜덤한 값의 pixel Array를 화면에 쏴준다. 이 부분에서 이전에 본 적 없는 API가 사용된다. 
        아래의 코드 조각들이 등장하는데 모르고 지나치기엔 꽤 중요한 부분이다. 
        픽셀 연산과는 상관없지만 직접 화면에 쏘는 바로 그 과정이 이 부분이다. 
        /*
        BufferedImage img 
        pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData()

        BufferStrategy
        createBufferStrategy(3)

        Graphics g = bs.getDrawGraphics();
            g.drawImage(img, 0, 0, WIDTH, HEIGHT, null);
            g.dispose();
            bs.show();
        */

    ✅ 3D Game Programming - Episode 4.5 - How Rendering Works
        렌더링과 관련된 간략한 설명이다. 

    ✅ 3D Game Programming - Episode 5 - Playing with Pixels!
        sin과 cos의 수학적인 기법을 사용하여 화면에 다양한 연출을 보여준다. 
        -> 참고로 출력문은 절대 연산과정에 삽입하지 말자. 속도가 지나치게 느려진다. 

    ✅️ 3D Game Programming - Episode 6 - Performance Boosting
        리팩토링하는 과정이다. 렌더링하지 않아도될 픽셀은 그냥 continue해도 된다. 
        그 외의 테크닉이 소개된다. 이건 다른 분야에서도 유용하게 써먹을 수 있는 내용이다.
        for문을 더 적게 돌 수 있는 방법이 소개된다.  

    ✅ 3D Game Programming - Episode 7 - FPS Counter
        FPS Counter를 만드는 공식이 어떻게 되는지 이해가 가지 않을 수 있다.
        특정 시간에 한해 렌더링이 일어나는 수를 측정하고 그 두 가지 수 사이의 계산이 진행된다. 
        아마 프레임 율에 관한 이해가 있어야지 이해되는 부분이라고 생각된다. 

    ✅ 3D Game Programming - Episode 8 - Alpha Suppport and More
        픽셀 연산에 관여되는 수를 바꿈으로써 다양한 연출이 가능하다. 

    ✅ 3D Game Programming - Episode 9 - Beginning 3D 
        3D에 대한 가장 기본적인 구현이 나온다. 정면에 보이는 간단한 바닥과 천장을 보여준다. 
        나오는 공식이 이해가 안될텐데 관련된 설명 중 가장 근접한 설명은 아래 설명인 것 같다. 
        사실 공식을 봐도 모르겠고 설명도 잘 이해가 안간다. 아마 실제 3D 좌표계와 화면 상의 
        차이를 공식으로 연결짓는 내용인데 난이도가 꽤 있다. 
        /*
        Comment below:
        From what I can tell, he basically derived the rendering mechanism from the following perspective equations,
        sx = x / z tan(fov / 2)
        sy = y / z tan(fov / 2)
        Where sx, sy are normalized screen coords and x, y, z are 3d space coords
        You can exclude the tan to get an fov of 90 degrees

        Then since already know sx, sy and y(the floor/ceiling distance/height) you loop through the y coordinate of the screen and for each row solve for z:
        z = 1 / sy * y
        Then you have a z distance for every horizontal row of pixels
        Next, for every horizontal row you loop through all the pixels based on the distance of the row from the camera,
        x = sx * z

        Now you have top down 2d coords x and z which you can directly translate to texture coordinates
        Am i on the right track? I'm very tired
         */

    ✅ 3D Game Programming - Episode 10 - Floors and Animation
        floor 생성에 관한 내용이다.  

    ✅ 3D Game Programming - Episode 11 - Rotation 
        floor 상에서 대각선, 전진, 후진, 우회전, 좌회전 등을 가능하게함. 이때 rotation
        의 경우 회전 동작인데 회전을 하려면 원을 그릴 수 있어야함. 원을 그리기 위해서 sin과 cos을 활용한다. 

    ✅ 3D Game Programming - Episode 12 - User Input
        우리가 게임을 조작하는 것처럼 W A S D키를 사용해서 앞,뒤,좌,우 움직임을 실행할 수 있도록 한다. 
        Key Event를 받아서 해당 키에 대한 조작을 실행한다. 

    ✅ 3D Game Programming - Episode 13 - Render Distance Limiter!
        floor에서 먼 거리까지 렌더링을 하기 때문에 보기 좋지 않은 부분이 있다. 
        이걸 gradient까지 넣어서 자연스럽게 거리표현까지 하면서도 먼 거리를 표현할 수 있을까? 
        그에 대해 다룬다. 

    ✅ 3D Game Programming - Episode 14 - Basic Mouse Movement
        마우스 커서를 추가한다. 마우스 커서가 밖으로 나가면 더 이상 회전하지 않는 이슈가 있다. 
        해당 이슈는 이후 강좌에서 추가적으로 수정될 예정임. 마우스의 현재 위치를 알아낸 뒤 해당 위치가 
        다음 위치와 다른지, 더 큰지, 작은지를 비교해서 좌우로 회전할 방향을 정한다. 히전은 이전에 Episode 11의 rotation에서
        다룬 기능을 그대로 사용한다.  

    ✅ 3D Game Programming - Episode 15 - Textures + More! 
        텍스쳐 적용을 구현한다. 텍스쳐 이미지의 int array데이터를 얻어와서 그걸 pixels인 array에
        넣어주면 된다. 

        이슈: 
        텍스쳐를 적용하기 위해서는 8비트x8비트 짜리 원하는 이미지를 생성하고 그 이미지의 
        경로를 지정해줘서 이미지를 읽어와야하는데 이때 사용하는 Texture.class.getResource(fileName)에 이슈가 있다. 
        java.lang.IllegalArgumentException: input == null! 이라는 Exception이 발생하는데 이건 
        경로를 제대로 지정해줘도 문제가 발생한다. 그래서 이 기능 대신에 FileInputStream을 사용해서 경로를 넘기면
        제대로된 데이터를 얻을 수 있다. 아래 링크를 참조하면 된다.

        해결방법: 
        https://stackoverflow.com/questions/15424834/java-lang-illegalargumentexception-input-null-when-using-imageio-read-to-lo

    ✅ 3D Game Programming - Episode 16 - Walking, Crouching, Sprinting + More
        게임을 run하고나서 따로 클릭을 하지 않고도 게임창에 자동으로 Focus가 맞춰져있도록 수정함. 
        crouch모드에서는 즉 앉아서 걸을때는 걷기 속도를 감소시킨다. 

    ✅ 3D Game Programming - Episode 16.5 - Exporting Runnable Jars
        jar 파일 export 하는 내용 

    ✅ 3D Game Programming - Episode 17 - Small Adjustments + Birthday!
        코드 최적화 및 걷기 효과 수정 

    ✅ 3D Game Programming - Episode 17.5 - Creating an Applet
        web browser 에서 돌릴 수 있도록 applet으로 만들어보자. 
        applet은 JAR File로 export 하면 된다. runnable jar file이 아니라 그냥 JAR File이다.   
        .html파일에 applet 태그를 선언해주면 applet사용이 가능함. 

    ✅ 3D Game Programming - Episode 18 - The Beginning of Walls
        z 축에 근거한 벽 생성. z축 즉 깊이가 얕으면 화면과 가까운 벽 생성, 깊으면 화면과 멀리 떨어진 벽 생성. 

    ✅ 3D Game Programming - Episode 18.1 - A Few More Things
        리팩토링. Episode 18에서 wall을 생성하게 되면 wall에도 render distance가 적용되서 벽의 중간이 어둡게 보인다. 
        이걸 해결하기 위해 Screen class에서 렌더를 돌릴때 render distance 를 돌리고 난 이후에 벽 생성을 진행하는 방식으로 수정한다. 

    ✅ 3D Game Programming - Episode 18.5 - Creating an EXE File in Java
        jar파일을 EXE파일로 생성하기, Launch4j를 활용함. 

    ✅ 3D Game Programming - Episode 19 - Rendering Walls
        rotation에 따라 sreen에 생성한 wall도 자연스러운 방향으로 렌더될 수 있도록 수정. 

    ✅ 3D Game Programming - Episode 20 - Continuing Walls, Fixing Bugs, and Managing Crashes
        벽 생성을 하고 나서 벽을 보고 걷게 되면 벽에도 마찬가지로 걷기 효과가 적용된다. 
        그래서 벽이 위아래로 bounce하는 연출이 나온다. 벽 or object 걷기 효과가 적용되면 안되기 떄문에 이와 관련된
        리팩토링을 진행한다. 

    ✅ 3D Game Programming - Episode 21 - Texturing Walls, Fixing Clipping, and Fixing the Mouse
        생성된 wall에 texture 를 입힌다. 텍스쳐를 하나의 이미지 파일로 간주하고 텍스쳐 하나하나는 해당 이미지에서
        일종의 섹터로 구분해서 읽어들인다. 마인크래프트에서 텍스쳐를 관리하는 방식과 비슷한 방식이다. 

    ✅ 3D Game Programming - Episode 22 - Random Level Generator + Properly Fixing Clipping
        랜덤 벽을 생성해서 미로를 연출해본다. 벽이 겹쳐 보이는 Clipping을 해결하기 위한 리팩토링이 진행된다. 
        아직 벽과의 충돌은 구현되지 않아서 벽이 통과되는 버그가 있음. 

    ✅ 3D Game Programming - Episode 23 - Graphical User Interface(GUI) Launcher
        Launcher를 만들어본다. 스크린 사이즈를 조정하고 간단한 play 준비 화면을 제공한다. 
        기본적인 GUI프로그래밍과 관련된 내용임. 

    ✅ 3D Game Programming - Episode 24 - Making Our Launcher Work
        전편에 이어지는 기본적인 GUI프로그래밍과 관련된 내용. 

    ✅ 3D Game Programming - Episode 25 - Writing and Reading Files
        resolution 정보 즉 화면 크기 정보를 파일로 저장하고 쓰고 읽을 수 있게 구현함. 
        이전 설정 정보를 XML파일에 저장한다. Properties를 활용한다. file IO 기능을 활용한다. 

    ✅ 3D Game Programming - Episode 26 - Custom Resolutions
        Resolution을 직접 설정할 수 있도록 JTextField를 활용함. 

    ✅ 3D Game Programming - Episode 27 - Decorating the Launcher
        GUI Launcher에 Background 이미지를 삽입한다. 다만 정적인 이미지가 아닌 graphics를 활용해서 삽입한다. 
        이 과정에서 문제가 발생. 리팩토링 및 수정은 다음 에피소드에서 진행됨. 

    ✅ 3D Game Programming - Episode 28 - Continuing our Custom Launcher!
        Launcher에서 이미지를 사용해서 메뉴를 구성한다. 또한 런쳐가 드래그될 수 있도록 커스텀한다. 
        반투명 이미지 생성 링크: https://pixlr.com/kr/remove-background/ 

    ✅ 3D Game Programming - Episode 29 - Launching The Game
        싱글톤 패턴을 Launcher에 적용한다. Display의 getInstance를 통해 항상 같은 Launcher를 받아올 수 있다. 
        추가적인 리팩토링이 진행됨. 

        이슈: 
        런쳐를 드래그해서 이동하는 movement가 깨지는 현상이 있었음. 

        해결방법: 
        updateFrame의 getLocation을 반드시 Jframe에서 호출해야 한다. 
        Canvas에서 호출할 경우 Canvas의 getLocation을 호출하기 때문에 드래그가 깨지게 됨. 

    ✅ 3D Game Programming - Episode 30 - Colour Processing In-Depth
        새로운 프로젝트 진행. 2D movement 구현 및 충돌 처리 구현. 다른 리포에 구현함. 
        레벨 디자인에 대해 알아본다. 게임의 맵을 구성할 수 있는 표현 방식.     

    ✅ 3D Game Programming - Episode 31 - Sprites!
        Sprite를 추가한다. 벽을 제외한 평면 공간에 새로운 오브젝트를 표현한다. 

    ✅ 3D Game Programming - Episode 32 - Sprite Mapping
        Sprite에 텍스쳐를 적용한다. 
        Sprite에 투명 효과를 적용하고 랜덤으로 생성될 수 있도록 한다.
        (Sprite의 특정 RGB를 렌더링하지 않는 방식으로 투명 효과를 적용한다.) 

    ✅ 3D Game Programming - Episode 33 - High Resolution Rendering
        Texture를 32x32사이즈 텍스쳐맵으로 관리함. 천장과 바닥을 분리해서 렌더링할 수 있음. 
        텍스쳐를 커스텀하고 파라미터값 조정을 통해서 원하는 텍스쳐를 사용할 수 있음. 

    ✅ 3D Game Programming - Episode 34 - Entities
        전반적인 리팩토링을 진행한다. Player 클래스와 Entity, Mob클래스를 새롭게 생성한다. 

 


오랜만에 몰두해서 배워보고 싶은 걸 배워봤다. 

 

좋은 강의를 제공해주신 TheCherno 님께 감사합니다. 

 

참고로 그 분 영상은 재밌게 보고 있긴 한데 가끔 올라오는 영상의 절반은 수학과 관련된 내용이라서 무슨 말씀을 하시는 지 이해가 안간다. 

 

그럼에도 불구하고 Java 3D Game Programming 등 만들어두신 강좌는 유익한 것 같다. 

 

감사합니다. 

 

 

The Cherno

I like to make videos.

www.youtube.com

 

반응형