{"id":22995,"date":"2014-05-15T09:36:13","date_gmt":"2014-05-15T14:36:13","guid":{"rendered":"http:\/\/www.shamusyoung.com\/twentysidedtale\/?p=22995"},"modified":"2014-05-15T09:40:35","modified_gmt":"2014-05-15T14:40:35","slug":"frontier-rebooted-part-3-act-normal","status":"publish","type":"post","link":"https:\/\/www.shamusyoung.com\/twentysidedtale\/?p=22995","title":{"rendered":"Frontier Rebooted Part 3: Act Normal"},"content":{"rendered":"<p>Working with shaders is a little strange.  They&#8217;re programs that run on your graphics card<span class='snote' title='1'>I don&#8217;t like to bog you down with terminology, but we&#8217;re going to need to use SOME. From here on, your computer is the CPU and the graphics card is the GPU.<\/span>. The pathway between your CPU and the GPU is a bit of a choke point. These two devices can only communicate so fast, and they need to share a monumental amount of data. In an ideal world, you would shove EVERYTHING over to the GPU. Then each frame you would just specify where the camera is, it would crunch all the numbers for you, and in return you&#8217;d get the completed image to show to the player. That&#8217;s not really feasible outside of the most rudimentary example programs, but we do want to get as close to that idea as possible. <\/p>\n<p>The GPU is very, very good at crunching 3D spatial values. It&#8217;s better than your CPU in the same way that a Formula 1 car is better at Formula 1 racing than a dusty pickup truck<span class='snote' title='2'>No quasi-technical explanation is complete without a Terrible Car Analogy.<\/span>. It sacrifices flexibility to excel at one very specific task. You can&#8217;t run Microsoft Office or a web browser on your GPU. It literally doesn&#8217;t have the ability to perform that sort of processing. But the one thing it is good at, it&#8217;s <em>really<\/em> good at. This creates an interesting challenge for us. Any job we want to give to the GPU, we have to first translate into the sort of job a GPU <em>can<\/em> do. <\/p>\n<p>This first one is easy. We just need to calculate some surface normals. <\/p>\n<p><!--more--><table width='499'  cellpadding='0' cellspacing='0' border='0' align='center'><tr><td><img src='http:\/\/upload.wikimedia.org\/wikipedia\/commons\/f\/f6\/Surface_normal.png' class='insetimage' width='499' alt='http:\/\/upload.wikimedia.org\/wikipedia\/commons\/f\/f6\/Surface_normal.png' title='http:\/\/upload.wikimedia.org\/wikipedia\/commons\/f\/f6\/Surface_normal.png'\/><\/td><\/tr><\/table><\/p>\n<p>Normals are the vectors that point directly away from a given surface. In a perfect sphere, they would all point directly away from the center. On a nice flat tabletop, they all point directly up. On a complex surface it&#8217;s&#8230; complex. I suppose one way to think of it is imagine throwing a <a href=\"http:\/\/en.wikipedia.org\/wiki\/Super_Ball\">superball<\/a> at whatever surface you&#8217;re interested in. Ignoring gravity (maybe you snuck your superball onto the international space station?) if the ball bounces back along exactly the same vector you threw it, then it traveled along the normal of the point of impact. Also you should probably stop throwing things in here.<\/p>\n<p>We want to offload this normal-calculating business to the GPU, but we have limited ways of communicating with it, and even more limited ways of getting data from it. We can only give it polygons and textures, and the only thing it can do is draw pixels on the screen.<\/p>\n<p>So what we do is we make a new shader. This one will also take the heightmap as input, but instead of drawing terrain, it will draw us the normal map we want. This drawing is done between frames, where the user can&#8217;t see it happen. We draw a plain square to the screen, and we give the shader the heightmap to work with.<\/p>\n<p><table width='600'  cellpadding='0' cellspacing='0' border='0' align='center'><tr><td><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/freboot5.jpg' class='insetimage' width='600' alt='freboot5.jpg' title='freboot5.jpg'\/><\/td><\/tr><\/table><\/p>\n<p>For any given point A, we sample its four neighbors: To the west and east we have X<sub>1<\/sub> and X<sub>2<\/sub>, and to the north and south we have Z<sub>1<\/sub> and Z<sub>2<\/sub>.  Our goal is to find the normal for A, but we only care about the position of these neighbors. Within the gameworld, this grouping would look so:<\/p>\n<p><table width='600'  cellpadding='0' cellspacing='0' border='0' align='center'><tr><td><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/freboot6.jpg' class='insetimage' width='600' alt='freboot6.jpg' title='freboot6.jpg'\/><\/td><\/tr><\/table><\/p>\n<p>Remember that we&#8217;re not actually <em>drawing<\/em> these polygons right now. We&#8217;re just pulling values out of the heightmap, figuring out where the polygons would be, and using these numbers to calculate the value we want. <\/p>\n<p>We use these values to generate a couple of vectors. They&#8217;re not the vectors we want, but they&#8217;re close. If we subtract Z<sub>1<\/sub> from Z<sub>2<\/sub>, we get a vector that points from one to the other. Likewise, we subtract X<sub>1<\/sub> from X<sub>2<\/sub> we get the vector that points from one of them to the other. Optional bonus info for go-getters: <em>We have to normalize these two vectors. That just means you divide them by their own length. The resulting vector still points exactly the same direction, but it&#8217;s now exactly 1 unit long, regardless of how far apart the original two points were.<\/em><\/p>\n<p><table width='600'  cellpadding='0' cellspacing='0' border='0' align='center'><tr><td><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/freboot7.jpg' class='insetimage' width='600' alt='freboot7.jpg' title='freboot7.jpg'\/><\/td><\/tr><\/table><\/p>\n<p>So now we have two vectors, which together form a plane.  The normal we want is perpendicular to this plane. Time to calculate the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Cross_product\">cross product<\/a>. In code, it looks like this:<\/p>\n<p>(For clarity and absolute precision, we&#8217;ve named our two red and blue input vectors <code>potato<\/code> and <code>eisenhower<\/code>, respectively.)<\/p>\n<pre lang=\"c\" line=\"1\">\r\nvector CrossProduct (vector eisenhower, vector potato)\r\n{\r\n  vector A;\r\n\r\n  A.x = potato.y * eisenhower.z - potato.z * eisenhower.y;\r\n  A.Y = potato.z * eisenhower.x - potato.x * eisenhower.z;\r\n  A.z = potato.x * eisenhower.y - potato.y * eisenhower.x;\r\n  return A;\r\n}\r\n<\/pre>\n<p>If you&#8217;re more of a pencil-and-paper type mathematician, then it looks like this:<\/p>\n<p><code>eisenhower &times; potato = ||eisenhower|| ||potato|| sin &theta; <em>n<\/em><\/code><\/p>\n<p>In this case the &#8220;&times;&#8221; symbol means &#8220;cross product&#8221; and not &#8220;multiply&#8221;. The rest of it is more mysterious to me. I&#8217;m never one for labor-intensive tasks like doing arithmetic or finding clean scratch paper. <\/p>\n<p>Anyway, when you take the cross product of two vectors like this, the result is a new vector that is perpendicular to both. You might notice that there are actually two such answers to this problem. Remember all that left hand \/ right hand crap we had to worry about <a href=\"?p=22965\" title=\"Frontier Rebooted Part 2: Welcome to Orientation\">last time<\/a>? This is one of those situations where it matters. In one coord system, your vector will point out of the surface, and in the other it will point into it. That&#8217;s fine either way, as long as you remember which way everything is facing. Or, you know, just flip the vector<span class='snote' title='3'>Multiply by negative one.<\/span> if it&#8217;s backwards from what you want. <\/p>\n<p>The result:<\/p>\n<p><table width='600'  cellpadding='0' cellspacing='0' border='0' align='center'><tr><td><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/freboot8.jpg' class='insetimage' width='600' alt='freboot8.jpg' title='freboot8.jpg'\/><\/td><\/tr><\/table><\/p>\n<p>So now we have the surface normal and a new problem. We can still only write color values. As far as the GPU is concerned, we&#8217;re drawing pixels on the screen so the user can look at them. We can write four values: Red green, blue, and alpha. These values are each stored in a single byte<span class='snote' title='4'>You can make textures that use more or less bytes per color value, but since we&#8217;re drawing to the screen we still have to match what the display supports.<\/span> and that&#8217;s all we have to work with.  <\/p>\n<p>Inside of the shader program we&#8217;re using real numbers like <code>0.2<\/code> and <code>-0.67114<\/code>. But as they&#8217;re written to the screen they&#8217;re clipped into the zero to one range. That&#8217;s quite a bit of damage, but then there&#8217;s another round of damage when it&#8217;s stored in a single byte, throwing away a lot of our precision. The smallest non-zero value we can store is something like <code>0.00390625<\/code> and the next unique value we can store is <code>0.0078125<\/code>. Values between these two will get rounded to one or the other<span class='snote' title='5'>0.00390625 because that&#8217;s 1\/256, because 256 is the number of distinct values you can store in a single byte.<\/span>. But! We also have some things working in our favor. We want to store the X, Y, and Z values of this vector, and we have red, green, and blue color channels to work with. So that&#8217;s handy.<\/p>\n<p>Normalized vector values fall between -1 and +1, but you might recall from the adventure that was the previous paragraph that color values must be between 0 and +1. So what we do is this: divide by two, add 0.5, and stuff XYZ into RGB. This gives us values centered around 0.5 instead of 0.0.  That&#8217;s fine as long as we remember to undo this step later when we extract the normal from the texture map for use. This process of turning spatial values into color values is pretty common when you&#8217;re dealing with shaders, and makes for some strange images. Here are some normal maps I found on Google:<\/p>\n<p><table width='600'  cellpadding='0' cellspacing='0' border='0' align='center'><tr><td><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/freboot9.jpg' class='insetimage' width='600' alt='freboot9.jpg' title='freboot9.jpg'\/><\/td><\/tr><\/table><\/p>\n<p>We&#8217;re creating something along these lines as we process our heightmap. When we&#8217;re done, we have an image like one of the above (except showing the contour of our hills instead of brick or whatever that thing in the middle is) sitting on screen<span class='snote' title='6'>The user doesn&#8217;t see it because we&#8217;re drawing on the back buffer, which I explained in <a href=\"?p=21274\" title=\"Project Good Robot 21: Resource Usage\">this post<\/a>.<\/span>. Now we tell OpenGL to grab a spatula and scrape those pixels off the screen<span class='snote' title='7'>Some mobile implementations allow the use of a butterknife instead.<\/span>, then stuff them in a texture map.  <\/p>\n<p>You might notice this is exactly the sort of situation we&#8217;re trying to avoid. We pull that completed image from GPU to CPU, then turn it into a texture map and send it back. This is an area where apparently consoles have an edge. On some consoles, graphics memory and CPU memory all share the same pool, so you can just copy the pixels directly to where you want them. On the PC, we&#8217;re doing at least 2 copy operations. There may be more happening deep down in the driver layers that we don&#8217;t even know about. This became an issue in RAGE, where they needed to constantly shuffle texture data around to do <a href=\"https:\/\/www.youtube.com\/watch?v=BiQCz2NjPR8\" title=\"Reset Button: Megatextures \">megatexturing<\/a> and the much-slower Xbox 360 could actually out-perform the PC at certain tasks. <\/p>\n<p>So now we take our flat-shaded terrain&#8230;<\/p>\n<p><table   class=\"\" cellpadding='0' cellspacing='0' border='0' align='center'><tr><td><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/freboot2.jpg' class='insetimage'   alt='freboot2.jpg' title='freboot2.jpg'\/><\/td><\/tr><\/table><\/p>\n<p>&#8230;and apply our normal map:<\/p>\n<p><table   class=\"\" cellpadding='0' cellspacing='0' border='0' align='center'><tr><td><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/freboot3.jpg' class='insetimage'   alt='freboot3.jpg' title='freboot3.jpg'\/><\/td><\/tr><\/table><\/p>\n<p>For the record: The world tiles, so as you travel you&#8217;ll eventually wrap around and pass the same scenery again and again. That odd seam on the right side of the image is where this wrapping happens. This is caused by using a heightmap with edges that don&#8217;t match up. This will be fixed later. <\/p>\n<p>Next time we&#8217;ll be doing something a lot more complex with our shaders.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Working with shaders is a little strange. They&#8217;re programs that run on your graphics cardI don&#8217;t like to bog you down with terminology, but we&#8217;re going to need to use SOME. From here on, your computer is the CPU and the graphics card is the GPU.. The pathway between your CPU and the GPU is [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[66],"tags":[387,388],"class_list":["post-22995","post","type-post","status-publish","format-standard","hentry","category-programming","tag-glsl","tag-opengl"],"_links":{"self":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts\/22995","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=22995"}],"version-history":[{"count":0,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts\/22995\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=22995"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=22995"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=22995"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}