Skip to main content
GameDev.net gamedev.net
🔒 Locked

Efficient drawing of 2d gridlines with shaders

Started by ChugginWindex Jan 19, 2012 at 12:23 AM 19 replies 20.8k views
Original Post
ChugginWindex
ChugginWindex
I'm working on an android app and I'm targeting OpenGL ES 2 only. I want to draw grid lines around quads and I'm currently compiling them as a VBO and drawing them after I draw the quad itself. It currently looks like this:
Untitled.png

Now as you can see the grid lines aren't entirely symmetrical with respect to the quad itself. Neither is the fade-out I'm applying in the shader. I know I could fix this with a bit of math when I'm calculating the grid line VBO and applying a translation to the center-point used to calculate the fade, but that won't work when I start varying the size of the quads itself. Is there a simpler way to draw the grid lines than what I've come up with? I just want them to show up surrounding the quad and with a bit of a fade off that I can adjust no matter whether the quad is 1 unit or 3 units on a side or whatever. I've considered using a texture that looks like the grid lines but I don't think that'll work because of aliasing as I zoom out.
Ruggostein
Ruggostein
You can just draw a fullscreen quad with a shader that tests the screen position. Use the modulus operator (%) to test if a point lies inside the grid (and then you can calculate an opacity value)
ChugginWindex
ChugginWindex

You can just draw a fullscreen quad with a shader that tests the screen position. Use the modulus operator (%) to test if a point lies inside the grid (and then you can calculate an opacity value)


I actually tried that recently. The problem is that when you zoom out you get horrendous aliasing issues:

nope.png

Thanks for the reply tho! I'm starting to wonder if this is even possible to do without just calculating a huge vbo to put all the lines in every time the view changes...
Tsus
Tsus
Hi!
The anti-aliasing is indeed a nasty problem. Would it be of any help to you if lines would fade out, if you zoom out? Let’s say, if you are close you see all lines, if you get farer away, you see each second. If you get even more farer away, you see each fourth and so on. There is an easy way to achieve this for procedural patterns without any need for mipmapped textures (so it is fast). If that would help you, I could search a little in my old code and show that here to you.
ChugginWindex
ChugginWindex

Hi!
The anti-aliasing is indeed a nasty problem. Would it be of any help to you if lines would fade out, if you zoom out? Let’s say, if you are close you see all lines, if you get farer away, you see each second. If you get even more farer away, you see each fourth and so on. There is an easy way to achieve this for procedural patterns without any need for mipmapped textures (so it is fast). If that would help you, I could search a little in my old code and show that here to you.


Yeah that would be great. I'd considered fading it out with the "altitude" of the camera so to speak, but I hadn't considered skipping grid lines entirely like you suggested. I might give this a shot myself if I get time today but if you've got something similar I'd love to take a look.
Tsus
Tsus
Alright. You can render a quad in world space with texture coordinates (tx) in [0,1]. We are going to use the screen space derivatives of the texture coordinate to compute the best fitting mipmap level. This is going to be some rational number so we will compute the pattern for the two adjacent integer levels and then interpolate between them. Since, we use texture derivatives to estimate the mipmap level, and not only the depth, this approach also works for different viewing angles. (Besides, texture2D uses the same approach to compute the mipmap level.) You find some notes on this approach in the IRIS paper of Hummel et al. in section 4.1.

// get the length of the texture derivatives
float lambda_s = length(dFdx(tx));
float lambda_t = length(dFdy(tx));

// compute the sample level, based on the maximum derivative, and the interpolation factor between ceiled and floored level
float level_s;
float interp_s = modf(-log2(max(lambda_s, lambda_t)), level_s);

// get the new texture coordinate by scaling the texture coordinate by the floored and ceiled level
vec2 tx_down = tx * pow(2, level_s);
vec2 tx_up = tx * pow(2, level_s + 1);

// compute the color procedural
vec2 highlight_down = clamp(cos(tx_down * 0.2) - 0.95, 0,1) * 10;
vec2 highlight_up = clamp(cos(tx_up * 0.2) - 0.95, 0,1) * 10;
// this might be faster, but it doesn't fade nicely...
//float highlight_down = step(0.9, fract(tx_down * 0.04)) * 0.8;
//float highlight_up = step(0.9, fract(tx_up * 0.04)) * 0.8;

// lerp between the intensities
fragColor = vec4(1 - clamp(mix(
max(highlight_down.x, highlight_down.y),
max(highlight_up.x, highlight_up.y),
interp_s), 0,1));


The scale factor in the computation of the procedural pattern depends on the size of your quad.
As Hummel I used this too, to put some path lines on a streak surface. Fairly simple to use and works great.

I hope this helps you a little. smile.png
ChugginWindex
ChugginWindex
Hmm, I'm struggling to get this working. Still relatively new to shaders.

I had to switch one thing to get it running at all, you had this:


// compute the sample level, based on the maximum derivative, and the interpolation factor between ceiled and floored level
int level_s;
float interp_s = modf(-log2(max(lambda_s, lambda_t)), level_s);


and from what I've read about modf(), it actually needed to be this:


// compute the sample level, based on the maximum derivative, and the interpolation factor between ceiled and floored level
float interp_s;
int level_s = modf(-log2(max(lambda_s, lambda_t)), interp_s);


No big deal I think. But this is what I get when I run the program:

nope2.png
Tsus
Tsus
Hi!
The order of parameters in modf was right. GLSL just wants the out parameter to be float, too. Sorry for that inconvenience.
I attached you a small sample and updated the code above. smile.png
ChugginWindex
ChugginWindex
Okay it worked after changing that one value. I'm sorry tho but I'm really having trouble understand how this works exactly. What's involved in modifying this to do vertical lines as well? I took a look at the paper you linked to but I'm failing to find anything familiar in how this works, so I can't seem to see what I would add for verticals.
Tsus
Tsus
The attached code from my post before and the piece of code above do both vertical and horizontal lines.
Let’s have a closer look then. I’ll just go over the shader code and tell you a little more about it.

At first we look how much the texture coordinate changes vertically and horizontally. If it changes very much we have to go down in the mipmap pyramid to some very coarse level. We will use the maximum of both as a conservative choice. (There would be other options which give qualitative better results if we look at the quad under some small angle, but for your use case this is totally fine, since you are looking right on top of it.)
// get the length of the texture derivatives
float lambda_s = length(dFdx(tx));
float lambda_t = length(dFdy(tx));


With each mipmap level we would like to halve the number of lines. We can do this by scaling our pattern. In fact, we would like to scale it by 2^miplevel. By this we ensure that with each level every second line vanishes.
Alright, here is an example:
[font=courier new,courier,monospace]|||||||||||||[/font] mip level: 0 (scaled by 1=2^0)
[font=courier new,courier,monospace]|_|_|_|_|_|_|[/font] mip level: 1 (scaled by 2=2^1)
[font=courier new,courier,monospace]|___|___|___|[/font] mip level: 2 (scaled by 4=2^2)
[font=courier new,courier,monospace]|_______|___ [/font]mip level: 3 (scaled by 8=2^3)
[font=courier new,courier,monospace]|___________ [/font] ...
As you can see we have exponential growth. This isn’t intended, since we would like our pattern to double linearly with distance to the quad. For this reason we take the log2 of the derivatives. Afterwards we have a nice linear miplevel, let’s call it m.
Since we can only compute the pattern at the integer miplevels and not in between them we have to find out which two levels are adjacent to our miplevel. Let’s say our miplevel is m=2.6. Then we would have to compute the pattern for the levels 2 and 3 and have to interpolate between them at 0.6. The lower level (in my example the 2) is stored in level_s and the 0.6 is store in interp_s.
// compute the sample level, based on the maximum derivative, and the interpolation factor between ceiled and floored level
float level_s;
float interp_s = modf(-log2(max(lambda_s, lambda_t)), level_s);


Okay, now comes the actual scaling of our pattern. As I have illustrated before, we scale the texture coordinate by a power of two. The lower adjacent mipmap level is level_s and the upper adjacent mipmap level is level_s+1.
// get the new texture coordinate by scaling the texture coordinate by the floored and ceiled level
vec2 tx_down = tx * pow(2, level_s);
vec2 tx_up = tx * pow(2, level_s + 1);


Next we compute the pattern. Therefore I just used a cosine, because it is periodic. As you now it is in [-1,1]. I moved that range down to [-1.95, 0.05] which gives us only closely around the maxima some positive values. Clamping this and scaling it by some big number gives us some peaks at the maximum with a small falloff to 0.
// compute the color procedural
vec2 highlight_down = clamp(cos(tx_down * 0.2) - 0.95, 0,1) * 10;
vec2 highlight_up = clamp(cos(tx_up * 0.2) - 0.95, 0,1) * 10;


The following thing does also give you a procedural pattern, but it won’t have that small falloff.
// this might be faster, but it doesn't fade nicely...
//float highlight_down = step(0.9, fract(tx_down * 0.04)) * 0.8;
//float highlight_up = step(0.9, fract(tx_up * 0.04)) * 0.8;


With that we have computed the pattern in both, the x and y component.
highlight_down.x is the horizontal pattern in the lower adjacent mipmap level.
highlight_down.y is the vertical pattern in the lower adjacent mipmap level.
And for the upper adjacent mipmap level we have the same with highlight_up.
We will show a line if either the vertical or horizontal component indicate the presence of a line, so we can simply take the maximum of both.
And last but not least we will interpolate between both adjacent miplevels. And we are done. smile.png
The 1-clamp(...) in the end is just done to invert the lines (lines are black now, the rest is white).
// lerp between the intensities
fragColor = vec4(1 - clamp(mix(
max(highlight_down.x, highlight_down.y),
max(highlight_up.x, highlight_up.y),
interp_s), 0,1));


I hope that clears this up a little. smile.png
ChugginWindex
ChugginWindex
It does help, thank you!

Edit: Actually I double-checked the code that you provided in the zip and it doesn't create both horizontal and vertical. I dropped it in exactly in my shader and I only get vertical lines.
Tsus
Tsus
Hm, interesting. On my end it looks fine (see attached image). So, I assume the difference lies on the CPU code side.
Have you tried writing out the texture coordinate? Does it look fine?
ChugginWindex
ChugginWindex
Writing out the texture coordinate looks fine. Nice smooth gradient that makes sense. Here's what I'm getting with your fragment shader:

nope3.png
Tsus
Tsus
Mhm… that is odd.
The code I attached is standard OpenGL. Does it work as it should on your end? If it does we’re likely experiencing here some difference between standard GL and GL ES. I jump to this conclusion, since your texture coordinate looks fine and the grid pattern only depends on said texture coordinate.

You could perhaps try to compute the pattern for the vertical and horizontal direction individually, instead of doing it at once and then compare the intermediate results.
Have you also written out highlight_up, highlight_down, tx_up and tx_down? I assume the y-component would not show a repetitive pattern, as the x-component should do, does it? Maybe you find in a reverse-engineering sort of way the root of the problem.
alb
alb
I have a question:
If I want to use the information contained in the output of the shader, how can I do that?

For example how can I convert the output of the shader in a texture?

I tried using glFramebufferTexture2DEXT, glBindFramebufferEXT, etc.. but in this case I have a texture with all the scene renderized, not just the output of the shader of the quad.
ChugginWindex
ChugginWindex
I don't get what you mean. If you bind a frame buffer with a texture, and then write to your quad as a fullscreen quad, you'd have the contents of the shader of the quad rendered to said texture. Is this what you're asking for?
alb
alb
I mean
Ok I have the output of the shader in the quad.

But how can I use the information contained in that output?

For example I would like to use that information in order to retrieve the level of details of each pixel.
I would like to have a buffer and each element represents the level of detail of the corrensponding pixel.

Is it clear?
Tsus
Tsus
Hi!

I think, I see what you would like to do. You want to render the texture coordinates into a texture to compute the derivatives with respect to the screen yourself, right?

So first, you need an FBO. An FBO (frame buffer object) is a container that describes where the output of your shader goes to. It can have one or more color textures and one depth texture.
This is some initialization code:


// --- Some global variables ---
// Texture Ids and Framebuffer Object Ids
GLuint textureId = 0; // color texture to render into
GLuint depthTextureId = 0; // depth texture used for depth testing
GLuint myFBO = 0; // FBO containing the two textures above.

// --- in the init function ---
// Create texture
glGenTextures (1, &textureId);
glBindTexture (GL_TEXTURE_2D, textureId);
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

// Create depth buffer
glGenTextures (1, &depthTextureId);
glBindTexture (GL_TEXTURE_2D, depthTextureId);
glTexImage2D (GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

// Create FBO and assign textures
glGenFramebuffers (1, &myFBO);
glBindFramebuffer (GL_FRAMEBUFFER, myFBO);
glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureId, 0);
glFramebufferTexture2D (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTextureId, 0);

// check framebuffer status --> everything fine?
GLenum status = glCheckFramebufferStatus (GL_FRAMEBUFFER);
switch (status)
{
case GL_FRAMEBUFFER_COMPLETE:
cout << "FBO complete" << endl;
break;
case GL_FRAMEBUFFER_UNSUPPORTED:
cout << "FBO configuration unsupported" << endl;
return 1;
default:
cout << "FBO programmer error" << endl;
return 1;
}
glBindFramebuffer (GL_FRAMEBUFFER, 0);
return 0;

This code creates and FBO, assigns a color texture (RGBA, 8 bit per component) and a depth texture and checks whether the initialization was successful.

Now, you want to render into the texture, which goes like this:

glBindFramebuffer (GL_FRAMEBUFFER, myFBO); // activate fbo -> from now on, we render to the FBO
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the content of fhe FBO
// ... rendering code
glBindFramebuffer (GL_FRAMEBUFFER, 0); // deactivate fbo -> from now on, we render to the backbuffer


How to proceed depends a little on what you're trying to do.
If you want to implement your effect as some sort of post effect, you'd render a quad covering the entire screen, which yields one pixel shader execution per pixel.
Rendering the quad:
void drawScreenFillingQuad()
{
glEnable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);

glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();

glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();

glBegin(GL_QUADS);
{
glTexCoord2f(0,0);
glVertex2f(-1,-1);
glTexCoord2f(1,0);
glVertex2f( 1,-1);
glTexCoord2f(1,1);
glVertex2f(1,1);
glTexCoord2f(0,1);
glVertex2f( -1,1);
}
glEnd();

glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();

glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glDisable(GL_TEXTURE_2D);
}


In the shader, you can take the texture coordinate and directly read from the texture. (Make sure you bind the color texture of your FBO (textureID) as a texture!)
Vertex Shader:
void main()
{
gl_Position = ftransform();
gl_TexCoord[0] = gl_MultiTexCoord0;
}


Fragment Shader:
uniform sampler2D texture;

void main()
{
gl_FragColor = texture2D(texture,gl_TexCoord[0].st); // just read the texture and display it
}


If you don't draw a full screen quad, but the real scene geometry instead, we need to figure out the texture coordinate for fetching from the background texture ourselves.
For this, you'd have to pass the clipping space position from the vertex shader to the fragment shader.
Vertex Shader:

varying vec4 clipPos;

void main()
{
gl_Position = ftransform();
clipPos = gl_Position;
}


And in the fragment shader we scale the clipping space position (which is in [-1,1]) to the texture coordinate domain [0,1].
Fragment Shader:
uniform sampler2D texture;
varying vec4 clipPos;

void main()
{
// project to cartesian coordinates and scale from [-1,1] to [0,1].
vec2 texCoord = clipPos.xy / clipPos.w * 0.5 + vec2(0.5);
gl_FragColor = texture2D(texture,texCoord);
}


Computing the derivatives would be something like:
uniform sampler2D texture;
uniform int2 screenRes; // resolution of the viewport
varying vec4 clipPos;

void main()
{
// project to cartesian coordinates and scale from [-1,1] to [0,1].
vec2 texCoord = clipPos.xy / clipPos.w * 0.5 + vec2(0.5);

// assume we only stored in the RG components the texture coordinates
vec2 center = texture2D(texture,texCoord).xy;
vec2 right = texture2D(texture,texCoord + vec2(1.0 / screenRes.x, 0)).xy;
vec2 bottom = texture2D(texture,texCoord+ vec2(0, 1.0 / screenRes.y)).xy;

// compute the partial derivatives
vec2 dx = (right - center) / screenRes.x;
vec2 dy = (bottom - center) / screenRes.y;

// compute the mipmap level
float level = -log2( max(length(dx), length(dy)) );

gl_FragColor = vec4(level); // debugging output.
}


I hope this leads you in the right direction. smile.png
Best regards!
alb
alb
Ok now I going to describe my situation:

In the screenshot there is my running program.

In this case I take what is rendered and I put all of it on a 2dtexture that I rendered on the left of the screen.

But I would like to put in the 2dtexture only the quad (that is the output of the shader); as you can see the output is a grid that represents a level of details based on the distance of the camera.
I would like to put that information on a texture in order to retrieve the level of details.

This is the fragmentShader:

[source lang="java"]#version 150out vec4 fragColor;varying vec4 clipPos;void main(){ vec2 tx=gl_TexCoord[0].xy;//vec2 tx = clipPos.xy / clipPos.w * 0.5 + vec2(0.5); // get the length of the texture derivatives float lambda_s = length(dFdx(tx)); float lambda_t = length(dFdy(tx)); // compute the sample level, based on the maximum derivative, and the interpolation factor between ceiled and floored level float level_s; float interp_s = modf(-log2(max(lambda_s, lambda_t)), level_s); // get the new texture coordinate by scaling the texture coordinate by the floored and ceiled level vec2 tx_down = tx * pow(2, level_s); vec2 tx_up = tx * pow(2, level_s + 1); // compute the color procedural vec2 highlight_down = clamp(cos(tx_down * 0.2) - 0.95, 0,1) * 10; vec2 highlight_up = clamp(cos(tx_up * 0.2) - 0.95, 0,1) * 10; // this might be faster, but it doesn't fade nicely... //float highlight_down = step(0.9, fract(tx_down * 0.04)) * 0.8; //float highlight_up = step(0.9, fract(tx_up * 0.04)) * 0.8; // lerp between the intensities fragColor = vec4(1 - clamp(mix( max(highlight_down.x, highlight_down.y), max(highlight_up.x, highlight_up.y), interp_s), 0,1));}[/source]
Tsus
Tsus
Hi!

I’m still struggling to understand, what you would like to do. I’ll guess a little and if I’m getting close, would you be so kind to let me know? smile.png

I noticed the keyword virtual texturing in the caption of the window. Do you want to create a texture atlas containing the texture of the rotated quad, being visible in the background? I assume your problem is that the texture in the small rectangle on the left (I assume, this is the texture atlas?) doesn’t have the same LOD pattern as the big quad in the background, right?

The smaller 2D texture displayed on the left is undergoing its own transformation. In the context of virtual texturing, I’m assuming that you just place the quad depending on its texture coordinates, thereby by-passing the viewing transformation. This is just fine. I’d do it the same way.

The problem is that the partial derivatives (dFdx, dFdy) are with respect to the viewport, in your case the texture atlas. (Which is why the derivatives are always constant, yielding a constant LOD.) What you would like to have instead, are the derivatives with respect to the camera’s view as being visible in the background.
What you need is a “conversion” of the derivatives, which should be just a linear matrix. I can help you to figure it out.
Is that what you would like to achieve?
If so, what you be so kind to attach the code you have so far? (The complete cpp file would be nice, including the rendering into the texture atlas). That would make it much easier for me to help you, as I’d like to test before I post. ;)

Best regards

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.