float getRawDepth(vec2 uv) {
    return texelFetch(depthtex0, ivec2(uv), 0).x;
}

vec3 viewSpacePosAtScreenUV(vec2 uv) {
    vec3 viewSpaceRay = (gbufferProjectionInverse * vec4((uv * texelSize) * 2.0 - 1.0, 1.0, 1.0) * near).xyz;
    float rawDepth = getRawDepth(uv);
    return viewSpaceRay * (ld(rawDepth));
}
vec3 viewSpacePosAtScreenUV2(vec2 uv, float depth) {
    vec3 viewSpaceRay = (gbufferProjectionInverse * vec4((uv * texelSize) * 2.0 - 1.0, 1.0, 1.0) * near).xyz;
    return viewSpaceRay * depth;
}
vec4 ld42(vec4 dist) {
    return (2.0 * near) / (far + near - dist * (far - near));
}

vec3 viewNormalAtPixelPosition2(vec2 vpos) {
    // screen uv from vpos
    vec2 uv = vpos;

    // current pixel's depth
    float c = getRawDepth(uv);

    // get current pixel's view space position
    vec3 viewSpacePos_c = viewSpacePosAtScreenUV(uv);
    
    const ivec2 texoffsets0[4] = ivec2[4](ivec2(-1.0, 0.0), ivec2(1.0, 0.0), ivec2(0.0, -1.0), ivec2(0.0, 1.0));
    vec4 viewSpacePos_depth = ld42(textureGatherOffsets(depthtex0, texcoord, texoffsets0, 0));

    // get view space position at 1 pixel offsets in each major direction
    vec3 viewSpacePos_l = viewSpacePosAtScreenUV2(uv + vec2(-1.0, 0.0), viewSpacePos_depth.x);
    vec3 viewSpacePos_r = viewSpacePosAtScreenUV2(uv + vec2(1.0, 0.0), viewSpacePos_depth.y);
    vec3 viewSpacePos_d = viewSpacePosAtScreenUV2(uv + vec2(0.0, -1.0), viewSpacePos_depth.z);
    vec3 viewSpacePos_u = viewSpacePosAtScreenUV2(uv + vec2(0.0, 1.0), viewSpacePos_depth.w);

    // get the difference between the current and each offset position
    vec3 l = viewSpacePos_c - viewSpacePos_l;
    vec3 r = viewSpacePos_r - viewSpacePos_c;
    vec3 d = viewSpacePos_c - viewSpacePos_d;
    vec3 u = viewSpacePos_u - viewSpacePos_c;

    // get depth values at 1 & 2 pixels offsets from current along the horizontal
    // axis
    const ivec2 texoffsetsH[4] = ivec2[4](ivec2(-1.0, 0.0), ivec2(1.0, 0.0), ivec2(-2.0, 0.0), ivec2(2.0, 0.0));

    vec4 H = (textureGatherOffsets(depthtex0, texcoord, texoffsetsH, 0));

    // get depth values at 1 & 2 pixels offsets from current along the vertical
    // axis
    const ivec2 texoffsetsV[4] = ivec2[4](ivec2(0.0, -1.0), ivec2(0.0, 1.0), ivec2(0.0, -2.0), ivec2(0.0, 2.0));

    vec4 V = (textureGatherOffsets(depthtex0, texcoord, texoffsetsV, 0));
    // current pixel's depth difference from slope of offset depth samples
    // differs from original article because we're using non-linear depth values
    // see article's comments
    vec2 he = abs((2 * H.xy - H.zw) - c);
    vec2 ve = abs((2 * V.xy - V.zw) - c);

    // pick horizontal and vertical diff with the smallest depth difference from
    // slopes
    vec3 hDeriv = he.x < he.y ? l : r;
    vec3 vDeriv = ve.x < ve.y ? d : u;

    // get view space normal from the cross product of the best derivatives
    vec3 viewNormal = normalize(cross(hDeriv, vDeriv));

    return viewNormal;
}