Wednesday, March 25, 2015

Inside the Sprite Example

Last time I posted a new example that uses OpenTK for a 2D view with sprites. Let's go into a bit more detail about how it works:


  • The Sprite class:  Every sprite is a quad, consisting of two triangles. The whole system is set up so the size and position of a Sprite is measured in pixels.
  • The shaders: The vertex shader is different than what we've used in the tutorials. Instead of passing a 3D vector for our position, we only need to pass 2D points. The shader just sets everything to the same z-coordinate when it draws the vertices. There are three shaders included in the example: a normal texture drawing shader, a shader that leaves everything white, and one that takes the color from the upper left corner of the texture and colors the whole sprite that color (but uses the alpha values from the texture).
  • Alpha blending:  The third sprite included has a big ol' hole in it, that lets us see right through it. This functionality is handled by two lines:
                GL.Enable(EnableCap.Blend);
                GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
    
    There are a lot of possible modes to use for GL.BlendFunc, some of which might be desirable effects. Here's a website that allows you to play around with them online.
  • View rectangle: There's a Rectangle variable in the main class to store where in the "world" we're looking. When the model matrix is calculated for a Sprite, it's offset based on the view.
  • Visibility culling: Sprites outside of the view aren't drawn, to be more efficient. If you look in the title bar of the window, you can see how many total sprites there are, and how few of them actually are being drawn at any time.
  • Object selection: If you click on a sprite, it changes textures! There's a function in the Sprite class that can determine if a point is inside the edges of the sprite. When the mouse is clicked, we determine the "global" coordinate of the mouse cursor based on the current view, and test if that's inside any visible sprites.
    • How do we find if a point is inside the sprite? If our sprites couldn't rotate, this would be really easy, but we're stuck with some extra math. We make three vectors, one along the top edge of the sprite, one along the left edge, and one from the top-left corner of the sprite to the point. With the dot product, we can find the angle between the vector to the point and the two sides. Since the sprite is a rectangle, an angle greater than 90 degrees to either side (really we're checking if the cosine is positive or negative) means that the point is outside the rectangle. We also check that the point isn't too far away from the corner on either axis so it isn't outside the other two sides.
  • View ordering: Here's a catch with how the demo works. The sprites in this example are drawn in the order they're read from a List<Sprite>. This is the simplest approach, but it doesn't give us any obvious options if we might want things drawn in a different order (say some of our sprites are the UI that needs to always be on top). There are a few ways to add this. With some changes to the shaders, you could return to using 3D vectors and give each sprite a z coordinate to draw at. With the orthogonal view, they'll look the same at any distance, but they'll exist on different planes. A simpler (but possibly more processor intensive) solution could be to store the sprites in a sorted data structure, and add make the Sprite class implement IComparable.

3 comments:

  1. Hey, Kabuto love the tutorials. I have a project do at the minute which is basically stacking 3 cubes together making them transparent at the side you look at with an emitter at the top which will spawn spheres that gravity will act upon them and force them to the bottom. I was just wondering if you have any tips to go about this from your examples?

    Thanks
    Great work!

    ReplyDelete
    Replies
    1. Hi,

      You might be able to "cheat" at making the near side of the cubes transparent by using GL.CullFace to cull the front faces (so you see through to the inside on the opposite side of the cubes). Just make sure you set the front face to not be culled when you draw the spheres.

      The simplest way to handle the spheres is to move them like the cubes in Tutorial 4. Store a velocity vector for each sphere and gravity as a vector. Increment the velocities with gravity and use them to move the spheres each update (you could actually handle all of this in a vertex shader with a time uniform if you're feeling particularly ambitious).

      Delete
    2. Thanks for the reply!

      I've enabled CullFace I was wondering what I would have to do after that to make the near sides transparent, it's supposed to work so I can see inside the cubes and the spheres inside.

      Delete