Computer Graphics/Vulkan

Vulkan - 6. deferred shading + phong shading 구현 (code)

surkim 2024. 12. 30. 00:16

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개 전부 클리어 해준다.

 

결과는

0

 

실패 기록과 느낀점은 다음글에서 써야겠다. 코드 쓰니깐 너무 길다...