##잿빛 에트나
이번에는 다음 두 개의 큐브를 사용해 봅시다:
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(0.0, 0.0, 0.1))
* na::Matrix4::new_scaling(0.1))
.into(),
colour: [0.2, 0.4, 1.0],
});
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(0.05, 0.05, 0.0))
* na::Matrix4::new_scaling(0.1))
.into(),
colour: [1.0, 1.0, 0.2],
});
두 큐브는 겹쳐져 있고, 파란색 큐브가 약간 더 뒤에 있습니다. 사진에서도 정확히 그렇게 보입니다. 좋습니다. 한번 다른 시도를 해보죠:
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(0.05, 0.05, 0.0))
* na::Matrix4::new_scaling(0.1))
.into(),
colour: [1.0, 1.0, 0.2],
});
cube.insert_visibly(InstanceData {
modelmatrix: (na::Matrix4::new_translation(&na::Vector3::new(0.0, 0.0, 0.1))
* na::Matrix4::new_scaling(0.1))
.into(),
colour: [0.2, 0.4, 1.0],
});
이제 파란색 상자가 다른 상자 앞에 그려집니다. 하지만 좌표상으로는 여전히 더 뒤에 있어야 합니다. 좌표는 중요하지 않고, 그리는 순서가 중요하다고요?! 이상하네요. 무슨 일일까요?
이 정점들을 그래픽 파이프라인으로 보낼 때 무슨 일이 일어날까요? 정점 셰이더(vertex shader)는 실제 좌표를 계산하고, 이 좌표들은 “화면의 이 지점을 칠해야 한다”는 명령어 집합으로 변환됩니다. 마지막으로 프래그먼트 셰이더(fragment shader)가 이 칠하기 작업을 수행합니다.
이 과정 어디에도 “이 점이 다른 점 뒤에 가려져 있는가? 그렇다면 그리지 말 것”과 같은 확인 절차가 포함되어 있지 않습니다. 우리는 이런 확인 절차를 추가해야 합니다.
각 프래그먼트를 그릴 때 z-좌표를 저장하고, 이전에 그려진 객체에 가려지지 않은 경우에만 칠하는 기능을 추가할 수 있습니다. 이 ‘기능’이 바로 뎁스 버퍼(depth buffer)입니다.
우리는 뎁스 버퍼에 렌더링할 것이므로, 렌더 패스(render pass) 중에 이 버퍼가 필요합니다. 이는 우리가 설정했던 프레임버퍼(framebuffer)와 관련이 있어 보입니다. 거기에도 Image와 ImageView가 포함되었었죠. 여기서도 비슷한 것이 필요할 겁니다.
스왑체인(swapchain)의 경우, 색상 렌더링을 위한 Image들은 자동으로 설정되었습니다. (스왑체인을 생성한 후 이 이미지들을 가져올 수 있었습니다.) 뎁스 이미지는 우리가 직접 생성해야 합니다.
이것을 다른 스왑체인 관련 항목들과 함께 넣어봅시다:
struct SwapchainDongXi {
swapchain_loader: ash::extensions::khr::Swapchain,
swapchain: vk::SwapchainKHR,
images: Vec<vk::Image>,
imageviews: Vec<vk::ImageView>,
depth_image: vk::Image,
depth_image_allocation: vk_mem::Allocation,
depth_image_allocation_info: vk_mem::AllocationInfo,
depth_imageview: vk::ImageView,
framebuffers: Vec<vk::Framebuffer>,
surface_format: vk::SurfaceFormatKHR,
extent: vk::Extent2D,
image_available: Vec<vk::Semaphore>,
rendering_finished: Vec<vk::Semaphore>,
may_begin_drawing: Vec<vk::Fence>,
amount_of_images: u32,
current_image: usize,
}
SwapChainDongXi::init() 내부에서, 먼저 이미지를 생성합니다.
let extent3d = vk::Extent3D {
width: extent.width,
height: extent.height,
depth: 1,
};
let depth_image_info = vk::ImageCreateInfo::builder()
.image_type(vk::ImageType::TYPE_2D)
.format(vk::Format::D32_SFLOAT)
.extent(extent3d)
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.queue_family_indices(&queuefamilies);
let allocation_info = vk_mem::AllocationCreateInfo {
usage: vk_mem::MemoryUsage::GpuOnly,
..Default::default()
};
let (depth_image, depth_image_allocation, depth_image_allocation_info) =
allocator.create_image(&depth_image_info, &allocation_info)?;
image_type은 여전히 2D 이미지이고, format은 이름에 ‘D’가 포함된 형식입니다(‘depth’를 의미합니다 — 참고: 32비트 버전은 성능 저하가 따르므로, 더 높은 정밀도가 필요하지 않다면 실제 애플리케이션에서는 24비트가 더 합리적일 것입니다). 이 함수에는 이미 스왑체인 이미지의 너비/높이인 extent가 있는데, 여기서는 너비, 높이, 깊이가 필요합니다. 밉매핑(mipmapping)은 없고, 레이어는 하나, 멀티샘플링도 없고, 큐 패밀리도 하나만 사용합니다(SharingMode::EXCLUSIVE와 이전에 사용했던 그래픽 큐 패밀리만 담긴 배열인 &queuefamilies). 메모리는 GPU에만 상주해도 되므로, 우리가 접근할 필요는 없습니다.
일부 옵션들은 우리가 스왑체인 생성에 사용했던 것들과 매우 유사합니다.
그 다음, 해당하는 ImageView를 생성합니다:
let subresource_range = vk::ImageSubresourceRange::builder()
.aspect_mask(vk::ImageAspectFlags::DEPTH)
.base_mip_level(0)
.level_count(1)
.base_array_layer(0)
.layer_count(1);
let imageview_create_info = vk::ImageViewCreateInfo::builder()
.image(depth_image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(vk::Format::D32_SFLOAT)
.subresource_range(*subresource_range);
let depth_imageview =
unsafe { logical_device.create_image_view(&imageview_create_info, None) }?;
중요: 포맷은 위에서 사용한 것과 일치해야 합니다. 우리가 생성했던 다른 ImageView들과의 차이점은, 이제 COLOR가 아닌 DEPTH에 관심이 있다는 것입니다.
이러한 변경 사항은 함수가 약간 다른 인자를 필요로 한다는 것을 의미합니다:
fn init(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
logical_device: &ash::Device,
surfaces: &SurfaceDongXi,
queue_families: &QueueFamilies,
allocator: &vk_mem::Allocator,
) -> Result<SwapchainDongXi, Box<dyn std::error::Error>> {
let surface_capabilities = surfaces.get_capabilities(physical_device)?;
let extent = surface_capabilities.current_extent;
let surface_present_modes = surfaces.get_present_modes(physical_device)?;
let surface_format = *surfaces.get_formats(physical_device)?.first().unwrap();
let queuefamilies = [queue_families.graphics_q_index.unwrap()];
let swapchain_create_info = vk::SwapchainCreateInfoKHR::builder()
.surface(surfaces.surface)
.min_image_count(
3.max(surface_capabilities.min_image_count)
.min(surface_capabilities.max_image_count),
)
.image_format(surface_format.format)
.image_color_space(surface_format.color_space)
.image_extent(extent)
.image_array_layers(1)
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
.queue_family_indices(&queuefamilies)
.pre_transform(surface_capabilities.current_transform)
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
.present_mode(vk::PresentModeKHR::FIFO);
let swapchain_loader = ash::extensions::khr::Swapchain::new(instance, logical_device);
let swapchain = unsafe { swapchain_loader.create_swapchain(&swapchain_create_info, None)? };
let swapchain_images = unsafe { swapchain_loader.get_swapchain_images(swapchain)? };
let amount_of_images = swapchain_images.len() as u32;
let mut swapchain_imageviews = Vec::with_capacity(swapchain_images.len());
for image in &swapchain_images {
let subresource_range = vk::ImageSubresourceRange::builder()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_mip_level(0)
.level_count(1)
.base_array_layer(0)
.layer_count(1);
let imageview_create_info = vk::ImageViewCreateInfo::builder()
.image(*image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(vk::Format::B8G8R8A8_UNORM)
.subresource_range(*subresource_range);
let imageview =
unsafe { logical_device.create_image_view(&imageview_create_info, None) }?;
swapchain_imageviews.push(imageview);
}
let extent3d = vk::Extent3D {
width: extent.width,
height: extent.height,
depth: 1,
};
let depth_image_info = vk::ImageCreateInfo::builder()
.image_type(vk::ImageType::TYPE_2D)
.format(vk::Format::D32_SFLOAT)
.extent(extent3d)
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.queue_family_indices(&queuefamilies);
let allocation_info = vk_mem::AllocationCreateInfo {
usage: vk_mem::MemoryUsage::GpuOnly,
..Default::default()
};
let (depth_image, depth_image_allocation, depth_image_allocation_info) =
allocator.create_image(&depth_image_info, &allocation_info)?;
let subresource_range = vk::ImageSubresourceRange::builder()
.aspect_mask(vk::ImageAspectFlags::DEPTH)
.base_mip_level(0)
.level_count(1)
.base_array_layer(0)
.layer_count(1);
let imageview_create_info = vk::ImageViewCreateInfo::builder()
.image(depth_image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(vk::Format::D32_SFLOAT)
.subresource_range(*subresource_range);
let depth_imageview =
unsafe { logical_device.create_image_view(&imageview_create_info, None) }?;
let mut image_available = vec![];
let mut rendering_finished = vec![];
let mut may_begin_drawing = vec![];
let semaphoreinfo = vk::SemaphoreCreateInfo::builder();
let fenceinfo = vk::FenceCreateInfo::builder().flags(vk::FenceCreateFlags::SIGNALED);
for _ in 0..amount_of_images {
let semaphore_available =
unsafe { logical_device.create_semaphore(&semaphoreinfo, None) }?;
let semaphore_finished =
unsafe { logical_device.create_semaphore(&semaphoreinfo, None) }?;
image_available.push(semaphore_available);
rendering_finished.push(semaphore_finished);
let fence = unsafe { logical_device.create_fence(&fenceinfo, None) }?;
may_begin_drawing.push(fence);
}
Ok(SwapchainDongXi {
swapchain_loader,
swapchain,
images: swapchain_images,
imageviews: swapchain_imageviews,
depth_image,
depth_image_allocation,
depth_image_allocation_info,
depth_imageview,
framebuffers: vec![],
surface_format,
extent,
amount_of_images,
current_image: 0,
image_available,
rendering_finished,
may_begin_drawing,
})
}
그리고 이 함수를 호출하기 전에 Allocator를 생성해야 하므로, Aetna::init()에서 순서를 일부 조정해야 합니다.
또한 SwapchainDongXi::cleanup()에도 몇 줄을 더 추가해야 합니다. 우리가 처리해야 할 새로운 ImageView와 Image가 있기 때문입니다:
unsafe fn cleanup(&mut self, logical_device: &ash::Device, allocator: &vk_mem::Allocator) {
logical_device.destroy_image_view(self.depth_imageview, None);
allocator.destroy_image(self.depth_image, &self.depth_image_allocation);
for fence in &self.may_begin_drawing {
logical_device.destroy_fence(*fence, None);
}
for semaphore in &self.image_available {
logical_device.destroy_semaphore(*semaphore, None);
}
for semaphore in &self.rendering_finished {
logical_device.destroy_semaphore(*semaphore, None);
}
for fb in &self.framebuffers {
logical_device.destroy_framebuffer(*fb, None);
}
for iv in &self.imageviews {
logical_device.destroy_image_view(*iv, None);
}
self.swapchain_loader
.destroy_swapchain(self.swapchain, None)
}
좋습니다. 뎁스 이미지를 만들었습니다. 이제 사용할 시간입니다. 이것을 FrameBuffer에 포함시켜야 합니다.
fn create_framebuffers(
&mut self,
logical_device: &ash::Device,
renderpass: vk::RenderPass,
) -> Result<(), vk::Result> {
for iv in &self.imageviews {
let iview = [*iv, self.depth_imageview];
let framebuffer_info = vk::FramebufferCreateInfo::builder()
.render_pass(renderpass)
.attachments(&iview)
.width(self.extent.width)
.height(self.extent.height)
.layers(1);
let fb = unsafe { logical_device.create_framebuffer(&framebuffer_info, None) }?;
self.framebuffers.push(fb);
}
Ok(())
}
한 줄이 변경되었습니다. 바로 우리가 사용하는 이미지 뷰입니다: let iview = [*iv, self.depth_imageview];.
다음과 같은 메시지를 받게 됩니다:
[Debug][error][validation] "vkCreateFramebuffer(): VkFramebufferCreateInfo attachmentCount of 2 does not match attachmentCount of 1 of renderPass (0x16) being used to create Frame
buffer. The Vulkan spec states: attachmentCount must be equal to the attachment count specified in renderPass (https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vk
spec.html#VUID-VkFramebufferCreateInfo-attachmentCount-00876)"
우리는 렌더 패스에서 프레임버퍼를 사용하는데, 당연히 렌더 패스에 다른 첨부 파일(attachment)을 사용하려 한다는 사실을 알려줘야 합니다.
fn init_renderpass(
logical_device: &ash::Device,
format: vk::Format,
) -> Result<vk::RenderPass, vk::Result> {
let attachments = [
vk::AttachmentDescription::builder()
.format(format)
.load_op(vk::AttachmentLoadOp::CLEAR)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::PRESENT_SRC_KHR)
.samples(vk::SampleCountFlags::TYPE_1)
.build(),
vk::AttachmentDescription::builder()
.format(vk::Format::D32_SFLOAT)
.load_op(vk::AttachmentLoadOp::CLEAR)
.store_op(vk::AttachmentStoreOp::DONT_CARE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL)
.samples(vk::SampleCountFlags::TYPE_1)
.build(),
];
let color_attachment_references = [vk::AttachmentReference {
attachment: 0,
layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
}];
let depth_attachment_reference = vk::AttachmentReference {
attachment: 1,
layout: vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
};
let subpasses = [vk::SubpassDescription::builder()
.color_attachments(&color_attachment_references)
.depth_stencil_attachment(&depth_attachment_reference)
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.build()];
let subpass_dependencies = [vk::SubpassDependency::builder()
.src_subpass(vk::SUBPASS_EXTERNAL)
.src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.dst_subpass(0)
.dst_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
)
.dst_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE
)
.build()];
let renderpass_info = vk::RenderPassCreateInfo::builder()
.attachments(&attachments)
.subpasses(&subpasses)
.dependencies(&subpass_dependencies);
let renderpass = unsafe { logical_device.create_render_pass(&renderpass_info, None)? };
Ok(renderpass)
}
무엇이 바뀌었을까요?
첫째, 약간의 정리를 했습니다: 불필요한 인자를 제거했습니다.
둘째, 두 번째 첨부 파일, 즉 우리의 뎁스 첨부 파일에 대한 설명이 추가되었습니다. 포맷은 이전과 같고, 로드 시 CLEAR, 저장 작업을 포함한 다른 작업에는 DONT_CARE를 사용합니다. (이것은 색상 첨부 파일과는 다릅니다. 뎁스 첨부 파일은 렌더링 중에만 사용되고, 이후에는 화면에 표시되는 등의 용도로 사용되지 않으므로 유지할 필요가 없습니다.) final_layout은 전용 뎁스 첨부 파일 형식이어야 합니다. (덧붙이자면, “depth”와 함께 자주 언급되는 “stencil”은 픽셀을 그리지 말아야 할지를 결정하는 또 다른 검사이지만, 우리는 사용하지 않습니다.)
셋째, 뎁스 첨부 파일에 대한 첨부 파일 참조(attachment reference)도 있습니다. 여기서도 레이아웃을 지정하고, 다음으로 사용 가능한 첨부 파일 번호를 사용합니다. 이 첨부 파일 참조는 서브패스(subpass)에 추가되어야 합니다. (색상 첨부 파일과의 작은 차이점: 이번에는 배열이 아니라 하나뿐입니다. 한 번에 하나의 뎁스 버퍼만 가질 수 있습니다.)
렌더패스: 완료. 다음 문제:
[Debug][error][validation] "Invalid Pipeline CreateInfo State: pDepthStencilState is NULL when rasterization is enabled and subpass uses a depth/stencil attachment. The Vulkan spec states: If the rasterizerDiscardEnable member of pRasterizationState is VK_FALSE, and subpass uses a depth/stencil attachment, pDepthStencilState must be a valid pointer to a valid VkPipelineDepthStencilStateCreateInfo structure (https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#VUID-VkGraphicsPipelineCreateInfo-rasterizerDiscardEnable-00752)"
우리는 파이프라인에게 뎁스 정보로 무엇을 해야 할지 알려주지 않았습니다. 이전에는 뎁스 관련 사항이 없었으므로 그럴 필요가 없었죠. 이제는 있으니 DepthStencilState가 필요합니다.
따라서 Pipeline::init()의 pipeline_info에 한 줄이 추가됩니다:
let pipeline_info = vk::GraphicsPipelineCreateInfo::builder()
.stages(&shader_stages)
.vertex_input_state(&vertex_input_info)
.input_assembly_state(&input_assembly_info)
.viewport_state(&viewport_info)
.rasterization_state(&rasterizer_info)
.multisample_state(&multisampler_info)
.depth_stencil_state(&depth_stencil_info)
.color_blend_state(&colourblend_info)
.layout(pipelinelayout)
.render_pass(*renderpass)
.subpass(0);
“depth”가 들어간 줄이 새로 추가되었습니다. 물론, 이것만으로는 많은 것을 알 수 없습니다.
let depth_stencil_info = vk::PipelineDepthStencilStateCreateInfo::builder()
.depth_test_enable(true)
.depth_write_enable(true)
.depth_compare_op(vk::CompareOp::LESS_OR_EQUAL);
여기서 우리는 뎁스 테스트를 활성화하고(이 모든 작업을 하는 이유입니다), 새로운 프래그먼트의 깊이(z-값)가 같은 위치에 이미 있는 값보다 작거나 같을 경우에만 그려야 한다고 지정합니다. 또한 그 경우 뎁스 값을 업데이트하기로 결정합니다(depth_write_enable — 새로운 프래그먼트를 그릴지 여부를 확인하고 저장된 뎁스 값을 변경하지 않는 것도 가능합니다).
새로운 메시지를 받게 됩니다:
[Debug][error][validation] "In vkCmdBeginRenderPass() the VkRenderPassBeginInfo struct has a clearValueCount of 1 but there must be at least 2 entries in pClearValues array to account for the highest index attachment in renderPass 0x16 that uses VK_ATTACHMENT_LOAD_OP_CLEAR is 2. Note that the pClearValues array is indexed by attachment number so even if some pClearValues entries between 0 and 1 correspond to attachments that aren\'t cleared they will be ignored. The Vulkan spec states: clearValueCount must be greater than the largest attachment index in renderPass that specifies a loadOp (or stencilLoadOp, if the attachment has a depth/stencil format) of VK_ATTACHMENT_LOAD_OP_CLEAR (https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#VUID-VkRenderPassBeginInfo-clearValueCount-00902)"
이것은 어느 정도 자명합니다: 렌더 패스에 두 번째 첨부 파일이 있고, 이를 초기화(clear)하겠다고 이미 말했지만, 이 두 번째 첨부 파일을 초기화할 때 어떤 값을 사용할지는 아직 프로그램에 알려주지 않았습니다.
fill_commandbuffers()의 renderpass_begininfo에는 일부 clearvalues가 포함되어 있으며, 이것을 조정해야 합니다.
let clearvalues = [
vk::ClearValue {
color: vk::ClearColorValue {
float32: [0.0, 0.0, 0.08, 1.0],
},
},
vk::ClearValue {
depth_stencil: vk::ClearDepthStencilValue {
depth: 1.0,
stencil: 0,
},
},
];
두 번째 항목이 새로 추가되었고, 뎁스를 가능한 가장 높은 값인 1.0으로 설정하고, 스텐실은 0으로 초기화합니다(여전히 이 구성요소는 신경 쓰지 않습니다).
이제 노란색 상자는 항상 파란색 상자 앞에 그려집니다. 이것이 목표였습니다.