Blobby things, Bezier Curves and Shaders(Part I)

At the last Global Game Jam (2016) I worked on a 2D physics puzzle game , and we wanted to have monsters which were like jello-o blobs of lava which you could interact with physically, and since we had some really good artists with us, we decided to implement them as spritesheet animations with some animation state-machine controlling code.

This didn't work out too well and we constantly ended up with situations that looked a bit like this.


Whats happening here is the state-machine script keeps switching the animation state between the falling and the resting animation as the collider bounces of the wall on the left and the platform on the right, and no amount of tweaking and rewriting the controller script with more conditions seemed to make a difference. 

The correct way to go about it would be would be to physically simulate the blob, but Unity really doesn't provide a way to do this in a way that doesn't look terrible and allows the artist to add some character to the blob ( can you spot the facial expressions in the gif above? ), I have some solutions in my head to tackle this and this blog post is the first in a series in which I try an find a one.

Step I : Setup the joints for the blob physics simulation

This is fairly straightforward in Unity, you basically want to set up a bunch of Rigibody2D's and SpringJoint2D's that look a bit like this,

Each of the green lines is one  SpringJoint2D and each square is a Rigidbody2D with a BoxCollider2D. ( Make sure all of the physics objects are in the same level and do NOT child them among each other )

Step II : Rendering the Blob

If we were to simply build up  triangles along the spring joints and fill up the inside, you get something that looks like this,
That behavior is pretty blobby, but its too jagged because we don't have enough triangles. Well, the easy solution to this is to just add a bunch more points on the outer loop, but this will slow down performance a lot, especially on mobile, especially if you have a lot of them simulating at the same time, we can do better.

We effectively want to round the corners on the blob above, and we want to let the GPU do this work for us, because CPU  bound curve smoothing code in C# would put us squarely back into to same problem of slowdown on mobile as we would have to compute a LOT of intermediary points to make it look smooth.

So, we want to apply a curve smoothing algorithm (Bezier Curves/B-Splines/ Catmull-Rom) on the GPU, while pretty much any smoothing algorithm can be implemented on the GPU using Geometry Shaders ,with that we would only be able to support OpenGL4+ or DX11+ devices, not ideal since a large portion of current mobile devices( and even WebGL) are stuck on OpenGL ES 2.1 and  only support Vertex and Fragment Shaders. However, Loop & Blinn at Microsoft Research  figured out how to do bezier curve rendering on fragment shaders at the per pixel level, and used it for creating resolution-independent texture maps of text on 3D surfaces, here's a really cool result from their paper.

The Math

I'll skip over the really Math-y bits in the paper, and get right to the final result that's useful for us, for the triangle given below,

with [u, v] co-ordinates of the vertices as shown, a point( x,y) is inside the curve  (colored red) if ( y * y - x ) < 0. 

This gives us a simple condition that we can evaluate at the per-pixel level of our fragment shader, if you want to see why you can go read the paper for their derivation or refer this GPU Gems article by Nvidia.

However, since we are restricted to quadratic Bezier Curves, we are going to have to change our triangulation scheme significantly to support this shader for the result we need.

Triangulation Scheme

We basically want our triangles to look like this,


What we're doing here is between each of the outer rigidbodies, we add TWO vertices at the mid-point, this is because,
  1. Two separate bezier curves are not guaranteed to be continuous at their end points, but a bezier curve is tangential at its end points, by putting the end points along a straight line, we guarantee that they have the same tangent and create a smooth curve.
  2. We're adding two vertices because, that same vertex needs to be (u,v) = (1,1) for the previous triangle and  (u,v) = (0,0) for the next one.
The code below does all this work, I've tweaked it a bit and added some additions like offsetting the vertices outwards from the rigibodies.

The Shader

Since I've already described the core logic of the shader earlier, for those that arent familiar, this is a overview of the OpenGL2 and DX9 rendering pipelines

An, important detail missed out in this image is that the Rasterizer( the GPU hardware) automagically interpolates color,uv and vertex-normal values within the pixels of the triangle, it is this interpolated data that you get at the fragment shader.

The final shader code is below.


Results

Now all you need to do is create the material, apply the shader and BOOM, the blob is transformed:


Up Next

Well, our mesh isn't manifold and has double vertices, plus we haven't allowed custom texture mapping of the blob yet, I have some ideas on how to achieve those goals using maybe some stencil buffering and masking.


Share this:

CONVERSATION

0 comments :

Post a Comment