2

I'm trying to figure out the correct math to rotate and translate a curve displayed in a fragment shader.

What I try to accomplish is to define a curve, for example a sine curve, in a local coordinate system, rotate it then translate it. Something like this:

enter image description here

That was made in MATLAB with the following code:

dens = 1080;
x = linspace(-1.0, 1.0, dens);        
y = 0.1*sin(25.0*x);

imax = 25;
for i = 1:imax    

    %transformation matrix:
    ang = (i/imax)*0.5*3.14;
    c = cos(ang); s = sin(ang);          
    T = [c,-s;s,c];

    %translation:
    P = [0.5;0.5];

    %transformed coordinates:
    xt = T(1,:)*[x;y] + P(1);
    yt = T(2,:)*[x;y] + P(2);

    plot(xt,yt);
    xlim([0 1.0]); ylim([0 1.0]); drawnow;
end

For the GLSL test I'm using the Book of Shaders Editor with the following code (can also be seen interactively here):

#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif

uniform float u_time;
uniform vec2 u_resolution;

// Plot a line on Y using a value between 0.0-1.0
float plot(vec2 st, float pct){
  return  smoothstep( pct-0.02, pct, st.y) -
          smoothstep( pct, pct+0.02, st.y);
}

float plotTransformed(vec2 st, float pct, vec2 transl, float ang){

    float c = cos(ang); float s = sin(ang);    
    mat2 trans = mat2(c,-s,s,c);    
    st = trans * st;

    st -= transl;

    return  smoothstep( pct-0.02, pct, st.y) -
          smoothstep( pct, pct+0.02, st.y);
}

void main(void) {
    bool use_plot_function = true;

    float mx =  max(u_resolution.x, u_resolution.y);
    vec2 uv = gl_FragCoord.xy /mx;
    vec3 color = vec3(0.4,0.4,0.4);

    //some screen position:
    vec2 p = vec2(0.5, 0.5);

    //the curve:
    vec2 cp = vec2(
        uv.x,
        0.08*sin(uv.x*40.0)
    );

    //the angle to rotate:
    float ang = -0.4 * 3.14 * sin(u_time);

    //Transform coordinates:
    float c = cos(ang); float s = sin(ang);    
    mat2 trans = mat2(c,-s,s,c);    
    vec2 cp_t = trans * cp;    
    cp_t +=p;



    if(use_plot_function){
        //Attempt 1: plot unrotated original curve translated upwards: 
        float curve1 = plot(uv, cp.y + p.y);
        color.g *= curve1;    

        //Attemp 2: plot the transformed curve using plotTransformed, rotates first, then translates:
        float curve2 = plotTransformed(uv, cp.y, p, ang);
        color.r *= curve2;

        //Attempt 3: curve is transformed first then ploted:
        float curve3 = plot(uv, cp_t.y);
        color.b *= curve3;
    }            
    else{
        float plotThk = 0.02;

         //Attempt 1: change color based on distance from unrotated original curve: 
        float dist = distance(uv, cp + vec2(0.0, p.y));
        if(dist < plotThk)
            color.g *= (1.0 -dist)/plotThk;   

        //Attempt 2: change color based on distance from transformed coordinates:
        dist = distance(uv, cp_t);
        if(dist < plotThk)
            color.r *= (1.0 -dist)/plotThk;   

    }

    gl_FragColor = vec4(color,1.0);
}

In the code above, there are two modes which can be toggled with use_plot_function set to false or true.

First mode attempts to plot using the functions plot() & plotTransformed(). Second mode sets a color to a fragment based on the distance from the calculated curve coordinates.

Result of first mode with use_plot_function set to true:

Three different attempts with use_plot_function set to true

Result of second mode with use_plot_function set to false:

use_plot_function set to false

Obviously I'm misunderstanding how this should be done in a fragment shader.

How should I correctly define a transformed curve in GLSL fragment shader?

remi
  • 937
  • 3
  • 18
  • 45

1 Answers1

3

Too lazy to go through your code looks too complicated for simple sinwave but rotation is much much simpler in Vertex shader however if you insist on fragment shader I would:

  1. define rotated coordinates system by basis vectors

    uniform float a;               // rotation angle [rad]
    vec2 U = vec2(cos(a),sin(a)); // U basis vector (new x axis)
    vec2 V = vec2(-U.y,+U.x);     // V basis vector (new y axis)
    vec2 O = vec2(0.5,0.5);       // O origin (center of rotation in global coordinates)
    

    this will enable you to compute rotated position of any fragment ... so if your fragment unrotated position in range <-1,+1> is:

    uniform vec2 pos;
    

    then the rotated position for our pos will be:

    float u=dot(pos-O,U);
    float v=dot(pos-O,V);
    

    and you can even convert back from u,v to x,y if you need:

    pos=O + u*U +v*V;
    
  2. parameter

    for any curve we usually use parameter. In your case it is angle of the sinwave wjich is also the x coordinate ion the rotated coordinates (which is equal to some_constant0 + u*some_constant1).

  3. parametric curve in fragment

    so when we have parameter we just compute the y of curve point, compute distance of our fragment position to it and if more distant than half of curve thickness then discard the fragment ...

    const float half_thickness=0.02;
    vec2 dP;
    float u,v;
    u=dot(pos-O,U);
    v=0.08*sin(u*40.0);
    dP=O + u*U +v*V - pos;
    if (length(dP)>half_thickness) discard;
    // here output your color
    

That is all you just render single QUAD covering your screen (or curve) and pass angle of rotation. Here is my attempt (putting all together)...

Vertex:

//------------------------------------------------------------------
#version 420 core
//------------------------------------------------------------------
layout(location=0) in vec2 in_pos;
out smooth vec2 pos;
//------------------------------------------------------------------
void main(void)
    {
    pos=in_pos;
    gl_Position=vec4(in_pos,0.0,1.0);
    }
//------------------------------------------------------------------

Fragment:

//------------------------------------------------------------------
#version 420 core
//------------------------------------------------------------------
in smooth vec2      pos;
out layout(location=0) vec4 col;
//uniform float a;                  // rotation angle [rad]
const float a=0.3;                  // rotation angle [rad]
const float half_thickness=0.02;    // curve half thicess
//---------------------------------------------------------------------------
void main(void)
    {
    vec2 U = vec2(cos(a),sin(a)); // U basis vector (new x axis)
    vec2 V = vec2(-U.y,+U.x);     // V basis vector (new y axis)
    vec2 O = vec2(0.5,0.5);       // O origin (center of rotation in global coordinates)in smooth vec3      pos;    // ray start position

    vec2 dP;
    float u,v;

    u=dot(pos-O,U);
    v=0.08*sin(u*40.0);
    dP=O + u*U +v*V - pos;
    if (length(dP)>half_thickness) discard;

    col=vec4(0.2,0.3,0.5,1.0);
    }
//---------------------------------------------------------------------------

preview

As you can see I used hardcoded color and angle. You can change the angle with uniform and color with either uniform or use direct color from VBO/VAO or glColor ...

I used O(0.5,0.5) if you want to rotate around center of screen then use O(0.0,0.0) instead ...

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • Nice. I was planing on doing this without the vertex shader. Here is a code based on your answer in action: http://thebookofshaders.com/edit.php?log=180516082237 I still need to read through the code a few more times to better understand the transformation you did there, but this is great, thanks. – remi May 16 '18 at 08:23
  • @remi it can be improved even more if you compute thickness in perpendicular direction to curve (normal) using first derivation. So construct normal from 1st derivation and than just dot product the normal and dP ... the result is scalar distance for the comparison that would lead to constant thickness of the curve regardless of the slope – Spektre May 16 '18 at 08:28
  • 1
    @remi the transformation is the same as using 3x3 2D uniform matrix I just disected it. For better understanding see [Understanding 4x4 homogenous transform matrices](https://stackoverflow.com/a/28084380/2521214) so you can use 3x3 matrix instead but having the vectors directly available is more didacticall I think – Spektre May 16 '18 at 08:34
  • Ok.. I still would think there is a correct way of doing this using a standard 2x2 [cos(ang), -sin(ang); sin(ang), cos(ang)] matrix? But yes, I might have to review transformation matrices. – remi May 16 '18 at 08:37
  • 1
    @remi the `U,V` together form the 2x2 rotation matrix – Spektre May 16 '18 at 08:44
  • that's what I suspected and I see that now :) – remi May 16 '18 at 08:46
  • 1
    @remi the `(x,y) = O + u*U +v*V` is the same as `(x,y,w)=M * (u,v,1)` where `M` is 3x3 matrix constructed with columns `U,V,O` and the last row is `(0.0,0.0,1.0)` ... the reverse is done by `(u,v,w) = Inverse(M)*(x,y,1)` .... the 2x2 matrix does not have O last row and `1,w` in the vectors but rotates only around `(0.0,0.0)` – Spektre May 16 '18 at 08:55