00:00:03
fellas welcome to a new video or not a
00:00:06
new video if you're watching this in the
00:00:08
future today we're talking about grass
00:00:11
and video games grass is pretty
00:00:13
important when it's present in a scene
00:00:16
you might not notice it but when it's
00:00:18
not present the environment just doesn't
00:00:20
feel right the catch here though is that
00:00:22
when there's not enough grass it feels
00:00:25
just as weird and the grass stands out
00:00:28
the solution to this of course though
00:00:30
is to just put more grass in
00:00:33
as we've discussed in previous videos
00:00:35
though having a lot of one thing can be
00:00:37
quite expensive and heavily affect the
00:00:39
performance of our video game so how do
00:00:42
games render so much grass while still
00:00:45
having more than one frame per second
00:00:47
there's plenty of techniques for grass
00:00:49
rendering several of which we can just
00:00:51
throw into the trash as they are
00:00:53
unacceptably bad one of these techniques
00:00:56
in particular though has been tried and
00:00:58
true for decades
00:01:00
which i use to turn this vacant plane
00:01:03
into a dense field of grass you may not
00:01:05
believe me right now but this is 1.2
00:01:08
million animated grass meshes rendered
00:01:11
at 500 frames per second and now you may
00:01:14
be asking yourself what's the technique
00:01:17
acerola well stay tuned to find out
00:01:37
to set the scene i have here in unity a
00:01:39
silly little 300 by 300 meter plane that
00:01:42
has its vertices displaced by a height
00:01:45
map this is the plane that we'll be
00:01:47
placing our grass on i also have a
00:01:50
little cube that functions as a player
00:01:52
model reference
00:01:54
usually when you're getting started on a
00:01:56
project it's hard to find a good
00:01:58
starting point but with this it's pretty
00:02:00
obvious in order to render grass you
00:02:03
need grass to render the different grass
00:02:05
rendering techniques i mentioned earlier
00:02:08
are what determines what our grass model
00:02:10
will look like let's start with an
00:02:12
example
00:02:13
take a look at this screenshot from
00:02:15
final fantasy 14 specifically the grass
00:02:19
here now if you look closely if you
00:02:21
really really analyze it you may notice
00:02:25
that it's not a 3d model at all and is
00:02:28
actually just an image
00:02:36
that's right this grass is in fact two
00:02:39
triangles that form a quad that samples
00:02:42
a grass texture
00:02:43
this general technique is called
00:02:45
billboarding and billboard grass is the
00:02:48
technique i'll be going over billboard
00:02:51
grass is commonly composed of multiple
00:02:53
intersecting quads for my project i
00:02:56
decided to use three
00:02:58
now with this technique the texture does
00:03:01
all the work so you need a good grass
00:03:03
image texture i decided to paint my own
00:03:06
but i've never made a texture before
00:03:08
like this so please don't laugh at me it
00:03:10
looks like this and when we apply it to
00:03:13
our billboard grass model the grass
00:03:15
really comes together
00:03:17
with our grass model complete we need to
00:03:19
actually place it all over the plane so
00:03:22
to simplify this let's flatten our plane
00:03:24
first it's going to take a lot of grass
00:03:27
to cover this entire plane much more
00:03:30
grass than the cpu can actually handle
00:03:33
so we're going to use a very handy dandy
00:03:35
method called
00:03:39
normally in a given scene the cpu takes
00:03:43
all the object mesh data that exists and
00:03:45
shoves it into this big old funnel that
00:03:47
gets processed by the gpu this mesh data
00:03:51
gets copied from the cpu to the gpu
00:03:54
every single frame
00:03:56
this is the biggest bottleneck of all
00:03:58
graphics programming because
00:03:59
communicating data is really costly we
00:04:03
want to render hundreds of thousands of
00:04:05
grass objects so obviously pushing
00:04:08
millions of vertices into that gpu
00:04:10
funnel is going to clog it up
00:04:12
what if we could conveniently
00:04:15
keep that data on the gpu so we don't
00:04:17
need to copy it over every single frame
00:04:21
we could create this massive buffer of
00:04:23
grass positions and then ask the gpu to
00:04:26
use that buffer to find the proper grass
00:04:28
position each frame rather than having
00:04:31
the cpu copy each individual grass's
00:04:34
position over
00:04:36
this technique is called gpu instancing
00:04:39
and it's perfect for stuff like grass as
00:04:41
it doesn't need to exist on the cpu side
00:04:44
which is the biggest downside of gpu
00:04:46
instancing anything instantiated by the
00:04:49
gpu itself doesn't technically exist as
00:04:53
far as the cpu is aware
00:04:55
before we do any gpu instancing though
00:04:57
we need to calculate some grass
00:04:59
positions we are going to calculate the
00:05:01
positions with a compute shader so that
00:05:04
the positions of the grass will go
00:05:05
directly into the gpu buffer and the cpu
00:05:09
doesn't need to do any copying at all
00:05:12
the positions we are creating will fill
00:05:14
a square space of whatever size we'd
00:05:16
like for example 300 for our 300 by 300
00:05:20
meter plane
00:05:22
we take the thread id of our compute
00:05:24
shader thread which will be in the range
00:05:26
0 to 300 for both axes then we subtract
00:05:30
150 so it centers it over the origin and
00:05:34
that's about it with these positions
00:05:36
generated we can tell the gpu to
00:05:38
instantiate our grass our billboard
00:05:41
grass we made earlier is composed of
00:05:43
three meshes so this will result in
00:05:46
three separate instancing calls taking a
00:05:48
look back at our plane there's now one
00:05:51
grass object every meter which is great
00:05:54
unfortunately it doesn't look quite
00:05:56
dense enough so let's double the amount
00:05:58
of grass objects this is attainable by
00:06:01
multiplying that position we calculated
00:06:03
earlier by 1 over whatever density we
00:06:06
want currently that's two so we multiply
00:06:09
the position by one half
00:06:11
this grass density looks nice to me so i
00:06:14
won't make my gpu suffer any more than
00:06:16
it already is
00:06:18
to put this all into perspective we are
00:06:20
rendering two grass objects per meter
00:06:23
each grass object is three quads which
00:06:26
is 12 vertices and six triangles per
00:06:29
grass object and there's 300 meters
00:06:32
squared this means that we are drawing
00:06:35
um
00:06:37
2 million 160 000 triangles every frame
00:06:41
just for the grass and currently we have
00:06:44
an average frame rate of 500 fps this
00:06:47
would not be possible at all without the
00:06:50
gpu instancing
00:06:54
alright cool we have grass it all looks
00:06:57
the same though and that's boring also
00:07:00
unrealistic but that matters to me less
00:07:03
this is the part of the video where i
00:07:04
remind everyone that i am a technical
00:07:07
artist
00:07:15
[Music]
00:07:19
the first thing i did was add some
00:07:21
variance to the positions of the grass
00:07:23
it's hard to see but it removes the
00:07:25
uniformity of the positions next i added
00:07:28
variance to the height of the grass with
00:07:30
simplex noise so that height variance is
00:07:33
clumped together meaning that higher
00:07:36
grass will be grouped with higher grass
00:07:38
and so on like in real life
00:07:43
that's about all for variants of the
00:07:45
grass model there's not much else to do
00:07:47
and i think it already improves the
00:07:49
visual is a good deal
00:07:52
[Music]
00:07:55
now comes the hard part and that's
00:07:57
animation grass is long and thin thus it
00:08:01
is susceptible to wind and it flows back
00:08:04
and forth with it
00:08:05
obviously we aren't going to simulate
00:08:07
wind we're just going to fake it
00:08:10
animation in this context is going to
00:08:12
happen in the vertex shader this is the
00:08:15
stage of the rendering pipeline that
00:08:17
finalizes vertex positions so i can have
00:08:20
the gpu modify vertices positions of a
00:08:22
model however i please we have a bit of
00:08:26
a problem though and that's that this
00:08:28
grass model only has four vertices to
00:08:30
work with that's not a lot even worse we
00:08:34
don't even have four to work with since
00:08:36
the bottom two remain stationary and
00:08:39
only the top two are gonna move the
00:08:41
basic idea here is that we're going to
00:08:43
skew the grass back and forth to make it
00:08:46
look like it's being blown around
00:08:48
luckily math provides us with
00:08:50
oscillators in the form of trig
00:08:52
functions so by using a mess of trig
00:08:54
values and random numbers we end up with
00:08:57
an animation that looks like so
00:09:12
how cool and calming i'm quite proud of
00:09:15
it wait what's that you actually want to
00:09:18
know how the animation works
00:09:21
okay
00:09:22
to begin we hash the instance id of the
00:09:24
grass object to get a random number
00:09:25
unique to an individual object then if
00:09:27
that hash value is above a certain
00:09:28
threshold we compute the cosine value
00:09:30
with a slightly faster frequency
00:09:31
otherwise we compute the default cosine
00:09:32
value with a parameterized frequency
00:09:34
this frequency on grass that is taller
00:09:36
since taller grass would realistically
00:09:37
take longer to swing back and forth next
00:09:38
we square the cosine wave reduce its
00:09:40
amplitude then subtract from it based on
00:09:41
the id hash to add local variance to the
00:09:43
cosine value and then we reduce the
00:09:45
amplitude even further amplitude in this
00:09:46
context refers to the distance that the
00:09:48
grass object will sway from its original
00:09:49
position finally we add this value to
00:09:51
the local position of our top side
00:09:52
vertices scaled by the hash id for
00:09:53
further localized variants and also the
00:09:55
height variance of the grass since
00:09:56
taller grass will sway further than the
00:09:57
shorter grass this animation obviously
00:09:59
isn't perfect but it is much cheaper
00:10:00
computationally than a full-on physics
00:10:02
simulation of wind also that simulation
00:10:03
isn't even realistically possible since
00:10:04
none of this grass data exists on the
00:10:06
cpu
00:10:09
if you recall from the beginning this
00:10:11
plane that we're rendering the grass on
00:10:13
top of has its vertices displaced the
00:10:16
grass doesn't move with it yet which is
00:10:18
a problem
00:10:20
this involves converting the world space
00:10:22
coordinates of our grass to uv
00:10:24
coordinates such that they can sample
00:10:26
the same height map as the terrain mesh
00:10:28
and now our grass properly displaces
00:10:32
along with the terrain our grass looks
00:10:34
more natural now and is largely complete
00:10:37
as an asset but i had one last idea the
00:10:41
color of our grass is uncomfortably
00:10:43
homogeneous it's boring to look at i
00:10:46
thought about this for a good while and
00:10:48
i came up with several different ideas
00:10:50
most of which i streamed live on twitch
00:10:52
at twitch.tv forward slash acerola
00:10:55
underscore t
00:10:56
link in the description streams on
00:10:58
monday at 5 pm pst
00:11:01
the dilemma though is that i wanted to
00:11:03
visualize both old and young grass the
00:11:06
older grass is more yellow while the
00:11:08
younger grass is more green or in our
00:11:11
case it's closer to the default color of
00:11:14
our texture how do we know though if
00:11:16
grass is old or not
00:11:18
taller grass is older than younger grass
00:11:21
so we can use our height modifier we
00:11:23
were using to cause height variance to
00:11:25
also calculate a new value that will
00:11:27
control how yellow the grass turns
00:11:30
with this value we also use the vertical
00:11:33
uv coordinates to interpolate the
00:11:35
strength of this value as the grass
00:11:38
pixels increase in height which will
00:11:40
cause only the tips of the grass to
00:11:42
become very yellow
00:11:44
with this effect applied i think it all
00:11:46
comes together in terms of visuals so
00:11:49
please let me know what you think about
00:11:50
it
00:11:53
alright
00:11:54
the performance of our grass is already
00:11:56
pretty good and in the real world you
00:11:58
would never instantiate this much grass
00:12:01
across such a large area anyways so the
00:12:03
grass would be even faster in that real
00:12:06
world scenario this is nerd so if
00:12:09
you don't care please skip ahead to the
00:12:11
conclusion uh oh disclaimer i'm about to
00:12:15
talk about something i've explained in
00:12:17
depth in my previous video dynamic
00:12:20
detail in video games please go watch it
00:12:23
now before continuing
00:12:25
wait what's that you don't want to watch
00:12:27
it okay well here's a quick recap
00:12:36
the easiest optimizations to be made
00:12:38
involve level of detail we can cull
00:12:41
grass in the vertex shader in the same
00:12:43
way we call triangles in the geometry
00:12:46
shader in that level of detail video by
00:12:49
checking if a grass vertex is outside
00:12:51
the view of the camera or is a certain
00:12:54
distance from the camera
00:12:56
this improves our performance a great
00:12:58
deal when we are looking away from all
00:13:00
the grass
00:13:03
as well as allowing custom distance
00:13:05
based level of detail cooling as
00:13:07
demonstrated here
00:13:09
another optimization involves reducing
00:13:12
the amount of tessellation that is
00:13:14
happening on our terrain in fact it
00:13:16
doesn't need to be tessellated at all
00:13:18
since the grass completely obscures it
00:13:21
this increases our fps a great deal
00:13:24
the last optimization that i can
00:13:26
confidently talk about is reducing the
00:13:29
amount of instructions in our vertex and
00:13:31
fragment shaders when we are
00:13:33
instantiating this many meshes even just
00:13:36
one instruction can scale considerably
00:13:39
in cost using numbers from earlier we
00:13:42
are rendering two million one hundred
00:13:44
and sixty thousand triangles which is
00:13:47
four million three hundred and twenty
00:13:48
thousand vertices meaning our vertex
00:13:51
shader executes that many times per
00:13:53
frame our fragment shader executes even
00:13:56
more than that by reducing the
00:13:58
complexity of our vertex shader
00:14:00
animation we can improve the performance
00:14:03
of the grass little by little
00:14:07
overall billboard grass is an incredibly
00:14:10
performant and fast option for rendering
00:14:12
foliage there's a reason it shows up
00:14:14
literally everywhere and i promise that
00:14:17
if you look for it you'll see it in
00:14:18
nearly every game that you play
00:14:21
but it has one major problem and that's
00:14:23
if you look down on the grass from above
00:14:26
it looks like
00:14:27
this isn't actually a problem if your
00:14:29
game has a fixed camera angle like age
00:14:32
of empires 4 that's a recent example
00:14:34
that uses this technique i'm pretty sure
00:14:37
but anyways this is a problem that
00:14:40
modern foliage rendering techniques seek
00:14:42
to solve with more geometry at the
00:14:45
expense of performance in my next video
00:14:48
i'll be trying my hand at 3d modeling
00:14:50
some foliage to improve the overall
00:14:52
appearance of this grass and remove this
00:14:55
issue that my billboard grass has the
00:14:58
appearance of the grass could also be
00:15:00
significantly improved with simple
00:15:02
post-processing effects so please look
00:15:04
forward to videos on that as well
00:15:07
if you'd like to check out the code for
00:15:08
this project there's a link in the
00:15:10
description and if you liked this video
00:15:12
i'd appreciate if you subscribed and
00:15:14
left a comment it means a lot but i've
00:15:17
got to go now have a great rest of your
00:15:19
day and i'll see you next time
00:15:51
you