Hello everybody.
I'd like to let everyone know that I'm moving my blog. I will stop posting blog entries here, and instead post them up at my company site MoPhoGames.com
You'll notice the site appears somewhat unfinished (for instance, some pretty but unrelated stock art in the slides). That's because I'm no good artist, and I would really need graphic designers to make those slide images (also if you'll notice, I have a Hiring page because I'm looking for team members to fulfill basically everything I can't do - which is everything but programming)
Anyway, I will not be posting here anymore. If you want to continue following please refer to mophogames.com instead.
Friday, March 14, 2014
Saturday, February 15, 2014
Oculus VR: FPS Integration Challenges
So I just got my shiny new Oculus Rift developer headset the other day. It is difficult to keep my jaw from dropping to the floor while using it - it truly is a sea-change moment as Valve is predicting.
I got started right away on getting support working in my game, which isn't really an FPS but definitely takes place from a first-person perspective and features guns (so it kind of is an FPS I guess, but doesn't focus solely on shooting).
Early on in the development of my game (before I had even considered Rift support), I decided my game would not have crosshairs. I feel that ADS-style aiming is where FPS games are headed, and it's deprecating the need for crosshairs. Hell, DOOM didn't need crosshairs - and if you think about it, DOOM basically took place from an ADS-perspective for the whole game (which is why it didn't need crosshairs).
It also gives gun attachments new meaning. In games like Call of Duty where you have crosshairs while hipfiring, they have to intentionally nerf the usability of the gun while the user isn't aiming down the sights. This has always annoyed me to no end - that if I aim down my sights, my shots magically line up, but the moment I don't hold the gun up to my face, my shots fly wildly around like my character is drunk.
Since there's no crosshair, I don't have to deal with this. The incentive to use ADS is that, well, you can see where your shots are going. It's something that anyone can just pick up and understand. The incentive to use a Holo Sight is that you get a handy crosshair painted onto glass instead of using the weapon's iron sights, the incentive to a laser pointer is that you can see where the gun is pointing from hipfiring, etc. It ends up feeling more realistic and more understandable because we're not just throwing stats at the player - we're showing the player that for instance aiming with a sight is objectively easier than iron sights, and infinitely better than hip firing, without having to explicitly tell them.
And now comes the problem. In your standard 2D monitor mode, the FPS is lined up smack in the middle of the screen.
This is a problem in VR. Want to see the problem first hand? Grab a paper towel tube, and aim down it as if you were firing a gun. You'll immediately notice that it's line up with one eye (probably your right eye, but maybe not if you're left hand dominant), and your other eye is either squinted or closed entirely.
Now try lining up the gun with the bridge of your nose instead. That's basically what lining up the gun smack in the middle looks like in VR, and causes significant eye strain.
It turns out, what you actually want to do in an FPS is pick an eye, and align the gun with that eye. By default this is probably the right eye, but you will also want to give players the option to use their left eye.
It's worth mentioning that this doesn't matter with motion controls such as the Razor - since the user will be doing the aiming rather than letting the game align the gun.
I got started right away on getting support working in my game, which isn't really an FPS but definitely takes place from a first-person perspective and features guns (so it kind of is an FPS I guess, but doesn't focus solely on shooting).
Early on in the development of my game (before I had even considered Rift support), I decided my game would not have crosshairs. I feel that ADS-style aiming is where FPS games are headed, and it's deprecating the need for crosshairs. Hell, DOOM didn't need crosshairs - and if you think about it, DOOM basically took place from an ADS-perspective for the whole game (which is why it didn't need crosshairs).
It also gives gun attachments new meaning. In games like Call of Duty where you have crosshairs while hipfiring, they have to intentionally nerf the usability of the gun while the user isn't aiming down the sights. This has always annoyed me to no end - that if I aim down my sights, my shots magically line up, but the moment I don't hold the gun up to my face, my shots fly wildly around like my character is drunk.
Since there's no crosshair, I don't have to deal with this. The incentive to use ADS is that, well, you can see where your shots are going. It's something that anyone can just pick up and understand. The incentive to use a Holo Sight is that you get a handy crosshair painted onto glass instead of using the weapon's iron sights, the incentive to a laser pointer is that you can see where the gun is pointing from hipfiring, etc. It ends up feeling more realistic and more understandable because we're not just throwing stats at the player - we're showing the player that for instance aiming with a sight is objectively easier than iron sights, and infinitely better than hip firing, without having to explicitly tell them.
And now comes the problem. In your standard 2D monitor mode, the FPS is lined up smack in the middle of the screen.
This is a problem in VR. Want to see the problem first hand? Grab a paper towel tube, and aim down it as if you were firing a gun. You'll immediately notice that it's line up with one eye (probably your right eye, but maybe not if you're left hand dominant), and your other eye is either squinted or closed entirely.
Now try lining up the gun with the bridge of your nose instead. That's basically what lining up the gun smack in the middle looks like in VR, and causes significant eye strain.
It turns out, what you actually want to do in an FPS is pick an eye, and align the gun with that eye. By default this is probably the right eye, but you will also want to give players the option to use their left eye.
It's worth mentioning that this doesn't matter with motion controls such as the Razor - since the user will be doing the aiming rather than letting the game align the gun.
Friday, February 7, 2014
Thoughts on uLink and TNet
During the development of my game, I've been using Tasharen Networking for multiplayer (by the developer of NGUI). But now I'm looking at moving my game over to a much more well-known (and much more expensive) framework, uLink.
Why would I move from a $60 framework to a nearly $800 framework?
Simply put, my project has outgrown TNet.
Actually, it had outgrown TNet the moment I added multiplayer, but I hadn't realized it until I started working on interest management. Area of Interest is something that, it turns out, TNet is fundamentally not designed to handle. Nor are authoritative servers.
TNet is meant to act a bit like a self-hosted Photon Cloud - your clients connect to a server, players create and join "channels" (which are basically rooms), within each channel there is one single host, etc. There's really a lot of similarities to be drawn there. It's designed for the server to act as little more than a middleman for players to connect to each other.
That was a mistake on my part - trying to force TNet to act in a way it was not intended. To be fair it helped me develop my multiplayer logic (which will remain largely intact in the move to uLink) but the creator (Michael Lyashenko) actively discourages using TNet as a dedicated server.
The limitations became glaringly obvious when I tried to add AoI. This required ripping out TNet code and hacking in the concept of player scope. Days later I'm still hacking away with no end in sight (after coming close several times, I'm currently left with a game that doesn't even remotely work). It's taking valuable time away from working on the rest of the game.
So it's time for uLink. Unlike TNet, uLink was designed for this kind of stuff. In fact, MuchDifferent (the company behind uLink) set a Guinness World Record for most concurrent players in an FPS game - a nearly 1,000 player match between team Man and team Machine. uLink is designed for authoritative servers, and optimized for performance. Granted the 1,000 player match also used other technologies besides uLink (likely load-balancing tech like Pikko), but nonetheless uLink still remains my best option.
And that is why I dumped $800 on an indie UnityPark Suite license.
Why would I move from a $60 framework to a nearly $800 framework?
Simply put, my project has outgrown TNet.
Actually, it had outgrown TNet the moment I added multiplayer, but I hadn't realized it until I started working on interest management. Area of Interest is something that, it turns out, TNet is fundamentally not designed to handle. Nor are authoritative servers.
TNet is meant to act a bit like a self-hosted Photon Cloud - your clients connect to a server, players create and join "channels" (which are basically rooms), within each channel there is one single host, etc. There's really a lot of similarities to be drawn there. It's designed for the server to act as little more than a middleman for players to connect to each other.
That was a mistake on my part - trying to force TNet to act in a way it was not intended. To be fair it helped me develop my multiplayer logic (which will remain largely intact in the move to uLink) but the creator (Michael Lyashenko) actively discourages using TNet as a dedicated server.
The limitations became glaringly obvious when I tried to add AoI. This required ripping out TNet code and hacking in the concept of player scope. Days later I'm still hacking away with no end in sight (after coming close several times, I'm currently left with a game that doesn't even remotely work). It's taking valuable time away from working on the rest of the game.
So it's time for uLink. Unlike TNet, uLink was designed for this kind of stuff. In fact, MuchDifferent (the company behind uLink) set a Guinness World Record for most concurrent players in an FPS game - a nearly 1,000 player match between team Man and team Machine. uLink is designed for authoritative servers, and optimized for performance. Granted the 1,000 player match also used other technologies besides uLink (likely load-balancing tech like Pikko), but nonetheless uLink still remains my best option.
And that is why I dumped $800 on an indie UnityPark Suite license.
Monday, February 3, 2014
Progress of my game
Figured I'd post a little bit on how far I've come in my personal project. I'm not sure what to call it yet (Alien Skies is the working title but may and probably will change).
After so long researching and playing with open-world type setups in Unity, I've determined that terrain streaming is nigh on impossible in Unity without rewriting the terrain engine itself - everything takes FOREVER to do, from modifying the heightmap, to adding trees, to even culling the terrain (with many terrain tiles with trees on them, performance dropped whenever the camera frustum changed). It's not very optimized, unfortunately, and is one of those things that needs tons of work in future version of Unity.
Therefore I'm sticking with a naive one big terrain approach. Seems almost too naive to work, then again Operation Flashpoint, Arma 1, and Arma 2 all got away with it (terrain streaming is new in Arma 3). DayZ is using the Arma 2 engine, which means the entire world is loaded at once (although pieces of the world are likely disabled to reduce the rendering burden). So I'm sticking with a fairly small world (10km per side), no streaming, with culling masks to disable rendering of distant objects (which are assigned to various layers which affect the distance at which they cull, so small objects are culled sooner than large objects).
After settling on this, I finally got to working on actual gameplay. I set up a Trello page for myself so I could keep track of my progress, and a Git repository on BitBucket so I can check it into remote backup and eventually share it with people.
This being a multiplayer game, I had to evaluate what I would use for networking fairly early on. At first I was dead set on buying uLink. Then, after evaluating the actual features uLink provided I realized that I already owned Tasharen Networking which provides much of the same functionality I'm actually going to be using, with a little extra effort.
I also had to make a little build tool for myself, to make server builds of my game (which I need to do quite frequently). This was easy enough - the tool adds SERVER to the list of scripting defines, ensures that a special ServerStartupScene is the startup scene in the build (this scene automatically loads up the map and starts a TNet server instance), builds a standalone player, and then reverts those changes. I have build tools for both Windows and Linux, so I can just pick Build Windows or Build Linux Server and have a ready-made game server.
Gameplay-wise, I actually spent a lot of time working on the singleplayer prototype before adding multiplayer. I'm nearly to where I was before adding multiplayer now, which includes:
- GUNS! Player can equip guns from their hotbar. They can fire the gun with LMB, and shoot with RMB.
- Loot and inventory. Players can move items between hotbar and inventory, and between loot box and inventory/hotbar. There's also a handy Take All function (which actually means "Take as many as possible")
- Alien( capsule)s. Aliens currently spawn in mobs MMO style, which I'll eventually change to a kind of random dropship encounter (which sounds better? aliens popping in before your eyes, or dropships unloading an alien squad before your eyes?). They have a fairly simple behavior tree, which causes them to wander, or upon seeing the player alert nearby allies and start engaging/pursuing. If there's no line of sight to the player, aliens will pathfind to player, otherwise they randomly strafe back and forth while firing in bursts. They also lead targets (broken in multiplayer, but will be fixed soon enough), which makes their bullets very tough (almost impossible) to dodge at close range.
I've been hearing things about Rust and DayZ being chock full of hackers, so I've been very careful during the addition of multiplayer to keep things server authoritative. Using items is server authoritative (server calls Use method and sends back the results), looting and inventory (dragging and dropping sends message to server, which handles the actual drag-drop and sends the results back), weapons (equip weapon is server authoritative, sending results back to player, as is weapon firing - no entity rewinding so players will have to lead targets, but I think I'm OK with that)
My next task is to work on movement in multiplayer (and I'll make THAT server authoritative too! ain't nobody gonna be noclipping or super jumping).
And then?
I have no idea. More content? Team recruiting?
I'll see when I get there.
After so long researching and playing with open-world type setups in Unity, I've determined that terrain streaming is nigh on impossible in Unity without rewriting the terrain engine itself - everything takes FOREVER to do, from modifying the heightmap, to adding trees, to even culling the terrain (with many terrain tiles with trees on them, performance dropped whenever the camera frustum changed). It's not very optimized, unfortunately, and is one of those things that needs tons of work in future version of Unity.
Therefore I'm sticking with a naive one big terrain approach. Seems almost too naive to work, then again Operation Flashpoint, Arma 1, and Arma 2 all got away with it (terrain streaming is new in Arma 3). DayZ is using the Arma 2 engine, which means the entire world is loaded at once (although pieces of the world are likely disabled to reduce the rendering burden). So I'm sticking with a fairly small world (10km per side), no streaming, with culling masks to disable rendering of distant objects (which are assigned to various layers which affect the distance at which they cull, so small objects are culled sooner than large objects).
After settling on this, I finally got to working on actual gameplay. I set up a Trello page for myself so I could keep track of my progress, and a Git repository on BitBucket so I can check it into remote backup and eventually share it with people.
This being a multiplayer game, I had to evaluate what I would use for networking fairly early on. At first I was dead set on buying uLink. Then, after evaluating the actual features uLink provided I realized that I already owned Tasharen Networking which provides much of the same functionality I'm actually going to be using, with a little extra effort.
I also had to make a little build tool for myself, to make server builds of my game (which I need to do quite frequently). This was easy enough - the tool adds SERVER to the list of scripting defines, ensures that a special ServerStartupScene is the startup scene in the build (this scene automatically loads up the map and starts a TNet server instance), builds a standalone player, and then reverts those changes. I have build tools for both Windows and Linux, so I can just pick Build Windows or Build Linux Server and have a ready-made game server.
Gameplay-wise, I actually spent a lot of time working on the singleplayer prototype before adding multiplayer. I'm nearly to where I was before adding multiplayer now, which includes:
- GUNS! Player can equip guns from their hotbar. They can fire the gun with LMB, and shoot with RMB.
- Loot and inventory. Players can move items between hotbar and inventory, and between loot box and inventory/hotbar. There's also a handy Take All function (which actually means "Take as many as possible")
- Alien( capsule)s. Aliens currently spawn in mobs MMO style, which I'll eventually change to a kind of random dropship encounter (which sounds better? aliens popping in before your eyes, or dropships unloading an alien squad before your eyes?). They have a fairly simple behavior tree, which causes them to wander, or upon seeing the player alert nearby allies and start engaging/pursuing. If there's no line of sight to the player, aliens will pathfind to player, otherwise they randomly strafe back and forth while firing in bursts. They also lead targets (broken in multiplayer, but will be fixed soon enough), which makes their bullets very tough (almost impossible) to dodge at close range.
I've been hearing things about Rust and DayZ being chock full of hackers, so I've been very careful during the addition of multiplayer to keep things server authoritative. Using items is server authoritative (server calls Use method and sends back the results), looting and inventory (dragging and dropping sends message to server, which handles the actual drag-drop and sends the results back), weapons (equip weapon is server authoritative, sending results back to player, as is weapon firing - no entity rewinding so players will have to lead targets, but I think I'm OK with that)
My next task is to work on movement in multiplayer (and I'll make THAT server authoritative too! ain't nobody gonna be noclipping or super jumping).
And then?
I have no idea. More content? Team recruiting?
I'll see when I get there.
Thursday, January 30, 2014
Keeping Track: Trello for Game Development
If you haven't heard of Trello, it's a free service which provides what boils down to a fancy to-do list.
In Trello, you have different categories. By default, these are To Do, Doing, and Done. You can also add your own categories.
To each category, you can add Cards. These are individual items, like say "Fix the sink" or "Buy milk". You can also move cards between categories (you might move a card from To Do to Doing, and then later from there to Done).
Trello also has a handy collaboration feature - multiple people can work on the same to-do list (or "Board" as Trello calls it).
As it turns out, despite being designed as a generic to-do list, Trello doubles as a fantastic project management tool.
You can create an "organization" in Trello, to which you invite other people, and then create a board private to that organization. You add as many categories as you need, and then use cards to keep track of tasks that need to be done on the project.
For instance, in my current project I have 5 categories: To Do, Bugs, Fixing, Doing, and Done. To-Do is for new features that need to be implemented, Bugs is for problems that need to be resolved, Fixing is for bugs which have been resolved, Doing is for features which need to be implemented, and Done is for any tasks which have been completed.
Even though I'm working alone (for now), I've found it immensely useful for staying focused and on track. Many times in the past have I tried working purely from memory, and I can say it almost never works. Keeping a list of things to do means I'm never sitting there trying to figure out what to work on next (and meanwhile losing the drive to work on the project). I add stuff to the To-Do list any time I think of something, move the next task to Doing, knock it out, move it to Done and immediately pick my next task.
Could I have done this with sticky-notes or a notepad? Sure, definitely. But Trello is arguably more convenient and paper-friendly ;)
In Trello, you have different categories. By default, these are To Do, Doing, and Done. You can also add your own categories.
To each category, you can add Cards. These are individual items, like say "Fix the sink" or "Buy milk". You can also move cards between categories (you might move a card from To Do to Doing, and then later from there to Done).
Trello also has a handy collaboration feature - multiple people can work on the same to-do list (or "Board" as Trello calls it).
As it turns out, despite being designed as a generic to-do list, Trello doubles as a fantastic project management tool.
You can create an "organization" in Trello, to which you invite other people, and then create a board private to that organization. You add as many categories as you need, and then use cards to keep track of tasks that need to be done on the project.
For instance, in my current project I have 5 categories: To Do, Bugs, Fixing, Doing, and Done. To-Do is for new features that need to be implemented, Bugs is for problems that need to be resolved, Fixing is for bugs which have been resolved, Doing is for features which need to be implemented, and Done is for any tasks which have been completed.
Even though I'm working alone (for now), I've found it immensely useful for staying focused and on track. Many times in the past have I tried working purely from memory, and I can say it almost never works. Keeping a list of things to do means I'm never sitting there trying to figure out what to work on next (and meanwhile losing the drive to work on the project). I add stuff to the To-Do list any time I think of something, move the next task to Doing, knock it out, move it to Done and immediately pick my next task.
Could I have done this with sticky-notes or a notepad? Sure, definitely. But Trello is arguably more convenient and paper-friendly ;)
Saturday, January 25, 2014
Just Write A Game
When developing a game you're passionate about, sometimes you turn into a perfectionist. Hours of googling are sunk into researching shiny bits of tech that don't really matter. There comes a time when you need to quit obsessing and just write a game.
Just this last week, I became utterly obsessed with the thought of huge-scale worlds. ArmA of course was a big inspiration, supporting absolutely gigantic maps of 50km or even 100km, far eclipsing the size of games like Skyrim. This is made possible because ArmA is based on a military simulation engine, which was designed for accuracy and huge scale and uses double-precision to represent coordinates. I don't get this luxury out of the box in Unity, but I wanted it so badly I sunk hours and even days of research into fighting Unity to get what I wanted. Floating point origin, terrain streaming, and even customized builds of third-party physics engines - nothing was out of the question to me.
I never even came close to completing my research and testing, and haven't even come close to creating a semblance of a game with it.
I realized that I needed to put down Google, and actually write a goddamn game.
I mean, hell, 32-bit floats give me 10km away from origin. That comes out to a usable world space of 20km (from -10km to +10km on each axis, if the world is perfectly centered around origin) before precision issues kick in.
That's huge. It's nearly the size of Skyrim (actually 37km, but approximately a quarter is hidden behind invisible walls and artificial barriers). It's bigger than Chernarus from ArmA/DayZ, which as far as I know is 15km x 15km. For a mostly on-foot multiplayer survival game, that is massive.
Even if I could have enormous 50km maps, what would I fill it with? Big stretches of the map would end up barren, leading to a huge but boring world. Small, but interesting, worlds are better than huge, and boring, ones.
So I need to just stop worrying and start writing something productive.
Saturday, January 18, 2014
The importance of custom-built solutions
It's only been somewhat recent that we've seen a massive surge in game development middleware. It used to be, not long ago, that games were entirely custom-built from the ground up. These days you can find an off-the-shelf product for nearly every task in game development, and you might even wonder where you would be without them.
But sometimes, you have a use-case so wierd, so uncommon, that there's just no product for you. You've tried everything, from the industry-tested AAA products to the backwaters-of-the-internet open source solutions, but nothing works.
And that's when you custom-build it.
I just recently encountered this while developing an open-world game project. The problem? How to make my AIs intelligently navigate a huge open world with pathfinding.
At first, as anyone would, I thought to myself, "There's such a HUGE number of pathfinding solutions for Unity. This should be a piece of cake!".
And that's where I was partially wrong. Yes, there's lots of them, but not a single one of them designed for even marginally big game worlds and also in my budget.
My first try was Unity's own pathfinding, basically a port of Recast. It failed right out of the gate, since it couldn't even export the scene data to be voxelized within a reasonable length of time.
My second try was Aron Granberg's pathfinding. I never had much luck with this one in the past, but I gave it another shot. Still no luck - it's grid generator seemed to have severe trouble generating a graph for such a large terrain.
Eventually I realized I needed something highly customized to my needs - something I wouldn't be able to solve with money. Time to break out the elbow grease and the thinking caps.
The problem here is that my world is designed a bit like Arma 2. There's a big 10km^2 island, upon which are various buildings which can be entered. Said buildings will NOT be separate zones, but luckily will be fairly simple (just a few rooms and hallways, not more than a couple stories, etc).
I figure I can divide this into separate graphs, which are somehow linked: A grid graph for my outdoor area, and a waypoint graph for my indoor areas. These will be linked with special navigation nodes (which I'll call "offmesh links" for lack of a better term).
My outdoor grid graph is simply generated based on the terrain's heightfield and "occluder boxes" (which I would use to punch holes in the terrain navigation grid). One really nice property of A* is that it does not explore much more than it needs to - so if my AIs are only chasing nearby opponents, even with a huge world there's still a fairly light workload on the pathfinder. I've also decided that I will not make the grid fine-grained enough to represent trees - it would take up a hell of a lot more memory than I would like, and I can just stick in some simple obstacle avoidance to navigate around trees. I've found a 512x512 grid represents my terrain quite nicely then, if I don't care about trees. I also added a cutoff to the pathfinder - if it explores more than 1000 nodes, it simply quits. This number is large enough that the AI can pathfind halfway across the map (I specifically tested this), but still small enough that if the AI tries reaching an unreachable island it will exit out long before it exhausts the search space.
One thing I needed was to make my pathfinding algorithm as generic as possible. To that end, I created interfaces for INode, IGraph, and ICostEstimator. A node has a position in the game world, a G and H cost (I don't really care about costs for traversing individual nodes, but that would be trivial to add), a graph it belongs to (entirely optional - for instance, offmesh links are technically INodes but do not belong to a graph), and neighboring nodes. A graph simply contains nodes and can locate the node nearest a particular point. A cost estimator estimates the H cost between a given node and the goal node (currently implemented are Manhattan and Euclidean).
Waypoint nodes have explicit neighbors - baked at design time by raycasting to waypoints in the same graph. Grid nodes, on the other hand, have implicit neighbors - these are calculated on the first request and cached for subsequent calls.
My waypoint graph also has a bounds property, which represents the extents of the graph. This is used in order to locate the nearest INode to a given world position - first all waypoint graphs in the scene are queried to check if their bounds contain the point. If one does contain the point, it is queried for the closest waypoint. The terrain grid is also queried for the closest point (which is found by quantizing the point and converting this to a 2D grid index). Whichever of the two points is closest is chosen.
This is used for two purposes - one, for pathfinding (first the start and end nodes are located, then pathfinding is performed), and two, for offmesh links.
Offmesh Links as mentioned before are a way to link my grid and waypoint graphs together. An offmesh link is a special type of node with exactly two neighbors. On startup, these neighbors are located by finding the nearest nodes to two points, and then the offmesh link is registered as a neighbor with both nodes (and therefore becomes a shared neighbor of both). By, for instance, placing one side of the offmesh link inside a building, and the other outside the building, the inside one will locate a waypoint, and the outside one will locate a grid cell. These two are then linked together via a shared neighbor. These nodes would be placed, for example, at the entrances of a building. The nice feature is that they don't care which type of nodes they link, so it's entirely possible to link two grid cells (for example, if there's a teleporter). If I wanted, I could also associated extra data with an offmesh link such as an enum of what type it is (for example, I could specify that a particular offmesh link is an elevator, which the AI would respond to by waiting for the elevator to arrive, getting in, waiting for the elevator to arrive at the appropriate floor, and getting out).
It took me two days to accomplish all of this, but it was well worth it. I added some extras as well, such as multithreaded pathfinding (all pathfinding work is offloaded onto a separate worker thread) with callbacks for when a path request is serviced. Pathfinding is reasonably fast, and agents can not only pathfind a fair around the world, but they can also pathfind in and out of buildings. None of this could be provided in a third-party solution, but was fairly quick to accomplish and fits my needs perfectly. It's also pluggable, which means I can reuse it in other projects and extend it (for instance, by adding navmesh support).
And that's why it's important to custom-build stuff - because when you're making a game that few others dare to attempt, you've got no choice but to build it yourself.
Monday, January 6, 2014
Large Streaming Terrain in Unity: An Adventure
Recently, I made a fair bit of progress towards a streaming terrain solution in Unity. This kind of system powers open-world games like Grand Theft Auto, Skyrim, Fallout, and more.
I wanted to share the process behind it, and current progress thus far.
Some of my work was initially based on the GIS talk from Unite. The idea is that I'll store terrain files to be loaded in the StreamingAssets folder. So far, I've got Heightmap files (RAW) and Splatmap files (PNG). Later on I'll probably figure out how to store detail densities, but that's enough for now. When a terrain tile is needed, I load both the Heightmap and Splatmap associated with the tile (by name, based on tile X and Y) and create a new Terrain object.
Just having a terrain isn't enough. I need scenery, of course. I decided on the condition that all scenery to be saved must be a prefab in a resources folder - this allows me to simply serialize the name of the object, and later I can load the object via Resources.Load.
Objects are stored in yet another file, in a custom binary format I call the World Tile Format, and files are stored as .WTFs. The obvious pun was probably the biggest driving factor in choosing the name.
My first attempts to save objects were unsuccessful. The files always came out empty - WTF indeed. Turned out to be a simple error - forgot to close a stream. After getting that sorted out, I could save and load objects for individual terrain tiles. I also drafted up a simple editor utility to let me do this - it allows me to load terrain tiles, and save WTF files (saves the list of objects within the bounds of each terrain tile).
The actual format is pretty simple.
The header consists of 10 bytes, and looks like this:
{
MAGIC: three ASCII chars ('WTF')
VERSION: one byte.
OBJ_COUNT: four bytes (uint)
TREE_COUNT: two bytes (ushort)
}
As you can see, I also decided that my WTF file should store trees for a terrain tile.
Following the header is a big dump of each object. Each one is 296 bytes and looks like this:
{
OBJECT_ID: 256 ASCII chars
POSITION_X/Y/Z: four bytes per (float)
SCALE_X/Y/Z: four bytes per (float)
ROTATION_X/Y/Z/W: four bytes per (float)
}
I may trim down OBJECT_ID later, depending on how I feel. Have yet to see a prefab with anywhere close to 256 characters in the name, but you never know.
And finally, a dump of all trees. Each tree is just 5 bytes and looks like this:
{
TREE_IDX: one byte
POSITION_X: two bytes (short)
POSITION_Y: two bytes (short)
}
Each position field is divided by short.MaxValue on load to map to the range 0-1 (I wanted to use bytes, but given a terrain tile spanning 650 meters that gives me a resolution of several meters, whereas a short gives me a resolution of smaller than a meter, but is still half the size of a full float value).
And this brings me to the current state of the thing.
Currently trees are unused (they are loaded from disk into a TreeInstance array, but not applied to the terrain - doing this would be trivial, however). My TerrainManager loads a radius of terrain and object tiles around the camera. The radius of each is different, so that I can load a big portion of terrain to avoid popping, but reduce the object load radius (since objects popping in/out is less noticeable than terrain popping in/out, especially with smaller objects). I believe Skyrim does something similar.
The last piece of the puzzle missing is detail objects like grass and bushes. I have not yet decided how I will store these, I might figure out how to store them in splatmap-style images (four detail objects neatly maps to R, G, B, and A channels which could map to a density range).
I also have a fair bit of optimizing to do. Loading terrain by itself is pretty speedy, although loading objects causes a noticeable hitch in framerate. I expected to spend time optimizing, however - it's my first try at anything remotely like this.
Still don't know what to do with this if I finish it.
I wanted to share the process behind it, and current progress thus far.
Some of my work was initially based on the GIS talk from Unite. The idea is that I'll store terrain files to be loaded in the StreamingAssets folder. So far, I've got Heightmap files (RAW) and Splatmap files (PNG). Later on I'll probably figure out how to store detail densities, but that's enough for now. When a terrain tile is needed, I load both the Heightmap and Splatmap associated with the tile (by name, based on tile X and Y) and create a new Terrain object.
Just having a terrain isn't enough. I need scenery, of course. I decided on the condition that all scenery to be saved must be a prefab in a resources folder - this allows me to simply serialize the name of the object, and later I can load the object via Resources.Load.
Objects are stored in yet another file, in a custom binary format I call the World Tile Format, and files are stored as .WTFs. The obvious pun was probably the biggest driving factor in choosing the name.
My first attempts to save objects were unsuccessful. The files always came out empty - WTF indeed. Turned out to be a simple error - forgot to close a stream. After getting that sorted out, I could save and load objects for individual terrain tiles. I also drafted up a simple editor utility to let me do this - it allows me to load terrain tiles, and save WTF files (saves the list of objects within the bounds of each terrain tile).
The actual format is pretty simple.
The header consists of 10 bytes, and looks like this:
{
MAGIC: three ASCII chars ('WTF')
VERSION: one byte.
OBJ_COUNT: four bytes (uint)
TREE_COUNT: two bytes (ushort)
}
As you can see, I also decided that my WTF file should store trees for a terrain tile.
Following the header is a big dump of each object. Each one is 296 bytes and looks like this:
{
OBJECT_ID: 256 ASCII chars
POSITION_X/Y/Z: four bytes per (float)
SCALE_X/Y/Z: four bytes per (float)
ROTATION_X/Y/Z/W: four bytes per (float)
}
I may trim down OBJECT_ID later, depending on how I feel. Have yet to see a prefab with anywhere close to 256 characters in the name, but you never know.
And finally, a dump of all trees. Each tree is just 5 bytes and looks like this:
{
TREE_IDX: one byte
POSITION_X: two bytes (short)
POSITION_Y: two bytes (short)
}
Each position field is divided by short.MaxValue on load to map to the range 0-1 (I wanted to use bytes, but given a terrain tile spanning 650 meters that gives me a resolution of several meters, whereas a short gives me a resolution of smaller than a meter, but is still half the size of a full float value).
And this brings me to the current state of the thing.
Currently trees are unused (they are loaded from disk into a TreeInstance array, but not applied to the terrain - doing this would be trivial, however). My TerrainManager loads a radius of terrain and object tiles around the camera. The radius of each is different, so that I can load a big portion of terrain to avoid popping, but reduce the object load radius (since objects popping in/out is less noticeable than terrain popping in/out, especially with smaller objects). I believe Skyrim does something similar.
The last piece of the puzzle missing is detail objects like grass and bushes. I have not yet decided how I will store these, I might figure out how to store them in splatmap-style images (four detail objects neatly maps to R, G, B, and A channels which could map to a density range).
I also have a fair bit of optimizing to do. Loading terrain by itself is pretty speedy, although loading objects causes a noticeable hitch in framerate. I expected to spend time optimizing, however - it's my first try at anything remotely like this.
Still don't know what to do with this if I finish it.
Thursday, January 2, 2014
2014. Yay.
Been a fair bit since I posted. Don't have anything groundbreaking or particularly rant-ish to post today, so I'll just sum up how my transition from 2013 to 2014 went.
Unity Multiplayer Games is now officially published. Yay! If you haven't considered buying it, for shame! I'm just kidding. But seriously, go buy it.
In other book-related events, I received a copy of Masters of Doom for Christmas. I finished it in two days (it was very difficult to put down). It's an excellent read, and I highly recommend it.
Got a new Xbox 360 E as well, plus GTA 5. First game I've owned in the series (I was previously uninterested, until Rockstar announced GTA Online, and watching Achievement Hunter videos thoroughly converted me). I expect to be playing plenty of that.
I'm toying with the idea of rebooting an old game of mine, NecroPixels. It was originally an entry for a multiplayer games contest which one third place. That said, I've lost the original source code - which is fine, since it was buggy as hell to begin with. One idea is to shoot for DOOM-style graphics. It's just a thought for the moment.
I'm also making more forays into physically-based shading. I've been toying with simplified/modified cook-torrance. I say "modified" because the fresnel effect was way too strong for my taste (could have been the implementation, not sure). Don't know if I'm going to really finish it or not.
Sorry if the post feels disorganized and rambling. I just felt the need to post something so people know I'm not dead. Or abducted by aliens.
Wednesday, December 11, 2013
First Foray Into Physically-Based Shaders
Yesterday I made my first foray into physically-based shading (with some minor fixes and tweaks applied today).
For those of you who don't know, physically based shading is based on the principle that a surface can never reflect more light energy than it receives. It's actually much simpler than it sounds.
For diffuse, you can get away with just using regular Lambert (some games use Oren-Nayer, but I don't think that's at all necessary). For specular, things get much more complicated, and I won't get into details in this post.
One thing physically-based shaders are REALLY good at is having a wide range of materials - with a few parameters, you can fairly easily reproduce a lot of hard surfaces.
Anyway, here's a screenshot of my hard surface shader in action. On the left, three plastic spheres, in the middle, three polished metal spheres, and on the right three unpolished metal spheres. All nine spheres use the same shader (as does the ground underneath them).
I'll post more on the technique in a later post.
Tuesday, December 10, 2013
Whitepapers Must Die: A Rant
There's something I've always found infuriating about new game graphics techniques. The moment a technique is discovered, someone goes and writes up a whitepaper about it. Then, everyone else just links to the whitepaper as the gold standard for learning that technique.
I hate this. I don't know about everyone else, but I don't speak whitepaper.
The thing about academic texts is that there's almost as much of a focus on sounding professional as there is on conveying useful information. Clearly, there are plenty of people out there that find these useful - I for one, do not.
In my opinion, something like a personal blog is actually a far better place to convey these kinds of techniques.
On a personal blog, there is no pressure to maintain professionalism. When you're trying to convey information on a blog, you often go for a friendly or even humorous tone, and try to boil things down to a format most of your readers can easily understand - usually relaying useful anecdotal information such as engine specifics that the author encountered which would otherwise be considered too unrelated for a whitepaper. It's always been on personal blogs and websites that I'm able to gain a much deeper understanding of a subject.
Anyway, those are my two cents on the subject. Whitepapers should die. Rant over.
I hate this. I don't know about everyone else, but I don't speak whitepaper.
The thing about academic texts is that there's almost as much of a focus on sounding professional as there is on conveying useful information. Clearly, there are plenty of people out there that find these useful - I for one, do not.
In my opinion, something like a personal blog is actually a far better place to convey these kinds of techniques.
On a personal blog, there is no pressure to maintain professionalism. When you're trying to convey information on a blog, you often go for a friendly or even humorous tone, and try to boil things down to a format most of your readers can easily understand - usually relaying useful anecdotal information such as engine specifics that the author encountered which would otherwise be considered too unrelated for a whitepaper. It's always been on personal blogs and websites that I'm able to gain a much deeper understanding of a subject.
Anyway, those are my two cents on the subject. Whitepapers should die. Rant over.
Monday, December 9, 2013
Better Skin Shading in Unity
In CG (both games and movies), there's something called the "uncanny valley". This refers to the trend where as human rendering becomes more and more realistic, at some point it suddenly stops looking realistic and instead starts looking creepy.
One technique seeing a rise in the AAA games industry is known as "sub surface scattering". I won't go into details, but the basics of it are this: light enters the skin, is scattered about, absorbed depending on how far it travels, and then exits (because skin is not actually opaque - it's semi-transparent).
There are many ways to go about this effect. There's texture space diffusion, screen space scattering, and (my personal favorite) pre-integrated skin shading.
What is pre-integrated skin shading? It's a very fast method of faking subsurface scattering, by baking the results into a lookup texture, and using the lookup texture as a lighting ramp.
Why is it important? Why can't we just use our old friends, Lambertian lighting and Blinn-Phong for shading our characters?
Because they end up looking like mannequins. Remove the skin color, and you're left with a statue.
Let's take a look at some pictures.
Here's the arm of a soldier character I'm using for testing. On the left, Blinn-Phong shading is used. On the right, pre-integrated skin shading is used.
I see the one on the left far too often, especially in indie/Unity games.
So how is it done?
First, you have your lookup texture. Here's your "realistic" lookup texture, using the lookup texture generator on an AltDevBlogADay article about it.
The U coordinate is lighting (from 0 to 1), while the V coordinate is basically skin depth (from 0 at the bottom, allowing no light through, to 1 at the top, allowing the most light through)
I don't like this one, however. In my opinion, realism is always trumped by what looks right. And to me, fiddling with that texture in Photoshop is what looks right.
Exaggerated? Definitely. Looks good? Hell yeah.
The article as linked above uses DDX and DDY to calculate skin depth, as well as tex2Dlod to sample a "blurred" version of the normal map. This imposes a lot of restrictions on the shader - too many, in my opinion.
Instead of using DDX/DDY, I'm going to use a plain old texture map for the same purpose. This puts the burden on the artist to paint skin depth, which is fine by me since that affords a lot more control over the final result anyway (and makes our shader more versatile, since a skin depth of zero gives us regular Lambert shading - we might give a character's clothes a value of zero, for instance). I'm also going to skip the tex2Dlod step, just using the normal map as-is.
And what does this give me? A realistic character shader which is miles ahead of your average blinn-phong shading, and is efficient enough to run on mobile platforms without breaking a sweat.
Hope this gives some people some ideas.
Happy gaming, everybody.
Sunday, December 8, 2013
Motion Blur in Unity Part 3: Rendering Motion Vectors
Today we're going to be rendering the motion vector buffer used for motion blur. To do this, we'll set up a special camera to render the scene with a replacement shader.
Image Effect Script
Before we continue, let's create an image effect skeleton.
using UnityEngine; using System.Collections; public class MotionBlurEffect : MonoBehaviour { // the shader used to render motion vectors public Shader MotionVectorShader; // the shader used to render motion blur // will do so in two passes: // first pass: render motion blur // second pass: blend half-resolution blurred image with full resolution screen buffer public Shader MotionBlurShader; private Material motionBlurMaterial; void Awake() { motionBlurMaterial = new Material( MotionBlurShader ); } void OnRenderImage( RenderTexture src, RenderTexture dest ) { Graphics.Blit( src, dest ); } }
This sets up the material we will use during Blit, and implements OnRenderImage (at the moment it does nothing, simply blits the source texture directly to the destination).
Next, we'll set up the camera to render motion vectors.
Next, we'll set up the camera to render motion vectors.
private Camera motionVectorCamera; void Awake() { motionBlurMaterial = new Material( MotionBlurShader ); // set up the motion vector camera motionVectorCamera = new GameObject("MotionVectorCamera").AddComponent<Camera>(); motionVectorCamera.transform.parent = transform; motionVectorCamera.transform.localPosition = Vector3.zero; motionVectorCamera.transform.localRotation = Quaternion.identity;
motionVectorCamera.backgroundColor = new Color( 0f, 0f, 0f, 0f );
motionVectorCamera.renderingPath = RenderingPath.Forward;// use motion vector replacement shader // we only want to render objects with matching 'RenderType' shader tag motionVectorCamera.SetReplacementShader( MotionVectorShader, "RenderType" ); // disable camera // we'll manually render it when needed motionVectorCamera.enabled = false; }
Let's also add a LayerMask field to this script. This LayerMask will affect which objects are to be motion blurred.
// affects which layers are to be motion blurred (default is everything) public LayerMask MotionBlurLayers = ~0; // ... void Awake() { motionBlurMaterial = new Material( MotionBlurShader ); // set up the motion vector camera motionVectorCamera = new GameObject("MotionVectorCamera").AddComponent<Camera>(); motionVectorCamera.transform.parent = transform; motionVectorCamera.transform.localPosition = Vector3.zero; motionVectorCamera.transform.localRotation = Quaternion.identity; motionVectorCamera.backgroundColor = new Color( 0f, 0f, 0f, 0f );
motionVectorCamera.renderingPath = RenderingPath.Forward; motionVectorCamera.cullingMask = MotionBlurLayers; // use motion vector replacement shader // we only want to render objects with matching 'RenderType' shader tag motionVectorCamera.SetReplacementShader( MotionVectorShader, "RenderType" ); // disable camera // we'll manually render it when needed motionVectorCamera.enabled = false; }
And finally, let's render this camera to a half-resolution render texture in preparation for motion blur.
void OnRenderImage( RenderTexture src, RenderTexture dest ) { // make sure motion vector camera matches current camera motionVectorCamera.fieldOfView = camera.fieldOfView; motionVectorCamera.nearClipPlane = camera.nearClipPlane; motionVectorCamera.farClipPlane = camera.farClipPlane; // get temporary render texture for motion vectors RenderTexture motionVectors = RenderTexture.GetTemporary( src.width / 2, src.height / 2, 0, RenderTextureFormat.ARGBFloat ); // render motion vector camera motionVectorCamera.targetTexture = motionVectors; motionVectorCamera.Render(); Graphics.Blit( src, dest ); // release temporary render textures RenderTexture.ReleaseTemporary( motionVectors ); }
Now that we have much of the framework in place to render our motion blur, let's get started on the actual motion vector shader.
Motion Vector Shader
We'll start with a simple vertex/fragment skeleton for our shader.
Shader "Custom/RenderMotionVector" {
Properties {
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f {
float4 position : SV_POSITION;
};
v2f vert( appdata_full v )
{
v2f o;
o.position = mul( UNITY_MATRIX_MVP, v.vertex);
return o;
}
float4 frag( v2f i ) : COLOR
{
return float4( 1,1,1,1 );
}
ENDCG
}
}
}
This does hardly anything yet, simply outputting White.
Let's add to our v2f structure the current and last positions of the vertex. We'll calculate the last position via the matrix our helper script is passing to the shader.
Let's add to our v2f structure the current and last positions of the vertex. We'll calculate the last position via the matrix our helper script is passing to the shader.
Shader "Hidden/RenderMotionVector" {
Properties {
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// the model*view matrix of the last frame
float4x4 _Previous_MV;
struct v2f {
float4 position : SV_POSITION;
float4 curPos : TEXCOORD0;
float4 lastPos : TEXCOORD1;
};
v2f vert( appdata_full v )
{
v2f o;
o.position = mul( UNITY_MATRIX_MVP, v.vertex);
o.curPos = o.position;
o.lastPos = mul( UNITY_MATRIX_P, mul( _Previous_MV, v.vertex ) );
return o;
}
float4 frag( v2f i ) : COLOR
{
return float4( 1,1,1,1 );
}
ENDCG
}
}
}
At first glance, it might seem odd that we're duplicating the 'position' field. This is done because we cannot read from the SV_POSITION semantic. We can, however, read from the TEXCOORD semantic, so we duplicate the position and pass it as TEXCOORD0 (the previous position is passed as TEXCOORD1).
Next, per pixel we will calculate the difference between curPos and lastPos and output this.
Next, per pixel we will calculate the difference between curPos and lastPos and output this.
float4 frag( v2f i ) : COLOR
{
float2 a = (i.curPos.xy / i.curPos.w) * 0.5 + 0.5;
float2 b = (i.lastPos.xy / i.lastPos.w) * 0.5 + 0.5;
float2 oVelocity = a - b;
return float4( oVelocity.x, -oVelocity.y, 0, 1 );
}
This is the basis of our motion vector shader. However, remember that our SetReplacementShader call specified the "RenderType" tag. Since this subshader was set to "Opaque", our shader will only replace objects with a shader set to "Opaque". In order to replace other types, you must write more than one subshader for these types. Many of these are simply minor variations. Here is the full shader code with four variations: Opaque, TransparentCutout, TreeBark, and TreeLeaf
Shader "Hidden/RenderMotionVector" {
Properties {
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// the model*view matrix of the last frame
float4x4 _Previous_MV;
struct v2f {
float4 position : SV_POSITION;
float4 curPos : TEXCOORD0;
float4 lastPos : TEXCOORD1;
};
v2f vert( appdata_full v )
{
v2f o;
o.position = mul( UNITY_MATRIX_MVP, v.vertex);
o.curPos = o.position;
o.lastPos = mul( UNITY_MATRIX_P, mul( _Previous_MV, v.vertex));
return o;
}
float4 frag( v2f i ) : COLOR
{
float2 a = (i.curPos.xy / i.curPos.w) * 0.5 + 0.5;
float2 b = (i.lastPos.xy / i.lastPos.w) * 0.5 + 0.5;
float2 oVelocity = a - b;
return float4( oVelocity.x, -oVelocity.y, 0, 1 );
}
ENDCG
}
}
SubShader {
Tags { "RenderType"="TreeBark" }
LOD 200
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "TerrainEngine.cginc"
// the model*view matrix of the last frame
float4x4 _Previous_MV;
struct v2f {
float4 position : SV_POSITION;
float4 curPos : TEXCOORD0;
float4 lastPos : TEXCOORD1;
};
v2f vert( appdata_full v )
{
v2f o;
v.vertex.xyz *= _Scale.xyz;
v.vertex = AnimateVertex( v.vertex, v.normal, float4( v.color.xyz, v.texcoord1.xy));
v.vertex = Squash( v.vertex );
o.position = mul( UNITY_MATRIX_MVP, v.vertex);
o.curPos = o.position;
o.lastPos = mul( UNITY_MATRIX_P, mul( _Previous_MV, v.vertex));
return o;
}
float4 frag( v2f i ) : COLOR
{
float2 a = (i.curPos.xy / i.curPos.w) * 0.5 + 0.5;
float2 b = (i.lastPos.xy / i.lastPos.w) * 0.5 + 0.5;
float2 oVelocity = a - b;
return float4( oVelocity.x, -oVelocity.y, 0, 1 );
}
ENDCG
}
}
SubShader {
Tags { "RenderType"="TransparentCutout" }
LOD 200
Pass {
ZWrite Off
AlphaTest Greater [_Cutoff]
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
// the model*view matrix of the last frame
float4x4 _Previous_MV;
struct v2f {
float4 position : SV_POSITION;
float4 curPos : TEXCOORD0;
float4 lastPos : TEXCOORD1;
float4 tex : TEXCOORD2;
};
v2f vert( appdata_base v )
{
v2f o;
o.position = mul( UNITY_MATRIX_MVP, v.vertex);
o.curPos = o.position;
o.lastPos = mul( UNITY_MATRIX_P, mul( _Previous_MV, v.vertex));
o.tex = v.texcoord;
return o;
}
float4 frag( v2f i ) : COLOR
{
float2 a = (i.curPos.xy / i.curPos.w) * 0.5 + 0.5;
float2 b = (i.lastPos.xy / i.lastPos.w) * 0.5 + 0.5;
float2 oVelocity = a - b;
return float4( oVelocity.x, -oVelocity.y, 0, tex2D( _MainTex, i.tex.xy ).a );
}
ENDCG
}
}
SubShader {
Tags { "RenderType"="TreeLeaf" }
LOD 200
Pass {
ZWrite Off
AlphaTest Greater [_Cutoff]
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "TerrainEngine.cginc"
sampler2D _MainTex;
// the model*view matrix of the last frame
float4x4 _Previous_MV;
struct v2f {
float4 position : SV_POSITION;
float4 curPos : TEXCOORD0;
float4 lastPos : TEXCOORD1;
float4 tex : TEXCOORD2;
};
v2f vert( appdata_full v )
{
v2f o;
ExpandBillboard (UNITY_MATRIX_IT_MV, v.vertex, v.normal, v.tangent);
v.vertex.xyz *= _Scale.xyz;
v.vertex = AnimateVertex( v.vertex, v.normal, float4( v.color.xyz, v.texcoord1.xy));
v.vertex = Squash( v.vertex );
o.position = mul( UNITY_MATRIX_MVP, v.vertex);
o.curPos = o.position;
o.lastPos = mul( UNITY_MATRIX_P, mul( _Previous_MV, v.vertex));
o.tex = v.texcoord;
return o;
}
float4 frag( v2f i ) : COLOR
{
float2 a = (i.curPos.xy / i.curPos.w) * 0.5 + 0.5;
float2 b = (i.lastPos.xy / i.lastPos.w) * 0.5 + 0.5;
float2 oVelocity = a - b;
return float4( oVelocity.x, -oVelocity.y, 0, tex2D( _MainTex, i.tex.xy ).a );
}
ENDCG
}
}
}
This should support most, if not all, cases.
If you don't really know what's going on in the Tree variations, don't worry. I'm not sure I understand, either :). The vertex shader code was copied over from the builtin tree shaders.
We're done with our replacement shader. As a final step, simply drag the shader onto the Motion Vector Shader field of the image effect.
In the next post, I'll cover creating the two-pass motion blur shader, and hooking it up to our image effect script.
If you don't really know what's going on in the Tree variations, don't worry. I'm not sure I understand, either :). The vertex shader code was copied over from the builtin tree shaders.
We're done with our replacement shader. As a final step, simply drag the shader onto the Motion Vector Shader field of the image effect.
In the next post, I'll cover creating the two-pass motion blur shader, and hooking it up to our image effect script.
Saturday, December 7, 2013
Motion Blur in Unity Part 2: The Model*View Matrix
The way our motion vectors are typically calculated is that, instead of just projecting a vertex with the model*view*projection matrix, we project it twice - once with the MVP matrix of the current frame, and once with the matrix of the last frame. Then, we calculate the displacement between them.
Unity, however, does not provide the model*view matrix of the last frame. So, we'll need to calculate it ourselves.
On the surface, this sounds easy. You can easily calculate the projection matrix of any camera in Unity, and a model matrix is just basically a local-to-world matrix, right? Yes. Usually. But in Unity, it's not so simple.
In Unity, how the model matrix is calculated depends on a wide array of factors. Whether it's a skinned or non-skinned mesh, whether it's uniform or non-uniformly scaled, whether it's static, whether it's statically batched, etc. The "local-to-world matrix" concept quickly breaks down.
I should note before we continue, that the following cases were discovered through personal trial and error.
Unity, however, does not provide the model*view matrix of the last frame. So, we'll need to calculate it ourselves.
On the surface, this sounds easy. You can easily calculate the projection matrix of any camera in Unity, and a model matrix is just basically a local-to-world matrix, right? Yes. Usually. But in Unity, it's not so simple.
In Unity, how the model matrix is calculated depends on a wide array of factors. Whether it's a skinned or non-skinned mesh, whether it's uniform or non-uniformly scaled, whether it's static, whether it's statically batched, etc. The "local-to-world matrix" concept quickly breaks down.
I should note before we continue, that the following cases were discovered through personal trial and error.
Model Matrix Cases
So when is a model matrix just a local-to-world matrix? As it turns out, there is one case when this is true - when the model is not skinned, isn't statically batched, and is uniformly scaled.
In all other cases, it will not be.
So first, what happens if the model is not uniformly scaled? In this case, our model matrix considers the scale of the object to just be (1,1,1).
Also note that we need to recursively multiply scale by each parent.
With this in mind, let's build a function to calculate scale for our matrix.
Vector3 calculateScale() { Vector3 scale = Vector3.one; // the model is uniformly scaled, so we'll use localScale in the model matrix if( transform.localScale.x == Vector3.one * transform.localScale.x ) { scale = transform.localScale; } // recursively multiply scale by each parent up the chain Transform parent = transform.parent; while( parent != null ) { scale = new Vector3( scale.x * parent.localScale.x, scale.y * parent.localScale.y, scale.z * parent.localScale.z ); parent = parent.parent; } return scale; }
This function starts with a scale value which depends on whether the transform is uniformly or non-uniformly scaled. Then, it recursively multiplies by the scale of each parent.
Let's also start putting together the function to calculate our model matrix.
This now handles non-static, non-skinned meshes.
Now, what about static meshes?
Well, unfortunately here's where we get a bit project-specific. If your project has static batching disabled, then you don't have to worry about this. However, if your project does have static batching enabled, for static meshes Unity actually pre-transforms the geometry and simply uses identity as the model matrix.
I'm going to assume your project has static batching enabled (it is enabled by default in Unity). If it doesn't, you can skip this portion.
Note that isPartOfStaticBatch does not seem to work as expected. That is, I found it reports true even if static batching is disabled. I do not know whether this is a bug or not.
So now our code handles regular and static meshes. But what about skinned meshes?
This one took me a while to figure out, but eventually I did. Skinned meshes for one act a bit like non-uniformly scaled meshes for one, and for another they do not use the transform the skinned mesh renderer component is attached to. Instead, they use the root bone transform.
And now, we've finally got an accurate model view matrix for all cases.
So, now that we can calculate the model matrix, let's create a helper script.
We'll use OnWillRenderObject to do this. Later on, our image effect will create a secondary camera to render the motion vector. We'll call it "MotionVectorCamera" so that our matrix helper script can detect which camera is rendering it (we won't update the matrix if it isn't being rendered by our motion vector camera).
Now that our shader has access to the model*view of the previous frame, we can get started on the motion vector shader. In the next post, I will cover creating the replacement shader and using it in an image effect script.
Let's also start putting together the function to calculate our model matrix.
Matrix4x4 calculateModelMatrix() { return Matrix4x4.TRS( transform.position, transform.rotation, calculateScale() ); }
Now, what about static meshes?
Well, unfortunately here's where we get a bit project-specific. If your project has static batching disabled, then you don't have to worry about this. However, if your project does have static batching enabled, for static meshes Unity actually pre-transforms the geometry and simply uses identity as the model matrix.
I'm going to assume your project has static batching enabled (it is enabled by default in Unity). If it doesn't, you can skip this portion.
Matrix4x4 calculateModelMatrix() { if( renderer.isPartOfStaticBatch ) { return Matrix4x4.identity; } return Matrix4x4.TRS( transform.position, transform.rotation, calculateScale() ); }
So now our code handles regular and static meshes. But what about skinned meshes?
This one took me a while to figure out, but eventually I did. Skinned meshes for one act a bit like non-uniformly scaled meshes for one, and for another they do not use the transform the skinned mesh renderer component is attached to. Instead, they use the root bone transform.
Matrix4x4 calculateModelMatrix() { if( renderer is SkinnedMeshRenderer ) { Transform rootBone = ((SkinnedMeshRenderer)renderer).rootBone; return Matrix4x4.TRS( rootBone.position, rootBone.rotation, Vector3.one ); } if( renderer.isPartOfStaticBatch ) { return Matrix4x4.identity; } return Matrix4x4.TRS( transform.position, transform.rotation, calculateScale() ); }
So, now that we can calculate the model matrix, let's create a helper script.
The Helper Script
Our script will keep track of the current and last model*view matrices, and will pass the matrix of the last frame to our material. Note that even if our shader does not have the matrix as a property, it will still store the matrix. We'll use this to our advantage when we go to render it with a replacement shader, which will be able to make use of this stored value.We'll use OnWillRenderObject to do this. Later on, our image effect will create a secondary camera to render the motion vector. We'll call it "MotionVectorCamera" so that our matrix helper script can detect which camera is rendering it (we won't update the matrix if it isn't being rendered by our motion vector camera).
using UnityEngine; using System.Collections; public class MotionBlurMatrixHelper : MonoBehaviour { private Matrix4x4 lastModelView = Matrix4x4.identity; void OnWillRenderObject() { if( Camera.current.name != "MotionVectorCamera" ) return; foreach (Material mat in renderer.materials) { mat.SetMatrix("_Previous_MV", lastModelView); } Matrix4x4 view = Camera.current.worldToCameraMatrix; Matrix4x4 model = calculateModelMatrix(); lastModelView = view * model; } Matrix4x4 calculateModelMatrix() { if( renderer is SkinnedMeshRenderer ) { Transform rootBone = ((SkinnedMeshRenderer)renderer).rootBone; return Matrix4x4.TRS( rootBone.position, rootBone.rotation, Vector3.one ); } if( renderer.isPartOfStaticBatch ) { return Matrix4x4.identity; } return Matrix4x4.TRS( transform.position, transform.rotation, calculateScale() ); } Vector3 calculateScale() { Vector3 scale = Vector3.one; // the model is uniformly scaled, so we'll use localScale in the model matrix if( transform.localScale.x == Vector3.one * transform.localScale.x ) { scale = transform.localScale; } // recursively multiply scale by each parent up the chain Transform parent = transform.parent; while( parent != null ) { scale = new Vector3( scale.x * parent.localScale.x, scale.y * parent.localScale.y, scale.z * parent.localScale.z ); parent = parent.parent; } return scale; } }
Now that our shader has access to the model*view of the previous frame, we can get started on the motion vector shader. In the next post, I will cover creating the replacement shader and using it in an image effect script.
Friday, December 6, 2013
Unity Multiplayer Games by Packt Publishing Available For Preorder
Hey everybody!
My new book, Unity Multiplayer Games, published by Packt Publishing, is now available for preorder and is expected sometime this month.
This book covers everything you wanted to know about multiplayer games in Unity, with sample applications for every chapter to teach you by concrete example. Learn to use Unity Networking, Photon, Player.IO, and more, and even delve into some of the deeper topics like server-authoritative networking.
Preorder it here!
Happy gaming everyone.
My new book, Unity Multiplayer Games, published by Packt Publishing, is now available for preorder and is expected sometime this month.
This book covers everything you wanted to know about multiplayer games in Unity, with sample applications for every chapter to teach you by concrete example. Learn to use Unity Networking, Photon, Player.IO, and more, and even delve into some of the deeper topics like server-authoritative networking.
Preorder it here!
Happy gaming everyone.
Motion Blur In Unity Part 1: Overview
Welcome to my series on implementing motion blur in Unity. This is something I managed to create after days of trial and error, and I must say it's an incredibly nifty effect.
Before we get into the code, I'll start by talking about what I mean when I talk about motion blur, and how it works.
Before we get into the code, I'll start by talking about what I mean when I talk about motion blur, and how it works.
Motion Blur? Doesn't Unity Have That?
The answer is yes. Kind of. Not really.
Unity has a few motion blur effects. There's your traditional accumulation motion blur (which, despite the name, doesn't really look like motion blur - in the end, it ends up looking very dream-like), and a variety of so-called "camera motion blur" effects (none of which I could get working correctly) which try to simulate motion blur of the camera moving.
What it doesn't have is a special kind of motion blur employed in games you've likely poured hours into and not even noticed. I'm not sure what the technical term is (UDN refers to it as "per-object motion blur"), so I'll be referring to it as Screen Space Velocity Blur from this point on. This is not the technical term, but it makes for an easy term to remember (SSVB).
SSVB is used in a lot of games. Unreal Engine 3 features this technique, and therefore is used in nearly every Unreal game on the market. CryEngine also features this technique.
SSVB does two things: it produces realistic motion blur when the camera moves, and also features plausible per-object motion blur.
So How Does It Work?
SSVB works by rendering a velocity buffer. This velocity buffer contains screen-space velocity, rendered per pixel. In order to produce this velocity, the velocity buffer shader needs access to the model view of the current frame, and of the last frame. It then projects each vertex using both matrices, finds the displacement, and uses this as the velocity. The velocity is generally encoded in a floating-point buffer in the Red and Green channels (X and Y velocity).
Finally, the motion blur shader samples the velocity per-pixel. It uses this to step along a line, sampling pixels along the velocity vector. The final pixel output is the average of each sampled pixel.
There are a number of changes which can be made here, and which I implemented for performance. In Unreal, they render velocity to a half-resolution image, perform motion blur in half-resolution, and then blend the motion blurred image per-pixel with the full resolution image based on velocity magnitude (so areas which are motion blurred use the half-resolution image, while areas which are not blurred use the full resolution image)
It's a lot of work to set up in Unity, but it's definitely possible.
In the next post, we'll be deconstructing the many quirks of the model*view matrix in Unity, and creating a helper script to feed the current and last model*view matrices to our shader.
Thursday, December 5, 2013
Unity Optimization Myths
Something has been bugging me as of late. It comes from the darkest corners of the Unity development community.
I'm talking about misguided optimization.
It astounds me just how many bizarre "tips" I've seen for making Unity games run faster or smaller. Today I'm going to be listing a few, and talking about why I disagree with them.
Not only is this untrue, it can also make code significantly less readable. Atomic types like floats, ints, and bools, are not garbage collected. They are allocated on the Stack, whereas Objects (instances of a Class) are placed on the Heap. The Stack is not garbage collected, but the Heap is. Allocating value types will never trigger garbage collection, allocating objects will.
Keeping value types as temporary variables will not afford any performance improvement whatsoever.
I'm talking about misguided optimization.
It astounds me just how many bizarre "tips" I've seen for making Unity games run faster or smaller. Today I'm going to be listing a few, and talking about why I disagree with them.
Mipmaps Are Bad
I've seen a blog or two mentioning that mipmaps take up more memory than just the texture itself, and therefore disabling mipmaps is better.
The truth is, yes mipmaps do take up memory. They do not, however, take up much.
With the next generation of consoles released, the now-last generation of consoles seem dated now - and it's because they have been for years. For instance, the Xbox 360 features 512MB total RAM - this RAM is shared by CPU and GPU.
As consoles become more dated, mobiles are quickly catching up. For instance, the Nexus 7 features 1GB of RAM. While much of it is taken up by the OS, that still leaves a bit more than the 512MB offered by the Xbox 360.
As consoles become more dated, mobiles are quickly catching up. For instance, the Nexus 7 features 1GB of RAM. While much of it is taken up by the OS, that still leaves a bit more than the 512MB offered by the Xbox 360.
Despite the 512MB limitation, mipmaps are still used quite heavily on the Xbox 360, and even megatextures as seen in the first-person shooter RAGE.
Mipmaps can also speed up rendering, as they can increase locality of texel access and reduce cache thrashing (especially important as GPUs tend to have small caches).
In general, if you need to reduce VRAM usage, reducing texture size or reusing textures is what you'll want to try first, before you consider disabling mipmaps.
Lower Poly Is Better
Many people love to advocate low poly counts. It sounds like it would make sense - fewer triangles and vertices is less for the GPU to process, right?
However, these days, GPUs are incredibly fast at processing raw geometry, even mobile GPUs. That's not to say you should go wild with polygons - you'll still want to keep polycounts reasonable, but don't break your brain trying to reduce polys - you're far more likely to encounter issues with draw calls and fillrate long before you hit poly limits.
Always Keep Temporary Variables
This one I suppose is borne out of a misunderstanding of the GC and how .NET handles memory. I've seen people suggest to keep temporary variables whenever possible to reduce memory allocations, such as turning this code:
void Update() { Vector3 x = calculateSomething(); }
Into this:
Vector3 x; void Update() { x = calculateSomething(); }
Not only is this untrue, it can also make code significantly less readable. Atomic types like floats, ints, and bools, are not garbage collected. They are allocated on the Stack, whereas Objects (instances of a Class) are placed on the Heap. The Stack is not garbage collected, but the Heap is. Allocating value types will never trigger garbage collection, allocating objects will.
Keeping value types as temporary variables will not afford any performance improvement whatsoever.
Use FixedUpdate
This one is kind of mixed. The idea is good, the implementation is not.
Some people have mentioned getting performance improvements by moving some logic, such as AI, to FixedUpdate rather than Update, and modifying the FixedUpdate timestep.
Thing is, FixedUpdate is where physics is supposed to take place. That's why it runs on a fixed timestep - so that you don't get odd behavior like tunneling when framerate drops.
In order to afford any benefit, your fixed timestep has to be smaller than your frametime. So if our game is running at 30 FPS, our FixedUpdate would have to run less than 30 FPS in order to afford any improvement whatsoever. If you use physics, this increases the likelihood of tunneling and other odd behaviour.
The better option, if you absolutely need to reduce how often code executes, is to keep track of a frame counter and only execute your update code once every 'n' frames (which is perfectly acceptable for AI routines). This keeps physics timestep independent from AI timestep, as it should be.
Declare Foreach Iteration Variables Beforehand
And lastly, I just today encountered a discussion on foreach and how it allocates memory. Someone suggested that, in order to eliminate the allocation, you should declare the iteration variable before the loop.
That is completely and utterly false (luckily someone more knowledgeable was present to correct the original poster of the suggestion).
The reasoning was that this:
foreach( SomeClass x in SomeCollection ) { // do stuff }
Would allocate "SomeClass x" for each entry in the collection (it doesn't). And that this:
SomeClass x; foreach( x in SomeCollection ) { // do stuff }
Would fix the allocation (it doesn't).
Consider the situation where we have this instead (a perfectly plausible situation):
foreach( Vector3 v in SomeCollection ) { // do stuff }
This still triggers a heap allocation. Not because it's allocating Vector3's (remember, those are stored on the Stack, not the heap), but because it's allocating an enumerator to iterate over the collection.
The correct solution is to use a regular for loop instead of a foreach. Note that you don't have to do this everywhere - just where it counts.
And that's biggest secret of optimization. You should focus your efforts on optimizing where it counts. If your login routine generates GC calls, it doesn't matter that much - because login doesn't happen regularly during the lifespan of the game, certainly not every frame, but AI or gameplay for example are much better candidates as they run frequently.
Anyway, those are my thoughts for today. I'm tired.
Happy game development, everybody.
And that's biggest secret of optimization. You should focus your efforts on optimizing where it counts. If your login routine generates GC calls, it doesn't matter that much - because login doesn't happen regularly during the lifespan of the game, certainly not every frame, but AI or gameplay for example are much better candidates as they run frequently.
Anyway, those are my thoughts for today. I'm tired.
Happy game development, everybody.
New Blog!
Woot, new blog!
After days of thoughts and words popping up in my head, I couldn't take it anymore. Twitter, with the 140 character limit, barely provides room for a few words, Google Plus just doesn't feel the right place for what I have to say, and Facebook - ugh, Facebook.
Here I'll be posting whatever thoughts come to mind about game development, Unity, occasional gaming, and whatever else I want to throw up on the internet.
Looking forward to it :)
After days of thoughts and words popping up in my head, I couldn't take it anymore. Twitter, with the 140 character limit, barely provides room for a few words, Google Plus just doesn't feel the right place for what I have to say, and Facebook - ugh, Facebook.
Here I'll be posting whatever thoughts come to mind about game development, Unity, occasional gaming, and whatever else I want to throw up on the internet.
Looking forward to it :)


