Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Can this be done with an OpenGL shader?  (Read 7281 times)

0 Members and 1 Guest are viewing this topic.

Pixel_Outlaw

  • Jr. Member
  • **
  • Posts: 50
    • View Profile
Can this be done with an OpenGL shader?
« on: August 04, 2014, 08:03:33 am »
I have a solid understanding of traditional deprecated OpenGL.
I know that a HUGE chunk of OpenGL will not be supported in the future and I'm expected to adopt OpenGL 3's shader based rendering.

I'd like to create the effect below. Essentially these are lines that glow when they overlap (simple additive blending). I think if I just render all the lines and apply a blurred alpha blended version over the top I'll not get the glowing intersections just a "halo" around the edges.

It seems that to be done correctly each line must be drawn thick then blurred then alpha blended with the ones drawn previously. Does this translate into a OpenGL 3.x shader very well? From what I've read shaders are not usually used on a per line basis taking into account previous lines.

This effect is essential for my project (emulating a Vectrex like display) and I'd like to know how feasable it is before sinking a lot of time into it. I could do it with textured quads (end cap textures and stretched middle texture) but this involves a LOT of calculations for distance, rotation and length on the GPU for each line. I'd rather have the GPU do it.

Thoughts?

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
AW: Can this be done with an OpenGL shader?
« Reply #1 on: August 05, 2014, 09:04:31 pm »
It depends what you want to achieve. Are the highlights purely for the visuals?

Personally I'd calculate the intersection mathematically and add the highlights on top of it, instead of blending lines in differnt ways.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: Can this be done with an OpenGL shader?
« Reply #2 on: August 05, 2014, 11:35:48 pm »
Shaders can do everything that was possible with legacy GL and more. Some pipeline stages are still the same even with modern GL (e.g. primitive assembly, clipping, backface culling, sample ops) since they are integral to rendering.

Judging by your picture, most of the work will be done in the fragment shader stage. You will just have to feed the vertex shader a GL_LINE_STRIP (lines are still perfectly valid primitives in modern GL although glLineWidth() is deprecated but still not removed) and simply pass the data through. All those effects you talk about can be achieved solely in the fragment shader since they are nothing more than fragment operations after all. The blending will be left to the fixed pipeline stage so you won't have to worry about that. Just make the fragment shader shade the lines with that blurred glow effect and the additive blending will take care of the intersections for you.
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

Pixel_Outlaw

  • Jr. Member
  • **
  • Posts: 50
    • View Profile
Re: Can this be done with an OpenGL shader?
« Reply #3 on: August 08, 2014, 05:05:12 am »
Thanks!
I'll have to look into fragment shaders!

Pixel_Outlaw

  • Jr. Member
  • **
  • Posts: 50
    • View Profile
Re: Can this be done with an OpenGL shader?
« Reply #4 on: August 19, 2014, 06:19:01 am »
Well I'm kind of stuck with shaders.
It seems like a lot of tutorials mix immediate mode calls with shaders and or insist on using GLUT.

I've downloaded and looked at the shader demonstration for SFML but am still having a hard time figuring it all out. I understand the concept of a textured triangle strip to get those fat glowing lines. I'll include a screen shot and my texture if anyone could just get me started. I've worked with OpenGL a few times before but only using immediate mode. I'm comfortable with translations, rotations in immediate mode etc but those are now deprecated and I'm a bit stuck. I believe this topic is something that people sometimes ask about due to the popularity of the glowing vector look for games.

the attached file "glow.png" is a white gradient texture on a transparent background, might not show on the current forum background.

If anyone has the time to help me get started that would be great. The biggest hurdle for me is just getting the right shaders the right information and calculations. Essentially the middle 32x32 tile gets stretched between the two points of a line and the two endcaps get rotated to make a smooth end...

« Last Edit: August 19, 2014, 06:26:01 am by Pixel_Outlaw »

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: Can this be done with an OpenGL shader?
« Reply #5 on: August 21, 2014, 11:05:06 pm »
Quote
Perhaps I need a geometry shader?
Geometry shaders are primarily used for generating geometry. If you already send the vertex shader the final number of vertices that are needed to construct each line, then you won't need a geometry shader.

If however, you want to save on data upload, you can tell OpenGL to render line primitives and extrude them into quads in the geometry shader. You would also have to compute the proper texture coordinates and colours to apply to the new vertices in that case.
Quote
It boils down to sending the shader two coordinates and having it draw a textured rectangle between them in screen space.
If you want to get really fancy, you can skip texturing all together and compute the amount of illumination a fragment receives in the fragment shader based on its gl_FragCoord and the data of the line that you pass in as an input from the geometry shader (think computing the distance of a point from a line/plane, back from school). It would be to a certain extent even more physically correct than texturing. This method can get rather complicated however, but if done right the results will probably end up being the nicest, having the least artefacts, especially around the caps.
Quote
My problem is that I'm applying a LOT of rotations scaling and such in OpenGL so only it knows the final screen coordinates.
It doesn't matter how many transforms you want to apply to the coordinates... as long as you chain them properly and the final vertex coordinate ends up being in clip coordinates. OpenGL will do the right thing if it is given the proper data.
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

the_mean_marine

  • Newbie
  • *
  • Posts: 8
    • View Profile
Re: Can this be done with an OpenGL shader?
« Reply #6 on: August 30, 2014, 02:45:07 pm »
Take a look at this example on ShaderToy which seems to be similar to the effect you're after: https://www.shadertoy.com/view/MdB3zK

Be aware that the method used is probably not the fastest as it need to compute the distance to each line for each pixel which will be pretty slow with a large number of lines but depending on exactly what you're using it for it may be work well.

Another similar although far more complex example can be found here: https://www.shadertoy.com/view/4slGz4
 

Pixel_Outlaw

  • Jr. Member
  • **
  • Posts: 50
    • View Profile
Re: Can this be done with an OpenGL shader?
« Reply #7 on: September 01, 2014, 05:45:52 am »
Thanks!
Yep, looks very close.

I edited their code a bit for asthetics:

Code: [Select]

vec2 rotate(vec2 v, float alpha)
{
float vx = v.x*cos(alpha)-v.y*sin(alpha);
float vy = v.x*sin(alpha)+v.y*cos(alpha);
v.x = vx;
v.y = vy;
return v;
}

// lineVecor must be normalized
float distancePointLine(vec2 linePoint, vec2 lineVector, vec2 point)
{
vec2 linePointToPoint = point-linePoint;
float projectionDistance = dot(lineVector,linePointToPoint);
return length(lineVector*projectionDistance-linePointToPoint);
}

float distToLineSquare(vec2 p1, vec2 p2, vec2 p, float thickness)
{
p -= p1;
vec2 lineVector = p2-p1;

float angle = -atan(lineVector.y,lineVector.x);
p = rotate(p,angle);

float dx = 0.0;
if(p.x<0.0)
dx = abs(p.x);
else if(p.x>length(lineVector))
dx = abs(p.x) - length(lineVector);

return thickness/(dx+abs(p.y));
}

float distToLineRound(vec2 p1, vec2 p2, vec2 p, float thickness)
{
float d = length(p-p2);
p -= p1;
vec2 lineVector = p2-p1;

float angle = -atan(lineVector.y,lineVector.x);
p = rotate(p,angle);

if(p.x<0.0)
d = length(p);
else if(p.x<length(lineVector))
d = abs(p.y);

return thickness/d;
}

float squareDrop(vec2 p1, vec2 p2, vec2 p, float thickness)
{
float d = 1.0; //length(p-p2);
p -= p1;
vec2 lineVector = p2-p1;

float angle = -atan(lineVector.y,lineVector.x);
p = rotate(p,angle);

float llv = length(lineVector);
if(p.x<0.0)
d = 0.1*length(p);
else if(p.x<llv)
d = (llv/(llv-p.x)*0.1)*abs(p.y);

return thickness/d;
}

float expDrop(vec2 p1, vec2 p2, vec2 p, float thickness)
{
float d = 1.0; //length(p-p2);
p -= p1;
vec2 lineVector = p2-p1;

float angle = -atan(lineVector.y,lineVector.x);
p = rotate(p,angle);

float llv = length(lineVector);
if(p.x<0.0)
d = 0.06*length(p);
else if(p.x<llv)
d = exp(10.0*(p.x-0.05))*(llv/(llv-p.x)*0.1)*abs(p.y);

return thickness/d;
}

void main(void)
{
vec2 p = gl_FragCoord.xy / iResolution.xx;
vec2 m = iMouse.xy / iResolution.xx;
p -= vec2(0.5,0.5*iResolution.y/iResolution.x);
m -= vec2(0.5,0.5*iResolution.y/iResolution.x);

vec2 o1 = vec2(0.15,0.15);
vec2 o2 = vec2(0.15,0.1);
vec2 o3 = vec2(0.4,0.0);
vec2 o4 = vec2(0.25,0.0);

float angle = 1.0*iGlobalTime;
o1 = rotate(o1,angle);


angle = 2.0*iGlobalTime;
o2 = rotate(o2,angle);
float thickness = 0.002;
float dist = 0.0;
dist += distToLineSquare(o1,o2,p,thickness);
dist += distToLineSquare(o1,-o2,p,thickness);
o1.y *= -1.0;
o2.y *= -1.0;
dist += distToLineRound(o1,o2,p,thickness);
dist += distToLineRound(o1,-o2,p,thickness);
dist += squareDrop(o3,o4,p,thickness*0.5);
dist += expDrop(-o3,-o4*0.5,p,thickness*0.35);
gl_FragColor = vec4(dist*vec3(0.0,0.2,1.0), 0.5);
}

I've taken binary1248's advice and used his middle method (calculating the distance to each line segment for each passed fragment from some chubby rotated boxes)

I'm kind of hacking on Laurent's examples until I get the result right. Currently I can draw a very fat glowing line of any thickness but need to figure out how to pass all my lines' geometry with ADDITIONAL data. (Laurent's examples don't seem to address varying per polygon data passing, just the geometry points and color).

I've almost got my shader working, I'll post it here for others when I finish. :)
« Last Edit: September 01, 2014, 05:49:17 am by Pixel_Outlaw »

Pixel_Outlaw

  • Jr. Member
  • **
  • Posts: 50
    • View Profile
Re: Can this be done with an OpenGL shader?
« Reply #8 on: September 02, 2014, 10:27:03 pm »
Ok, as promised I've cobbled together something that appears to work and have tried to document.

The testbed for the shaders...
Code: [Select]
#include <SFML/Graphics.hpp>
#include <cmath>

int main()
{
  // Create the main window
  sf::RenderWindow window(sf::VideoMode(800, 600), "Glowing Line Shader");
  window.setVerticalSyncEnabled(true);

  // Slow down the action since I'm tossing out random lines per frame below
  window.setFramerateLimit(1);

  // Create a new shader object
  sf::Shader line_shader;

  // Set how thick we need the lines
  float thickness = 48.0;

  // Create a container to store the points in for the shader
  sf::VertexArray line_points;

  // Load the vertex shader and also fragment shader
  line_shader.loadFromFile("point.vert", "glow.frag");

  // This shader is for Quads, the lines are actually chubby rectangles
  line_points.setPrimitiveType(sf::Quads);

  // The render state will hold blending type and shader
  sf::RenderStates render_attributes;

  // Set the attributes in our sf::RenderStates object
  render_attributes.shader = &line_shader;

  // For a nice healthy glow we use additive blending
  render_attributes.blendMode = sf::BlendAdd;

  // Start the program loop
  while (window.isOpen())
  {
    // Process events
    sf::Event event;
    while (window.pollEvent(event))
    {
      // Close window: exit
      if (event.type == sf::Event::Closed)
        window.close();

      if (event.type == sf::Event::KeyPressed)
      {
        switch (event.key.code)
        {
        // Escape key: exit
        case sf::Keyboard::Escape:
          window.close();
          break;

        default:
          break;
        }
      }
    }

    // Clear the window
    window.clear(sf::Color(0, 0, 0, 255));

    // 30 chubby glowers (recall I've forced the FPS speed to be slow above)
    for(int i = 0; i < 30; ++i)
    {
      // line endpoints/verts , this could be moved to GPU...maybe?
      // Or Geometry shader once they become more common with drivers...
      float x  = static_cast<float>(rand() % 800);
      float y  = static_cast<float>(rand() % 600);
      float x2 = static_cast<float>(rand() % 800);
      float y2 = static_cast<float>(rand() % 600);

      float sub_angle = (2.0 * M_PI) / 8.0;
      float theta = atan2(y2 - y, x2 - x);
      float theta2 = atan2(y - y2, x - x2);
      float radius = .5 * thickness * sqrt(2.0);

      // Make a fat rotated rectangle of points this is HORRID
      float xx = x + cos(theta2 + sub_angle) * radius;
      float yy = y + sin(theta2 + sub_angle) * radius;

      float xx2 = x + cos(theta2 - sub_angle) * radius;
      float yy2 = y + sin(theta2 - sub_angle) * radius;

      float xx3 = x2 + cos(theta - sub_angle) * radius;
      float yy3 = y2 + sin(theta - sub_angle) * radius;

      float xx4 = x2 + cos(theta + sub_angle) * radius;
      float yy4 = y2 + sin(theta + sub_angle) * radius;

      // LOL FUN Kolrs
      sf::Color col_a = sf::Color(rand() % 255, rand() % 255, rand() % 255);
      sf::Color col_b = sf::Color(rand() % 255, rand() % 255, rand() % 255);

      // Put verticies into the line_points buffer
      line_points.append(sf::Vertex(sf::Vector2f(xx2, yy2), col_a));
      line_points.append(sf::Vertex(sf::Vector2f(xx, yy), col_a));
      line_points.append(sf::Vertex(sf::Vector2f(xx3, yy3), col_b));
      line_points.append(sf::Vertex(sf::Vector2f(xx4, yy4), col_b));

      // Shaders use text strings to set their parameters...strange
      line_shader.setParameter("line_width", thickness);

      // SFML does not share OpenGL's coord system now, subtract y pos from 600
      line_shader.setParameter("start", sf::Vector2f(x, 600 - y));
      line_shader.setParameter("end", sf::Vector2f(x2, 600 - y2));

      window.draw(line_points, render_attributes);
      line_points.clear();
    }
    // Finally, display the rendered frame on screen
    window.display();
  }

  return EXIT_SUCCESS;
}

glow.frag
Code: [Select]
uniform float line_width;
uniform vec2 start;
uniform vec2 end;

float point_distance(vec2 test)
{
  float A = test.x - start.x;
  float B = test.y - start.y;
  float C = end.x  - start.x;
  float D = end.y  - start.y;

  float dot_p  = A * C + B * D;
  float len_sq = C * C + D * D;
  float param = dot_p / len_sq;

  float xx = 0.0;
  float yy = 0.0;

  if (param < 0.0 || (start.x == end.x && start.y == end.y))
  {
    xx = start.x;
    yy = start.y;
  }
  else if (param > 1.0)
  {
    xx = end.x;
    yy = end.y;
  }
  else
  {
    xx = start.x + param * C;
    yy = start.y + param * D;
  }

  float dx = test.x - xx;
  float dy = test.y - yy;
  return(sqrt(dx * dx + dy * dy));
}

void main()
{
    vec4 pixel = gl_Color;
    vec4 pos = gl_FragCoord;

    float dist = point_distance(pos.xy);

    if(dist > line_width * 0.5) // Cut off the corners
    {
      pixel.a = 0.0;
    }
    else
    {
      float intencity = dist / (line_width * 0.5);
      if(intencity < 0.1) // If the fragment is in the closest 10% make it full bright
      {
        pixel.a = mix(1.0, 0.0, intencity);
      }
      else // Interpolate normally
      {
      pixel.a = mix(1.0, 0.0, intencity) * .5;
      }
    }

  gl_FragColor = pixel;
}

point.vert
Code: [Select]

//"out" varyings to our fragment shader
uniform float line_width;
varying vec2 vstart;
varying vec2 vend;

void main()
{
  vec4 vertex = gl_ModelViewMatrix * gl_Vertex;
gl_Position = gl_ProjectionMatrix * vertex;
gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
gl_FrontColor = gl_Color;
}

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: Can this be done with an OpenGL shader?
« Reply #9 on: September 03, 2014, 12:50:46 am »
I don't know if that is what you were trying to initially do, but with a bit more effort, you can probably attain a result more like the picture in your first post.

Make the quads that make up the lines completely transparent, so they don't contribute any luminosity, that way you won't see that quads are actually being rendered. You can simply expand what you already have to apply a bell-curve like luminosity function to the fragments, but make sure that it peaks at maximum intensity so that the lines don't look too transparent. You can then easily tune the look of your lines by adjusting the parameters of the bell-curve. If you want really fancy and flexible lines, you can overlay multiple curves, each contributing to a single colour channel, thus enabling you to adjust how the lines decay hue-wise as well.
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

Pixel_Outlaw

  • Jr. Member
  • **
  • Posts: 50
    • View Profile
Re: Can this be done with an OpenGL shader?
« Reply #10 on: September 03, 2014, 07:46:27 am »
I don't know if that is what you were trying to initially do, but with a bit more effort, you can probably attain a result more like the picture in your first post.

Well I think my effect is pretty much what I initially wanted. :)
This is what I was going for:
http://rickardremelin.wikidot.com/vectrex

My ownly visual complaint is how jaggie the edges on my center lines are...

I've tried a few interpolation methods and linear seemed the cheapest CPU wise. I also tried smoothstep and cosine interpolation methods. They were not too much different visually when adding a very soft faint glow around the lines.

Ideally I'd like to be able to send the sf::Shader more than just vertex and color data, I'd like to store the two additional two points for each line inside the polygons. Then I could render all lines in one big pass...

I read something about "striding" across data with shaders where you store more than just vertices and color in an array. As it stands I call the shader once for every single line which I guess might cause some extra overhead?

Then again, I'd hate to be generating huge arrays of packed data to pass to the shader with every frame...

Also the line coordinates I'm passing in don't adapt to transformations which is why you see the 600 - y to correct the coordinates in my code above.


« Last Edit: September 03, 2014, 07:52:48 am by Pixel_Outlaw »