Some time ago I was asked to work on a really cool VR project as Technical Artist and VFX Artist. The game, in this first production phase, was about fighting against another player in an arena with magic spells in a cool Harry Potter style, using your hands and your voice.
The images that you see in this article don’t represent the final quality of the project, as I used this prototype (as my client asked me) to show the team how to optimize their VR project and which workflow in terms of assets and textures they should have follow.
When I started workin on this project, I had a middle level GPU card: a GTX 770. Based on other games I tried in VR with that VGA, I supposed that a game like Project Kyro should have reached at least 50-60 fps on my card with maximum settings (in VR.)
The game in the initial status ran at 10-18 fps in VR (screens below are without VR).
I started with very basic checks: shader complexity, light complexity, poly count, draw calls, occlusion culling.
The stat unit command on console showed me the what was killing the frame rate:
- Frame is its rendering time in ms.
- Game is the time taken by CPU (basically the game logic). 4 ms is good, this means that the programming logic wasn’t affecting the performance too much
- Draw is the delay of CPU on render thread, so how much your CPU is delaying due to render jobs.
- GPU is the time that your GPU needs to render the current frame.
Once I knew that the fps were bounded by GPU, I used the profile gpu command to go deeper in my analisys:
I analyzed every section of the SCENE area, starting from the first sector from the left of the bar.
Particle simulation was taking 0.17 ms, it was ok considering the number of candles flames in the scene which were generated by particle systems, but then I would change them in simple static meshes, since particle simulation would have been affected more by magic spells, and I wanted this voice clear for better profiling later.
Second thig was the Prepass of forward shading, I didn’t analyze it too much since I would switch to deferred in a second moment.
The third was about forward shading too
The fourth was the worst:
I started checking light complexity since in a room like that with a low number of actors the problem was surely the lighting.
- Light Complexity
Considering that light complexity visualization assigns a light based on complexity in a black to red range, I can say that the situation was pretty bad.
Reducing the attenuation radius of the lights (they had something like 1500) to 300 gave me this:
(In order of complexity: black, blue, green, orange, red).
Raising it up to 600:
Now you know why it was pink before! Lights were overlapping a lot of times making gpu calculations heavier.
(For the tests after this I disabled the forward render because of the several lacks on translucency rendering on the spells)
I tested different lights attenuation to show my team mates how the lights were affecting the frame rate.
Attenuation: 1000 – 18 fps
Attenuation: 600 – 36 fps
Attenuation: 300 – 60 fps
Attenuation 300 with forward – 70 fps.
I would solve the darkness problem later.
After gaining a lot of fps I detected all the rendering features that we didn’t need for that project, explaining my team why I was disabling them and how could they use them for other purposes.
Distance Field AO/Shadow: is a great feature for lights far shadows, movable skylight occlusion and shading purposes like distances from other objects.
Why don’t we need it in Kyro Arena Pvp?
Distance Field technique generate a distance field texture map for every static mesh, approximately 500kb to 1mb for every asset, depending on its poly count, this map is then used to generate a static shadow map which won’t change (if you use the distance field shadowing on a tree with moving leaves, the leaves will not move in the distance field shadow, this is for optimization purposes in large open worlds.)
- Add realism to the external scenes with skylight dynamic shadows
- Optimize far shadows on directional light
- Useful for many shaders maths.
- Gets memory based on your meshes number.
- Not 100% stable in internal spaces.
- A lot of compiling time when it builds distance field maps in engine.
- DF AO is not so fps friendly.
Occlusion Culling: culls the objects that we can’t see using other objects, since the arena is very small and we always see almost everything we can avoid this calculations on cpu disabling it for the moment.
Once I got the game running at faster fps I started solving the darkness problem using other lights and postprocesses.
I also added some shadows casted by a top spotlight at the center of the scene. The result is not only more optimized but also better looking in terms of color balancing, contrast and lighting.
After the light optimization I switched on shading optimization, looking for heavy material functions around the scene. (I mean, heavy for a VR project.)
This was the shader complexity visualization:
POM was used in walls, floors and roofs shader, I disabled it to check if it actually was needed in the game:
Since it was not bringing more depth to scene I decided to disable it to gain some fps more.
Shader complexity after this:
Another optimization I would have done was size down the candles flame, since the opacity of the texture was affecting a lot of pixels than needed.
Here is a comparison from what we had at the beginning and what it turned after my optimization work:
(The second screenshot is on a GTX 1080, on my old 770 it ran in a range from 60 to 70 fps.)
After this optimization task, I switched on shading, vfx and scripting tasks for the spell particles of the game.
I just made some particle behaviour in BP, some cascade and shading tweaks for this one.
made from scratch by me, basically just some particle sheets with a nucleus shape and random velocity, a spawn emitter location for the sparks, a light and an emissive shader.
made from scratch by me, this was tricky, not for the shading but for the particles. It was thinked as a burst of nucleus with random direction, noisy movement and a tail, and Unreal Engine 4 cascade module didn’t had this function (orbit or spawn emitter location don’t work with trail, so achieving this result was hard.)
made from scratch by me, a magic shield done with a static mesh circle shaped and a particle system sizing it up from 0,0,0 to a user defined parameter.
Shading is made by a couple of textures moving around (my setup can also reduced thanks to 4motionway node), refraction and emissive controlled with a masks texture
made from scratch by me, I also made the behavior via blueprint to control the hit point, since the resulting particle is different based on ray cast hit location:
if the ray doesn’t hit the floor it generates a little circle
if the ray hits the floor it generates a rock wall.
This spell has a shader which modulates a beam spawned in blueprints using wand position as source and ray hit position as target for the beam modules in cascade.
made from scratch by me, but the dragon head. Head model has been provided by the client, and an artist painted the emissive mask on it, I setup the shader and the fire effect.
Shader is a blend between two materials: a black one and an emissive one using a mask painted by the artist, to make the fire panning through a single direction I edited the world aligned function to make a world aligned panner.
If you have any question about the results please write a comment below and I will answer you!