μμ§μ΄ μ μμ μΌλ‘ μλνκΈ΄ νμ§λ§, μ¬μ ν λΉν¨μ¨μ μΈ λΆλΆμ΄ μ‘΄μ¬ν©λλ€. μ΄μ μ΄λ¬ν λΆλΆλ€μ κ°μ ν΄ λ³΄κ² μ΅λλ€.
νμ΄λ¨Έ UI
μ±λ₯ μ΅μ νλ₯Ό μμνκΈ° μ μ, κ° μμ μ΄ μΌλ§λ λΉ λ₯΄κ² μνλλμ§ μΈ‘μ ν μ μλ μλ¨μ΄ νμν©λλ€. μ΄λ₯Ό μν΄ std::chronoμ ImGuiλ₯Ό νμ©νμ¬ κ°λ¨ν λ²€μΉλ§νΉ νμ΄λ¨Έ UIλ₯Ό μ€μ νκ² μ΅λλ€. μνλ€λ©΄ Trancyμ κ°μ λꡬλ₯Ό μ¬μ©νλ κ²λ κ°λ₯νμ§λ§, μ¬κΈ°μλ κΈ°λ³Έμ μΈ UI κΈ°λ° νμ΄λ¨Έλ₯Ό ꡬννλ λ°©μμΌλ‘ μ§ννκ² μ΅λλ€. GPUμΈ‘ μ±λ₯ νλ‘νμΌλ§μ νμ΄νλΌμΈ 쿼리 λ± λ³΅μ‘ν μ€μ μ΄ νμνλ―λ‘ μ΄λ²μλ λ€λ£¨μ§ μμ΅λλ€. μ°λ¦¬κ° νμν μμ€μμλ NSightμ κ°μ GPU νλ‘νμΌλ§ λꡬλ₯Ό μ¬μ©νλ νΈμ΄ μ’μ΅λλ€.
λ¨Όμ μκ° κ΄λ ¨ μ 보λ₯Ό μ μ₯ν ꡬ쑰체λ₯Ό vk_engine.hμ μΆκ°ν΄λ΄ μλ€.
struct EngineStats {
float frametime;
int triangle_count;
int drawcall_count;
float scene_update_time;
float mesh_draw_time;
};
VulkanEngine ν΄λμ€μ EngineStats stats;
λ©€λ²λ₯Ό μΆκ°ν©λλ€.
νλ μ μκ°μ μ 체μ μΈ μκ°μ μΈ‘μ νλ λ° μ¬μ©λλ©°, VSyncλ₯Ό μ¬μ©νκ³ μκΈ° λλ¬Έμ λλΆλΆ λͺ¨λν° μ£Όμ¬μ¨μ λ§μΆ°μ Έ μμ κ²μ λλ€. νμ§λ§ κ·Έ μΈμ κ°λ€μ μ±λ₯ μΈ‘μ μ λ μ μ©νκ² μ°μΌ μ μμ΅λλ€.
κ·ΈλΌ νλ μ μκ°μ κ³μ°ν΄λ΄ μλ€.
μμ§μ λ©μΈ 루νμΈ run()
μ μλ μ½λλ₯Ό μΆκ°ν©λλ€.
// main loop
while (!bQuit) {
//begin clock
auto start = std::chrono::system_clock::now();
//everything else
//get clock again, compare with start clock
auto end = std::chrono::system_clock::now();
//convert to microseconds (integer), and then come back to miliseconds
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
stats.frametime = elapsed.count() / 1000.f;
}
auto start = std::chrono::system_clock::now();
λ₯Ό μ¬μ©νλ©΄ νμ¬ μκ°μ λμ μ λ°λλ‘ μΈ‘μ ν μ μμ΅λλ€. μ΄ κ°μ μ΄ν λ€μ νΈμΆλ μμ μ μκ°κ³Ό λΉκ΅νλ©΄, νΉμ ꡬκ°μ΄ μ€νλλ λ° μΌλ§λ κ±Έλ Έλμ§λ₯Ό μ μ μμ΅λλ€. μ΄λ₯Ό λ°λ¦¬μ΄(ms) λ¨μμ μκ°μΌλ‘ λ³ννλ €λ©΄, λ¨Όμ λ§μ΄ν¬λ‘μ΄λ‘ μΊμ€ν
ν λ€ 1000.fλ₯Ό κ³±ν΄μΌ ν©λλ€. μ΄λ κ² νλ©΄ μμμ μ
μ§Έ μ리κΉμ§ ννλλ κ°μ΄ λ©λλ€.
draw_geometry ν¨μμμ ν΄λΉ μ½λλ₯Ό μΆκ°νκ³ λμμ λλ‘μ°μ½ νμμ μΌκ°ν μλ₯Ό κ³μ°νλ μ½λλ λ£μ΄λ³΄κ² μ΅λλ€.
void VulkanEngine::draw_geometry(VkCommandBuffer cmd)
{
//reset counters
stats.drawcall_count = 0;
stats.triangle_count = 0;
//begin clock
auto start = std::chrono::system_clock::now();
/* code */
auto draw = [&](const RenderObject& r) {
/* drawing code */
vkCmdDrawIndexed(cmd, draw.indexCount, 1, draw.firstIndex, 0, 0);
//add counters for triangles and draws
stats.drawcall_count++;
stats.triangle_count += draw.indexCount / 3;
}
/* code */
auto end = std::chrono::system_clock::now();
//convert to microseconds (integer), and then come back to miliseconds
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
stats.mesh_draw_time = elapsed.count() / 1000.f;
}
μμν λ μΉ΄μ΄ν°λ₯Ό 0μΌλ‘ μ΄κΈ°νν©λλ€. κ·Έλ° λ€μ draw λλ€μμ 그리기 λ‘μ§ μ΄νμ λλ‘μ°μ½ νμμ μΌκ°ν κ°μλ₯Ό λμ ν©λλ€. ν¨μμ λ§μ§λ§μλ μ΅μ’ μκ°μ μΈ‘μ νμ¬ stats ꡬ쑰체μ μ μ₯ν©λλ€.
λμΌν μμ
μ update_scene()
ν¨μμμλ μννλ, μμ/μ’
λ£ μκ°μ μΈ‘μ ν΄ scene_update_time
μ μ μ₯ν©λλ€.
μ΄μ ImGuiλ₯Ό μ¬μ©ν΄ μ 보λ₯Ό νλ©΄μ νμν΄μΌ ν©λλ€.
run()
ν¨μμμ ImGui::NewFrame()
κ³Ό ImGui::Render()
μ¬μ΄μ μλ μ½λλ₯Ό μΆκ°νμ¬ μλ‘μ΄ ImGui μ°½μ 그립λλ€.
ImGui::Begin("Stats");
ImGui::Text("frametime %f ms", stats.frametime);
ImGui::Text("draw time %f ms", stats.mesh_draw_time);
ImGui::Text("update time %f ms", stats.scene_update_time);
ImGui::Text("triangles %i", stats.triangle_count);
ImGui::Text("draws %i", stats.drawcall_count);
ImGui::End();
μ¬κΈ°μ μμ§μ μ€ννλ€λ©΄ κ° μ 보λ₯Ό νμΈν μ μμ΅λλ€. νμ¬λ κ²μ¦ λ μ΄μ΄κ° νμ±νλμ΄ μκ³ , λλ²κ·Έ λͺ¨λκ° μΌμ Έμμ κ°λ₯μ±μ΄ λμ΅λλ€. μ»΄νμΌλ¬ μ€μ μμ λ¦΄λ¦¬μ¦ λͺ¨λλ₯Ό νμ±ννκ³ , constexpr bool bUseValidationLayers = true
κ°μ falseλ‘ λ³κ²½ν΄ κ²μ¦ λ μ΄μ΄λ₯Ό λΉνμ±ννμΈμ. λΌμ΄μ 5950x CPU κΈ°μ€μΌλ‘ κ²μ¦ λ μ΄μ΄λ₯Ό λΉνμ±ννλ©΄ draw_timeμ΄ μ½ 6.5msμμ 0.3msλ‘ μ€μ΄λλλ€. μ¬μ΄ μ¬λ°λ₯΄κ² λ λλ§λκ³ μλ€λ©΄ λλ‘μ°μ½ νμλ 1700μΌλ‘ νμλ κ²μ
λλ€.
μ λ ¬ ν 그리기
νμ¬ μ°λ¦¬λ λ§€ λλ‘μ°μ½λ§λ€ νμ΄νλΌμΈ λ±μ λ°λ³΅ν΄μ λ°μΈλ©νκ³ μκΈ° λλ¬Έμ, νμ μ΄μμΌλ‘ λ§μ Vulkan νΈμΆμ μννκ³ μμ΅λλ€. λ°μΈλ©λ μνλ₯Ό μΆμ νκ³ , μνκ° μ€μ λ‘ λ³κ²½λ λμλ§ ν΄λΉ Vulkan ν¨μλ₯Ό νΈμΆνλλ‘ ν΄μΌ ν©λλ€.
μ΄μ κΈμμ μκ°νλ draw() λλ€λ₯Ό μμ νμ¬ μν μΆμ κΈ°λ₯μ μΆκ°ν κ²μ λλ€. νλΌλ―Έν°κ° λ³κ²½λ κ²½μ°μλ§ Vulkan ν¨μλ₯Ό νΈμΆνλλ‘ κ΅¬ννκ² μ΅λλ€.
//defined outside of the draw function, this is the state we will try to skip
MaterialPipeline* lastPipeline = nullptr;
MaterialInstance* lastMaterial = nullptr;
VkBuffer lastIndexBuffer = VK_NULL_HANDLE;
auto draw = [&](const RenderObject& r) {
if (r.material != lastMaterial) {
lastMaterial = r.material;
//rebind pipeline and descriptors if the material changed
if (r.material->pipeline != lastPipeline) {
lastPipeline = r.material->pipeline;
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r.material->pipeline->pipeline);
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,r.material->pipeline->layout, 0, 1,
&globalDescriptor, 0, nullptr);
VkViewport viewport = {};
viewport.x = 0;
viewport.y = 0;
viewport.width = (float)_windowExtent.width;
viewport.height = (float)_windowExtent.height;
viewport.minDepth = 0.f;
viewport.maxDepth = 1.f;
vkCmdSetViewport(cmd, 0, 1, &viewport);
VkRect2D scissor = {};
scissor.offset.x = 0;
scissor.offset.y = 0;
scissor.extent.width = _windowExtent.width;
scissor.extent.height = _windowExtent.height;
vkCmdSetScissor(cmd, 0, 1, &scissor);
}
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r.material->pipeline->layout, 1, 1,
&r.material->materialSet, 0, nullptr);
}
//rebind index buffer if needed
if (r.indexBuffer != lastIndexBuffer) {
lastIndexBuffer = r.indexBuffer;
vkCmdBindIndexBuffer(cmd, r.indexBuffer, 0, VK_INDEX_TYPE_UINT32);
}
// calculate final mesh matrix
GPUDrawPushConstants push_constants;
push_constants.worldMatrix = r.transform;
push_constants.vertexBuffer = r.vertexBufferAddress;
vkCmdPushConstants(cmd, r.material->pipeline->layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(GPUDrawPushConstants), &push_constants);
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
//stats
stats.drawcall_count++;
stats.triangle_count += r.indexCount / 3;
};
λ§μ§λ§μΌλ‘ μ¬μ©ν νμ΄νλΌμΈ, λ¨Έν 리μΌ, μΈλ±μ€ λ²νΌλ₯Ό μ μ₯ν©λλ€. λ¨Όμ νμ΄νλΌμΈμ΄ λ°λμλμ§ νμΈνκ³ , λ°λμλ€λ©΄ νμ΄νλΌμΈμ λ€μ λ°μΈλ©νμ¬ μ μ λμ€ν¬λ¦½ν° μ λ ν¨κ» λ°μΈλ©ν©λλ€. μ΄λ SetViewportμ SetScissor λͺ λ Ήλ λ€μ νΈμΆν΄μΌ ν©λλ€.
κ·Έ λ€μ, λ¨Έν 리μΌμ΄ λ³κ²½λλ©΄ λ¨Έν λ¦¬μΌ νλΌλ―Έν°μ ν μ€μ³λ₯Ό μν λμ€ν¬λ¦½ν° μ μ λ°μΈλ©ν©λλ€. λ§μ§λ§μΌλ‘ μΈλ±μ€ λ²νΌκ° λ³κ²½λλ©΄ ν΄λΉ μΈλ±μ€ λ²νΌλ λ€μ λ°μΈλ©ν©λλ€.
μ΄μ λ¨ 2κ°μ νμ΄νλΌμΈλ§ μ¬μ©λλ―λ‘ vkCmdBindPipeline νΈμΆμ΄ λν μ€μ΄λ€μ΄ μ±λ₯ ν₯μμ κΈ°λν μ μμ΅λλ€. νμ§λ§ μ¬κΈ°μ λ μ΅μ νν΄λ³΄κ² μ΅λλ€. λΌμ΄μ 5950x μ»΄ν¨ν° κΈ°μ€μΌλ‘ draw_geometryμ μ€ν μκ°μ΄ μ λ°μΌλ‘ λ¨μΆλμμ΅λλ€.
νΈμΆ μλ₯Ό μ΅μννκΈ° μν΄ λ λλ§ν κ°μ²΄λ€μ μ΄λ¬ν νλΌλ―Έν° κΈ°μ€μΌλ‘ μ λ ¬ν΄μΌ ν©λλ€. μ΄ μ λ ¬μ λΆν¬λͺ κ°μ²΄μλ§ μννκ² μ΅λλ€. ν¬λͺ ν κ°μ²΄λ€μ κΉμ΄ μ λ ¬μ΄ νμνμ§λ§, μμ§μ ν΄λΉ μ 보λ₯Ό κ°μ§κ³ μμ§ μκΈ° λλ¬Έμ λλ€.
μ λ ¬μ ꡬνν λ, κ°μ²΄ μμ²΄κ° ν¬κΈ° λλ¬Έμ draw λ°°μ΄ κ·Έ μ체λ₯Ό μ λ ¬νμ§λ μμ κ²μ λλ€. λμ , ν΄λΉ λ°°μ΄μ μ°Έμ‘°νλ μΈλ±μ€ λ°°μ΄μ μ λ ¬νκ² μ΅λλ€. μ΄λ λν μμ§μμλ μΌλ°μ μΌλ‘ μ¬μ©λλ κΈ°λ²μ λλ€.
draw_geometry() ν¨μμ μμ λΆλΆμ λ€μ μ½λλ₯Ό μΆκ°νμΈμ.
std::vector<uint32_t> opaque_draws;
opaque_draws.reserve(mainDrawContext.OpaqueSurfaces.size());
for (uint32_t i = 0; i < mainDrawContext.OpaqueSurfaces.size(); i++) {
opaque_draws.push_back(i);
}
// sort the opaque surfaces by material and mesh
std::sort(opaque_draws.begin(), opaque_draws.end(), [&](const auto& iA, const auto& iB) {
const RenderObject& A = mainDrawContext.OpaqueSurfaces[iA];
const RenderObject& B = mainDrawContext.OpaqueSurfaces[iB];
if (A.material == B.material) {
return A.indexBuffer < B.indexBuffer;
}
else {
return A.material < B.material;
}
});
std::algorithmsμλ opaque_draws 벑ν°λ₯Ό μ λ ¬νλ λ° μ μ©ν μ λ ¬ ν¨μκ° μμ΅λλ€. μ¬κΈ°μ <
μ°μ°μλ₯Ό μ μν λλ€λ₯Ό μ λ¬νλ©΄ ν¨μ¨μ μΌλ‘ μ λ ¬ν μ μμ΅λλ€.
λ¨Όμ draw λ°°μ΄μ μΈλ±μ€λ₯Ό κΈ°μ€μΌλ‘ λ¨Έν 리μΌμ΄ κ°μμ§ νμΈνκ³ , κ°λ€λ©΄ μΈλ±μ€ λ²νΌλ₯Ό κΈ°μ€μΌλ‘ μ λ ¬ν©λλ€. λ§μ½ λ¨Έν 리μΌμ΄ λ€λ₯΄λ€λ©΄ λ¨Έν λ¦¬μΌ ν¬μΈν° κ·Έ μ체λ₯Ό λΉκ΅ν©λλ€. μ΄λ₯Ό μννλ λ λ€λ₯Έ λ°©λ²μ μ λ ¬ ν€λ₯Ό κ³μ°νλ κ²μ λλ€. μ΄ κ²½μ° opaque_drawsλ 20λΉνΈμ μΈλ±μ€μ 44λΉνΈμ μ λ ¬ ν€(λλ ν΄μ)λ‘ κ΅¬μ±λ μ μμ΅λλ€. μ΄ λ°©μμ λ³΄λ€ λΉ λ₯Έ μ λ ¬ μκ³ λ¦¬μ¦μ μ¬μ©ν μ μκΈ° λλ¬Έμ μ±λ₯μ λ μ 리ν©λλ€.
μ΄μ μ λ ¬λ λ°°μ΄μ κΈ°λ°μΌλ‘ λλ‘μ°λ₯Ό μνν©λλ€. κΈ°μ‘΄μ λΆν¬λͺ κ°μ²΄ λλ‘μ° λ°λ³΅λ¬Έμ μλ μ½λλ‘ λ체ν©μλ€.
for (auto& r : opaque_draws) {
draw(mainDrawContext.OpaqueSurfaces[r]);
}
μ΄λ κ² νλ©΄ λ λλ¬κ° λμ€ν¬λ¦½ν° μ λ°μΈλ© μλ₯Ό μ΅μνν μ μμ΅λλ€. λ¨Έν λ¦¬μΌ λ¨μλ‘ μ²λ¦¬νκΈ° λλ¬Έμ λλ€. μΈλ±μ€ λ²νΌ λ°μΈλ©μ μ¬μ ν μ²λ¦¬ν΄μΌ νμ§λ§, μ΄λ λΉκ΅μ λΉ λ₯΄κ² μ νλ©λλ€.
μ΄λ κ² νλ©΄ μμ§μ μ±λ₯μ΄ μ½κ° ν₯μλ μ μμ΅λλ€. νμ§λ§ νλμ μ¬λ§ λ λλ§νλ κ²½μ° μ λ ¬ λΉμ© λλ¬Έμ μ±λ₯ ν₯μ ν¨κ³Όκ° κ±°μ μμ μ μμ΅λλ€. νμ¬ μ°λ¦¬λ λΉν¨μ¨μ μΈ λ¨μΌ μ°λ λ μ λ ¬μ μννκ³ μκΈ° λλ¬Έμ, μ λ ¬ λΉμ©μ΄ Vulkan νΈμΆ κ°μλ‘ μ»λ μ±λ₯ ν₯μμ μμν μλ μμ΅λλ€. λ°λμ μ¬μ νλ‘νμΌλ§νμ¬ μ΄ κΈ°λ₯μ νμ±νν μ§ μ¬λΆλ₯Ό κ²°μ νμΈμ.
μ±λ₯μ΄ κ°μ λμμ§λ§ μμ§ κ°μ ν λΆλΆμ΄ λ¨μμμ΅λλ€. νμ¬ μ°λ¦¬λ μΉ΄λ©λΌ λ€μ μλ κ°μ²΄λ€κΉμ§ λ λλ§νκ³ μμ΄, CPUμ GPU λͺ¨λμμ λΆνμνκ² λ§μ κ°μ²΄λ₯Ό μ²λ¦¬νκ³ μμ΅λλ€. μ΄λ₯Ό νλ¬μ€ν 컬λ§μ ν΅ν΄ κ°μ ν μ μμ΅λλ€.
νλ¬μ€ν 컬λ§
νμ¬λ λ§΅μ μλ λͺ¨λ κ°μ²΄λ₯Ό λ λλ§νκ³ μμ§λ§, μμΌ λ°μ μλ κ²λ€κΉμ§ 그릴 νμλ μμ΅λλ€. μ°λ¦¬λ μ΄λ―Έ 그릴 κ°μ²΄μ λͺ©λ‘μ κ°μ§κ³ μμΌλ―λ‘, μ΄λ₯Ό νν°λ§νμ¬ μμΌ μμ λ€μ΄μ€λ κ°μ²΄λ§ λ λλ§νκ³ , κ·Έλ μ§ μμ κ°μ²΄λ 건λλ°κ² μ΅λλ€. νν°λ§ λΉμ©μ΄ κ°μ²΄λ₯Ό λ λλ§νλ λΉμ©λ³΄λ€ μ κΈ°λ§ νλ€λ©΄, μ±λ₯μμ μ΄λμ μ»μ μ μμ΅λλ€.
νλ¬μ€ν 컬λ§μ μννλ λ°©λ²μλ μ¬λ¬ κ°μ§κ° μμ§λ§, νμ¬ μ°λ¦¬κ° κ°μ§ λ°μ΄ν°μ μν€ν μ²μ μ ν©ν λ°©μμ OBB(Oriented Bounding Box)λ₯Ό μ¬μ©νλ κ²μ λλ€. κ° GeoSurfaceλ§λ€ λ°μ΄λ© λ°μ€λ₯Ό κ³μ°νκ³ , κ·Έκ²μ΄ μμΌμ λ€μ΄μ€λμ§ νμΈν κ²μ λλ€.
vk_loaders.hμ ꡬ쑰체μ λ°μ΄λ© μ 보λ₯Ό μΆκ°νμΈμ.
struct Bounds {
glm::vec3 origin;
float sphereRadius;
glm::vec3 extents;
};
struct GeoSurface {
uint32_t startIndex;
uint32_t count;
Bounds bounds;
std::shared_ptr<GLTFMaterial> material;
};
RenderObjectμ Bounds ꡬ쑰체λ₯Ό μΆκ°ν©λλ€.
struct RenderObject {
uint32_t indexCount;
uint32_t firstIndex;
VkBuffer indexBuffer;
MaterialInstance* material;
Bounds bounds;
glm::mat4 transform;
VkDeviceAddress vertexBufferAddress;
};
Bounds ꡬ쑰체λ μ€μ¬μ , ν¬κΈ°, ꡬμ λ°μ§λ¦μ ν¬ν¨ν©λλ€. ꡬμ λ°μ§λ¦μ λ€λ₯Έ νλ¬μ€ν μ»¬λ§ μκ³ λ¦¬μ¦μ΄λ κ·Έ μΈ λ€λ₯Έ μ©λλ‘λ μ¬μ©λ μ μμ΅λλ€.
μ΄λ₯Ό κ³μ°νκΈ° μν΄, ν΄λΉ λ‘μ§μ loader μ½λμ μΆκ°ν΄μΌ ν©λλ€.
μ΄ μ½λλ λ©μ λ°μ΄ν°λ₯Ό λΆλ¬μ€λ λ°λ³΅λ¬Έμ λλΆλΆμ μλ loadGLTF ν¨μ λ΄λΆμ μμ±ν κ²μ λλ€.
//code that writes vertex buffers
//loop the vertices of this surface, find min/max bounds
glm::vec3 minpos = vertices[initial_vtx].position;
glm::vec3 maxpos = vertices[initial_vtx].position;
for (int i = initial_vtx; i < vertices.size(); i++) {
minpos = glm::min(minpos, vertices[i].position);
maxpos = glm::max(maxpos, vertices[i].position);
}
// calculate origin and extents from the min/max, use extent lenght for radius
newSurface.bounds.origin = (maxpos + minpos) / 2.f;
newSurface.bounds.extents = (maxpos - minpos) / 2.f;
newSurface.bounds.sphereRadius = glm::length(newSurface.bounds.extents);
newmesh->surfaces.push_back(newSurface);
MeshNode::Draw() ν¨μμμ Boundsκ° RenderOBjectλ‘ λ³΅μ¬λλλ‘ ν΄μΌ ν©λλ€. μ΄λ λ€μκ³Ό κ°μ΄ λ³΄μΌ κ²μ λλ€.
void MeshNode::Draw(const glm::mat4& topMatrix, DrawContext& ctx) {
glm::mat4 nodeMatrix = topMatrix * worldTransform;
for (auto& s : mesh->surfaces) {
RenderObject def;
def.indexCount = s.count;
def.firstIndex = s.startIndex;
def.indexBuffer = mesh->meshBuffers.indexBuffer.buffer;
def.material = &s.material->data;
def.bounds = s.bounds;
def.transform = nodeMatrix;
def.vertexBufferAddress = mesh->meshBuffers.vertexBufferAddress;
if (s.material->data.passType == MaterialPass::Transparent) {
ctx.TransparentSurfaces.push_back(def);
} else {
ctx.OpaqueSurfaces.push_back(def);
}
}
// recurse down
Node::Draw(topMatrix, ctx);
}
μ΄μ GeoSurfaceμ Boundsκ° μ€μ λμμΌλ―λ‘, RenderObjectκ° μμΌμ λ€μ΄μ€λ μ§ νμΈν΄μΌ ν©λλ€. μλ ν¨μλ₯Ό vk_engine.cppμ μ μ ν¨μλ‘ μΆκ°νμΈμ.
bool is_visible(const RenderObject& obj, const glm::mat4& viewproj) {
std::array<glm::vec3, 8> corners {
glm::vec3 { 1, 1, 1 },
glm::vec3 { 1, 1, -1 },
glm::vec3 { 1, -1, 1 },
glm::vec3 { 1, -1, -1 },
glm::vec3 { -1, 1, 1 },
glm::vec3 { -1, 1, -1 },
glm::vec3 { -1, -1, 1 },
glm::vec3 { -1, -1, -1 },
};
glm::mat4 matrix = viewproj * obj.transform;
glm::vec3 min = { 1.5, 1.5, 1.5 };
glm::vec3 max = { -1.5, -1.5, -1.5 };
for (int c = 0; c < 8; c++) {
// project each corner into clip space
glm::vec4 v = matrix * glm::vec4(obj.bounds.origin + (corners[c] * obj.bounds.extents), 1.f);
// perspective correction
v.x = v.x / v.w;
v.y = v.y / v.w;
v.z = v.z / v.w;
min = glm::min(glm::vec3 { v.x, v.y, v.z }, min);
max = glm::max(glm::vec3 { v.x, v.y, v.z }, max);
}
// check the clip space box is within the view
if (min.z > 1.f || max.z < 0.f || min.x > 1.f || max.x < -1.f || min.y > 1.f || max.y < -1.f) {
return false;
} else {
return true;
}
}
μ΄λ νλ¬μ€ν 컬λ§μ μνν μ μλ μ¬λ¬ λ°©λ² μ€ νλμ λλ€. μ΄ λ°©μμ λ©μ 곡κ°μ λ°μ΄λ© λ°μ€ 8κ°μ κΌμ§μ μ κ°μ²΄ νλ ¬κ³Ό λ·° νλ‘μ μ νλ ¬μ μ¬μ©ν΄ μ€ν¬λ¦° 곡κ°μΌλ‘ λ³ννμ¬ μλν©λλ€. λ³νλ κΌμ§μ μΌλ‘λΆν° μ€ν¬λ¦° 곡κ°μ λ°μ΄λ© λ°μ€λ₯Ό κ³μ°ν λ€, ν΄λΉ λ°μ€κ° ν΄λ¦½ κ³΅κ° λ·° μμ μλμ§ νμΈν©λλ€. μ΄λ¬ν κ²½κ³ κ³μ° λ°©μμ λ€λ₯Έ 곡μλ€μ λΉν΄ λ€μ λλ¦° νΈμ΄λ©°, μ€μ λ‘λ 보μ΄μ§ μλ κ°μ²΄λ₯Ό 보μ΄λ κ²μΌλ‘ νλ¨νλ false positiveκ° λ°μν μ μμ΅λλ€. κ° λ°©μλ§λ€ νΈλ μ΄λμ€νκ° μ‘΄μ¬νλ©°, μ΄ λ°©μμ λ¨μμ±κ³Ό μ μ μ °μ΄λμμ μννλ μμ κ³Όμ μ μ¬μ± λλ¬Έμ μ ννμμ΅λλ€.
μ΄ ν¨μλ₯Ό μ¬μ©νλ €λ©΄, opaque_draws λ°°μ΄μ μ±μ°λ 루νλ₯Ό μμ ν΄μΌ ν©λλ€.
for (int i = 0; i < mainDrawContext.OpaqueSurfaces.size(); i++) {
if (is_visible(mainDrawContext.OpaqueSurfaces[i], sceneData.viewproj)) {
opaque_draws.push_back(i);
}
}
μ΄μ i
λ₯Ό μΆκ°νλ λμ , κ°μ²΄κ° μμΌμ λ€μ΄μ€λ μ§ νμΈν©λλ€.
μ΄μ λ λλ¬λ μμΌ μΈλΆμ κ°μ²΄λ€μ 건λλΈ κ²μ
λλ€. νλ©΄μ 보μ΄λ κ²°κ³Όλ λμΌνμ§λ§, λλ‘μ°μ½ μκ° μ€μ΄λ€κ³ μ±λ₯μ λ λΉ¨λΌμ§ κ²μ
λλ€. λ§μ½ μκ°μ μ΄μμ΄ λ°μνλ€λ©΄, GeoSurface
μ λ°μ΄λ© λ°μ€ μμ± κ³Όμ κ³Ό is_visible ν¨μμ μ€νκ° μλμ§ λ€μ νμΈν΄λ³΄μΈμ
ν¬λͺ ν κ°μ²΄μ λν΄μλ λμΌν 컬λ§κ³Ό μ λ ¬μ μ μ©νλ μ½λλ 건λλ°μμ΅λλ€. λΆν¬λͺ κ°μ²΄μ μ μ©ν λ°©μκ³Ό λμΌνλ―λ‘ μ§μ ꡬνν΄λ³΄μΈμ.
ν¬λͺ ν κ°μ²΄μ κ²½μ°, κ²½κ³μ μΉ΄λ©λΌ κ°μ 거리λ₯Ό κΈ°μ€μΌλ‘ μ λ ¬νλ λ°©μμΌλ‘ μ λ ¬ μ½λλ₯Ό λ³κ²½ν΄μΌ λ μ ννκ² λ λλ§ν μ μμ΅λλ€. λ€λ§ κΉμ΄ κΈ°μ€ μ λ ¬μ νμ΄νλΌμΈ κΈ°μ€ μ λ ¬κ³Ό νΈνλμ§ μκΈ° λλ¬Έμ, μ΄λ€ λ°©μμ΄ μμ μ μν©μ λ μ ν©νμ§ κ²°μ ν΄μΌ ν©λλ€.
μΉ΄λ©λΌλ₯Ό νμ μν€λ©΄μ μ€ν― μ°½μμ λλ‘μ°μ½ μμ μΌκ°ν κ°μκ° λ³ννλ κ²μ νμΈν μ μμ κ²μ λλ€. νΉν κ°μ²΄λ₯Ό λ°λΌλ³΄μ§ μλ μν©μμλ μ±λ₯μ΄ λμ λκ² ν₯μλ κ²μ λλ€.
λ°λ§΅ μμ±νκΈ°
ν μ€μ³λ₯Ό λΆλ¬μ¬ λ λ°λ§΅μ μμ±νμ§λ μμμ΅λλ€. OpenGLκ³Όλ λ¬λ¦¬, Vulkanμμλ λ°λ§΅μ μλμΌλ‘ μμ±ν΄μ£Όλ λ¨μΌ νΈμΆ ν¨μκ° μ‘΄μ¬νμ§ μκΈ° λλ¬Έμ, μ°λ¦¬κ° μ§μ μμ±ν΄μ£Όμ΄μΌ ν©λλ€.
create_image
λ μ΄λ―Έ λ°λ§΅μ μ§μνλλ‘ μμ±λμ΄ μμ§λ§, μ€μ λ°μ΄ν°λ₯Ό μ
λ‘λ νλ κ²μ μμ νμ¬ λ°λ§΅μ μμ±νλλ‘ ν΄μΌ ν©λλ€. μ΄λ₯Ό μν΄ ν΄λΉ ν¨μλ₯Ό λ³κ²½νκ² μ΅λλ€.
AllocatedImage VulkanEngine::create_image(void* data, VkExtent3D size, VkFormat format, VkImageUsageFlags usage, bool mipmapped)
{
size_t data_size = size.depth * size.width * size.height * 4;
AllocatedBuffer uploadbuffer = create_buffer(data_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
memcpy(uploadbuffer.info.pMappedData, data, data_size);
AllocatedImage new_image = create_image(size, format, usage | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, mipmapped);
immediate_submit([&](VkCommandBuffer cmd) {
vkutil::transition_image(cmd, new_image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
VkBufferImageCopy copyRegion = {};
copyRegion.bufferOffset = 0;
copyRegion.bufferRowLength = 0;
copyRegion.bufferImageHeight = 0;
copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copyRegion.imageSubresource.mipLevel = 0;
copyRegion.imageSubresource.baseArrayLayer = 0;
copyRegion.imageSubresource.layerCount = 1;
copyRegion.imageExtent = size;
// copy the buffer into the image
vkCmdCopyBufferToImage(cmd, uploadbuffer.buffer, new_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
©Region);
if (mipmapped) {
vkutil::generate_mipmaps(cmd, new_image.image,VkExtent2D{new_image.imageExtent.width,new_image.imageExtent.height});
} else {
vkutil::transition_image(cmd, new_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
});
destroy_buffer(uploadbuffer);
return new_image;
}
immediate_submit
λ μ΄μ ν΄λΉ μ΄λ―Έμ§μ λ°λ§΅μ μ¬μ©νκ³ μΆμ κ²½μ° vkutil::generate_mipmaps()
ν¨μλ₯Ό νΈμΆν μ μμ΅λλ€. μ΄ ν¨μλ₯Ό vk_images.hμ μΆκ°ν©μλ€.
namespace vkutil {
void generate_mipmaps(VkCommandBuffer cmd, VkImage image, VkExtent2D imageSize);
}
λ°λ§΅μ μμ±νλ λ°©λ²μλ μ¬λ¬ κ°μ§κ° μμ΅λλ€. λ°λμ λΆλ¬μ€λ μμ μ μμ±ν νμλ μμΌλ©°, λ°λ§΅μ΄ μ¬μ μμ±λμ΄ μλ KTX νΉμ DDS κ°μ ν¬λ§·μ μ¬μ©νλ κ²λ κ°λ₯ν©λλ€. κ°μ₯ λ§μ΄ μ¬μ©λλ λ°©λ² μ€ νλλ μ»΄ν¨νΈ μ °μ΄λλ₯Ό μ΄μ©ν΄ μ¬λ¬ λ 벨μ ν λ²μ μμ±νλ λ°©μμ΄λ©°, μ΄λ μ±λ₯ ν₯μμ λμμ΄ λ μ μμ΅λλ€. μ°λ¦¬κ° μ¬μ©νλ λ°©μμ VkCmdImageBlit νΈμΆμ μ°μμΌλ‘ μννμ¬ λ°λ§΅μ μμ±νλ κ²μ λλ€
κ° λ°λ§΅ λ 벨μ λν΄, μ΄μ λ 벨μ μ΄λ―Έμ§λ₯Ό λ€μ λ λ²¨λ‘ λ³΅μ¬νλ©΄μ ν΄μλλ₯Ό μ λ°μΌλ‘ μ€μ¬μΌ ν©λλ€. μ΄ κ³Όμ μμ λ³΅μ¬ μλ³ΈμΈ λ°λ§΅ λ 벨μ λ μ΄μμμ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
λ‘ μ νν΄μΌ ν©λλ€. λͺ¨λ 볡μ¬κ° μλ£λλ©΄ μ 체 λ°λ§΅ λ 벨μ λν΄ λ λ€λ₯Έ 배리μ΄λ₯Ό μΆκ°νκ³ , μ΄λ―Έμ§μ λ μ΄μμμ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
λ‘ μ νν΄μΌ ν©λλ€.
μμ¬ μ½λλ λ€μκ³Ό κ°μ΅λλ€.
//image already comes with layout VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL on all mipmap levels from image creation
int miplevels = calculate_mip_levels(imageSize);
for (int mip = 0; mip < mipLevels; mip++) {
barrier( image.mips[mip] , VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
//not the last level
if (mip < mipLevels - 1)
{
copy_image(image.mips[mip], image.mips[mip+1];)
}
}
barrier( image , VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
μ€μ μ½λλ₯Ό μ΄ν΄λ΄ μλ€.
void vkutil::generate_mipmaps(VkCommandBuffer cmd, VkImage image, VkExtent2D imageSize)
{
int mipLevels = int(std::floor(std::log2(std::max(imageSize.width, imageSize.height)))) + 1;
for (int mip = 0; mip < mipLevels; mip++) {
VkExtent2D halfSize = imageSize;
halfSize.width /= 2;
halfSize.height /= 2;
VkImageMemoryBarrier2 imageBarrier { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, .pNext = nullptr };
imageBarrier.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
imageBarrier.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT;
imageBarrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
imageBarrier.dstAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT | VK_ACCESS_2_MEMORY_READ_BIT;
imageBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
imageBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageBarrier.subresourceRange = vkinit::image_subresource_range(aspectMask);
imageBarrier.subresourceRange.levelCount = 1;
imageBarrier.subresourceRange.baseMipLevel = mip;
imageBarrier.image = image;
VkDependencyInfo depInfo { .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .pNext = nullptr };
depInfo.imageMemoryBarrierCount = 1;
depInfo.pImageMemoryBarriers = &imageBarrier;
vkCmdPipelineBarrier2(cmd, &depInfo);
if (mip < mipLevels - 1) {
VkImageBlit2 blitRegion { .sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2, .pNext = nullptr };
blitRegion.srcOffsets[1].x = imageSize.width;
blitRegion.srcOffsets[1].y = imageSize.height;
blitRegion.srcOffsets[1].z = 1;
blitRegion.dstOffsets[1].x = halfSize.width;
blitRegion.dstOffsets[1].y = halfSize.height;
blitRegion.dstOffsets[1].z = 1;
blitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blitRegion.srcSubresource.baseArrayLayer = 0;
blitRegion.srcSubresource.layerCount = 1;
blitRegion.srcSubresource.mipLevel = mip;
blitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blitRegion.dstSubresource.baseArrayLayer = 0;
blitRegion.dstSubresource.layerCount = 1;
blitRegion.dstSubresource.mipLevel = mip + 1;
VkBlitImageInfo2 blitInfo {.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2, .pNext = nullptr};
blitInfo.dstImage = image;
blitInfo.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
blitInfo.srcImage = image;
blitInfo.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
blitInfo.filter = VK_FILTER_LINEAR;
blitInfo.regionCount = 1;
blitInfo.pRegions = &blitRegion;
vkCmdBlitImage2(cmd, &blitInfo);
imageSize = halfSize;
}
}
// transition all mip levels into the final read_only layout
transition_image(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
배리μ΄λ transition_image
μμμ μ μ¬νλ©°, blit λν copy_image_to_image
μμ νλ κ²κ³Ό μ μ¬νμ§λ§, λ°λ§΅ λ 벨μ μ²λ¦¬νλ€λ μ λ§ λ€λ¦
λλ€. μ΄ ν¨μλ λ κΈ°λ₯μ κ²°ν©ν ννλΌκ³ λ³Ό μ μμ΅λλ€
κ° λ°λ³΅λ¬Έμμλ μ΄λ―Έμ§ ν¬κΈ°λ₯Ό μ λ°μΌλ‘ λλμ΄ κ°λ©°, 볡μ¬ν λ°λ§΅ λ 벨μ μ ννκ³ , νμ¬ λ°λ§΅ λ 벨μμ λ€μ λ λ²¨λ‘ VkCmdBlitμ μνν©λλ€.
vk_loader.cppμ load_image
μ μλ create_image
νΈμΆ μ λ§μ§λ§ μΈμλ₯Ό trueλ‘ μ€μ νλμ§ νμΈνμ¬, λ°λ§΅μ΄ μ€μ λ‘ μμ±λλλ‘ ν΄μ£ΌμΈμ.
μ΄λ κ² νλ©΄ νμν λ°λ§΅μ΄ μλμΌλ‘ μμ±λ©λλ€. μ΄λ―Έ μνλ¬λ μ¬λ°λ₯Έ μ€μ μΌλ‘ μμ±λμ΄ μμΌλ―λ‘, λ³λμ μμ μμ΄ μ μλν κ²μ λλ€.