1. 지금 잘되고있는 렌더링에 서브패스만 추가해서 감마값만 바꿔보기 - 서브패스 추가하기
2. 이 서브패스에 유니폼 버퍼를 추가해보기
3. 택스쳐도 넣어보기 <- 요녀석은 그냥 했다 치고 다음단계로 넘어가야겠다. 어렵지 않기에
4. 지연 렌더링 기본 구현하기 - 퐁모델로 간단하게
5. pbr적용
6. 렌더패스 추가하여 쉐도우맵 생성해보기 - 일단 방향성 광원으로
7. 이걸 받아서 그림자 만들어 보기
8. 렌더 패스 동적 생성 - 빛의 개수만큼
9. 다중 광원 적용 - 방향성 광원만
10. 큐브맵 활용해서 점광원 + 스포트라이트까지 추가
여기서 4단계!
를 드디어 구현했다.
이미 서브패스 두개 연결되어있는 상태라 쉬울 것이라 예상했지만
생각보단 만만하진 않았다.
생각해보니깐 블로그에 코드를 진짜 안쓴 거 같아서
사람들이 블로그에 vulkan 찾아왔다가 내 하소연만 보고 가는 거 같아
코드 좀 첨부하겠다. 물론 완성된 코드가 아니고 그러므로 깔끔하지도 않은 코드라 참고가 될지 모르겠네
일단 shader코드
지연 셰이딩이니 두쌍이고
#version 450
layout(binding = 0) uniform UniformBufferObject {
mat4 model;
mat4 view;
mat4 proj;
} ubo;
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec2 inTexCoord;
layout(location = 0) out vec3 fragPosition;
layout(location = 1) out vec3 fragNormal;
layout(location = 2) out vec2 fragTexCoord;
void main() {
gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0);
fragPosition = vec3(ubo.model * vec4(inPosition, 1.0));
fragNormal = mat3(transpose(inverse(ubo.model))) * inNormal;
fragTexCoord = inTexCoord;
}
#version 450
layout(binding = 1) uniform sampler2D texSampler;
layout(location = 0) in vec3 fragPosition;
layout(location = 1) in vec3 fragNormal;
layout(location = 2) in vec2 fragTexCoord;
layout(location = 0) out vec4 outPosition;
layout(location = 1) out vec4 outNormal;
layout(location = 2) out vec4 outAlbedo;
void main() {
outPosition = vec4(fragPosition, 1.0);
outNormal = vec4(normalize(fragNormal), 1.0);
outAlbedo = texture(texSampler, fragTexCoord);
}
#version 450
layout(location = 0) out vec2 fragTexCoord;
vec2 positions[6] = vec2[](
vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0),
vec2(-1.0, 1.0), vec2(1.0, -1.0), vec2(1.0, 1.0)
);
vec2 texCoords[6] = vec2[](
vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 1.0),
vec2(0.0, 1.0), vec2(1.0, 0.0), vec2(1.0, 1.0)
);
void main() {
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
fragTexCoord = texCoords[gl_VertexIndex];
}
#version 450
layout(input_attachment_index = 0, binding = 0) uniform subpassInput positionAttachment;
layout(input_attachment_index = 1, binding = 1) uniform subpassInput normalAttachment;
layout(input_attachment_index = 2, binding = 2) uniform subpassInput albedoAttachment;
layout(binding = 3) uniform LightingInfo {
vec3 lightPos;
vec3 lightColor;
vec3 cameraPos;
} lighting;
layout(location = 0) in vec2 fragTexCoord;
layout(location = 0) out vec4 outColor;
void main() {
vec3 fragPosition = subpassLoad(positionAttachment).rgb;
vec3 fragNormal = normalize(subpassLoad(normalAttachment).rgb);
vec3 albedo = subpassLoad(albedoAttachment).rgb;
vec3 lightDir = normalize(lighting.lightPos - fragPosition);
float diff = max(dot(fragNormal, lightDir), 0.0);
vec3 viewDir = normalize(lighting.cameraPos - fragPosition);
vec3 reflectDir = reflect(-lightDir, fragNormal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
vec3 ambient = 0.1 * albedo;
vec3 diffuse = diff * albedo * lighting.lightColor;
vec3 specular = spec * lighting.lightColor;
outColor = vec4(ambient + diffuse + specular, 1.0);
// outColor = vec4(fragPosition, 1.0);
// outColor = vec4(fragNormal, 1.0);
// outColor = vec4(albedo, 1.0);
}
geometrypass vert -> geometrypass frag -> lightingpass vert -> lightpass frag 순 이다.
간단한 텍스쳐 보는 셰이더 + gamma적용하는 셰이더에서
geometry계산 + light 계산하는 셰이더로 바뀌었다.
여기서 중요한건 subpass간 연결해주는 attachment가 1개에서 3개로 늘어났다는 점인데
이번 단계에서 가장 중요한 변경점이다.
우선 shader바뀌면 뭐부터 봐야한다? descriptorsetlayout부터 봐야지
void DescriptorSetLayout::initLightingPassDescriptorSetLayout() {
auto& context = VulkanContext::getContext();
VkDevice device = context.getDevice();
VkDescriptorSetLayoutBinding inputAttachmentBinding0{};
inputAttachmentBinding0.binding = 0;
inputAttachmentBinding0.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
inputAttachmentBinding0.descriptorCount = 1;
inputAttachmentBinding0.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
inputAttachmentBinding0.pImmutableSamplers = nullptr;
VkDescriptorSetLayoutBinding inputAttachmentBinding1{};
inputAttachmentBinding1.binding = 1;
inputAttachmentBinding1.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
inputAttachmentBinding1.descriptorCount = 1;
inputAttachmentBinding1.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
inputAttachmentBinding1.pImmutableSamplers = nullptr;
VkDescriptorSetLayoutBinding inputAttachmentBinding2{};
inputAttachmentBinding2.binding = 2;
inputAttachmentBinding2.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
inputAttachmentBinding2.descriptorCount = 1;
inputAttachmentBinding2.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
inputAttachmentBinding2.pImmutableSamplers = nullptr;
VkDescriptorSetLayoutBinding lightingPassBufferBinding{};
lightingPassBufferBinding.binding = 3;
lightingPassBufferBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
lightingPassBufferBinding.descriptorCount = 1;
lightingPassBufferBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
lightingPassBufferBinding.pImmutableSamplers = nullptr;
std::array<VkDescriptorSetLayoutBinding, 4> bindings = {inputAttachmentBinding0, inputAttachmentBinding1, inputAttachmentBinding2, lightingPassBufferBinding};
VkDescriptorSetLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
layoutInfo.pBindings = bindings.data();
if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
throw std::runtime_error("failed to create LightingPass descriptor set layout!");
}
}
이건 두번째 subpass의 descriptorsetlayout을 초기화 해주는 함수인데
보면 lightingpass fragment shader의 인풋들과 타입과 바인딩 넘버가 매칭이 되는 것을 볼 수 있다.
descriptorsetlayout 바뀌면? uniform과 descriptorset 봐줘야지
void ShaderResourceManager::createLightingPassDescriptorSets(VkDescriptorSetLayout descriptorSetLayout,
VkImageView positionImageView, VkImageView normalImageView, VkImageView albedoImageView) {
auto& context = VulkanContext::getContext();
VkDevice device = context.getDevice();
VkDescriptorPool descriptorPool = context.getDescriptorPool();
std::vector<VkDescriptorSetLayout> layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout);
VkDescriptorSetAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = descriptorPool;
allocInfo.descriptorSetCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
allocInfo.pSetLayouts = layouts.data();
descriptorSets.resize(MAX_FRAMES_IN_FLIGHT);
if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) {
throw std::runtime_error("failed to allocate descriptor sets!");
}
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VkDescriptorImageInfo positionImageInfo{};
positionImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
positionImageInfo.imageView = positionImageView;
positionImageInfo.sampler = VK_NULL_HANDLE;
VkDescriptorImageInfo normalImageInfo{};
normalImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
normalImageInfo.imageView = normalImageView;
normalImageInfo.sampler = VK_NULL_HANDLE;
VkDescriptorImageInfo albedoImageInfo{};
albedoImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
albedoImageInfo.imageView = albedoImageView;
albedoImageInfo.sampler = VK_NULL_HANDLE;
VkDescriptorBufferInfo bufferInfo{};
bufferInfo.buffer = m_uniformBuffers[i]->getBuffer();
bufferInfo.offset = 0;
bufferInfo.range = sizeof(LightingPassUniformBufferObject);
std::array<VkWriteDescriptorSet, 4> descriptorWrites{};
descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[0].dstSet = descriptorSets[i];
descriptorWrites[0].dstBinding = 0;
descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
descriptorWrites[0].descriptorCount = 1;
descriptorWrites[0].pImageInfo = &positionImageInfo;
descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[1].dstSet = descriptorSets[i];
descriptorWrites[1].dstBinding = 1;
descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
descriptorWrites[1].descriptorCount = 1;
descriptorWrites[1].pImageInfo = &normalImageInfo;
descriptorWrites[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[2].dstSet = descriptorSets[i];
descriptorWrites[2].dstBinding = 2;
descriptorWrites[2].descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
descriptorWrites[2].descriptorCount = 1;
descriptorWrites[2].pImageInfo = &albedoImageInfo;
descriptorWrites[3].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[3].dstSet = descriptorSets[i];
descriptorWrites[3].dstBinding = 3;
descriptorWrites[3].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrites[3].descriptorCount = 1;
descriptorWrites[3].pBufferInfo = &bufferInfo;
vkUpdateDescriptorSets(device, static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}
}
uniform은 변경된 거 없고
위의 함수도 마찬가지로 lightingpass의 descriptorset을 초기화하는 함수인데
다른 건 다 빼놓고 봐도 함수 외부에서 image view를 가져와서 imageinfo를 만들어 descriptorWrite의 0,1,2번에 갖다 박는 건 꼭 봐야한다. (for문 안쪽) 이 image view가 뭐냐? framebuffer에서 생성한 image view들인데 요녀석들이 실제로 subpass간 연결 이미지라고 보면 되겠다. deferred shading 할 거니깐 앞에 subpass 에서 position, normal, albedo값이 전달될 공간?을 미리 알아두는거다.
근데 framebuffer 수정 전이잖아? 그럼 framebuffer도 바꿔야지
void FrameBuffers::initSwapChainFrameBuffers(SwapChain* swapChain, VkRenderPass renderPass) {
auto& context = VulkanContext::getContext();
VkDevice device = context.getDevice();
VkFormat colorFormat = swapChain->getSwapChainImageFormat();
VkExtent2D extent = swapChain->getSwapChainExtent();
std::vector<VkImageView> swapChainImageViews = swapChain->getSwapChainImageViews();
VulkanUtil::createImage(
extent.width, extent.height, 1, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R16G16B16A16_SFLOAT,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
positionImage, positionImageMemory);
positionImageView = VulkanUtil::createImageView(positionImage, VK_FORMAT_R16G16B16A16_SFLOAT, VK_IMAGE_ASPECT_COLOR_BIT, 1);
VulkanUtil::createImage(
extent.width, extent.height, 1, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R16G16B16A16_SFLOAT,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
normalImage, normalImageMemory);
normalImageView = VulkanUtil::createImageView(normalImage, VK_FORMAT_R16G16B16A16_SFLOAT, VK_IMAGE_ASPECT_COLOR_BIT, 1);
VulkanUtil::createImage(
extent.width, extent.height, 1, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
albedoImage, albedoImageMemory);
albedoImageView = VulkanUtil::createImageView(albedoImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT, 1);
VkFormat depthFormat = VulkanUtil::findDepthFormat();
VulkanUtil::createImage(
extent.width, extent.height, 1, VK_SAMPLE_COUNT_1_BIT, depthFormat,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
depthImage, depthImageMemory);
depthImageView = VulkanUtil::createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 1);
framebuffers.resize(swapChainImageViews.size());
for (size_t i = 0; i < swapChainImageViews.size(); i++) {
std::array<VkImageView, 5> attachments = {
positionImageView,
normalImageView,
albedoImageView,
depthImageView,
swapChainImageViews[i]
};
VkFramebufferCreateInfo framebufferInfo{};
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferInfo.renderPass = renderPass;
framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
framebufferInfo.pAttachments = attachments.data();
framebufferInfo.width = extent.width;
framebufferInfo.height = extent.height;
framebufferInfo.layers = 1;
if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &framebuffers[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create framebuffer!");
}
}
}
함수 이름이 swapchainframebuffer는 swapchain과 연결되어있는 framebuffer라 그렇고
여기서 중요한건 아까 descriptorset을 만들때 사용되었던 imageview가 다 여기서 생성된거고 뒤에서 한번 더 말할텐데 지금 msaa용 이미지 뷰가 빠졌다. 지연 셰이딩하면서 msaa를 포기하게 되었다. 이따 이유를 말하겠다.
포맷도 조금 바뀐게 있는데 요것도 이따가..
큰 흐름상에는 중요하지 않다! 지금 흐름은 descriptorset만들 때 쓴 imageview, 이거 subpass간 연결에 쓸 거고
그래서 framebuffer에는 attachment가 position, normal, albedo, depth, swapchain이 있다!
저기 renderpass 받아오는데 아직 renderpass 수정 안했다. renderpass로 가자
void RenderPass::initDeferredRenderPass(VkFormat swapChainImageFormat) {
auto& context = VulkanContext::getContext();
VkDevice device = context.getDevice();
// Position Attachment
VkAttachmentDescription positionAttachment{};
positionAttachment.format = VK_FORMAT_R16G16B16A16_SFLOAT;
positionAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
positionAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
positionAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
positionAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
positionAttachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
// Normal Attachment
VkAttachmentDescription normalAttachment = positionAttachment;
// Albedo Attachment
VkAttachmentDescription albedoAttachment{};
albedoAttachment.format = VK_FORMAT_R8G8B8A8_UNORM;
albedoAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
albedoAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
albedoAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
albedoAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
albedoAttachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
// Depth Attachment
VkAttachmentDescription depthAttachment{};
depthAttachment.format = VulkanUtil::findDepthFormat();
depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
// SwapChain Attachment
VkAttachmentDescription swapChainAttachment{};
swapChainAttachment.format = swapChainImageFormat;
swapChainAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
swapChainAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
swapChainAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
swapChainAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
swapChainAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
// Subpass 1 - Geometry Pass
VkAttachmentReference positionRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
VkAttachmentReference normalRef{1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
VkAttachmentReference albedoRef{2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
VkAttachmentReference depthRef{3, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
VkAttachmentReference geometryAttachments[] = {positionRef, normalRef, albedoRef};
VkSubpassDescription subpass1{};
subpass1.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass1.colorAttachmentCount = 3;
subpass1.pColorAttachments = geometryAttachments;
subpass1.pDepthStencilAttachment = &depthRef;
// Subpass 2 - Lighting Pass
VkAttachmentReference positionInputRef{0, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
VkAttachmentReference normalInputRef{1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
VkAttachmentReference albedoInputRef{2, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
VkAttachmentReference swapChainRef{4, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
VkAttachmentReference inputAttachments[] = {positionInputRef, normalInputRef, albedoInputRef};
VkSubpassDescription subpass2{};
subpass2.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass2.inputAttachmentCount = 3;
subpass2.pInputAttachments = inputAttachments;
subpass2.colorAttachmentCount = 1;
subpass2.pColorAttachments = &swapChainRef;
// Subpass Dependencies
std::array<VkSubpassDependency, 2> dependencies{};
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
dependencies[0].dstSubpass = 0;
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencies[0].srcAccessMask = 0;
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependencies[1].srcSubpass = 0;
dependencies[1].dstSubpass = 1;
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
// Render Pass 생성
std::array<VkAttachmentDescription, 5> attachments = {
positionAttachment, normalAttachment, albedoAttachment,
depthAttachment, swapChainAttachment
};
VkSubpassDescription subpasses[] = {subpass1, subpass2};
VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
renderPassInfo.pAttachments = attachments.data();
renderPassInfo.subpassCount = 2;
renderPassInfo.pSubpasses = subpasses;
renderPassInfo.dependencyCount = 2;
renderPassInfo.pDependencies = dependencies.data();
if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
throw std::runtime_error("failed to create deferred render pass!");
}
}
framebuffer에서 만든 attachment들 똑같이 있고 subpass들이 어떤 attachment를 가지고 있는지, 어떻게 연결되는지, 종속 되는지 정의하는 부분이다. 어지러운데 침착하게 보면 보인다. 순서만 살짝
각 attachment들 정의 -> 첫번째 subpass 정의 -> 두번째 subpass 정의 (앞에서 attachment 정의한 거 갖다 쓴다.)
-> subpass간 종속성 정의 -> renderpass 생성 순이다.
그럼 이제 pipeline만 남았다.
void Pipeline::initGeometryPassPipeline(VkRenderPass renderPass, VkDescriptorSetLayout descriptorSetLayout) {
auto& context = VulkanContext::getContext();
VkDevice device = context.getDevice();
// SPIR-V 파일 읽기
std::vector<char> vertShaderCode = VulkanUtil::readFile("./spvs/GeometryPass.vert.spv");
std::vector<char> fragShaderCode = VulkanUtil::readFile("./spvs/GeometryPass.frag.spv");
// shader module 생성
VkShaderModule vertShaderModule = createShaderModule(vertShaderCode);
VkShaderModule fragShaderModule = createShaderModule(fragShaderCode);
// vertex shader stage 설정
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; // 쉐이더 종류
vertShaderStageInfo.module = vertShaderModule; // 쉐이더 모듈
vertShaderStageInfo.pName = "main"; // 쉐이더 파일 내부에서 가장 먼저 시작 될 함수 이름 (엔트리 포인트)
// fragment shader stage 설정
VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; // 쉐이더 종류
fragShaderStageInfo.module = fragShaderModule; // 쉐이더 모듈
fragShaderStageInfo.pName = "main"; // 쉐이더 파일 내부에서 가장 먼저 시작 될 함수 이름 (엔트리 포인트)
// shader stage 모음
VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
// [vertex 정보 설정]
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
auto bindingDescription = Vertex::getBindingDescription(); // 정점 바인딩 정보를 가진 구조체
auto attributeDescriptions = Vertex::getAttributeDescriptions(); // 정점 속성 정보를 가진 구조체 배열
vertexInputInfo.vertexBindingDescriptionCount = 1; // 정점 바인딩 정보 개수
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size()); // 정점 속성 정보 개수
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; // 정점 바인딩 정보
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); // 정점 속성 정보
// [input assembly 설정] (그려질 primitive 설정)
VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; // primitive로 삼각형 설정
inputAssembly.primitiveRestartEnable = VK_FALSE; // 인덱스 재시작 x
VkPipelineViewportStateCreateInfo viewportState{};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1; // 사용할 뷰포트의 수
viewportState.scissorCount = 1; // 사용할 시저의수
// [rasterizer 설정]
VkPipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE; // VK_FALSE로 설정시 depth clamping이 적용되지 않아 0.0f ~ 1.0f 범위 밖의 프레그먼트는 삭제됨
rasterizer.rasterizerDiscardEnable = VK_FALSE; // rasterization 진행 여부 결정, VK_TRUE시 렌더링 진행 x
rasterizer.polygonMode = VK_POLYGON_MODE_FILL; // 다각형 그리는 방법 선택 (점만, 윤곽선만, 기본 값 등)
rasterizer.lineWidth = 1.0f; // 선의 굵기 설정
// rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; // cull 모드 설정 (앞면 혹은 뒷면은 그리지 않는 설정 가능)
rasterizer.cullMode = VK_CULL_MODE_NONE;
rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; // 앞면의 기준 설정 (y축 반전에 의해 정점이 시계 반대방향으로 그려지므로 앞면을 시계 반대방향으로 설정)
rasterizer.depthBiasEnable = VK_FALSE; // depth에 bias를 설정하여 z-fighting 해결할 수 있음 (원근 투영시 멀어질 수록 z값의 차이가 미미해짐)
// VK_TRUE일 경우 추가 설정 필요
// [멀티 샘플링 설정]
VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
// [depth test]
VkPipelineDepthStencilStateCreateInfo depthStencil{};
depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthStencil.depthTestEnable = VK_TRUE; // 깊이 테스트 활성화 여부를 지정
depthStencil.depthWriteEnable = VK_TRUE; // 깊이 버퍼 쓰기 활성화 여부
depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; // 깊이 비교 연산 설정 (VK_COMPARE_OP_LESS: 현재 픽셀의 깊이가 더 작으면 통과)
depthStencil.depthBoundsTestEnable = VK_FALSE; // 깊이 범위 테스트 활성화 여부를 지정
depthStencil.stencilTestEnable = VK_FALSE; // 스텐실 테스트 활성화 여부를 지정
std::array<VkPipelineColorBlendAttachmentState, 3> colorBlendAttachments = {};
// position attachment
colorBlendAttachments[0].colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachments[0].blendEnable = VK_FALSE;
// normal attachment
colorBlendAttachments[1].colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachments[1].blendEnable = VK_FALSE;
// albedo attachment
colorBlendAttachments[2].colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachments[2].blendEnable = VK_FALSE;
VkPipelineColorBlendStateCreateInfo colorBlending{};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.logicOp = VK_LOGIC_OP_COPY;
colorBlending.attachmentCount = static_cast<uint32_t>(colorBlendAttachments.size());
colorBlending.pAttachments = colorBlendAttachments.data();
colorBlending.blendConstants[0] = 0.0f;
colorBlending.blendConstants[1] = 0.0f;
colorBlending.blendConstants[2] = 0.0f;
colorBlending.blendConstants[3] = 0.0f;
// [파이프라인에서 런타임에 동적으로 상태를 변경할 state 설정]
std::vector<VkDynamicState> dynamicStates = {
// Viewport와 Scissor 를 동적 상태로 설정
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState{};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
dynamicState.pDynamicStates = dynamicStates.data();
// [파이프라인 레이아웃 생성]
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1; // 디스크립터 셋 레이아웃 개수
pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; // 디스크립투 셋 레이아웃
if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
throw std::runtime_error("failed to create GeometryPass pipeline layout!");
}
// [파이프라인 정보 생성]
VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2; // vertex shader, fragment shader 2개 사용
pipelineInfo.pStages = shaderStages; // vertex shader, fragment shader 총 2개의 stageinfo 입력
pipelineInfo.pVertexInputState = &vertexInputInfo; // 정점 정보 입력
pipelineInfo.pInputAssemblyState = &inputAssembly; // primitive 정보 입력
pipelineInfo.pViewportState = &viewportState; // viewport, scissor 정보 입력
pipelineInfo.pRasterizationState = &rasterizer; // 레스터라이저 설정 입력
pipelineInfo.pMultisampleState = &multisampling; // multisampling 설정 입력
pipelineInfo.pDepthStencilState = &depthStencil; // depth-stencil 설정
pipelineInfo.pColorBlendState = &colorBlending; // 블랜딩 설정 입력
pipelineInfo.pDynamicState = &dynamicState; // 동적으로 변경할 상태 입력
pipelineInfo.layout = pipelineLayout; // 파이프라인 레이아웃 설정 입력
pipelineInfo.renderPass = renderPass; // 렌더패스 입력
pipelineInfo.subpass = 0; // 렌더패스 내 서브패스의 인덱스
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // 상속을 위한 기존 파이프라인 핸들
pipelineInfo.basePipelineIndex = -1; // Optional (상속을 위한 기존 파이프라인 인덱스)
// [파이프라인 객체 생성]
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline) != VK_SUCCESS) {
throw std::runtime_error("failed to create GeometryPass graphics pipeline!");
}
vkDestroyShaderModule(device, fragShaderModule, nullptr);
vkDestroyShaderModule(device, vertShaderModule, nullptr);
}
쉬운데 변경점은 첫번째 pipeline, 그니깐 geometrypass 부분에서 아웃풋이 3개니깐 그부분만 변경해주었다.
그럼 다했다. 렌더링부분만 고쳐주면 되는데
void Renderer::recordDeferredRenderPassCommandBuffer(Scene* scene, VkCommandBuffer commandBuffer, uint32_t imageIndex) {
// 커맨드 버퍼 기록 시작
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("failed to begin recording command buffer!");
}
// 렌더 패스 시작
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = deferredRenderPass;
renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = swapChainExtent;
// ClearValues 수정
std::array<VkClearValue, 5> clearValues{};
clearValues[0].color = {0.0f, 0.0f, 0.0f, 1.0f};
clearValues[1].color = {0.0f, 0.0f, 0.0f, 1.0f};
clearValues[2].color = {0.0f, 0.0f, 0.0f, 1.0f};
clearValues[3].depthStencil = {1.0f, 0};
clearValues[4].color = {0.0f, 0.0f, 0.0f, 1.0f};
renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
renderPassInfo.pClearValues = clearValues.data();
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, geometryPassGraphicsPipeline);
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast<float>(swapChainExtent.width);
viewport.height = static_cast<float>(swapChainExtent.height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = swapChainExtent;
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
const std::vector<std::shared_ptr<Object>>& objects = scene->getObjects();
size_t objectCount = scene->getObjectCount();
static float x = 0.0f;
static float d = 0.005f;
if (x > 2.0f) {
d = -0.005f;
} else if (x < -2.0f) {
d = 0.005f;
}
x += d;
scene->updateLightPos(glm::vec3(x, 1.5f, 0.0f));
// std::cout << "Light Pos: " << scene->getLightPos().x << ", " << scene->getLightPos().y << ", " << scene->getLightPos().z << std::endl;
for (size_t i = 0; i < objectCount; i++) {
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, geometryPassPipelineLayout, 0, 1, &geometryPassDescriptorSets[MAX_FRAMES_IN_FLIGHT * i + currentFrame], 0, nullptr);
GeometryPassUniformBufferObject ubo{};
ubo.model = objects[i]->getModelMatrix();
ubo.view = scene->getViewMatrix();
ubo.proj = scene->getProjMatrix(swapChainExtent);
ubo.proj[1][1] *= -1;
geometryPassUniformBuffers[MAX_FRAMES_IN_FLIGHT * i + currentFrame]->updateUniformBuffer(&ubo, sizeof(ubo));
objects[i]->draw(commandBuffer);
}
vkCmdNextSubpass(commandBuffer, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, lightingPassGraphicsPipeline);
vkCmdBindDescriptorSets(
commandBuffer,
VK_PIPELINE_BIND_POINT_GRAPHICS,
lightingPassPipelineLayout,
0,
1,
&lightingPassDescriptorSets[currentFrame],
0,
nullptr
);
LightingPassUniformBufferObject lightingPassUbo{};
// lightingPassUbo.lightPos = scene->getLightPos();
lightingPassUbo.lightPos = scene->getLightPos();
lightingPassUbo.lightColor = glm::vec3(1.0f, 1.0f, 1.0f);
lightingPassUbo.cameraPos = scene->getCamPos();
lightingPassUniformBuffers[currentFrame]->updateUniformBuffer(&lightingPassUbo, sizeof(lightingPassUbo));
vkCmdDraw(commandBuffer, 6, 1, 0, 0);
vkCmdEndRenderPass(commandBuffer);
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
throw std::runtime_error("failed to record deferred renderpass command buffer!");
}
}
테스트용으로 광원을 실시간으로 움직이게 해줬고
앞에서 설명과 마찬가지로 renderpass 선언 -> pipeline 선언 -> descriptorset 선언 -> uniform넣어주고 -> draw
-> 다음 subpass 넘어간다 선언 -> pipeline 선언 -> descriptorset 선언 -> uniform 넣어주고 -> draw
그리고 위에서 clearvalue도 수정해줬다. attachment 5개 전부 클리어 해준다.
결과는
실패 기록과 느낀점은 다음글에서 써야겠다. 코드 쓰니깐 너무 길다...
'Computer Graphics > Vulkan' 카테고리의 다른 글
Vulkan - 8. 키보드 마우스 입력 추가와 imgui 적용 (1) | 2025.01.02 |
---|---|
Vulkan - 7. deferred shading + phong shading 삽질 기록들 (0) | 2024.12.30 |
Vulkan - 5. 두번째 Subpass에 uniform buffer추가 (0) | 2024.12.26 |
Vulkan - 4. 감마 셰이더 추가해보기 (0) | 2024.12.26 |
Vulkan - 3. 벽을 느낌 (0) | 2024.12.24 |