Advertisement

(GLSL) depth prepass refuses to work - z-fighting

Started by June 07, 2018 02:23 PM
6 comments, last by tanzanite7 6 years, 7 months ago

Cannot get rid of z-fighting (severity varies between: no errors at all - ~40% fail).

* up-to-date validation layer has nothing to say.

* pipelines are nearly identical (differences: color attachments, descriptor sets for textures, depth write, depth compare op - LESS for prepass and EQUAL later).

* did not notice anything funny when comparing the draw commands via NSight either - except, see end of this post.

* "invariant gl_Position" for all participating vertex shaders makes no difference ('invariant' does not show up in decompile, but is present in SPIR-V).

* gl_Position calculations are identical for all (also using identical source data: push constants + vertex attribs)

However, when decompiling SPIR-V back to GLSL via NSight i noticed something rather strange:

Depth prepass has "gl_Position.z = 2.0 * gl_Position.z - gl_Position.w;" added to it. What is this!? "gl_Position.y = -gl_Position.y;", which is always added to everything, i can understand - vulcans NDC is vertically flipped by default in comparison to OpenGL. That is fine. What is the muckery with z there for? And why is it only selectively added?

Looking at my perspective projection code (the usual matrix multiplication, just simplified):

vec4 projection(vec3 v) { return vec4(v.xy * par.proj.xy, v.z * par.proj.z + par.proj.w, -v.z); }

All it ends up doing is doubling w-part of 'proj' in z (proj = vec4(1.0, 1.33.., -1.0, 0.2)). How does anything show at all given that i draw with compare op EQUAL. Decompile bug?

I am out of ideas.

The code modifying gl_Position.z is the standard modification intended to switch from OpenGL's clip space (-w -> w), to Vulkan's (0 -> w). How are you compiling your shaders to SPIR-V? Some tools have an option to automatically insert the depth-range fixup (and the inverted-Y fixup) -- perhaps that is what's happening in your case.

Advertisement

Ah, yeah, that z clipspace fix rings some bells. Now to figure out why it is missing for some shaders - or what is going on. I vaguely remember seeing somewhere something about compatibility options about [-1,1] <-> [0,1] z clipspace - cannot see that to enable/disable itself without me having any say. Have to investigate - perhaps i can force it to be consistent.

Using shaderc (from VulkanSDK 1.1.73.0) with compile options:

* target environment: vulkan

* set warnings as errors

* shaders have identical headers/extensions "GL_ARB_separate_shader_objects" + what shaderc adds.

Not using any cache - all always recompiled by the same code.

edit: Could not find anything. I wonder what convention the hardware prefers - or whether it makes any difference. If it makes no difference for the hardware then it would make sense to completely omit any muckery with y and z (combining what needs to be done into the projection constants pushed to shader). Could not find anything about how to do that either with shaderc x_x.

edit2: I am now virtually certain the y and z muckery is purely an artifact of NSight decompile. While the SPIR-V is a binary format and hard to read - it is still readable in its "assembler" form.

Findings for depth prepass:

* main calls one function (my projection code - a common shared function) and does nothing else.

Findings for normal pass:

* SPIR-V has 3 constants with value 2.

* main calls my projection function and other stuff. Constant 2 is never referenced.

Findings for both:

* gl_Position is referenced in only one place and once - inside my shared projection function:


        %135 = OpFunctionCall %9 %12 %134   // %9 = vec4, %12 = one_of_my_helper_functions, %134 = input variable
        %137 = OpAccessChain %136 %122 %40  // %136 = pointer to vec4, %122 = gl_Position, %40 = 0 (struct offset)
               OpStore %137 %135

Which brings me back to - why is there z-fighting happening?

A few questions that can help shed light on the problem:

* What depth range are you rendering -- e.g. what are your near and far planes? Where are most of your objects located? If the range is too large, then there may not be enough precision in the buffer where it matters.

* What depth format are you using? A larger format could help.

* Are you using the reverse-Z technique with a 32-bit floating point depth buffer? I recently implemented that and saw significant improvements.

I don't know Vulkan but to get a clearer idea of what might be happening I'll ask the following question: What exactly do you mean by z-fighting in this context?  Do you mean nothing is passing your depth test in the second pass or something else?

-potential energy is easily made kinetic-

I cannot see how depth buffer resolution can be relevant to this kind of z-fighting between two passes. Wiki example to illustrate how exactly it looks in my case (stuff gets through unless math errors make it fail - ie. exact same input with exact same code gives different end results): ZfightingCB.png

NB! With one major difference from the image given - it is not different geometry intersecting with insufficient depth resolution. It is the exact same geometry fighting with itself (depth prepass) - no amount of extra depth precision can fix it.

Anyway, the stats: float depth buffer, range 0.1-1000, not reversed. I actually do not need that range. For sanity sake, i just tried 0.5-100.0 range (which cuts off half my scene - so, projection math works, yay!) - as expected, it makes no difference at all.

Both SPIR-V's (prepass and draw pass) do have the "Invariant" decoration for "BuiltIn Position" (named 'gl_Position'). This kind of thing is essential for many - so, driver errors i can not reasonably believe in. I must be doing something wrong, but what?

Advertisement

Minimized vertex shaders (source printout of what gets sent to the compiler):


// depth prepass
#version 450
#pragma shader_stage(vertex)
#extension GL_ARB_separate_shader_objects : enable

layout(location=0) in vec3 inPos;

invariant gl_Position;
layout(push_constant) uniform Push { vec4 proj, pos, rot; } par;

vec4 projection(vec3 v) { return vec4(v.xy * par.proj.xy, v.z * par.proj.z + par.proj.w, -v.z); }
vec3 qrot(vec4 q, vec3 v) { return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v); }
vec4 qinv(vec4 q) { return vec4(-q.xyz, q.w); }
vec3 transInv(vec3 v, vec4 pos, vec4 rot) { return qrot(qinv(rot), (v - pos.xyz) / pos.w); }

vec3 projAndGetPos() {
    vec3 pos = inPos * (32767.0 / 1024.0);
    gl_Position = projection(transInv(pos, par.pos, par.rot));
    return pos;
}

void main() {
    projAndGetPos();
}

// draw pass
#version 450
#pragma shader_stage(vertex)
#extension GL_ARB_separate_shader_objects : enable

layout(location=0) in vec3 inPos;
layout(location=1) in vec2 inSelColorTex;
layout(location=2) in vec4 inNormSelCover;
layout(location=3) in vec4 inTexSet;

invariant gl_Position;
layout(push_constant) uniform Push { vec4 proj, pos, rot; } par;
layout(location=0) out Frag { vec3 pos; float selTex; vec4 color; vec3 normal; float selCover; vec3 tocam; flat vec4 texSet; } sOut;

vec4 projection(vec3 v) { return vec4(v.xy * par.proj.xy, v.z * par.proj.z + par.proj.w, -v.z); }
vec3 qrot(vec4 q, vec3 v) { return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v); }
vec4 qinv(vec4 q) { return vec4(-q.xyz, q.w); }
vec3 transInv(vec3 v, vec4 pos, vec4 rot) { return qrot(qinv(rot), (v - pos.xyz) / pos.w); }

vec3 projAndGetPos() {
   vec3 pos = inPos * (32767.0 / 1024.0);
   gl_Position = projection(transInv(pos, par.pos, par.rot));
   return pos;
}

void main() {
    sOut.pos = projAndGetPos();
    sOut.selTex = inSelColorTex.y;
    sOut.color = vec4(0.0);
    sOut.normal = inNormSelCover.xyz;
    sOut.selCover = inNormSelCover.w;
    sOut.tocam = par.pos.xyz - sOut.pos;
    sOut.texSet = inTexSet * 255.0;
}

 

This topic is closed to new replies.

Advertisement