이제껏, WireFrame 과 Normal 을 봤다. 또한 중요한 부분중에 하나는 Model 이 우리가 봤던 박스처럼 모든 모델이 그렇게 생겨있지 않다. 예를 들어서 구, Cylinder 등의 모형들은 격자 무늬로 이루어져 있으며, 특히나 지형 같은 경우도 Grid Plane 이라고 볼수 있다. 그럼 Grid Plane 은 어떻게 생겨 먹은 친구 인지 한번 봐보자. 아래를 보면, 저런 격자 모양인 Mesh 가 결국엔 Grid Mesh 라고 볼수 있다. 여러개의 박스 (triangle mesh 2개) 가 여러개 모여서 격자 모형의 Plane 을 만들수 있다고 할수 있다.
결국에는 너무 쉽게도 Mesh 의 Vertex 정보와, Normal 정보, 그리고 Vertex 들의 관계 (index) 정보들을 가지고 만들수 있다. 일단 Texture 를 준비해보자. 그리고 현재 Game Engine 에 올려보자. 아래의 Texture 는 물을 표현한 Texture 이다.
그리고 격자를 그리기위해서, 결국에는 Vertex 의 정보가 필요하다. 격자를 그리기위해나 Parameter 로서는 Width, Height, Stack, Slice 라고 보면 될것 같다.
Width & Height 는 격자의 총길이, 그리고 stack 몇개의 Box 를 위쪽으로 쌓을건지와, Slice 는 Width 에서 얼마나 자를건지를 표현한다.
코드는 따로 공유는 하지 않겠다, 하지만 격자는 아래와 같이 그려낼수 있다. 결국에는 평면이기 때문에 임의 Normal 값을 -z (모니터가 나를 바라보는 쪽) 으로 되어있고, 그리고 격자의 Vertex 의 정보는 Slice 와 Stack 으로 Point 를 Translation 해줬으며, 그리고 Index 들은 간단한 offset 으로 구현을 했었다.
재밌으니까, 물결 Texture 니까, 물결을 나타내는 Texture 를 한번 구부려보자. z 축을 x 의 sin graph 로 그려내보자. 그리고 이거에대한 Normal 값도 따로 적용한다고 하면 두개의 Image 를 확인할수 있다. x 에 대한 변화량에 대한 z 값을 그려냈기때문에, 편미분을 통해서 결과값을 도출해낼수 있다.
이제껏 해본걸 종합해보자. Plane 도 만들어보았고, Box Mesh 도 만들어보았다. 근데 Cylinder 는? 사실 Cylinder 는 앞에 Grid 의 평면을 말아 놓은거라고 볼수 있다. 그렇다면, 어떻게 해야할까?가 고민일텐데, Texture 좌표 때문에 Vertex 를 + 1 을 해줘야한다. 그 이유는 Texture 좌표계 때문이다. (0~1) 로 반복되는거로 되어야하기때문에 그렇다.
그리고 앞서서 배웠듯이 Normal Vector 는 inverse transpose 값을 해줘야 Scale 값에 영향이 없는 결과를 가지고 올수 있다.
일단 이거는 살짝의 코드의 방향성을 생각을 해보면 좋을것 같다. 월드좌표계에서 Cylinder 를 만든다고 가정을 했을때, 화면 안으로 들어가는 좌표 z, Right Vector 는 X 축, Up Vector 는 Y 축이라고 생각을하고. 모델링을 만들때는 Y 축을 기준 회전 (즉 x-z 평면에서 만든다고 볼수있다.)
그렇다고 하면, 모든 Vertex 를 얼마나 회전을 하느냐에 따라서, 각도를 const float dTheta = -XM_2PI / float(sliceCount); 결국에는 얼마나 잘라내는지에 따른것에 따라서 더 부드러운 원동모형을 만들수 있을것 같다. 그리고 Y 축의 회전이다 보니, 모든 Vertex 를 Y 축을 통해서 Rotation 값을 누적해가면 된다.
그러면 위의 Radius 와 아래의 Radius 를 x-z 평면으로 시작점으로 해서 돌리면 된다. 결과는 아래와 같다. 원통을 그리려면 Vertex 의 정보를 SimpleMath 에 있는 CreateRotationY 로 충분히 해도 되지만, sin, cos 을 사용해서 원통을 만들어도 똑같은 결과를 나타낸다.
이전 Post 와 마찬가지로, 목적은 우리가 그려야할 Mesh 들의 Wireframe 도 봐야하지만, 각 면에 있는 Face Normal 값을 확인하는것도 Graphics Tool 로서는 중요하다. WireFrame 같은 경우는 DirectX 에서 는 RasterizerState 으로 해주었었다. 하지만 Normal 같은 경우 직접 그려야한다. 그래서 각 Vertex 별 Normal 값을 구하는게 중요하다. 물론 Unreal Engine 같은 경우 아래처럼 World Normal 을 볼수 있게끔 되어있다.
그렇다면 일단 만들어보자. 간단한 Box 의 Vertex 와 Normal 값들을 직접적으로 넣어주는 코드는 생략하겠다. 단 여기서 Point 는 HLSL 에서 어떻게 사용되는지를 알아보는게 더 중요하다.
일단 ConstantBuffer 에 들어가 있는걸로도 충분하니 아래의 코드를 일단 봐보자.
일단 Shader 코드를 살표 보자면, View 로 Transform 하기 이전에, Model 좌표계에서 의 Normal Vector 들을 World 좌표계로 구한다. 그런다음에, 시작점과 끝점을 확실히 하기위해서, texcoord 를 CPU 쪽에서 넘겨줄때 .x 값을 넘겨서 시작과 끝을 알려주는거를 넣어주면 될것 같다. 그리고 t 가 1 면 normal vector 의 원점 (노란색) 그리고 t 가 0 이면, normal vector 의 끝점인 (빨간색) 으로 표시할수 있게한다.
이제 CPU 쪽 작업을 해보자. CPU 에서 보내줄 정보는 GPU 에서의 보내줄 정보와 같다. CPU 쪽에서는 새로운 Normal 값들을 집어넣어야 하기에 정점 정보와 Normal 의 Indices 정보를 넣어서, Buffer 안에다가 채워넣어준다. 그리고 말했던 Normal 의 시작과 끝을 알리는 정보로서 texcoord 에 Attribute 로 집어 넣어준다. 마찬가지로 ConstantBuffer 도 Model, View, Projection 을 원하는 입맛에 맛게끔 집어넣어주면 될것 같다. 그리고 마지막으로 ConstantBuffer 를 Update 만해주면 내가 바라보는 시점에 따라서 Normal 도 같이 움직이는걸 확인할수 있다.
structMyVertexConstantBuffer{matrixmodel;matrixinvTranspose;matrixview;matrixprojection;}MyVertexConstantBufferm_MyVertexConstantData;ComPtr<ID3D11Buffer>m_vertexConstantBuffer;ComPtr<ID3D11Buffer>m_vertexBuffer;ComPtr<ID3D11Buffer>m_indexBuffer;ComPtr<ID3D11VertexShader>m_normalVertexShader;ComPtr<ID3D11PixelShader>m_normalPixelShader;UINTm_indexCount=0;//-----------------------------------------------------//// Init ()std::vector<Vertex>normalVertices;std::vector<uint16_t>normalIndices;for(size_ti=0;i<vertices.size();i++){autodata=verticies[i];data.texcoord.x=0.0f;normalVertices.push_back(data);data.texcoord.x=1.0f;normalVertices.push_back(data);normalIndices.push_back(uint16_t(2*i));normalIndices.push_back(uint16_t(2*i+1));}CreateVertexBuffer(normalVertices,m_vertexBuffer);m_indexCount=UINT(normalIndices.size());CreateIndexBuffer(normalIndicies,m_indexBuffer);CreateConstantBuffer(m_MyVertexConstantData,m_vertexConstantBuffer);// Then you need to Create Vetex / InputLayout & PixelShader to bind the resources.//-----------------------------------------------------//// Update ()// occluded the (M)odel(V)iew(P)rojection CalculationUpdateBuffer(m_MyVertexConstantData,m_vertexConstantBuffer);//-----------------------------------------------------//// Render()m_context->VSSetShader(m_normalVertexShader.Get(),0,0);ID3D11Buffer*pptr[1]={m_vertexConstantBuffer.Get()};m_context->VSSetConstantBuffers(0,1,pptr);m_context->PSSetShader(m_normalPixelShader.Get(),0,0);m_context->IASetVertexBuffers(0,1,>m_vertexBuffer.GetAddressOf(),&stride,&offset);m_context->IASetIndexBuffer(m_indexBuffer.Get(),DXGI_FORMAT_R16_UINT,0)m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);m_context->DrawIndexed(m_indexCount,0,0);
결과는 아래와 같다.
RenderDoc 으로도 돌려봐야하지 않겠냐? 싶어서, Vertex 의 Input 정보들을 확인할수 있다. 그리고 내가 어떠한 Parameter 로 넣어줬는지에 대한 State 들도 왼쪽에 명시 되어있다.
그리고 Vertex Shader 로 부터 Output 이 생성이 되면 아래와 같이 각 Vertex 과 면에 대해서 Normal 값이 나오는걸 확인할수 있다.
그리고 이건 DrawIndexed 의 호출을 동그라미 친것이다. ImGUI 도 쓰기때문에 저 뒤에 두번째는 ImGUI 가 현재 RenderTarget 에 DrawIndexed 를 해주고, 내가 Rendering 하고 싶은 결과는 노란색 두개 이다.
이렇게해서 RenderDoc 을 사용해서 검증을 하고 내가 Pipeline 에잘 넣었는지도 확인할수 있다.
결국에는 우리가 직접 Lighting 을 CPU 에서 계산하는게 아닌 GPU 에서 계산하도록 해야한다, 그럴려면 Shader Programming 으로 할수 있는 방법들을 찾아야한다.
일단 기본적인 어떤 Lighting 을 계산하기 위해서는 물체의 Normal 값이 필요하기 때문에, ModelViewProjection 이라는 Constant Buffer 에다가 InverseTransform 을 넣어줘야한다. 여기서 잠깐! 왜 Model 그대로 Normal 을 구하지 않는지를 물어볼수 있다. 그건 바로 Model 의 Scaling 이 변한다고 했을때, Normal 의 길이(크기)가 달라지기 때문에, Inverse Transpose Model Matrix 를 넣어줘야한다. 처음에는 위치값을 제거하고 (Translation), 그리고 .Invert().Transpose() 를 해주면, World Normal 값을 Shader 에서 구할수 있다.
자 일단 ConstantBuffer 를 Update 해주는 곳은 Render() 에서 매 Frame 별로 Update 를 해준다. 그러기때문에 아래의 방식처럼 CPU 에서 GPU 로 Data 넘길것들을 정의 해준다. 물론 이야기는 따로 하진 않겠지만, Model -> View -> Projection 으로 행렬을 곱하는데, 월드 좌표계에서의 Camera 의 위치를 구하기 위해서, m_pixelConstantBufferData.eyeWorld = Vector3::Transform(Vector3(0.0f), m_vertexConstantBufferData.view.Invert()); 이부분이 들어간거다.
structVertex{Vector3position;Vector3normal;Vector2texcoord;};structVertexConstantBuffer{Matrixmodel;MatrixinvTranspose;Matrixview;Matrixprojection;};// Render() -> Constant Buffer Updatem_vertexConstantBufferData.model=Matrix::CreateScale(m_modelScaling)*Matrix::CreateRotationX(m_modelRotation.x)*Matrix::CreateRotationY(m_modelRotation.y)*Matrix::CreateRotationZ(m_modelRotation.z)*Matrix::CreateTranslation(m_modelTranslation);m_vertexConstantBufferData.model=m_vertexConstantBufferData.model.Transpose();m_vertexConstantBufferData.invTranspose=m_vertexConstantBufferData.model.;m_vertexConstantBufferData.invTranspose.Translation(Vecctor3(0.0f));// get rid of translationm_vertexConstantBufferData.invTranspose=m_vertexConstantBufferData.invTranspose.Transpose().Invert()m_vertexConstantBufferData.view=Matrix::CreateRotationY(m_viewRot)*Matrix::CreateTranslation(0.0f,0.0f,2.0f);m_pixelConstantBufferData.eyeWorld=Vector3::Transform(Vector3(0.0f),m_vertexConstantBufferData.view.Invert());m_vertexConstantBufferData.view=m_vertexConstantBufferData.view.Transpose();
이렇게 필요한 Data 가 주어졌을떄, Lighting 기반 Bling Phong with Lambert equation 을 사용해서 Shader Programming 을 해야한다. 일단 CPU 쪽의 Data 를 정의 한다. 카메라의 위치와 어떤 Light 인지를 담는, Constant Buffer 를 사용하자. Lights 의 종류는 3 가지 (Directional Light, Point Light, Spotlight) 이렇게 나누어진다.
그리고 Render() 하는 부분에서, ConstantBuffer 값을 Update 를 한다. 빛의 종류에 따른 Update. Directional Light = 0, Point Light = 1, Spot Light = 2. 이렇게 되어있으며, 내가 필요한 Light 만 사용할수 있게끔 다른 Light 들을 꺼주는것이다. (.strength *= 0.0f)
가끔 우리는 어떤 Mesh를 사용하고 있는지, 그리고 그 Mesh가 얼마나 많은 삼각형으로 이루어져 있는지를 파악해야 할 필요가 있다.. 이는 성능 최적화나 실시간 렌더링 가능 여부를 평가할 때 중요하고 특히, 고해상도 Terrain이나 복잡한 조각상 같은 모델이 있는 경우 (물론 다른 stage 에서..), 런타임에서 모델을 사용할 수 있는지 판단하는 데 중요한 기준이 됩니다.
예를 들어, Unreal Engine의 Nanite System은 매우 많은 Vertex를 활용하여 고도로 디테일한 모델을 실시간으로 렌더링할 수 있다.. 이는 LOD(레벨 오브 디테일)를 효율적으로 관리하여 성능 저하 없이도 높은 품질의 그래픽을 제공할 수 있게 해줍니다. 그렇다면, 실제로 삼각형 개수를 확인하려면 어떻게 해야 할까요? 정답은 Rendering Pipeline의 상태를 확인하는 것입니다. 특히, Rasterizer 단계에서 어떻게 삼각형을 그릴 것인지 결정하는 RasterizerState를 통해 설정할 수 있다. 그리고 나중에 Rasterizer State 들을 만들어주면 된다.
| 특징 | Premake | CMake | | ———— | —————————————————– | —————————————————– | | 목적 | 프로젝트 파일 생성 (주로 IDE 지원) | Makefile이나 Visual Studio 프로젝트 등을 자동 생성 | | 설정 파일 형식 | Lua 스크립트 (premake5.lua) | 독자적인 스크립트 언어 (CMakeLists.txt) | | 언어 | Lua 스크립트 기반 | 자체 DSL(도메인 특화 언어) | | 지원 플랫폼 | Windows, macOS, Linux | 대부분의 플랫폼 및 툴체인 지원 | | 생성 파일 | Visual Studio, Xcode, GNU Make, Code::Blocks, gmake 등 | Visual Studio, Xcode, Ninja, Makefile 등 다양한 빌드 시스템 지원 | | 학습 곡선 | Lua를 알고 있다면 비교적 간단 | 자체 문법 학습이 필요, 복잡한 구조일 때 난이도 증가 | | 사용 사례 | 게임 엔진, 그래픽 라이브러리 등 주로 C++ 프로젝트에 많이 사용 | 오픈소스 프로젝트, 다양한 플랫폼 지원이 필요한 프로젝트 | | 설정 방식 | Lua 스크립트를 통해 논리적 설정 가능 | 선언형으로 프로젝트를 기술, 간단하지만 복잡한 설정은 코드가 길어짐 | | 빌드 속도 | 비교적 빠름 | Ninja와 같은 고속 빌드 툴과 함께 사용 가능 | | 커뮤니티와 문서 | 상대적으로 작음 | 커뮤니티가 크고 문서가 방대 | | | | |
지원 플랫폼 및 유연성
Premake: 주로 Visual Studio, Xcode, GNU Make를 타겟으로 사용하며, 게임 엔진이나 그래픽 라이브러리 개발에 많이 사용됩니다.
CMake: 거의 모든 플랫폼과 빌드 시스템을 지원합니다. 특히 Ninja와 함께 사용할 때 빌드 속도가 매우 빠릅니다.
생태계와 커뮤니티 지원
Premake: 게임 엔진 및 일부 그래픽 라이브러리에서 주로 사용됩니다. 대표적으로 LunaEngine이나 Unreal Engine에서 사용합니다.
CMake: 오픈소스 프로젝트에서 표준으로 사용됩니다. 예를 들어 LLVM, Qt, OpenCV 등이 CMake를 사용합니다.
I’m currently working on my own game engine, called Luna Game Engine. Sometimes, the workload can feel overwhelming, especially when balancing personal projects and portfolio building. I find that taking a break and reflecting helps me maintain focus and productivity.
While working on Luna, I often draw inspiration from existing game engines like Unreal Engine, Unity, and FrostBite. Each of these engines has a distinct philosophy and approach to game development. For example, Unity is particularly focused on multi-platform support, which makes it versatile for mobile, console, and desktop applications. There’s a great video on YouTube that outlines Unity’s roadmap and how they plan to evolve the engine.
One remarkable feature of Unreal Engine is its support for large-scale triangle meshes through Nanite. However, I am a bit concerned about the runtime performance when loading these heavy meshes. While the visuals are undeniably impressive, maintaining performance is always a critical consideration.
Unity’s collaboration with Zyva is also noteworthy, especially when it comes to realistic facial animations. The demo shows incredibly vivid muscle movements that mimic human expressions. This kind of technology could significantly impact the VR industry and even healthcare applications.
Personally, I have a soft spot for FrostBite, as I have enjoyed the Battlefield series for years. I also find it fascinating how the engine has evolved, especially for FIFA. In older versions, scoring from long-distance shots was quite easy, but recent iterations have introduced more nuanced physics and player attributes, making gameplay more realistic. Small details, like players sweating, might seem minor, but they reflect just how far game engines have come in terms of visual fidelity and realism.
As I continue developing Luna Game Engine, I aim to incorporate some of the strengths I admire in these established engines while maintaining my own vision. Taking time to study how industry leaders approach engine design helps me make more informed decisions about Luna’s features and structure.
The Vulkan is the graphical API made by Khronos providing the better abstraction on newer graphic cards. This API outperform? the Direct3D and OpenGL by explaining what it perform. The idea of the Vulkan is similar to the Direct3D12 and Metal, but Vulkan is cross-platform, and it can be used and developed in Linux or Window Environment.
However, the drawback of this could be is there will be many detailed procedure while using the Vulkan API, such as creating the buffer frame, managing the memory for buffer and texture image objects. That means we would have to set up properly on application. I realized that the Vulkan is not for everyone, it is only for people who’s passionate on Computer Graphics Area. The current trends for computer graphics in Game Dev, they are switching the DirectX or OpenGL to Vulkan : Lists of Game made by Vulkan. One of the easier approach could be is to learn and play the computer graphics inside of Unreal Engine and Unity.
By designing from scratch for the latest graphics architecture, Vulkan can benefit from improved performance by eliminating bottlenecks with multi-threading support from CPUs and providing programmers with more control for GPUs. Reducing driver overhead by allowing programmers to clearly specify intentions using more detailed APIs, and allow multiple threads to create and submit commands in parallel and Reducing shader compilation inconsistencies by switching to a standardized byte code format with a single compiler. Finally, it integrates graphics and computing into a single API to recognize the general-purpose processing capabilities of the latest graphics cards.
There are three preconditions to follow
Vulkan (NVIDA, AMD, Intel) compatible graphic cards and drivers
Expereince in C++ ( RAII, initializer_list, Modern C++11)
Compiler above C++17 (Visual Studio 2017, GCC 7+, Clang 5+)
사실상 Game Engine 을 만들고 싶은건 아니다. Game Engine 을 사용한 어떠한 Product 를 만들고 싶었고, 그 Platform 이 Unreal Engine 이 됬든, Unity 가 됬든 사용하면 된다. 하지만 이미 상용화? 된 엔진들의 확실한 장점은 있지만, 그렇다고 해도, 너무 많은 방대한 정보를 이해하기에는 쉽지 않다. 예를 들어서, DirectX11 에서는 확실히 Low Level API 라고 하지만, 거의 High Level API 이다. 특히나 드라이버(인력사무소)가 거의 많은 작업들을 처리 해주었다. 그에 반대 되서, DirectX12 는 대부분의 작업을 따로 처리해줘야한다 (RootSignature, PipelineState, etc) 그리고 병렬 지원에 대해서도 충분히 이야기할수 있다. CommandList 를 병렬 처리가 가능하다고 한다. (이부분은 실제로 해보진 않았다.)
특히나, Commit 을 하기전에 Stream 방식인지, CommandList 에 일할것과, 일의 양을 명시해서 CommandQueue 에다가 넣어준다. 그리고 OMSetRenderTargets, IASetVertexBuffers 등으로 DX11 에서는 알아서 자동 상태 전이가 되지만, DX12 에서는 D3D12_RESOURCE_BARRIER 를 통해서 상태를 명시적으로 지정해주어야 할 필요가 있다. 이것 말고 등등 오늘은 DX12 의 어려움 또는 DX11 와 비교를 말을 할려는 목적은 아니다. 오늘은 나의 개발 로그를 공유하려고한다.
Motivation
예전부터 내가 직접 만들어보고 싶고, 표현해보고 싶었던게 있었고, 그걸 표현하기위해서, Game Engine 관련되서 Youtube 를 찾아보게 되다가 우연치 않게, Cherno 라는 Youtuber 를 보았다. 이 분은 EA 에서 일을 하다가 이제는 직접적으로 Game Engine Hazel 을 만들고 있다. 꼭 그리고 다른 Contents 도 상대적으로 퀄리티가 있다. 그리고 Walnut 에 보면 아주 좋은 Vulkan 과 Imgui 를 묶어놓은 Template Engine 이 있다. 꼭 추천한다. 그리고 개발하면서 다른 Resource 도 올려놓겠다.
Abstraction Layer
일단 나는 Multiplatform 을 Target 으로 Desktop Application 으로 정했다. 즉 Rendering 부분을 DX12 Backend 와 Vulkan Backend 로 나누어서, 추상화 단계를 거쳤다.
지금의 Project 의 구조를 설명하겠다. (전체적으로 HAL=Hardware Abstraction Layer 를 구상중)이며, 게임 엔진 내부에서 Platform 에 구애 받지 않게 설계 기준을 잡았다. 물론 추상화 계층은 삼각형 그리기 기준으로 일단 추상화를 작업을 진행하였다. 전체적으로 Resource 는 한번 추상화 작업을하고, IRenderBackend 로 부터 DirectX12 으로 할건지, Vulkan 으로 할건지 정의 하였다. 아직 작업할 일은 많지만, 한번에 하지 않으려고 진행중이다.
첫번째로, ImGUI 를 같이 사용하려면, ImGUI 용 descriptor heap 을 따로 만들어줘야한다. Example-DX12 Hook 여기에 보면, 아래 처럼 descriptor 를 만드는걸 확인 할수 있다. 그리고 내 DX12Backend 쪽에서도 이러한 방식으로 진행하고 있다.
삼각형을 그리기만하는데, 삼각형이 그려지질 않는다. 그래서 이걸 RenderDoc 으로 체크를 해보겠다.
삼각형은 완벽하게 그려지고 있다. 하지만 그 다음 Pass 에 보면 없어진다.
이거에 대해서 찾아보다가, RenderTarget 에 둘다 그릴려구 해서 그렇고, 마지막에 Update 하는 부분이 ImGUI 에서 Docking 또는 Viewports Enable 을 했을시에 문제가 있다고 한다. 이럴떄, 기본적으로, ImGUI 에서는 GPU Rendering 상으로 기능을 독릭접인 자원을 활용하기 위해서 따로 만든다고 말을 하였다. 기본적으로 Viewports 를 Enable 했을시에, 새로운 Viewport 의 배경색은 Gray 색깔이라고 한다. 그래서 마지막에 Rendering 을 했을시에, Gray 로 덮어버린다. 즉 ImGUI 는 Texture 기반의 UI 요소를 그린다. (즉, ImGUI 의 내부 관리가 아닌 OS 창으로 Rendering 이 된다.)
둘다 방법론은 같다. 그래서 아래와 같이 결과가 나왔다. 결국에는 해야하는 일은 하나의 RenderTarget 에 병합? (Aggregation) 이 맞는것 같다. 그리고 중요한건 RenderTarget 이 정확하게 ImGUI 와 삼각형 그리는게 맞는지 확인이 필요하다. 아래는 Aggregation 한 결과 이다.
Dependencies
모든 Dependency 는 PCH 에서 참조하고 있다. 참고로 vcpkg 는 premake 에서 아직 disable 하는걸 찾지 못했다. src/vendor 안에 모든 dependency 가 있다.
Rendering API Related
d3d12ma -> Direct3D Memory Allocation Library
dxheaders -> DirectX related headers
dxc -> HLSL compiler
volk -> Vulkan Loader
vulkan -> you need to download sdk from vulkan webpage