Wednesday, June 28, 2017

textures - Painting terrain with a selection circle


I don't mean putting a circle under a unit like done in an RTS, that's fairly simply done with a scenegraph(-like structure). What I'm referring to is selection circles for things like Area of Effects that are painted on the terrain.


It seems relatively simple until I think about it.


Here are some images for reference:


http://i.stack.imgur.com/iXWXB.jpg http://im.tech2.in.com/images/2009/jan/img_116282_initialthreat_640x360.jpg


The first image gets the idea across, but not the full scope. Basically, the floor is painted with a decal detailing the region that will be affected by the spell.


The second image gets it across much better, it's not just a flat painted circle, it wraps along the mesh.


At first I figured to use some sort of object selection, and then in the vertex shader draw the texture on the correct vertices based on... some criteria that I'm still working out. Most likely based on distance and angle from the intersection point. The difficulty seems to come in that the methods wrap onto objects that seem to be completely different meshes.


I can't find an image that shows it (and don't play WoW anymore so I can't take a screenshot), but say you have a terrain mesh and a house on top of it and I select a point very close to the house's wall. Instead of continuing on the terrain mesh, the selection circle will go as far along the terrain mesh as it can, and then wrap up onto the wall of the house. It also ignores certain objects (players, etc), but that's pretty easy to ignore with correct collision tags/callbacks.


It's not so much that I can't think of a way to do this, but all the ways I can think of would probably lag your game out or turn your processor into slag. Somehow I have to:




  1. Draw a texture

  2. With no breaks

  3. Wrapping on multiple objects according to the terrain

  4. Centered at a given point

  5. Completely dynamically (so no pre-baking it into a bunch of dummy textures)

  6. Quickly enough to be computed every frame


Is there a standard method for this? Am I just overthinking it?


My best guess is rather than do one ray cast and selecting that point as the "center" I do a bunch of casts and in each case select the nearest vertex and bind the appropriate UV coord to that vertex, but it seems messy and potentially performance heavy to select that many vertices that way at the CPU frame-by-frame level. Especially since ray casts in general are expensive, even with a good physics engine.



It also doesn't seem particularly portable to anything other than a selection circle. A similar case would be like drawing the movement arrow over the terrain in Total War games, a thick arrow is drawn between the unit and its destination (see here: http://webguyunlimited.com/pixelperfectgaming/wp-content/gallery/total_war_shogun_2_rise_of_the_samurai/total_war_rots_screenshot_014.jpg ), this would seem to be a very similar technique to the AOE selection circle, but the ray cast method I mentioned wouldn't really work for it. There are, of course, numerous other examples too (such as trajectory markers).



Answer



Overthinking is an understatement! In your case it's especially easy, just Offset and Scale.


To get an orthogonal projection looking down Y axis, simply discard Y component. Your (unprocessed) texture coordinate [X,Y] is your world position [X,Z]. Now offset, scale, and limit to control the effect.


Here is a pseudo GLSL example of an effect like the one in your links:


        // These should of course come from somewhere in your pipeline
vec2 SelectionPoint = vec2(0.5, 0.5); //Red X
vec2 SelectionRadius = vec2(0.3, 0.3); //White Box Radius
vec2 TopDownWorld = (ModelMatrix * gl_Vertex).xz; //Blue X


// This is a simplification of the projection, since you are axis locked.
vec2 ProjectedTexCoord = TopDownWorld;
ProjectedTexCoord -= (SelectionPoint - SelectionRadius);
ProjectedTexCoord /= SelectionRadius * 2.0;
vec4 ProjectedTexel = texture(SelectionTexture, ProjectedTexCoord);

//* Stop the texture from repeating forever
if (ProjectedTexCoord.x > 1.0 || ProjectedTexCoord.x < 0.0 ||
ProjectedTexCoord.y > 1.0 || ProjectedTexCoord.y < 0.0) {
ProjectedTexel.a=0.0;

}
/*///* A bit cleaner but less academic:
if (ProjectedTexCoord != fract(ProjectedTexCoord)) ProjectedTexel.a=0.0; //*/

// Alpha blend the projected texture onto the final pixel color
gl_FragColor = gl_FragColor * (1.0 - ProjectedTexel.a) +
ProjectedTexel * ProjectedTexel.a;

Note that ModelMatrix is NOT gl_ModelViewMatrix (which is ViewMatrix * ModelMatrix)


Now for an example: (red X is the selection, blue X is the pixel we are shading) world space and eye space Lets work through some now, we are the pixel shader for the blue X



[0.75, 0.0] is our world coordinate
[0.5, 0.5] is our selection point
[0.3, 0.3] is our box radius

\frac{[0.75, 0]-([0.5, 0.5]-[0.3, 0.3])}{2*[0.3, 0.3]} = [0.91\overline{6}, -0.\overline{3}]


Or Simpler: \frac{[0.75, 0]-0.2}{0.6} = [0.91\overline{6}, -0.\overline{3}]


This gives our texture coordinate as [0.916, -0.3], which has a Y component less than zero, so the pixel is hidden using alpha=0. Although the X component is inside the box, near the edge (as you can see in the picture)




Now lets follow our selection point, which should clearly be right in the middle of the texture:


\frac{[0.5, 0.5]-0.2}{0.6} = [0.5, 0.5]



and it is :)




One more, to drive the point home, is the top right corner of the box where the answer intuitively should be [1, 1] because it's the top right corner ;)


\frac{[0.8, 0.8]-0.2}{0.6} = [1, 1]


and again it is


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...