Let's start coding!

To start a simple toy just declare an unnamed function that is called once per frame (EvalDraw-style). Here is an example:
()
{
  // Clear screen with a light-blue color.
  cls( 101, 153, 255 );
 
  // Draw orange circle at the position of a mouse:
  setcol( 255, 153, 0 );
  drawsph( mousx, mousy, 100 );
}
  
Note, that the above example will work in EvalDraw as well.

To make things more exciting, we can do the same using simple fragment shader. The easiest way to do it will be to draw full-screen quad in ()-section and use fragment shader defined after "@f:" keyword to draw the quad content (PolyDraw-style):
()
{
   // Set sun position to mouse cursor (flip y coordinate):
   glUniform2f("uPos", mousx, yres - mousy);

   // Draw full-screen quad using fragment shader:
   glquad(1);
}

//======================================
// Start a fragment shader section here:
@f:sun

// Sun position uniform:
uniform vec2 uPos;

void main()
{
   // Calculate difference to sun position:
   vec2 delta = gl_FragCoord.xy - uPos;

   // Check if pixel is inside the circle,
   // with smoothstep for anti-aliasing:
   float inside = smoothstep(0.0, 1.0, 100.0 - length(delta));
   
   // Mix with background color:
   vec3 bg_color = vec3(101.0/255.0, 153.0/255.0, 255.0/255.0);
   vec3 fg_color = vec3(255.0/255.0, 153.0/255.0,   0.0/255.0);
   vec3 color = mix(bg_color, fg_color, inside);

   // Set the final color:
   gl_FragColor = vec4(color, 1.0);
}
  
And since we are using GLSL why not enchance it with some fancy rays?
()
{
   // Set sun position to mouse cursor (flip y coordinate):
   glUniform2f("uPos", mousx, yres - mousy);
   
   // Set animation timer uniform:
   glUniform1f("uTime", klock());

   // Draw full-screen quad using fragment shader:
   glquad(1);
}

//======================================
// Start a fragment shader section here:
@f:sun

// Sun position uniform:
uniform vec2 uPos;

// Animation timer uniform:
uniform float uTime;

// Declare that we will be using RiceScript PI constant:
#use PI

void main()
{
   // Calculate difference to sun position:
   vec2 delta = gl_FragCoord.xy - uPos;

   // Check if pixel is inside the circle,
   // with smoothstep for anti-aliasing:
   float dist = length(delta);
   float inside = smoothstep(0.0, 1.0, 100.0 - dist);
   
   // To add rays, calculate angular space:
   float angle = atan( delta.y / delta.x );
   
   // Add time to add rotation animation:
   angle += uTime * .2;
   float scale = 20.0 / PI;
   
   // Use smoothstep with step interval inverse
   // to distance to get proper anti-aliasing:
   float rays = smoothstep( 0.6, 0.6 + scale / dist,
      abs( mod( angle * scale, 2.0 ) - 1.0 ));
   inside = max(inside, rays);
   
   // Mix with background color:
   vec3 bg_color = vec3(101.0/255.0, 153.0/255.0, 255.0/255.0);
   vec3 fg_color = vec3(255.0/255.0, 153.0/255.0,   0.0/255.0);
   vec3 color = mix(bg_color, fg_color, inside);

   // Set the final color:
   gl_FragColor = vec4(color, 1.0);
}
  

PolyCube allows you to combine CPU-side logic with GPU shaders. In our example we will add simple keyboard controls.
// Declare that we will be using GLSL vector math:
#use glsl_math

()
{
  // Declare sun position as a static variable:
  static vec2 pos = { 200, 200 };
  
  // Calculate time delta from previous frame:
  static global_time;
  delta_time = klock() - global_time;
  global_time += delta_time;

  // Move sun with arrow keys:
  speed = delta_time * 100.0;
  pos.x += (keystatus[KEY_RIGHT] - keystatus[KEY_LEFT]) * speed;
  pos.y += (keystatus[KEY_DOWN] - keystatus[KEY_UP]) * speed;
  
  // Set uniforms (flip y coordinate):
  glUniform2f("uPos", pos.x, yres - pos.y);
  
  // Set animation timer uniform:
  glUniform1f("uTime", global_time);
  
  // Draw full-screen quad using fragment shader:
  glquad(1);
  
  // Draw instructions:
  printg(xres-250, 10, 0xffffff, "Use arrow keys to move");
}

//======================================
// Start a fragment shader section here:
@f:sun

... // Insert previous code of the sun shader here...
  
The above example uses RiceScript syntax with extended GLSL math. However, old PolyDraw syntax is still supported - you can try out simplified version here that you can paste to PolyDraw directly.

PolyCube allows you to draw 2d elements on top of 3d / fragment shaders. This way you can quickly start your toy applications with interactive 2d skeleton and later replace 2d primitives with 3d meshes or fragment shader effects. It can be also useful for debugging.

So, let's extend our example by drawing clouds floating around the sun in 2d with simple dragging UI:
// Declare that we will be using GLSL vector math:
#use glsl_math

()
{
  // Declare sun position as a static variable:
  static vec2 pos = { 200, 200 };
  
  // Calculate time delta from previous frame:
  static global_time;
  delta_time = klock() - global_time;
  global_time += delta_time;

  // Move sun with arrow keys:
  speed = delta_time * 100.0;
  pos.x += (keystatus[KEY_RIGHT] - keystatus[KEY_LEFT]) * speed;
  pos.y += (keystatus[KEY_DOWN] - keystatus[KEY_UP]) * speed;
  
  // Mouse dragging state:
  static vec2 drag_delta;
  static drag = 0;
  static hover_sun = 0;
  static hover_cloud = -1;
  
  if (bstatus == 0) {
     hover_sun = 0;
     hover_cloud = -1;
     
     // Check if mouse hovers sun:
     drag_delta.x = pos.x - mousx;
     drag_delta.y = pos.y - mousy;
     if (drag_delta.x^2 + drag_delta.y^2 < 110^2) 
     {
        hover_sun = 1;
     }
  }

  // Initialize random clouds:
  enum { MAX_CLOUDS = 10 };
  struct {
     vec2 p; // position
     vec2 r; // radius
  } cloud;
  static cloud clouds[MAX_CLOUDS];
  static int num_clouds;
  if (num_clouds < MAX_CLOUDS) {
     num_clouds = MAX_CLOUDS;
     srand(28);
     for(int i = 0; i < num_clouds; ++i)
     {
        double r = 67 + (i%2)*33; // two size variations
        double rx = r;
        double ry = r*.75;
        cloud &c = clouds[i];
        c.p.x = rnd*(xres - rx*2) + rx;
        c.p.y = rnd*(yres - ry*2) + ry;
        c.r.x = rx;
        c.r.y = ry;
     }
  }

  // Animate clouds & check mouse hover:
  speed = delta_time * 150.0;
  for(int i = 0; i < num_clouds; ++i)
  {
     cloud &c = clouds[i];
     c.p.x += (rnd - .5)*speed;
     c.p.y += (rnd - .5)*speed;
     c.p.x = min(max(c.p.x, c.r.x), xres-c.r.x);
     c.p.y = min(max(c.p.y, c.r.y), yres-c.r.y);

     // Check if mouse hovers it:
     vec2 dp = c.p;
     dp.x -= mousx;
     dp.y -= mousy;
     if (bstatus == 0 && abs(dp.x) < c.r.x && abs(dp.y) < c.r.y)
     {
        hover_sun = 0;
        hover_cloud = i;
        drag_delta = dp;
     }
  }
  
  // Do dragging:
  drag = (bstatus == 1) && (hover_cloud >= 0 || hover_sun);
  if (drag) {
     vec2 p;
     p.x = mousx + drag_delta.x;
     p.y = mousy + drag_delta.y;
     if (hover_sun) {
        pos = p;
     }
     else if (hover_cloud >= 0) {
        // Set new cloud position and limit to viewport:
        cloud &c = clouds[hover_cloud];
        c.p.x = min(max(p.x, c.r.x), xres-c.r.x);
        c.p.y = min(max(p.y, c.r.y), yres-c.r.y);
     }
  }
  
  //-----------------------------------------------
  // Draw sun:
  
  // Set uniforms (flip y coordinate):
  glUniform2f("uPos", pos.x, yres - pos.y);
  
  // Set animation timer uniform:
  glUniform1f("uTime", global_time);
  
  // Draw full-screen quad using fragment shader:
  glquad(1);
  
  //-----------------------------------------------
  // Draw 2d part:
  
  // Clear 2d canvas as transparent (no specificed color):
  cls( );
  
  for(int i = 0; i < num_clouds; ++i)
  {
     vec2 p = clouds[i].p;
     vec2 r = clouds[i].r;
     
     // Draw 2d clouds:
     s = r.x; // scale
     drawsph(p.x - .03*s, p.y + .195*s, .42*s);
     drawsph(p.x + .6*s,  p.y + .225*s, .345*s);
     drawsph(p.x - .3*s,  p.y - .255*s, .3*s);
     drawsph(p.x + .15*s, p.y - .225*s, .405*s);
     drawsph(p.x - .6*s,  p.y,          .315*s);
     
     if (hover_cloud == i) {
        // Draw bounding box:
        drawrect(p.x-r.x, p.y-r.y, r.x*2, r.y*2);
     }
  }
  
  if (hover_sun)
  {
     // Indicate that mouse hovers sun:
     drawsph(pos.x, pos.y, -110);
  }
  
  //-----------------------------------------------
  // Draw instructions:
  printg(xres-330, 10, 0xffffff, "Drag sun and clouds using mouse");
}

//======================================
// Start a fragment shader section here:
@f:sun

... // Insert previous code of the sun shader here...
  

Now, we can built on top of the 2d prototype (printed above) and use only OpenGL commands and shader effects. We will draw every cloud as 2d quad with alpha blending and procedural texture rendered in a fragment shader. Here is modified example:
... // Insert here previous code before the "Draw sun" section
  
  //-----------------------------------------------
  // Draw sun:

  // Set "sun" vertex and fragment shader by name:
  glSetShader("sun");
  
  // Set uniforms (flip y coordinate):
  glUniform2f("uPos", pos.x, yres - pos.y);
  
  // Set animation timer uniform:
  glUniform1f("uTime", global_time);
  
  // Draw full-screen quad using fragment shader:
  glquad(1);
  
  //-----------------------------------------------
  // Draw shaded clouds:
  
  gluOrtho2D(0,xres,yres,0); // use screen coordinates

  // Set "cloud" vertex and fragment shader by name:
  glSetShader("cloud");
  glAlphaEnable();
  
  for(int i = 0; i < num_clouds; ++i)
  {
     vec2 p = clouds[i].p;
     vec2 r = clouds[i].r;
     double s = r.y/r.x;
     glBegin(GL_QUADS);
     glTexCoord(-1,-s, r.x, i); glVertex(p.x-r.x, p.y-r.y);
     glTexCoord( 1,-s, r.x, i); glVertex(p.x+r.x, p.y-r.y);
     glTexCoord( 1, s, r.x, i); glVertex(p.x+r.x, p.y+r.y);
     glTexCoord(-1, s, r.x, i); glVertex(p.x-r.x, p.y+r.y);
     glEnd();
  }
  
  glAlphaDisable();
  
  //-----------------------------------------------
  // Draw instructions:
  printg(xres-330, 10, 0xffffff, "Drag sun and clouds using mouse");
}

//======================================
// Sun vertex shader:
@v:sun

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

//======================================
// Sun fragment shader:
@f:sun

// Sun position:
uniform vec2 uPos;

// Animation timer uniform:
uniform float uTime;

// Declare that we will be using RiceScript PI constant:
#use PI

void main()
{
   // Calculate difference to sun position:
   vec2 delta = gl_FragCoord.xy - uPos;

   // Check if pixel is inside the circle,
   // with smoothstep for anti-aliasing:
   float dist = length(delta);
   float inside = smoothstep(0.0, 1.0, 100.0 - dist);
   
   // To add rays, calculate angular space:
   float angle = atan( delta.y / delta.x );
   
   // Add time to add rotation animation:
   angle += uTime * .2;
   float scale = 20.0 / PI;
   
   // Use smoothstep with step interval inverse
   // to distance to get proper anti-aliasing:
   float rays = smoothstep( 0.6, 0.6 + scale / dist,
      abs( mod( angle * scale, 2.0 ) - 1.0 ));
   inside = max(inside, rays);
   
   // Mix with background color:
   vec3 bg_color = vec3(101.0/255.0, 153.0/255.0, 255.0/255.0);
   //bg_color *= (fragCoord.y/iResolution.y)*.2 + .8;
   vec3 sun_color = vec3(255.0/255.0, 153.0/255.0,   0.0/255.0);
   vec3 ray_color = vec3(255.0/255.0, 255.0/255.0,   0.0/255.0);
   vec3 fg_color = mix(sun_color, ray_color, clamp((dist-100.0)/800.0,0.0,1.0));
   vec3 color = mix(bg_color, fg_color, inside);

   // Set the final color:
   gl_FragColor = vec4(color, 1.0);
}

//======================================
// Cloud vertex shader:
@v:cloud

varying vec4 vTexCoord;

void main()
{
   gl_Position = ftransform();
   vTexCoord = gl_MultiTexCoord0;
}


//======================================
// Cloud fragment shader:
@f:cloud

varying vec4 vTexCoord;

#use noise

void blob(out float d, in vec2 p, in vec2 dp, in float r)
{
   float n = abs(noise((p + dp)*3.5 + vTexCoord.w))*.5;
   p -= dp;
   d = min(d, dot(p,p) - (n + 1.0)*r*r);
}

float cloud(in vec2 p)
{
   float d = 1.0;
   blob(d, p, vec2(-.03,  .195), .42);
   blob(d, p, vec2( .6,   .225), .345);
   blob(d, p, vec2(-.3,  -.255), .3);
   blob(d, p, vec2( .15, -.225), .405);
   blob(d, p, vec2(-.6,     0.), .315);
   return d;
}

void main()
{
   vec2 p = vTexCoord.xy;
   float d = cloud(p);
   
   // Calculate normal:
   const float eps = .01;
   vec2 n = vec2(cloud(p + vec2(eps,0)), cloud(p + vec2(0,eps))) - d;
   normalize(n);
   
   // Averaged distance field correction for anti-aliasing:
   const float avgr = .3;
   d = sqrt(d + avgr*avgr) - avgr;
   d = clamp(d*vTexCoord.z, 0.0, 1.0);
   
   // Final shading:
   gl_FragColor = vec4(.85 - p.yyy*.2 - n.y*5.0, 1.0 - d);
}
  

For more information check out PolyCube Documentation or RiceScript specification.
Go back to main page.
Gallery     Docs     Tutorial     RiceScript    
Follow
   
Feedback
polycu.be (C) 2015
Sign out