~redstrate/graph

d77c071e3d43da784c95b142687f78213c034658 — Joshua Goins 2 years ago fccaf1f
Fix depth of field pass

It now works correctly, and will output a "proper" depth of field effect.
There's still work to be done to make it look smoother, but it's already pretty convincing.
4 files changed, 86 insertions(+), 40 deletions(-)

M shaders/dof.frag
M shaders/dof.vert
M shaders/post.frag
M src/dofpass.cpp
M shaders/dof.frag => shaders/dof.frag +7 -4
@@ 12,15 12,18 @@ layout(binding = 2) uniform sampler2D depthSampler;

layout(push_constant) uniform PushConstants {
    vec4 dpack;
    vec4 dpack2;
    int reverse;
} pushConstants;

void main() {    
    const vec2 res = vec2(pushConstants.dpack[2], pushConstants.dpack[3]);

    outColor = texture(sceneSampler, vec2(inPos.x / res.x, inPos.y / res.y)) * texture(bokehSampler, inUV);
    outColor.a = (cocRadius / 9000.0);
    // bokeh luminance is also based off of Bart Wronski's work.
    const vec3 bokehSample = texture(bokehSampler, inUV).rgb;
    const float lum = dot(bokehSample, vec3(0.299, 0.587, 0.114));

    if(texture(depthSampler, gl_FragCoord.xy / res).r < pushConstants.dpack[1])
        discard;
    outColor.rgb = bokehSample * texture(sceneSampler, vec2(inPos.x / res.x, inPos.y / res.y)).rgb * cocRadius;
    outColor.a = cocRadius * lum;
}


M shaders/dof.vert => shaders/dof.vert +37 -9
@@ 8,32 8,60 @@ layout(binding = 2) uniform sampler2D depthSampler;

layout(push_constant) uniform PushConstants {
    vec4 dpack;
    vec4 dpack2;
    int reverse;
} pushConstants;

// we calculate a circle of confusion based off of a bias and scale
// we basically do aperture * difference based off of surrounding depth
// the bias and scale calculations are based off of Bart Wronski's work:
// https://bartwronski.com/2014/04/07/bokeh-depth-of-field-going-insane-part-1/
float calculate_coc(vec2 inUV) {
    const float bias = pushConstants.dpack[0] * (1.0 - pushConstants.dpack2[0] / pushConstants.dpack2[1]);
    const float scale = pushConstants.dpack[0] * pushConstants.dpack2[0] * (pushConstants.dpack2[2] - pushConstants.dpack2[1]) / (pushConstants.dpack2[2] * pushConstants.dpack2[1]);

    const vec4 depth = textureGather(depthSampler, inUV);
    const float maxDepth = max(max(depth.x, depth.y), max(depth.z, depth.w));

    return scale * maxDepth + bias;
}

void main() {
    const vec2 res = vec2(pushConstants.dpack[2], pushConstants.dpack[3]);

    const vec2 loc = vec2((gl_InstanceIndex % int(res.x)), ((gl_InstanceIndex / int(res.x)) % int(res.y))); 
    outLoc = loc;

    const float depth = texture(depthSampler, vec2(loc.x / res.x, loc.y / res.y)).r;
    float size = 0.0;
    
    if(depth > pushConstants.dpack[1])
        size = (depth - pushConstants.dpack[1]) * 500.0 * pushConstants.dpack[0];
    
    const float coc = calculate_coc(vec2(loc.x / res.x, loc.y / res.y));

    // this dof implementation relies on two separate fields, so we want to cull near objects when rendering the far field, and vice versa
    const bool near = coc < 0.0f;
    float cocScale = abs(coc);
    if(pushConstants.reverse == 1) {
        if(!near) {
            cocScale = 0.0;
        }
    } else {
        if(near) {
            cocScale = 0.0;
        }
    }

    // we limit the radius of every bokeh sample to 32, any higher risks heavy overdraw
    const float size = min(cocScale, 32.0);
    cocRadius = size;
            

    outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
    
    vec2 pos = outUV * 2.0 + -1.0;
    pos *= size;
    pos *= vec2(1.0 / res.x, 1.0 / res.y);
    pos *= min(size, 32.0);
    pos.x -= 1;
    pos.y -= 1;
    pos.x += loc.x / (res.x / 2.0);
    pos.y += loc.y / (res.y / 2.0);

    gl_Position = vec4(pos, 0.0, 1.0);
    // invalid bokeh is culled out of viewport
    gl_Position = vec4(pos, 0.0, (cocScale < 1.0) ? -1.0 : 1.0);
}


M shaders/post.frag => shaders/post.frag +17 -14
@@ 29,25 29,28 @@ layout(push_constant) uniform PushConstants {
} pushConstants;

void main() {
    vec3 sceneColor = vec3(0);
    sceneColor = SMAANeighborhoodBlendingPS(inUV, inOffset, sceneSampler, blendSampler).rgb;
    vec3 sceneColor = SMAANeighborhoodBlendingPS(inUV, inOffset, sceneSampler, blendSampler).rgb;

    // alpha divide reconstruction
    vec3 farColor = texture(farFieldSampler, inUV).rgb / max(texture(farFieldSampler, inUV).a, 0.0001) * 0.02;
    vec3 nearColor = texture(nearFieldSampler, inUV).rgb / max(texture(nearFieldSampler, inUV).a, 0.0001) * 0.02;
    const vec4 farPlaneColor = texture(farFieldSampler, inUV);
    const vec4 nearPlaneColor = texture(nearFieldSampler, inUV);

    // read coc stored in the alpha channel
    float coc = texture(farFieldSampler, inUV).a;
    // we perform alpha divide reconstruction, or else the results will look really blown out because of additive blending
    const vec3 farColor = farPlaneColor.rgb / max(farPlaneColor.a, 0.0001);
    const vec3 nearColor = nearPlaneColor.rgb / max(nearPlaneColor.a, 0.0001);

    // transistion between out of focus and regular scene
    vec3 farColorBlurred = mix(sceneColor, farColor, clamp(coc, 0.0, 1.0));
    // read coc stored in the alpha channel
    const float coc = texture(farFieldSampler, inUV).a;
    const float coc2 = texture(nearFieldSampler, inUV).a;

    // smoother transistion between the normal scene and the "out of focus" portions
    farColorBlurred = mix(sceneColor, farColorBlurred, clamp(0.5 * coc + 1.0, 0.0, 1.0));
    // transition between out of focus and regular scene
    // TODO: make this softer
    vec3 farColorBlurred = mix(sceneColor, farColor, clamp(coc - 2.0, 0.0, 1.0));
    farColorBlurred = mix(sceneColor, farColor, clamp(coc * 5.0, 0.0, 1.0));

    //float coc2 = texture(nearFieldSampler, inUV).a;
    //vec3 finalColor = mix(farColorBlurred, nearColor, clamp(clamp(-coc2 - 1.0, 0.0, 1.0) + texture(nearFieldSampler, inUV).a * 8.0, 0.0, 1.0));
    // now we take into account the near field, using it's own coc
    const vec3 final = mix(farColorBlurred, nearColor, clamp(coc2 * 5.0, 0.0, 1.0));

    // sobel calculation
    float thickness = 3.0;
    float thicknessX = thickness * pushConstants.viewport.x * (pushConstants.viewport.z / 1920.0);
    float thicknessY = thickness * pushConstants.viewport.y * (pushConstants.viewport.w / 1080.0);


@@ 68,7 71,7 @@ void main() {

    vec4 outlineColor = vec4(0.1, 0.5, 0.9, 1.0);

    outColor = vec4(farColorBlurred, 1.0);
    outColor = vec4(final, 1.0);
    outColor = outlineColor*sobel + outColor*(1.0 - sobel);
    outColor += outlineColor * texture(sobelSampler, inUV) * 0.3;
}

M src/dofpass.cpp => src/dofpass.cpp +25 -13
@@ 8,6 8,11 @@
#include "rendercollection.h"
#include "ecs.h"

struct DoFPushConstant {
    glm::vec4 dpack, dpack2;
    int reverse;
};

DoFPass::DoFPass(Renderer& renderer) : renderer_(renderer) {
    createRenderPass();
    createDescriptorSetLayout();


@@ 57,16 62,20 @@ void DoFPass::render(VkCommandBuffer commandBuffer, CameraComponent& camera, Ren
    // far field
    vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);

    glm::vec4 dpack;
    dpack[0] = camera.aperture;
    dpack[1] = (camera.far - camera.focusDistance) / camera.far;
    dpack[2] = target->extent.width / renderer_.getConfig().dofDownscale;
    dpack[3] = target->extent.height / renderer_.getConfig().dofDownscale;
    DoFPushConstant pushConstant;
    pushConstant.reverse = 0;
    pushConstant.dpack[0] = camera.aperture;
    pushConstant.dpack[1] = camera.near + (camera.far - camera.near);
    pushConstant.dpack[2] = target->extent.width / renderer_.getConfig().dofDownscale;
    pushConstant.dpack[3] = target->extent.height / renderer_.getConfig().dofDownscale;
    pushConstant.dpack2[0] = camera.near + camera.focusDistance;
    pushConstant.dpack2[1] = camera.near;
    pushConstant.dpack2[2] = camera.far;

    vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_);
    vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_, 0, 1, &target->dofSets[target->currentResource], 0, nullptr);

    vkCmdPushConstants(commandBuffer, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(glm::vec4), &dpack);
    vkCmdPushConstants(commandBuffer, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(DoFPushConstant), &pushConstant);

    if(camera.aperture > 0.0f)
        vkCmdDraw(commandBuffer, 3, (target->extent.width / renderer_.getConfig().dofDownscale) * (target->extent.height / renderer_.getConfig().dofDownscale), 0, 0);


@@ 75,16 84,18 @@ void DoFPass::render(VkCommandBuffer commandBuffer, CameraComponent& camera, Ren

    //near field
    renderPassBeginInfo.framebuffer = target->nearFieldFramebuffers[target->currentResource];
    pushConstant.reverse = 1;
    //pushConstant.dpack[1] = (camera.near - camera.focusDistance) / camera.near;

    vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);

    vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_);
    vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_, 0, 1, &target->dofSets[target->currentResource], 0, nullptr);

    vkCmdPushConstants(commandBuffer, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(glm::vec4), &dpack);
    vkCmdPushConstants(commandBuffer, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(DoFPushConstant), &pushConstant);

    //FIXME: near field is bugged
    //vkCmdDraw(commandBuffer, 3, (target->extent.width / 2) * (target->extent.height / 2), 0, 0);
    if(camera.aperture > 0.0f)
        vkCmdDraw(commandBuffer, 3, (target->extent.width / 2) * (target->extent.height / 2), 0, 0);

    vkCmdEndRenderPass(commandBuffer);
}


@@ 267,6 278,7 @@ void DoFPass::createPipeline() {
    colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
    colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
    colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
    colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;

    VkPipelineColorBlendStateCreateInfo colorBlending = {};
    colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;


@@ 284,7 296,7 @@ void DoFPass::createPipeline() {
    dynamicState.pDynamicStates = dynamicStates.data();

    VkPushConstantRange pushConstant = {};
    pushConstant.size = sizeof(glm::vec4);
    pushConstant.size = sizeof(DoFPushConstant);
    pushConstant.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;

    VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};


@@ 374,9 386,9 @@ void DoFPass::createBokehImage() {
    samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
    samplerInfo.magFilter = VK_FILTER_LINEAR;
    samplerInfo.minFilter = VK_FILTER_LINEAR;
    samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
    samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
    samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
    samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
    samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
    samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
    samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
    samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;