Please write a basic tutorial for incorporating multi pass rendering into the code of a 3d renderer. The multi pass tutorial should make use of the subpass functionality in Vulkan if possible. Assume that the renderer is written in C++ using Vulkan. The current state of the renderer at the beginning of the tutorial is that it is rendering a cube to a window with a texture, color and multisampling with simple lighting. The aim of the tutorial should be to split the lighting into a separate pass with deferred shading.

Here is the current code for the render pass:

void Renderer::CreateRenderPass()
{
    vk::AttachmentDescription colorAttachment{};
    colorAttachment.format = swapChainImageFormat;
    colorAttachment.samples = msaaSamples;
    colorAttachment.loadOp = vk::AttachmentLoadOp::eClear;
    colorAttachment.storeOp = vk::AttachmentStoreOp::eStore;
    colorAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
    colorAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
    colorAttachment.initialLayout = vk::ImageLayout::eUndefined;
    colorAttachment.finalLayout = vk::ImageLayout::eColorAttachmentOptimal;

    vk::AttachmentDescription colorAttachmentResolve{};
    colorAttachmentResolve.format = swapChainImageFormat;
    colorAttachmentResolve.samples = vk::SampleCountFlagBits::e1;
    colorAttachmentResolve.loadOp = vk::AttachmentLoadOp::eClear;
    colorAttachmentResolve.storeOp = vk::AttachmentStoreOp::eStore;
    colorAttachmentResolve.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
    colorAttachmentResolve.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
    colorAttachmentResolve.initialLayout = vk::ImageLayout::eUndefined;
    colorAttachmentResolve.finalLayout = vk::ImageLayout::ePresentSrcKHR;

    vk::AttachmentDescription depthAttachment{};
    depthAttachment.format = FindDepthFormat(*GetPhysicalDevice());
    depthAttachment.samples = msaaSamples;
    depthAttachment.loadOp = vk::AttachmentLoadOp::eClear;
    depthAttachment.storeOp = vk::AttachmentStoreOp::eDontCare;
    depthAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
    depthAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
    depthAttachment.initialLayout = vk::ImageLayout::eUndefined;
    depthAttachment.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;

    vk::AttachmentReference colorAttachmentRef{};
    colorAttachmentRef.attachment = 0;
    colorAttachmentRef.layout = vk::ImageLayout::eColorAttachmentOptimal;

    vk::AttachmentReference colorAttachmentResolveRef{};
    colorAttachmentResolveRef.attachment = 2;
    colorAttachmentResolveRef.layout = vk::ImageLayout::eColorAttachmentOptimal;

    vk::AttachmentReference depthAttachmentRef{};
    depthAttachmentRef.attachment = 1; // Index of the depth attachment in the attachments array
    depthAttachmentRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;

    vk::SubpassDescription subpass{};
    subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
    subpass.colorAttachmentCount = 1;
    subpass.pColorAttachments = &colorAttachmentRef;
    subpass.pDepthStencilAttachment = &depthAttachmentRef; // Specify the depth attachment reference
    subpass.pResolveAttachments = &colorAttachmentResolveRef;

    vk::RenderPassCreateInfo renderPassInfo{};
    std::array<vk::AttachmentDescription, 3> attachments = { colorAttachment, depthAttachment, colorAttachmentResolve };
    renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
    renderPassInfo.pAttachments = attachments.data();
    renderPassInfo.subpassCount = 1;
    renderPassInfo.pSubpasses = &subpass;

    vk::SubpassDependency dependency{};
    dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
    dependency.dstSubpass = 0;
    dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
    dependency.srcAccessMask = {};
    dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
    dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;

    renderPassInfo.dependencyCount = 1;
    renderPassInfo.pDependencies = &dependency;

    if (device.createRenderPass(&renderPassInfo, nullptr, &renderPass) != vk::Result::eSuccess)
    {
        throw std::runtime_error("Failed to create render pass.");
    }
}

Here is the current vertex shader:

#version 450

layout(binding = 0) buffer ModelBufferObject {
    mat4 models[];
} modelUBO;

layout(binding = 1) uniform ViewBufferObject {
    mat4 view;
} viewUBO;

layout(binding = 2) uniform ProjBufferObject {
    mat4 proj;
} projUBO;

layout(binding = 4) uniform ModelCountBuffer {
    int modelCount; // Number of models
} modelCountUBO;

layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec2 inTexCoord;
layout(location = 3) in vec3 inNormal;

layout(location = 0) out vec3 fragColor;
layout(location = 1) out vec2 fragTexCoord;
layout(location = 2) out vec3 fragNormal;
layout(location = 3) out vec3 fragPosition;

void main() {
    mat4 model = modelUBO.models[gl_InstanceIndex]; // Use the model matrix for the current instance
    gl_Position = projUBO.proj * viewUBO.view * model * vec4(inPosition, 1.0);
    fragColor = inColor;
    fragTexCoord = inTexCoord;
    fragNormal = mat3(modelUBO.models[gl_InstanceIndex]) * inNormal;
    fragPosition = vec3(modelUBO.models[gl_InstanceIndex] * vec4(inPosition, 1.0));
}

Here is the current fragment shader:

#version 450

layout(binding = 3) uniform sampler2D texSampler;

layout(location = 0) in vec3 fragColor;
layout(location = 1) in vec2 fragTexCoord;
layout(location = 2) in vec3 fragNormal;
layout(location = 3) in vec3 fragPosition;

layout(location = 0) out vec4 outColor;

layout(set = 0, binding = 5) uniform Ambient_Data {
    vec3 color;
    float intensity;
} ambient;

layout(set = 0, binding = 6) uniform Directional_Data {
    vec3 position;
    vec3 color;
    float intensity;
} directional;

void main() {
    vec4 textureColor = texture(texSampler, fragTexCoord);
    vec4 ambient_color = ambient.intensity * vec4(ambient.color,1.0);
    vec3 light_direction = normalize(directional.position - fragPosition);
    float directional_intensity = max(dot(fragNormal, light_direction), 0.0);
    vec3 directional_color = directional_intensity * directional.color;
    vec4 fragmentColor = vec4(fragColor, 1.0); // Convert fragColor from vec3 to vec4
    outColor = textureColor * fragmentColor * (ambient_color + vec4(directional_color, 1.0)); // Blend the texture and fragment colors
}