5

My aim is to pass an array of points to the shader, calculate their distance to the fragment and paint them with a circle colored with a gradient depending of that computation.

For example:
enter image description here
(From a working example I set up on shader toy)

Unfortunately it isn't clear to me how I should calculate and convert the coordinates passed for processing inside the shader.

What I'm currently trying is to pass two array of floats - one for x positions and one for y positions of each point - to the shader though a uniform. Then inside the shader iterate through each point like so:

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif

uniform float sourceX[100];
uniform float sourceY[100];
uniform vec2 resolution;

in vec4 gl_FragCoord;

varying vec4 vertColor;
varying vec2 center;
varying vec2 pos;

void main()
{
    float intensity = 0.0;

    for(int i=0; i<100; i++)
    {
        vec2 source = vec2(sourceX[i],sourceY[i]);
        vec2 position = ( gl_FragCoord.xy / resolution.xy );
        float d = distance(position, source);
        intensity += exp(-0.5*d*d);
    }

    intensity=3.0*pow(intensity,0.02);

    if (intensity<=1.0) 
        gl_FragColor=vec4(0.0,intensity*0.5,0.0,1.0);
    else if (intensity<=2.0)
        gl_FragColor=vec4(intensity-1.0, 0.5+(intensity-1.0)*0.5,0.0,1.0);
    else 
        gl_FragColor=vec4(1.0,3.0-intensity,0.0,1.0);
}

But that doesn't work - and I believe it may be because I'm trying to work with the pixel coordinates without properly translating them. Could anyone explain to me how to make this work?

Update:

The current result is: current result The sketch's code is:

PShader pointShader;

float[] sourceX;
float[] sourceY;


void setup()
{

  size(1024, 1024, P3D);
  background(255);

  sourceX = new float[100];
  sourceY = new float[100];
  for (int i = 0; i<100; i++)  
  {
    sourceX[i] = random(0, 1023);
    sourceY[i] = random(0, 1023);
  }


  pointShader = loadShader("pointfrag.glsl", "pointvert.glsl");  
  shader(pointShader, POINTS);
  pointShader.set("sourceX", sourceX);
  pointShader.set("sourceY", sourceY);
  pointShader.set("resolution", float(width), float(height));
}


void draw()
{
  for (int i = 0; i<100; i++) {   
    strokeWeight(60);
    point(sourceX[i], sourceY[i]);
  }
}

while the vertex shader is:

#define PROCESSING_POINT_SHADER

uniform mat4 projection;
uniform mat4 transform;


attribute vec4 vertex;
attribute vec4 color;
attribute vec2 offset;

varying vec4 vertColor;
varying vec2 center;
varying vec2 pos;

void main() {

  vec4 clip = transform * vertex;
  gl_Position = clip + projection * vec4(offset, 0, 0);

  vertColor = color;
  center = clip.xy;
  pos = offset;
}
Giuseppe
  • 379
  • 4
  • 13
  • Can you expand on how it "doesn't work"? Apart from not handling point scale or viewport aspect ratio, nothing looks immediately wrong. Actually I'm not sure about the uniform arrays as there is a limit on the number of uniforms you can have. How do you set their values? Do you use uniform buffers? – jozxyqk Jun 30 '15 at 15:03
  • Ignore that, [setting a uniform array](http://stackoverflow.com/a/8100273/1888983) is fine, and it looks like you won't run out of locations given [at least 1024](https://www.opengl.org/wiki/Uniform_(GLSL)), but you might want to look at uniform buffer objects. – jozxyqk Jun 30 '15 at 15:18
  • Thanks for the reply, the problem is the shader draws all the circles black. From what I can get, there's some problems with the value of d. Here's what I get: [Image](http://i.imgur.com/TIZ0TIq.png) – Giuseppe Jun 30 '15 at 19:34

2 Answers2

2

Update:

Based on the comments it seems you have confused two different approaches:

  1. Draw a single full screen polygon, pass in the points and calculate the final value once per fragment using a loop in the shader.
  2. Draw bounding geometry for each point, calculate the density for just one point in the fragment shader and use additive blending to sum the densities of all points.

The other issue is your points are given in pixels but the code expects a 0 to 1 range, so d is large and the points are black. Fixing this issue as @RetoKoradi describes should address the points being black, but I suspect you'll find ramp clipping issues when many are in close proximity. Passing points into the shader limits scalability and is inefficient unless the points cover the whole viewport.

As below, I think sticking with approach 2 is better. To restructure your code for it, remove the loop, don't pass in the array of points and use center as the point coordinate instead:

//calc center in pixel coordinates
vec2 centerPixels = (center * 0.5 + 0.5) * resolution.xy;

//find the distance in pixels (avoiding aspect ratio issues)
float dPixels = distance(gl_FragCoord.xy, centerPixels);

//scale down to the 0 to 1 range
float d = dPixels / resolution.y;

//write out the intensity
gl_FragColor = vec4(exp(-0.5*d*d));

Draw this to a texture (from comments: opengl-tutorial.org code and this question) with additive blending:

glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);

Now that texture will contain intensity as it was after your original loop. In another fragment shader during a full screen pass (draw a single triangle that covers the whole viewport), continue with:

uniform sampler2D intensityTex;
...
float intensity = texture2D(intensityTex, gl_FragCoord.xy/resolution.xy).r;
intensity = 3.0*pow(intensity, 0.02);
...

The code you have shown is fine, assuming you're drawing a full screen polygon so the fragment shader runs once for each pixel. Potential issues are:

  • resolution isn't set correctly
  • The point coordinates aren't in the range 0 to 1 on the screen.
  • Although minor, d will be stretched by the aspect ratio, so you might be better scaling the points up to pixel coordinates and diving distance by resolution.y.

This looks pretty similar to creating a density field for 2D metaballs. For performance you're best off limiting the density function for each point so it doesn't go on forever, then spatting discs into a texture using additive blending. This saves processing those pixels a point doesn't affect (just like in deferred shading). The result is the density field, or in your case per-pixel intensity.

These are a little related:

Community
  • 1
  • 1
jozxyqk
  • 16,424
  • 12
  • 91
  • 180
  • What do you mean for a full screen polygon? From what I get (the image above) the figures are correctly drawn, while the logic about color isn't. I think the resolution is correct - as it should be passed by Processing - but I probably have made a mistake about the point coordinates passed trough the uniform, because they aren't in 0-1 range, but I randomly inializated into 0-1023, as processing would accept that : could me explain better how they should be? How can I convert manually them in "glsl" coordinates before confronting with the coordinate of the shader? I've updaded first post – Giuseppe Jun 30 '15 at 19:46
  • I am beginning to see how do your solution, hoping it at least works. Would you mind to explain more in details how to do the texture drawning? I've read the documents you've linked, but they honestly aren't very clear to me and I haven't understood well how do that passage. Or could you give me an example to start from? I've found this one [link](http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-14-render-to-texture/), but I don't know if it can apply to the solution you're provided. – Giuseppe Jul 03 '15 at 15:20
  • Furthermore, is it strictly necessary to start another shader to render the texture? Afaik Processing let to render 1 shader per sketch, so, I don't believe (and the documentation\examples on this are painfully scarce) it's possible to use one to do the "math" and another one to effectively render the texture. – Giuseppe Jul 03 '15 at 15:22
  • You can stick with your method and it will work if you fix the point coordinates and give the circles a conservatively large radius, but is less efficient. You could also change to point 1 with a single quad and keep your uniform arrays, avoiding multiple renders. It all depends on your application, how fast you need it and if you have the time to experiment with different methods. – jozxyqk Jul 03 '15 at 16:02
  • Regarding FBOs, that link looks good, although it covers all its bases and you don't need everything (e.g. the depth buffer). I also can't spot an actual `glDraw*` call but the code is [here](https://code.google.com/p/opengl-tutorial-org/source/browse/tutorial14_render_to_texture/tutorial14.cpp). There's [this](http://stackoverflow.com/questions/9742840/what-are-the-steps-necessary-to-render-my-scene-to-a-framebuffer-objectfbo-and) too. Rendering to textures is a common thing, which is good to get familiar with at some point. Maybe start with small examples and get those working first. – jozxyqk Jul 03 '15 at 16:04
  • Well, I'm going to try the method you've suggested because I'm still not able to find what's wrong with the point coordinates in my code and I've lost already a bit of time on it, so I hope that using yours will be faster rather than just keep hitting my head on that code (and at least I'd learn something new). And the point to why I'm using glsl is to improve efficiency, as with processing render alone isn't fast enough. Thanks for the addendum, tonight/tomorrow I'll work on it... I hope I can post something new soon which works... or at least some meaningful question on other actual code! – Giuseppe Jul 03 '15 at 16:58
  • @ jozxyqk : I had other things to do so I stopped to work on this one for a little bit - but I've got back on this from a couple of days ago. Unfortunately, afaik I don't think it's possible to use the solution you've proposed, because I can't find any way to include other glsl libraries in Processing. Therefore, some necessary code to draw that texture ins't regognized. – Giuseppe Jul 17 '15 at 11:55
  • However, I've found the error in my code - there was a part I considered correct as I took from an example, but after I read it again I noticed it missed a piece of code.The problem is the circles are coloured, but like the distance\intensity is calculated from a single central point, so the effect ins't still what I need - [here's the result](http://i.imgur.com/xkHvgcU.png). That hasn't much sense: from Processing, I'm drawing 100 different point and in the shader of each one I'm counting the distance from the various points in array I've passed. Any idea how is possible the result is that? – Giuseppe Jul 17 '15 at 12:08
  • Maybe it's better I expand the previous comment: from what I know, for each call of `point(sourceX[i], sourceY[i]);` a different shade is set. They draw a circle (so not a full screen poly ). For each one of the frag shader, the frag colour should depend from the intensity and so the distance, which should be calculated between the current frag coordinate and the coordinates of the 100 different points I've passed in the uniform array. So, I can't get how is possible that, judging from the result, those values are computed like from the frag coordinate and a single central point. – Giuseppe Jul 17 '15 at 12:27
0

It looks like the point center and fragment position are in different coordinate spaces when you subtract them:

vec2 source = vec2(sourceX[i],sourceY[i]);
vec2 position = ( gl_FragCoord.xy / resolution.xy );
float d = distance(position, source);

Based on your explanation and code, source and source are in window coordinates, meaning that they are in units of pixels. gl_FragCoord is in the same coordinate space. And even though you don't show that directly, I assume that resolution is the size of the window in pixels.

This means that:

vec2 position = ( gl_FragCoord.xy / resolution.xy );

calculates the normalized position of the fragment within the window, in the range [0.0, 1.0] for both x and y. But then on the next line:

float d = distance(position, source);

you subtrace source, which is still in window coordinates, from this position in normalized coordinates.

Since it looks like you wanted the distance in normalized coordinates, which makes sense, you'll also need to normalize source:

vec2 source = vec2(sourceX[i],sourceY[i]) / resolution.xy;
Reto Koradi
  • 53,228
  • 8
  • 93
  • 133
  • Not sure but I suspect this will have aspect ratio issues. Maybe either `d = length((position - source) * vec2(resolution.x/resolution.y, 1.0))` or leave source as is and `position = gl_FragCoord.xy` followed by `d /= resolution.y`? – jozxyqk Jul 01 '15 at 05:24
  • @jozxyqk The window is square in the OP's code. Yes, if the window weren't square, this would create ellipsis. – Reto Koradi Jul 01 '15 at 05:34
  • Trying to run with normalizing source how you said, but the result is that all the circle are still black - no matter how I try to adjust the value of gl_FragColor. There must be something else which isn't working, or this kind of solution to get the values in the the same coordinates isn't right for some reasons I don't get. I'd gladly accept any other idea, meanwhile I'll try to see if and how I can implement the solution [@jozxyqk](http://stackoverflow.com/a/31141725/5065842) proposed in the comment above – Giuseppe Jul 02 '15 at 15:00
  • A mistake I've just realized is that Processing consider as origin of the coordinates system the upper left corner, while GSLS the lower left and that pixel centers are located at half-pixel centers. However, forcing glsl to same convention of Processing with `layout(pixel_center_integer, origin_upper_left) in vec4 gl_FragCoord;` doesn't change anything... – Giuseppe Jul 02 '15 at 16:05
  • @Giuseppe Sorry to ask the probably obvious, but if it comes out black not matter what: Are you checking that the shader compiles successfully? – Reto Koradi Jul 02 '15 at 16:10
  • @Reto Koradi Well yes: if there's any kind of compilation error Processing doesn't launch the sketch at all and display the error(s), so no mistake from that point of view, the code syntax-wise is correct... I think it's almost certainly still due to some faulty logics with the calculus of distance - I mean, if I hard-code something like `gl_FragColor=vec4(255.0,0.0,0.0,1.0);` the circle are correctly drawn all red – Giuseppe Jul 02 '15 at 16:20