Jump to content

  • Log In with Google      Sign In   
  • Create Account

Awesome job so far everyone! Please give us your feedback on how our article efforts are going. We still need more finished articles for our May contest theme: Remake the Classics

Efficient drawing of 2d gridlines with shaders


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
20 replies to this topic

#1 ChugginWindex   Members   -  Reputation: 142

Like
0Likes
Like

Posted 18 January 2012 - 06:23 PM

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:
Posted Image

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.

Ad:

#2 Relfos   Members   -  Reputation: 217

Like
0Likes
Like

Posted 19 January 2012 - 08:40 AM

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)

#3 ChugginWindex   Members   -  Reputation: 142

Like
0Likes
Like

Posted 19 January 2012 - 08:54 AM

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:

Posted Image

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...

#4 Tsus   Members   -  Reputation: 788

Like
1Likes
Like

Posted 19 January 2012 - 10:15 AM

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.

Acagamics e.V. – IGDA Student Game Development Club (University of Magdeburg, Germany)


#5 ChugginWindex   Members   -  Reputation: 142

Like
0Likes
Like

Posted 19 January 2012 - 10:31 AM

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.

#6 Tsus   Members   -  Reputation: 788

Like
1Likes
Like

Posted 19 January 2012 - 11:12 AM

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. Posted Image

Acagamics e.V. – IGDA Student Game Development Club (University of Magdeburg, Germany)


#7 ChugginWindex   Members   -  Reputation: 142

Like
0Likes
Like

Posted 19 January 2012 - 12:32 PM

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:

Posted Image

#8 Tsus   Members   -  Reputation: 788

Like
0Likes
Like

Posted 19 January 2012 - 01:07 PM

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. Posted Image

Attached Files


Acagamics e.V. – IGDA Student Game Development Club (University of Magdeburg, Germany)


#9 ChugginWindex   Members   -  Reputation: 142

Like
0Likes
Like

Posted 19 January 2012 - 01:33 PM

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.

#10 Tsus   Members   -  Reputation: 788

Like
1Likes
Like

Posted 19 January 2012 - 02:27 PM

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:
||||||||||||| mip level: 0 (scaled by 1=2^0)
|_|_|_|_|_|_| mip level: 1 (scaled by 2=2^1)
|___|___|___| mip level: 2 (scaled by 4=2^2)
|_______|___ mip level: 3 (scaled by 8=2^3)
|___________ ...
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. Posted Image
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. Posted Image

Acagamics e.V. – IGDA Student Game Development Club (University of Magdeburg, Germany)


#11 ChugginWindex   Members   -  Reputation: 142

Like
0Likes
Like

Posted 19 January 2012 - 07:19 PM

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.

#12 Tsus   Members   -  Reputation: 788

Like
1Likes
Like

Posted 20 January 2012 - 03:43 AM

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?

Attached Thumbnails

  • AdaptiveGrid.jpg

Acagamics e.V. – IGDA Student Game Development Club (University of Magdeburg, Germany)


#13 ChugginWindex   Members   -  Reputation: 142

Like
0Likes
Like

Posted 21 January 2012 - 01:06 PM

Writing out the texture coordinate looks fine. Nice smooth gradient that makes sense. Here's what I'm getting with your fragment shader:

Posted Image

#14 Tsus   Members   -  Reputation: 788

Like
0Likes
Like

Posted 21 January 2012 - 03:27 PM

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.

Acagamics e.V. – IGDA Student Game Development Club (University of Magdeburg, Germany)


#15 alb   Members   -  Reputation: 107

Like
0Likes
Like

Posted 28 June 2012 - 09:59 AM

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.

#16 ChugginWindex   Members   -  Reputation: 142

Like
0Likes
Like

Posted 28 June 2012 - 10:23 AM

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?

#17 alb   Members   -  Reputation: 107

Like
0Likes
Like

Posted 28 June 2012 - 10:40 AM

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?

#18 Tsus   Members   -  Reputation: 788

Like
0Likes
Like

Posted 01 July 2012 - 03:06 AM

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. Posted Image
Best regards!

Edited by Tsus, 01 July 2012 - 03:12 AM.

Acagamics e.V. – IGDA Student Game Development Club (University of Magdeburg, Germany)


#19 alb   Members   -  Reputation: 107

Like
0Likes
Like

Posted 04 July 2012 - 08:05 AM

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]

Attached Thumbnails

  • Capture.PNG


#20 Tsus   Members   -  Reputation: 788

Like
0Likes
Like

Posted 04 July 2012 - 02:12 PM

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? Posted Image

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

Acagamics e.V. – IGDA Student Game Development Club (University of Magdeburg, Germany)





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS