Why is my GLSL shader rendering a cleavage? - opengl

I'm working on a deferred lighting technique in 2D, using a frame buffer to accumulate light sources using the GL_MAX blend equation.
Here's what I get when rendering one light source (the geometry is a quad without a texture, I'm only using a fragment shader for colouring) to my buffer:
Which is exactly what I want - attenuation from the light source. However, when two light sources are near each other, when they overlap, they seem to produce a lower RGB value where they meet, like so:
Why is there a darker line between the two? I was expecting that with GL_MAX blend equation they would smoothly blend into each other, using the maximal value of the fragments in each location.
Here's the setup for the FBO (using LibGDX):
Gdx.gl.glClearColor(0.14f, 0.14f, 0.19f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
Gdx.gl.glBlendEquation(GLMAX_BLEND_EQUATION);
Gdx.gl.glBlendFunc(GL20.GL_SRC_COLOR, GL20.GL_DST_COLOR);
Gdx.gl.glEnable(GL20.GL_BLEND);
I don't think the call to glBlendFunc is actually necessary when using this equation. GLMAX_BLEND_EQUATION is set to 0x8008.
varying vec2 v_texCoords;
varying vec2 v_positionRelativeToLight;
uniform sampler2D u_texture;
uniform vec3 u_lightPosition;
uniform vec3 u_lightColor;
void main() {
float distanceToLight = length(v_positionRelativeToLight);
float falloffVarA = 0.1;
float falloffVarB = 1.0;
float attenuation = 1.0 / (1.0 + (falloffVarA*distanceToLight) + (falloffVarB*distanceToLight*distanceToLight));
float minDistanceOrAttenuation = min(attenuation, 1.0-distanceToLight);
float combined = minDistanceOrAttenuation * attenuation;
gl_FragColor = vec4(combined, combined, combined, 1.0);
}
There are extra variables passed in there as this fragment shader is usually more complicated, but I've cut it down to just show how the attenuation and blending is behaving.
This happens between every light source that I render where they meet - rather than the colour that I'm expecting, the meeting of two light sources - the equidistant point between the two quads, is a darker colour that I'm expecting. Any idea why and how to fix it?

This is the result of subtracting the first image from the second:
The background on the first isn't quite black, hence the yellowing on the right, but otherwise you can clearly see the black region on the left where original values were preserved, the darker arc where values from both lights were evaluated but the right was greater, then all the area on the right that the original light didn't touch.
I therefore think you're getting max-pick blending. But what you want is additive blending:
Gdx.gl.glBlendFunc(GL20.GL_ONE, GL20.GL_ONE);
... and leave the blend equation on the default of GL_FUNC_ADD.

Your result is the expected appearance for maximum blending (which is just like the lighten blend mode in Photoshop). The dark seam looks out of place perhaps because of the non-linear falloff of each light, but it's mathematically correct. If you introduce a light with a bright non-white color to it, it will look much more objectionable.
You can get around this if you render your lights to a frame buffer with inverted colors and multiplicative blending, and then render the frame buffer with inverted colors. Then the math works out to not have the seams, but it won't look unusually bright like what additive blending produces.
Use a pure white clear color on your frame buffer and then render the lights with the standard GL_ADD blend equation and the blend function GL_ONE_MINUS_DST_COLOR. Then render your FBO texture to the screen, inverting the colors again.
Two lights drawn using your method
Two lights drawn additively
Two lights, drawn sequentially with GL_ONE_MINUS_DST_COLOR, GL_ZERO and GL_ADD
The above result, inverted

Related

Read write an image with the GL_ARB_fragment_shader_interlock extension

I am testing whether the GL_ARB_fragment_shader_interlock extension can execute the critical section code of the same pixel position in the order of instance rendering. I used instanced rendering to draw five translucent planes (instances in order from farthest to nearer) and the result is the same as the fixed pipeline blending result (the blending result is random without this extension). But one problem is that there will be an extra line in the middle of each plane (not when using fixed pipeline blending). I found that the adjacent sides of the triangle generating the fragments twice. But rasterization should ensure that adjacent triangles do not have overlapping pixels. How to solve this please? I don't know where I went wrong, here is the code and result, please enlighten me!
The GLSL code:
#version 450 core
#extension GL_ARB_fragment_shader_interlock : require
//out vec4 Color_;
in vec4 v2f_Color;
layout(binding = 0, rgba16f) uniform image2D uColorTex;
void main()
{
beginInvocationInterlockARB();
vec4 color = imageLoad(uColorTex, ivec2(gl_FragCoord.xy));
color = (1 - v2f_Color.a) * color + v2f_Color * v2f_Color.a;
imageStore(uColorTex, ivec2(gl_FragCoord.xy), color);
endInvocationInterlockARB();
//Color_ = v2f_Color;
}
Tthe result using extension to read write an image manually:
The result using fixed pipeline blending:
I am a bit suspicious of the ivec2(gl_FragCoord.xy). This is doing a conversion from floating-point to an integer, and it could be the case that it gets rounded in different directions between the different triangles. This might explain not only the overlap, but why there's a missing top-left pixel in one of the squares.
Judging by the spec, ivec2(gl_FragCoord.xy) should be equivalent to ivec2(trunc(gl_FragCoord.xy)), which really ought to be consistent, but maybe the implementation is bad...
You might want to try:
ivec2(round(gl_FragCoord.xy))
ivec2(round(gl_FragCoord.xy + 0.5))

Is gl_FragDepth equal gl_FragCoord.z when msaa enable?

I know gl_FragDepth will take the value of gl_FragCoord.z from opengl wiki.
https://www.khronos.org/opengl/wiki/Fragment_Shader/Defined_Outputs
But I have a problem. If I enable MSAA and write gl_FragDepth = gl_FragCoord.z in fragment shader, the display will not work fine. You can see a black line on the white triangle as below:
If I don't write gl_FragDepth in fragment shader, it will works fine.
If I disable MSAA, it also works fine no matter if I write gl_FragDepth.
The correct display image has no black line:
The render scene is easy, I just draw 2 white triangles and they are intersected on an edge.
I add a simple light in vertex shader. The codes show as below:
const char *vertexShaderSource[] = {
"#version 120\n",
"varying vec4 lightColor;\n",
"void main()\n",
"{\n",
" vec3 n = normalize(gl_NormalMatrix * gl_Normal);\n",
" vec3 l = normalize(vec3(0.0, 1.0, 1.0));\n",
" float NdotL = clamp(dot(n, l), 0.001, 1.0);\n",
" lightColor = vec4(1.0)*(NdotL + 0.2);\n",
" gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n",
"}\n"
};
const char *fragmentShaderSource[] = {
"#version 120\n",
"varying vec4 lightColor;\n",
"void main(void)\n",
"{\n",
" gl_FragColor = vec4(lightColor.rgb, 1.0);\n",
" gl_FragDepth = gl_FragCoord.z;\n"
"}\n"
};
The positions of 6 vertices of 2 triangles are (-5,-5,0),(5,-5,0),(-5,5,0),(-5,0,0),(5,0,0),(-5,0,-10).
The normals are perpendicular to triangles.
I wanna know why the display images are different if I write gl_FragDepth in fragment shader?
Your two triangles intersect. Specifically, the grey triangle has an edge which can generate depth values equal to the depth values of the white triangle. As such, it is entirely possible for a particular sample from the grey triangle at that intersection to generate a depth value that is equal to the depth value of the white triangle.
So you were never guaranteed to not see a line there; you just happened not to many cases.
However, that all assumes that:
The grey triangle is being rendered after the white one.
Your depth test will pass on equal values.
The result you are getting here may happen even outside of these two conditions. The reason for that is complex.
See, the whole point of multisampling is that the number depth values generated by the rasterizer and the number of fragment shader executions are not the same. So a single FS invocation is mapped to multiple depth values.
However, a single FS invocation can still write to gl_FragDepth. If it does this, then all samples that map to that FS invocation will receive the same depth. This depth overrides the multisample-generated depth values.
Also, interpolation at the edges of a primitive is weird under multisampling. Each sample that is within the bounds of the triangle at that pixel will result in a sample value being written (unless something else culls it out). But the center point of the pixel need not be one of these sample locations. So a triangle that doesn't pass through the center of a pixel can still contribute some samples to the pixel, so long as the triangle passes through at least one sample in that pixel.
The fragment shader gets interpolated values based on some location inside the pixel. With multisampling, this location may not be inside of the triangle. For example, if the location the implementation selects for the FS's interpolation within the pixel is in the center of the triangle, and the triangle doesn't pass through the center of that pixel, you will still get an FS invocation so long as it passes through some sample.
But this means that the interpolated values can represent locations outside of the area of the triangle. The interpolation math can produce values for areas not within the triangle; they just don't make sense.
gl_FragCoord, being an interpolated value, could therefore generate values outside of the triangle. Since the grey triangle is aimed towards the viewer, the values from locations "above" the oncoming edge of the grey triangle will be closer than they should be. And since the edge of the grey triangle intersects the white triangle, values closer than its actual edge values will be considered closer than the white triangle
The normal way to counter this would be to use the centroid interpolation qualifier. However, the standard doesn't really allow this; even if you redeclared gl_FragCoord with the centroid qualifier, it won't have any effect:
The use of centroid does not further restrict this value to be inside the current primitive.
Also, as previously stated, depth-replacement in regular multisampled rendering destroys all of the per-sample depth information anyway. Every sample in a pixel would get the same depth value if your FS writes to the depth. That's not really what you wanted, even if you could do centroid interpolation of gl_FragCoord (which is probably why they don't allow it).
So if it is absolutely essential to do depth-replacement in a shader used for multisampling (and you should avoid this whenever possible), you will need to use per-sample shading. You can redeclare gl_FragCoord with sample to achieve this.

How could I remove this colour interpolation artefact across a quad?

I've been reading up on a vulkan tutorial online, here: https://vulkan-tutorial.com. This question should apply to any 3D rendering API however.
In this lesson https://vulkan-tutorial.com/Vertex_buffers/Index_buffer, the tutorial had just covered using indexed rendering in order to reuse vertices when drawing the following simple two-triangle quad:
The four vertices were assigned red, green, blue and white colours as vertex attributes and the fragment shader had those colours interpolated across the triangles as expected. This leads to the ugly visual artefact on the diagonal where the two triangles meet. As I understand it, the interpolation will only be happening across each triangle, and so where the two triangles meet the interpolation doesn't cross the boundary.
How could you, generally in any rendering api, have the colours smoothly interpolated over all four corners for a nice colour wheel affect without having this hard line?
This is a correct output from a graphics api point of view. You can achieve your own desired output (a color gradient) within the shader code. You basically need to interpolate the colors yourself. To get an idea on how to do this, here is a glsl piece of code from this answer:
uniform vec2 resolution;
void main(void)
{
vec2 p = gl_FragCoord.xy / resolution.xy;
float gray = 1.0 - p.x;
float red = p.y;
gl_FragColor = vec4(red, gray*red, gray*red, 1.0);
}

OpenGL shader gamma on solid color

I want to implement gamma correction on my OpenGL 3D renderer, I understand that it's absolutely relevant on texture loaded in sRGB, so I do this:
vec4 texColor;
texColor = texture(src_tex_unit0, texCoordVarying);
texColor = vec4(pow(texColor.rgb,vec3(2.2)),1.0);
vec4 colorPreGamma = texColor * (vec4(ambient,1.0) + vec4(diffuse,1.0));
fragColor = vec4(pow(colorPreGamma.rgb, vec3(1.0/gamma)),1.0);
But my question is about solid color, when the surface of the 3D object I want lit is not textured but just colored by a per vertex RGB value. In this case, do I have to transform my color in Linear space, and after the lighting operation, transform back to gamma space like I do for a texture?
Does this apply when my light are colored?
In this case, do I have to transform my color in Linear space, and after the lighting operation, transform back to gamma space like I do for a texture?
That depends: what colorspace are your colors in?
You're not doing this correction because of where they come from; you're doing it because of what the colors actually are. If the value is not linear, then you must linearize it before using it, regardless of where it comes from.
You are ultimately responsible for putting that color there. So you must have to know whether that color is in linear RGB or sRGB colorspace. And if the color is not linear, then you have to linearize it before you can get meaningful numbers from it.
In OpenGL there isn't a huge distinction between color data and other kinds of data: if you have a vec3 you can access components as .xyz or .rgb. It's all just data.
So ask yourself this: "Do I have to gamma correct my vertex positions in the vertex shader?"
Of course not, because your vertex positions are already in linear space. So if you are similarly setting your vertex colors in linear space, again no gamma correction is needed.
In other words, do you imagine vec3(0.5, 0.5, 0.5) as being a gray that is visually halfway between black and white? Then you need gamma correction.
Do you imagine it as being mathematically halfway between black and white (in terms of measurable light intensity)? Then it's already linear.

Texture lookup into rendered FBO is off by half a pixel

I have a scene that is rendered to texture via FBO and I am sampling it from a fragment shader, drawing regions of it using primitives rather than drawing a full-screen quad: I'm conserving resources by only generating the fragments I'll need.
To test this, I am issuing the exact same geometry as my texture-render, which means that the rasterization pattern produced should be exactly the same: When my fragment shader looks up its texture with the varying coordinate it was given it should match up perfectly with the other values it was given.
Here's how I'm giving my fragment shader the coordinates to auto-texture the geometry with my fullscreen texture:
// Vertex shader
uniform mat4 proj_modelview_mat;
out vec2 f_sceneCoord;
void main(void) {
gl_Position = proj_modelview_mat * vec4(in_pos,0.0,1.0);
f_sceneCoord = (gl_Position.xy + vec2(1,1)) * 0.5;
}
I'm working in 2D so I didn't concern myself with the perspective divide here. I just set the sceneCoord value using the clip-space position scaled back from [-1,1] to [0,1].
uniform sampler2D scene;
in vec2 f_sceneCoord;
//in vec4 gl_FragCoord;
in float f_alpha;
out vec4 out_fragColor;
void main (void) {
//vec4 color = texelFetch(scene,ivec2(gl_FragCoord.xy - vec2(0.5,0.5)),0);
vec4 color = texture(scene,f_sceneCoord);
if (color.a == f_alpha) {
out_fragColor = vec4(color.rgb,1);
} else
out_fragColor = vec4(1,0,0,1);
}
Notice I spit out a red fragment if my alpha's don't match up. The texture render sets the alpha for each rendered object to a specific index so I know what matches up with what.
Sorry I don't have a picture to show but it's very clear that my pixels are off by (0.5,0.5): I get a thin, one pixel red border around my objects, on their bottom and left sides, that pops in and out. It's quite "transient" looking. The giveaway is that it only shows up on the bottom and left sides of objects.
Notice I have a line commented out which uses texelFetch: This method works, and I no longer get my red fragments showing up. However I'd like to get this working right with texture and normalized texture coordinates because I think more hardware will support that. Perhaps the real question is, is it possible to get this right without sending in my viewport resolution via a uniform? There's gotta be a way to avoid that!
Update: I tried shifting the texture access by half a pixel, quarter of a pixel, one hundredth of a pixel, it all made it worse and produced a solid border of wrong values all around the edges: It seems like my gl_Position.xy+vec2(1,1))*0.5 trick sets the right values, but sampling is just off by just a little somehow. This is quite strange... See the red fragments? When objects are in motion they shimmer in and out ever so slightly. It means the alpha values I set aren't matching up perfectly on those pixels.
It's not critical for me to get pixel perfect accuracy for that alpha-index-check for my actual application but this behavior is just not what I expected.
Well, first consider dropping that f_sceneCoord varying and just using gl_FragCoord / screenSize as texture coordinate (you already have this in your example, but the -0.5 is rubbish), with screenSize being a uniform (maybe pre-divided). This should work almost exact, because by default gl_FragCoord is at the pixel center (meaning i+0.5) and OpenGL returns exact texel values when sampling the texture at the texel center ((i+0.5)/textureSize).
This may still introduce very very very slight deviations form exact texel values (if any) due to finite precision and such. But then again, you will likely want to use a filtering mode of GL_NEAREST for such one-to-one texture-to-screen mappings, anyway. Actually your exsiting f_sceneCoord approach may already work well and it's just those small rounding issues prevented by GL_NEAREST that create your artefacts. But then again, you still don't need that f_sceneCoord thing.
EDIT: Regarding the portability of texelFetch. That function was introduced with GLSL 1.30 (~SM4/GL3/DX10-hardware, ~GeForce 8), I think. But this version is already required by the new in/out syntax you're using (in contrast to the old varying/attribute syntax). So if you're not gonna change these, assuming texelFetch as given is absolutely no problem and might also be slightly faster than texture (which also requires GLSL 1.30, in contrast to the old texture2D), by circumventing filtering completely.
If you are working in perfect X,Y [0,1] with no rounding errors that's great... But sometimes - especially if working with polar coords, you might consider aligning your calculated coords to the texture 'grid'...
I use:
// align it to the nearest centered texel
curPt -= mod(curPt, (0.5 / vec2(imgW, imgH)));
works like a charm and I no longer get random rounding errors at the screen edges...