XaiJu
TheGatewayOfRealities
TheGatewayOfRealities

patreon


Rocks and trees, refining visual style.

Author: Salireths.

I've spent a lot of time working on new graphical assets, but it's not just about making models - that would be much easier....

There is many ways you can go about making models, many styles and workflows. But in our case it is about making them very fast and still have them decent looking, because so far I'm the only one who works on all the graphics, models, world design, and animation. I need to be extremely efficient.

So far I've added around 15 new textures, countless rocks, re-made grass and figured out what style of trees I'd like to see. I've also been re-writing  shaders, making them easy to use - all to help myself produce assets faster. 

I wanted to share some technical art info of how various things are made. Its important to figure out the process on simple examples before starting to mass-produce various variants and flavors, that's why I haven't done a lot of variety yet.


GRASS

You've probably seen this grass before, but I haven't shown how it works. Plus, I re-modeled it with reduced density and better distribution.

So one grass "object" is now just made out of 64 triangles. Each grass blade is just one triangle, very cheap and efficient - works well for the stylized look. I made it in blender by using a particle system, just putting grass blades randomly in a circle.

However when you look at how it moves - each individual grass blade rotates around its root. And no, before you ask - its not rigged or animated, that would require a supercomputer to render a grassy field like that. This is all done with vertex shaders and math (similarly to how wall construction/destruction animation is done in Fortnite). 

Each blade of grass needs to encode its pivot point around which it will be rotated when the wind is applied. In this case I've done that with a single UV map, since it can pack additional information aside from texture mapping. Initially I did that with two UV maps, but that doubled the rendering cost. 

Grass texture atlas, notice the triangle on the left, this is the grass blade within the typical 0-1 UV coordinate range.
When you go outside that range the texture repeats, so on the picture on the right I'm offsetting this triangle by only whole numbers which keeps the texture at the same spot, but lets the system to know where the grass blade is, giving me its XY pivot point in object space.
Z coordinate for the grass blades is always the same so I don't need to encode it.

I wrote this simple blender script to encode the pivots - you just select all individual grass blade objects and it creates the new UV map with the encoded pivot. With using only one UV map I had to decide between texture vs pivot precision (it uses half floats), as using more than 16 whole positive or negative numbers lead it to the texture shifting around, thus I had to space (quantize) pivots 4 centimeters apart to not go over the limit, as the grass is ~50 cm in radius.

<code>
import bpy
import math

# select multiple grass objects (make sure they are not instances, press U to make them unique) then run this script.
# make sure the texture uv map already exists
   
def main(context):
   for obj in context.selected_objects:
       data = obj.data
       
       # Get object location     
       loc = obj.location
       
       # if Pivots UV layer exist, delete it first
       if data.uv_layers.find('Pivots') > 0:
           data.uv_layers.remove(data.uv_layers['Pivots'])
           
       # copy the current uv layer and name it Pivots
       data.uv_layers.new(name='Pivots', do_init=True)
 
       # data pointer
       uvl = data.uv_layers['Pivots']
       
       # iterate each uv vertex and set coords
       for uvldata in uvl.data:
           uvldata.uv.x += (round((loc.x*25))*1)
           uvldata.uv.y += (round((loc.y*25))*1)-1  
           
           # *100 for quantization scale of 1, but *25 to use with quantization scale is 4. While the precision of the pivot point is lost within 4 centimeters, if we dont scale it down UE4 is gonna have float precision errors with the texture because it only uses half floats.
           
           # -1 offsets the center for UE4, because UE4's UV center is at the left upper corner, as opposed to blender's left lower corner
       
       
main(bpy.context)
</code>
LOD level visualization

Grass also has various LODs (Level of Detail), a system that swaps meshes, loading ones with less detail as camera goes further away, and showing higher poly versions when its closer. Trick is to produce LODs in a way to make the popping less noticeable. For the furthest LOD I baked the model to texture, applying it to a couple flat "cardboard cutouts" like in old games. Up-close it looks ugly, but far away it is not that noticeable and it helps to make grass cheaper for your videocard.

LOD transition (popping) is almost unnoticeable.


TREES

The bushes that we had before, while serving as a solid proof of concept, weren't that pretty at all - so I needed to finally figure out how to better make trees. So I took on a simple generic tree example and after re-making it a few times, I think I achieved something that is both quick to make and looking pretty! 

The trees also react to wind, and to do so it reuses the grass shader, though pivots are entered by hand, since we only need to know about where is the center of the tree crown.


Without textures the crown looks like a polygonal mess - this technique was also used in The Witness game (which I think has some of the best looking stylized trees in gaming). To create this mess of polygons I once again used particle tools to procedurally splatter octagon-shaped planes onto a distorted sphere that represents the shape of the crown. 

Particles spawned on top of a sphere create a simple ball-like crown, which still has a lot of natural leafy fuzziness, not like it's been shaped by a gardener.


However if you just simply apply an alpha-masked texture on a crown like that you will see that it looks quite bad at specific angles, as it is noticeable how the tree is made out of many "cardboard cutouts". To workaround this issue we dither away (hide) the "cardboard sheets" that are facing opposite direction from the camera.

This GIF shows the difference between hiding and showing planes that face away from the camera.

This is how this effect is achieved in both Unreal Engine 4 and Blender 2.8 shaders, its very simple, just have to do a dot product between camera vector in world space and the normal of the surface:


There is more we need to do on the tree shader. To make tree look "rounder" than it would be (since it's made out of a bunch of flat planes it looks rather flat by default), we need to modify the normals. 

Usually artists for other games do it in the 3d modelling software (like that), however we can't do it in this case because we need to have the normals to be flat so we can do the hiding technique above, yet we need them round for better light interaction... So we have no choice but to re-calculate the spherical normals on runtime.

Difference with and without modified spherical normals. It adds a lot of volume to the tree, making it look much more appealing, especially at the distance. It also reduces the visible seam at places where planes intersect each other.

The crown uses a tiling leaf texture and an additional opacity mask with a secondary UV channel.

Thanks to that there are some nice bonuses we can do, such as making the leaf size stay the same regardless of the tree size (when the same models are placed many times, we usually randomize scale and rotation to increase variety),

Other feature is ability to control how dense the crown is and how dense the shadow it casts independently. Leaves also can change textures as long as it uses our texture system (more about it below). 

There are some floating leaves, but that is actually intentional, makes it look more abstract and painterly, which is good for the style we chose for the game. Otherwise I'd have to be spending weeks just modelling photorealistic trees and nothing else, which is of course unacceptable.

First making leaves appear, then shadow, then changing texture to voronoi pattern, then changing its color.

Trunk also uses a bunch of tricks - it can have any bark texture applied, also it has branch normal matching to minimize the seams on branches and roots while keeping the model simple (got inspired by Speed Tree techniques).

Toggling normal matching on and off.


ROCKS

I just randomly threw a bunch of rocks, boulders, cliffs and mountains into the valley...

Making rocks is also not that simple. Many games employ artists to individually sculpt various rocks, retopologize, bake normal and AO maps, and paint unique diffuse textures for each... Which works well when your project has 30+ artists...

So I had to simplify this idea, I took inspiration from the Legend of Zelda: Breath of the Wild, with how simple yet elegant all the rocky formations are there. 

So I just began throwing various shapes together in Blender and Zbrush to see what produces interesting silhouettes, trying different modelling techniques, testing which is faster.

My rocksploration journey - tried different styles and modelling techniques.

I tried doing retopology manually on sculpted rocks, but then quickly realized that it eats too much time, thankfully instant meshes has saved the day, producing very good results in just a few clicks.

Automatic topology straight out of Instant Meshes - I choose to have a vertex each 2 meters on every axis, this keeps those large (~50 meters on every axis) rocks fairly low-poly (around 4k triangles), yet comparatively keeps the details similar to the landscape, which puts a vertex every 1 meter.

Then, as we all know, 3d models need textures, and textures need UV-mapping... except I chose to not do it at all. 

Instead we will rely on using triplanar mapping, its a technique of having three different texture projections on every axis. You might have seen this in action if you played any smooth-voxel game, like Space Engineers, No Man Sky, or Crysis (funnily enough, old versions of Cryengine had a voxel system just to help level designers make caves, rocks and overhangs, a system like this would be extremely useful for us as well, but we're definitely not going to be developing voxel terrains).

Triplanar shader with height blending for grass in action. Notice how textures automatically change as I rotate meshes.


TEXTURES

Textures for the rocks, trees and landscapes are not going to appear from nowhere, but thankfully I've got a good stable relationship with Substance Designer! With it's procedural magic it allows me to make multiple high quality stylized tileable textures per day! And the ability to modify and change anything about them at any point it is truly a life savior. 

Typical TGOR texture looks like this. This is the forest ground with ferns, by the way

All textures in our game look like colorful mess to the naked eye. The reason is that they are made out of four different black and white masks, packed in each RGBA channel. Let's look at them channel-by-channel.

Red channel - Gradient X. Used for determining color of the final material.
Green channel - Gradient Y. Also used for color gradient hue shifts

With red and green channels it defines information for applying colors. Unlike most other games which just provide albedo/base color textures, I thought it would be better to have dynamic texture colorization for everything, it works by sampling another 2D gradient texture, the red channel defines its X location, and green defines Y. 

Grass color gradient - it's easy to swap colors at any point, makes it much easier to control color composition of the scene or entire level. 

The gradient encodes a lot of color and hue variety, so it doesn't suffer from common coloration pitfalls (like with tinting). We can also change the color of depending on position in the world with the help of a global noise - which helps to reduce texture tiling at the distance and make things look more interesting. 

Texture color randomization and tiling reduction  with a global noise. Color only, fog and shading is disabled



Blue channel - Roughness. 

Like in all modern Physically Based Rendering games, roughness describes how polished or rough the surface is, resulting in appearance of different amount of reflected or scattered light respectively (example).


Alpha channel - Height

When making textures in Substance Designer you're always working with height information, think about it as if you were to etch a pattern on wood or metal - its the bumpiness, the depth that you can feel with your fingers. The black pixels represent the most deep point, the white ones represent the closest, kind of like an infra-red camera shot.

We utilize height not only at the time of making the texture, but also to do height-blending between different textures. Instead of "blurry" linear interpolations we can have things like grass growing between the rocks as textures transition into each other. 

Difference between Height Blend and traditional Smooth Blend

Another use of height for landscapes is displacement and tessellation, working together they add depth to the flat surface, and the effect is really amazing! Though sadly its not that simple... 

Tessellation and displacement effect for small details on the landscape.

Unfortunately tessellation is really expensive on your GPU, easily eating 40 frames away, especially when looking at a large landscape. Making it an optional setting is also not that easy, just by having an ability to switch this feature on an off seems to already reduce performance, more work needs to be done on that part to figure out why.


Normal Map.

Aside from the multilayered surface texture, there are also normal maps, which work by changing how light interacts with the surface, giving it much more detail. No question why it is the standard in all games. Usually normal maps require artists to sculpt high-poly meshes and bake them, but in the case of tiling textures, thanks to Substance Designer they are are made from height map, so no additional time is required to produce them. 

CONCLUSION


I got introduced to level design with games like Half Life 2, Unreal Tournament 2004, and Serious Sam. Back in the day when you wanted to make, say, a room, it was very simple - you just cut it with a box brush and assign some textures to the walls. 

If you were to do it like that nowadays - it would look way too outdated and empty, lacking a lot of details we got "spoiled" by in modern games. However I'm still amazed by simplicity that we could get away with back then, map-making was fun and it didn't require so much work hours to be put in photoreal models first. 

Of course it would be a dream come true to be able to make worlds with just a few "brushstrokes", but since I'm the only one who does all art and design for the game (and much more than just that), I kind-of don't have another choice.

To help myself with this I'm trying to use all the tools that I have access to, such as procedural techniques, automatic topology and UVs, reusable tiling textures, multipurpose shaders - all to simplify the creation process and yet achieve modern-looking stylized results fit for an indie game.  

There is plenty of other tools that I have yet to play with, hoping to make process even faster and more automatic. All because, the more efficient I make it, the more time I will free myself to spend creating better world design, doing more lewd animations, writing lore and adding more content in general.

Rocks and trees, refining visual style. Rocks and trees, refining visual style. Rocks and trees, refining visual style. Rocks and trees, refining visual style.

Comments

Just started funding today! this game is looking great!

LuftRaptor

Progress is looking incredible!

Lucian

Wow ! There is a lot of works !! Great Job !!

Furry Lover


More Creators