Blogger Templates
Next
Previous

Tuesday, December 12, 2017

0

Graphics (∞ of ∞) The Game

Posted in ,
EAE 6320-001
write-ups
It was an awesome learning experience building individual graphics systems like meshes, textures, sprites, effects, asset build pipeline and architecturing them. In this write up, I will carry forward all those learning to make a cool video game. That way, based on my game requirement, I can reuse the existing system to build the whole game loop.


The game idea
It’s always a good practise to solidify the game idea before getting into implementation as that would save a lot of time from rework. So I started by thinking about a planet defender game where a space shuttle will protect earth from incoming projectiles. Having done some of the concept, the top down graphics did not appeal me much. Since I was still ideating, it's easy for me to jump between ideas and I won’t lose anything. Like the usual software development saying goes “If you are failing, fail faster” The next idea I came up with was a car game that can work like an infinite runner by clashing through some obstacles. Since I had already made some assets for the previous project using the car and planets, it was easy for me to visualize how the game can come and I was confident that the game experience can be made really better. Seeing a 3D model closely in the game window fascinated me and doing an infinite runner style game would allow me to render multiple 3D objects close to the screen at a high pace.


Architectural Changes
After thinking a while, I realized I would end up with many game objects to complete this game. With that setup, my existing system which relied on vector and vector index to access each game object was fragile and error prone. To get rid of this index, I need to come up with a key value pair setup like a map. So I replaced my vector logic with a map based logic and each gameObject will have its own name and that name will be used as a key to access the gameobject in a map.


The Infinite Loop
In most games, the platform will be procedurally generated to give an infinite running experience. I thought about this approach first and realized that this could complicate my implementation of maintaining all the game object offsets relative to the new position. To solve this, the approach I took was to give an artificial moving experience by having the player car to be at one position and moving the other game objects relative to the player car. This motion of objects will be reset after a certain threshold and will start from the same place where it originated and keep doing its motion in a loop. Once I got the loop working for the tree, I kept adding other objects like house, mountains, road strips and all possible bear types. My game system was capable of handling large game objects with ease. If you are using Unity or Unreal, I would highly recommend the first approach for infinite running experience.


More environment objects
Nowadays I am getting used to creating quick assets using Maya. The trees and mountains you are seeing in this game were vertex painted in maya and then imported into the game. I also reused some of the old assets I was using and took some additional models from turbosquid. For instance, I kept the planets to be seen from this planet as you drive through a highway. These planets also have a subtle rotation which makes the game world look good. Most of the existing game architecture design came handy when I had to create all these environment objects through a single wrapper representation called the gameObject which support mesh and texture rendering.


Enemies and it’s Collision
The enemies in this game are basically the bears. The white polar bears are allies and will reduce our score index if we kill it. Our score index will climb up when I hit an enemy bear. I achieved the detection of hit through a very simple collision logic. I wrote a anonymous namespace method PerformCollision and CheckCollision. CheckCollision will basically tell me if the two objects are in an accepted proximity. PerformCollision is built on top of the CheckCollision and it can work only when CheckCollision evaluates to true. For our game, PerformCollision will do the role of resetting the game object to the start of its loop on successful collision. Since this class is more related to graphics and code architecture, I spent little time on building a complete collision system. An ideal way to solve this is to implement the separating axis theorem to determine if two objects collide with each other.


The NOS Mode
The nos mode experience is similar to using nitrous oxide in fast and furious movies. Whenever the up arrow is hit, I simply add an extra offset to all of the moving environment objects. That extra offset will give an experience as if you are moving fast. To make it more intuitive, I also move the camera backwards to make it look as if the car is plummeting in. This camera movement will be reset whenever the player do not press the up arrow key. I thought this was interesting as I could pace the car’s speed according to the will of the player.


Game UI
I added couple of UI widgets to give a consistent experience for the player to jump start with the game. This includes the intro title game which gives a base idea to the player about the game followed by the narrative screen which describes the mission from the commander to the sergeant. This will help the player understand that he is in a different planet than that of earth; and the white polar bear are allies and not to be harmed. I also added an end game screen which gives a positive indication on completion of level. For our game, we are relying on 20 bear hits as the win state.


Game Score
The game score is calculated by each successful mutated bear hit. For every new successful hit, we will add 1 to the existing value. At the time of this writing, I did not have an elegant system to show numbers, So I literally created image png’s representing 0 to 9. Since these images are added to the start of the textureVector, it's easy for me to access the individual digits and show its equivalent texture from texture vector.


Final Thoughts on Good coding
I am a person who believes in design driven engineering. There are two kinds of programmers to my understanding, one who wants to get the task done in the least amount of time and get it working and the other one who spends some time in thinking about how the system would interface in future and with other system. I have always found good code with the second style of programming as I have found their code to be more readable and scalable. If the code is not scalable, every new change feature request would eventually make the code look like a spaghetti and be not useful to anyone. This class has definitely taught me how to jump start in an existing code base and figure out why a code is written like that. In most cases, whenever we join a new company, its very rare that we will get to work on a project from scratch rather we will have to understand an existing code base. I think I learnt how to decipher existing code and architecture good code from this class and applied those learning on my thesis project and other areas as well. For example, whenever I am tasked with a feature, I always think about how the feature is used in game and what is the expected experience and what is the future scope. Once I have that clarity, I use some judgement to build systems that are scalable and can support that feature and enable designers to plug and play values(Data driven systems). There could be cases where I decide to get an hackish implementation in if the time is really really less but it’s good to leave notes there as to why that was done and what refactoring is required. This could help future engineers understand the intent of the usage and arrive at a better refactoring.


Final Thoughts on Good Design Vs Bad Design
Good designed systems/software are the ones that are less error prone, highly readable, and easily modifiable. Everything is opposite when it comes to bad design and from my personal experience, I would highly recommend to think of a good design before jumping into code. For example, when I was learning AI in unreal, to implement an AI mechanic, me and another AI programmer kept working on the same behaviour tree and it became difficult for both of us to work simultaneously. The behaviour tree kept growing such that it became difficult to trace its flow. This happened because of poor design systems and to solve this, I built an emotion system that uses data driven system and has a design pattern that all systems can adhere to. Now the behaviour tree was broken into individual pieces and it's easy for me and the other AI programmer to work parallely. We saved a lot of time and we made meaningful design decisions with a good design and with bad design, it just hindered our progress.
Screenshots



Controls
Car Movement
Right Arrow - Move the 3D game object to the right.
Left Arrow - Move the 3D game object to the left.
Up Arrow - Move forward with NOS mode.

Enter to skip the title screen. Space to skip the narrative screen.
Camera Movement
A - Rotate towards the left side
D - Rotate towards the right side
Q - Rotate Upwards
E - Rotate Downwards

Check out the game via this



Thursday, December 07, 2017

0

Graphics (14 of ∞) Translucent Game Objects

Posted in ,
EAE 6320-001
write-ups
In this write up, we will talk more about making translucent objects and using a sorting order
to render them in order. The translucency will apply based on the z order the object is placed
in the camera space.


Translucent fragment shader
For the translucent fragment shader we will just follow our existing fragment shader format
and just update the alpha value of the output color. Anything close to 1 is more opaque and
anything close to 0 is more translucent. For the time being, this value is hardcoded and the
assumption is that we would use this fragment on the effect that has to be transparent.


New effects & Translucent Objects
Once I have the required translucent fragment shader, I have to map them to new effects.
Then I will use those effects on the game objects that have to be transparent.
For the purpose of this project, I added two new game objects representing Mars and
Uranus planets. Both these game objects would use the new effect that uses the translucent
fragment shader.


Enabling Transparency
To enable the transparency, the gameobject should use the render state which has its bits
set for EnableTransparency and EnableDepthBuffering. Now having done this, our game
object will be transparent in some places of the camera rendering and in some places it
won’t be. When we did depth buffering, we did not care much about the order the way the
game objects were submitted. But with translucent game objects, we have to follow painters
algorithm to paint from back to front. The reason why we do this approach is because we
need to allow the objects that are in back to be see-through from the objects that are in front.


The Ordering Math
As an architecture style, I decided to have a separate vector for my translucent game objects
rather than having one in the engine side. With this setup, in the SubmitDataToBeRendered
method of the application thread, I can simply do my sort operations on translucent
gameobject vector and submit them after the camera submission. All I had to do here was
to find all the translucent gameobject position in camera space and sort them using a
fancy c++ lambda based comparator.  I simply checked the z value of the object's position
in camera space to complete this comparison. The higher the value of z, the farther the
object is from the camera viewport. I spend some time understanding how I would capture
the values inside a lambda function, it was interesting how it did not allow me to capture the
entire class until I used ‘this’. The only reason for me to use the application thread is that,
it was handy for me to access the vector of translucent objects and do the required operation
before the render frame was started. But ideally I think the benefits are different on case
to case basis and its mostly an architectural decision.


I had an issue where my maya export had inherent position values and it was not set to
0,0,0 So I ended up rendering the position based on Maya values. This affected my
calculation for sorting of the game objects and Ameya helped in finding the issue. Once
I used models of 0,0,0 and set the position values, my sorting logic worked like a charm.


Controls
Cube Movement
Right Arrow - Move the 3D game object to the right.
Left Arrow - Move the 3D game object to the left.
Up Arrow - Move the 3D game object upwards.
Down Arrow - Move the 3D game object downwards.

Camera Movement
W - Forward Acceleration
S - Backward Acceleration
A - Rotate towards the left side
D - Rotate towards the right side
Q - Rotate Upwards
E - Rotate Downwards

Checkout the game via this release.




Wednesday, November 29, 2017

0

Graphics (13 of ∞) Smarter build process

Posted in ,
EAE 6320-001
write-ups


So far I always had to wait a lot of time building all the textures, meshes, shaders that I am using in the game. This had severely impacted the time I spend in completing a project. In this write-up, I will walk you through the experience of building an efficient build process to speed up my development pipeline.


Isolating Asset Inclusions


It’s always a good practise to have all our asset references to be in one place so a gameplay programmer/designer can easily modify them. One idea here is to assume all content inside a specific folder could be built altogether. This approach could be very handful for a designer but in our case, we will use a separate lua file like AssetsToBuild.lua to list all the required files that have to be built. For meshes and textures, I can go with an array like lua tables and for shaders, I will be using a dictionary like lua tables. This would allow me to define all textures, meshes and vertex with their exact pathnames and without have do much scrolling on the same file.


I also had to set the custom build tool params to allow my BuildExampleGameAssets to build the required assets using AssetBuildExe.exe


NewAssetType References
Then I had to link the appropriate builders for the asset types. This include MeshBuilder.exe, TextureBuilder.exe, ShaderBuilder.exe. Based on asset type info, we would pull the builder relative path for our build process.Since we had the source path here, we have to come up with a regular expression to extract the individual file name, relative directory and reuse the same to construct the target path. Since in my project, the source and destination can vary in many ways like extension or file name, I need to rely only on the source path to get the relative path for the output files.


No builder files in the build process
We do have some files like the settings.ini and license which gets copied directly without the need for having a builder. When we copy those files, we need to ensure we copy only when there is no existing file or even if the file exists, we should perform copy if and only if the source is more recent than the target. The way I do this is by comparing the timestamp of file modified from the target with the timestamp of the source file modification.


Layout Shaders
After having the layout shaders referenced in the AssetsToBuild, I don't have an option to control certain paths to build for a specific platform in AssetsToBuild.lua. To solve this, I will continue to build the assets across all platforms but I will make that macro specific check inside the shader file directly. For instance, my mesh and vertex layout geometry will encapsulate all of its content in a flag like #if defined( EAE6320_PLATFORM_D3D )


Asset Build System
After completing this whole asset build system, I saw a significant difference in the time it would take for me to make a complete build. Earlier, I use to build every file whenever I make a build but now I would build the files which are modified. I will also build every possible asset only when there is a change in the builder itself. This saves a ton of time for me whenever I work on a new module for this project. I would definitely recommend the readers to keep the asset build system as part of an early milestone in a project so developers can be more productive later.

Game Screenshot
Controls
Cube Movement
Right Arrow - Move the 3D game object to the right.
Left Arrow - Move the 3D game object to the left.
Up Arrow - Move the 3D game object upwards.
Down Arrow - Move the 3D game object downwards.

Camera Movement
W - Forward Acceleration
S - Backward Acceleration
A - Rotate towards the left side
D - Rotate towards the right side
Q - Rotate Upwards
E - Rotate Downwards



Thursday, November 23, 2017

0

Graphics (12 of ∞) Time to go 0s and 1s

Posted in ,
EAE 6320-001
write-ups


One of the problem we had with the previous setup of our project is the time it took for our application to start. Since I was relying on lot of lua based human readable mesh files, I need to load them in their existing format and parse them based on my need. This would take time for the game to get ready and in this writeup, I will walk you through my experience of getting that process fastened up.


0s and 1s
To fasten up things, I should give my system the readily available data so it don’t have to wait for any extraction. The way I achieved that was to move my mesh reading logic from my mesh.cpp to the MeshBuilder project. This way I would be able to read the lua content when the mesh is being built. Then all I have to do was to store the data in a meaningful format. The order I took was vertex count, index count, vertex data and index data. The reason why I need to start with the count is because I need to know how much I should offset the data to access each individual elements. This wouldn’t have been possible if our starting or the ending elements did not have that data. Having said that, I feel like either of vertex or index count could be at the initial offset of the file. As soon as I had my structure planned for the file and had the data ready to be stored, I started make file IO operations to write the binary data on the file. To debug/read the file contents, I used an external tool called the HXD which reads the binary data and shows equivalent hex values. This way I could easily decipher the binary data and understand where my original data pattern is.


The above is a reference for my plane mesh that is stored in binary and viewed using HXD. The following color pattern is used to identify each data type.
Green - Vertex Count ( 2 bytes )
Red - Index Count ( 2 bytes )
No color - Vertex Data ( 12 bytes for position, 8 bytes for UV, 4 bytes for color )
Blue - Index Data (12 bytes)


Another important thing to take account while writing this binary file is that the intent to do this is to give our graphics system the readily available data. To reach here, we need to overcome our platform based difference which are based on UV and winding order. An ideal way is to have our mesh builder build platform specific binary files that would format the data considering the platform based winding order and uv format.


Let the decipher begin
Pointer math is always fun and I was able to take advantage of some pointer math when I had to read the binary data back into the system. The mesh code where I had the lua based parsing earlier will now be replaced with extracting data from the binary file.


The above screenshot gives a basic idea of how I tracked down each individual data piece by moving through an offset based on the data type. I was able to debug this part in visual studio and confirm if the data I wrote and the data that is being read is correct.


As soon as I had the data ready, I passed it to the graphics system and was able to simulate my plane, cube, disc and the earth model. There was one difference where the runtime was almost instantaneous. This shows how much performance improvement the binary file gave over the human readable lua file. The reason why we have to use a binary format for runtime is because it allows a better load time for our game and that is mainly due to smaller file size and zero parsing requirement. And the reason we need to use a human readable file for our build is because, we need a more control on the data reading/modification as gameplay programmers. That way we will be less error prone while making changes to the data and be more optimized in run time which is for the actual end users.


Benchmark Tests
Comparison on the sizes of meshes and the time to process the data really wowed me and it made me think how important is to have these optimization in place. Here are some comparison that I did with the Earth mesh and the Car mesh.


Earth Mesh
Vertex count is 19307
Index count is 22827

Human readable Mesh file
Time taken to build : 0.30252s
Size : 6.82MB

Binary Mesh file
Time taken to run : 0.001273s
Size : 497KB

Car Mesh :
Vertex count is 5020
Index count is 8412

Human readable Mesh file
Time taken to build :  0.092652s
Size -  1.80 MB

Binary Mesh file
Time taken to run :  0.000859s
Size - 134KB


Comparing the above for binary file and human readable file, the binary file is a clear winner for runtime here in terms of smaller size and faster reading/processing time.


Checkout my awesome new meshes in the Screenshot/Gif below.




Controls
Cube Movement
Right Arrow - Move the 3D game object to the right.
Left Arrow - Move the 3D game object to the left.
Up Arrow - Move the 3D game object upwards.
Down Arrow - Move the 3D game object downwards.

Camera Movement
W - Forward Acceleration
S - Backward Acceleration
A - Rotate towards the left side
D - Rotate towards the right side
Q - Rotate Upwards
E - Rotate Downwards

Checkout the fancy elements via this release.