hello everyone and welcome back to the
kohi game engine series
today we're going to talk about the
vulcan graphics pipeline
before we jump in i'd like to take a
minute and
thank the supporters of the channel
starting with the partners who are a
arcelia
wen shang and kaden the partner is the
highest tier of membership either via
youtube or the patreon page so thank you
very much
for your support i would also like to
thank everybody
else that is listed here on this screen
for your support it is
greatly appreciated if you wish to
support my work you can either join
via youtube memberships there is a link
in the description below
to that or you can take a look at my
patreon page which is at patreon.com
forward slash travis roman all right
so the vulcan graphics pipeline
basically uh in this video we are going
to talk about
what the vulcan graphics pipeline is and
what shaders are
and how they work from a high level and
then we will go ahead
and look at what we need to do to get
set up to actually compile shaders
the vulcan graphics pipeline is a
sequence of operations
that take vertex data such as positions
or textures
and eventually output them as pixels on
the screen
now i have a sample here on the screen
of what some of that vertex data may
look like
so we have things like the xyz position
we have texture coordinates we have
normals we have
colors and all these things i'll get
into as we actually implement them but i
wanted to give you guys
some sort of an idea of what vertex data
might actually contain right so it can
contain
any number of things and each individual
vertex
ultimately represents at least a point
in space a three-dimensional point in
space but it can represent other things
as well and these are some of those
things that i can represent
the vulcan graphics pipeline has
multiple stages
and this is what that pipeline looks
like from a very high level
this is a simplified view of course but
this is a good place to start in my
opinion
what we have here is our flow from left
to right
and our vertex index buffers that's
basically the data that's going to come
in
obviously we'll get more into detail in
that as we start to
implement those things and after that we
have something called the input
assembler now the input assembler
is basically a process that collects the
raw vertex data from the buffers that
you specify
uh so the index and vertex buffers
and sometimes the index buffer
um is used sometimes an index buffer is
not used and that's basically
uh to allow us to reuse the same vertex
data over and over again
and again that is something i'll get
into in a bit but basically
what this does is it takes all that data
that is passed in
to the shader and sort of splits it up
and and
sets it up in a way where we can
actually use it in
the shader program and i'll get more in
to that in just a second as to what that
is so the vertex index buffers
are our raw data coming in and the input
assembler
takes that raw data and basically
sets it up in a format that is usable
for us in the next stages
so the next stage would be the vertex
shader
and that is basically run for every
vertex
and generally apply some sort of
transformation to turn vertex positions
from your model
space to screen space and it also
passes like per vertex data down the
pipeline so
that positional data and some of the
other items that i had shown you on the
previous
screen are some of the things that get
passed in this stage
there's then something called the
tessellation shader
which allows you to subdivide geometry
based on certain rules
to increase the mesh quality so a couple
of examples of this might be
in a terrain engine as you get closer to
various bits of the terrain you might
want to add more geometry
to smooth out curves and things of that
nature so that it doesn't look as
blocky or as you know you don't have
huge planes
showing up right it's not as obvious as
to what it's made out of
another example of this might be
some sort of rounded surface where as
you get closer to it you might
see the faces of that surface
unless you actually have some
tessellation happening
where there are more faces added to
those
rounded areas thus making them look more
round versus
octagonal for example so after that
there is a geometry shader which is run
on every primitive and a primitive is
anything from a triangle to a line to a
point
and we will cover primitives once we
actually get implementing those things
but
basically it can either discard or
output more primitives than actually
came in so
we can actually use this to generate
geometry on the fly so
the probably the widest use case for
this will be some of our debugging
utilities that we're going to be
building
we'll use the geometry shader to
generate some geometry for us so that we
don't actually have to pass in vertex
data in some scenarios
so after that comes the rasterization
stage and this basically approximates
the primitives that are
handed to it into fragments and these
fragments ultimately become
the pixel elements that fill the frame
buffer so the frame buffer if you recall
is the render target
and ultimately in most cases that
becomes pixels on the screen
and i say most cases because sometimes
we might render to a texture or
something along those lines and
in that case maybe that's not rendered
directly to the screen
so another important bit about the
rasterization stage
is any fragments that fall outside of
the screen space or the
the frame buffer space are discarded
and the attributes outputted by the
vertex shader
are interpolated across fragments so
what that basically means is let's say
for example that you have one vertex
that's red
and then you have one vertex that's
maybe a full unit away that's green
what's going to happen is as it fills in
the geometry from one vertex to the next
it's actually going to interpolate from
red to green
and you guys will actually see some
examples of that once we actually have
something up and run
one other item that's important to point
out about the rasterization stage
is typically fragments that are behind
other primitive fragments
are discarded because of depth testing
so what that basically means
is if you have an object that is in
front of another object in 3d space
and that object is not transparent in
any way
then any object behind it is not then
rendered
those pixels or fragments would then be
discarded and not actually used at that
point
and there are some exceptions to this
that we'll have to get into
but for now just take that at face value
and we will discuss those when we get to
that point
so the next stage here is the fragment
shader
and this is called for every fragment
that survives along the process that
isn't discarded
and determines which frame buffer or
frame buffers
the fragments are written to and it also
at this point determines what the color
of those fragments are
and what the depth values are this can
be used later on
when doing post-processing type effects
or
things of that nature we can use all of
this data for things like that
and again we'll touch on that when we
actually get to that part but just
understand at face value for now that's
basically what it does
so basically the way that it determines
color information is as i said before
where we have that vertex data it
interpolates from one vertex to the next
and in the color example it will
interpolate between
red and green finally we have the color
blending stage
and this applies various operations to
mix different fragments
that map to the same pixel in the frame
buffer
and what this basically means is we can
have
more than one frame buffer that gets
basically blended
together and this is essentially how
transparency for example is handled
where we would draw a transparent object
and then we would draw a non-transparent
object
behind that object and then blend the
two together
to actually get that final what the
final color of that particular
fragment or pixel should be and so this
color blending stage is the stage that
handles
that so if we don't have any
transparency obviously this is a pretty
straightforward step it just basically
outputs the same data
but i don't know of any game engine out
there that doesn't
have some sort of transparency so this
will definitely be something that
we will need to look into now you may be
asking
what the difference is between these
green colored
stages and the yellow colored stages
and that difference is simple the green
stages are basically
what is known as fixed function stages
they have
predetermined functionality the only
thing that we can do
is we can feed them some parameters but
otherwise how they work is predetermined
and not adjustable
the yellow stages are stages that
are programmable and what i mean by
programmable
is we pass little programs to the
graphics card for each one of these
stages here and those programs take an
input
and provide an output so for example
the fragment shader takes in data from
the rasterization stage
and outputs color information and the
way that this is done
is programmatic so we literally
write a program that gets handed to
this particular stage to execute and
these programs are called shaders now
you'll notice
on all of these yellow ones we have
actually called all of these shaders
except tessellation because
it was too big to fit here but this is
also a
shader that's available right so all of
these are programmable
via programs that are called shaders
okay and ultimately this gets output to
a frame buffer
which then at some point will get output
either to
the screen or be used somewhere else in
the scene
as a rendered texture like for a camera
something like that
and that is basically the overall
graphics pipeline
stage from beginning to end
there are other pipeline stages
available that i haven't covered yet
there is a a compute pipeline but that's
not something i'm going to cover here
we'll cover that later in the series if
and when we need it
okay so i mentioned shaders
but vulcan actually doesn't use shaders
per se
it uses something called shader modules
so there is a shader module for each
individual
stage as i mentioned before so there's
fragment
there is vertex tessellation geometry
etc
and the shader module is actually the
compiled
program that is fed to the graphics card
now i said compiled program what exactly
does this mean
so basically the inde there are two
industry standard
shading languages and and there are
technically more than this but these are
the two major ones
there are two languages known as glsl
which is gl
shading language and hlsl which is
high level shading language glsl as you
might have guessed is used with opengl
and hlsl is used with directx
so ultimately they are two very similar
languages but different
and they perform very similar tasks
and ultimately get fed in through the
appropriate graphics api to the graphics
card driver which then hands them off
to be processed by the actual gpu i'm
not going to go into the differences
between these two
just know that we can technically use
either one
for any of you that have used opengl
typically the way that this works
is you have a file that you read in
that contains your glsl code
and then you just compile that during
runtime and
feed it to the opengl api
during runtime vulkan does not work this
way vulcan handles this a little bit
differently
so as you can see here i've got this
third thing at the bottom called
spur v so basically spur is an industry
open standard intermediate language
right it actually stands for standard
portable
intermediate representation and what
this basically is
is it's byte code similar to what you
might get from
java or c sharp or one of those types of
languages
where you compile it ahead of time and
then that same byte code
is fed into the driver no matter what
platform you're on so you don't have to
worry about
platform specific things in terms of how
that data gets read in you don't have to
worry about
bigger little endian any of that stuff
you
basically compile either glsl or hlsl
through a program that outputs spur v
and we are actually going to
get into what that program is and how to
set that up in a moment but it doesn't
matter
whether you use glsl or hlsl ultimately
either one of those will get compiled
into spurvi which
is what we are actually going to read in
our engine
with all of that said we are actually
going to be utilizing glsl
in this series because it's most what
i'm it's what i'm most familiar with but
if you guys are more familiar with hlsl
you can certainly do that as well
okay so now for a little bit more
detail so the vertex shader converts
each incoming vertex to clip or frame
buffer coordinates
and you can see that i've got that down
here so what this might look like in a
typical scenario if we're not rendering
to a texture or something like that
let's say we're just
we're rendering essentially to the
screen quote unquote
we will have a frame buffer that is the
resolution of the screen in this case
it's 1080p right
by 1080 and so our frame buffer
coordinates is
essentially the size of what that
texture is that we're going to write out
to
right and so the vertex shader takes
those three-dimensional or
two-dimensional
vertex pieces and
converts them into frame buffer
coordinates so basically
essentially flattens them via our
transformations that we provide
in our matrices and we're going to see
all these things come together
eventually but i just i'm sort of
wanting to give the high level
of this now by the time this is all
output
it is output to something called
normalized device coordinates which is
what the actual graphics card driver
and graphics card itself knows how to
deal with and that is typically a
negative one
to one range where zero zero is in the
center of the screen
so just understand that there are
certain cases where we
we will be working in frame buffer
coordinates and there will be certain
cases where we're working in
normalized device coordinates so
remember that there is a difference
between those two
i will cover that difference again when
we get there just wanted to point that
out for now
so as i mentioned before the fragment
shader interpolates between
vertices using past data so that could
be things like color or texture
and then outputs color and depth data to
one or more frame buffers
okay so with all that explanation in
mind
there are a couple of things that i want
to do in this video before we actually
kick off
the development of our pipeline and
these are just a couple of small things
in terms of tooling
that i want to set up now so that we
don't have to worry about this in the
future
so when i mentioned spur v there is
actually a
program that compiles that for us that
vulcan provides with
the sdk
so if we change directory to the vulcan
sdk directory
right and we have a look in there there
is a bin
folder so if we change directory into
that bin folder
and we list that out
we have a glslc exe
right here and this is actually the
program that we want to call
to compile our shader code and this will
be used with
our shader modules that i talked about
earlier
and so i want to sort of get a pipeline
set setup
to call this with the necessary
parameters
so that we can go ahead and reference
this when needed and we don't have to
come back and worry about it later
so what i'm going to be setting up is
something that i'm going to call
the post build process and i'm actually
going to set this up for both windows
and linux at the same time so to do this
i'm going to go ahead and right click
here and create a new file
and i'm going to call that postbuild.bat
and
what this is basically going to do is
it's going to do two things for us it's
going to compile our shaders
and then it is going to copy
a assets folder that we're going to
create
and it's going to take all the content
from that assets folder and put it into
our bin folder so that our engine can
access it
we don't currently have that now so i'm
actually going to go ahead and
create that first so again in the
root we're going to create a new folder
this time and we're going to call this
assets and then within that
we are going to create a new file
and we're just going to call it dot get
keep and this is just so that
the assets folder makes it into the get
repository
okay and this is where our assets are
going to live so
whenever we have textures or models or
anything that we need to import into the
engine
this is where those things are going to
let live and then
our post build process is going to take
the files
that are in the assets folder and it is
going to copy it into the bin folder
as an assets subfolder in here alongside
our executable so that we can
actually work with them during runtime
okay so our post build process is going
to take care of both of those things for
us
so the very first thing that we are
going to do is we are going to start off
our batch file with an echo off
so that we don't echo all this stuff
because it's not necessary
and we're going to say if it does not
exist
we are going to create in the bin folder
an assets and shaders directory now
if this does not exist it'll create it
otherwise it'll just sort of move on
right and then the next bit as i
mentioned
is we are going to compile shaders
and we are going to compile our shaders
using
that glslc.exe program
so the way that we navigate to that is
we have our vulcan sdk
environment variable that gives us the
leading path
and then it's under the bin folder and
then it's called glslc
exe and to this we actually need to
pass some arguments however before i
jump into that i do want to go ahead and
just sort of
run this from the command line so that
you guys can
see what some of the arguments are so if
we do
jlslc double dash help
it'll actually give us a listing of all
the available
flags that we can pass into this there's
quite a lot of them
right so the one that we are concerned
with
at least for right now is let me find it
f shader stage okay and we basically say
f
dash f shader dash stage equals and then
the name of the stage
and uh this treats the subsequent input
files as having the stage
and if you recall the different stages
that we um have
are vertex fragment
tessellation um geometry
and compute okay and uh you'll notice
here that there are sort of these longer
versions and shorthand versions
we're going to be using the shorthand
for each one of these so vertex is say
the same as vert
fragment is the same as frag so we're
just going to use written frag for right
now
okay so uh the format that this is
actually looking for
is uh it basically takes in uh the
options which are here
and then uh the file right and then
we're also going to want to include uh
it's in here somewhere the
dash o which is the output file okay and
then
of course a path to that so with all
that in mind
what we are going to do is the first
thing that we are going to do is we are
going to build a shader
for our vertex stage right so we have f
shader stage equals vert
and then after that we need to point to
a glsl file so in this case
we are going to point to something in
assets shader's built-in dot object
shader
right and this is the name of the shader
this is what i'm calling it this is my
naming
scheme you guys don't have to follow
this if you don't want you can name it
anything that you want
but this is what i'm going to stick with
however there
is a portion of this that our pipeline
is going to rely on and that is this
dot vert dot glsl so the dot vert tells
us what type of shader it is and then
the glsl obviously um is the type of
file
and so this portion of it is somewhat
important
but this could be anything that you want
so with that that is our input file
and then obviously we need to tell it uh
what our output file will be
so we use dash o and then we have here
bin
assets shaders and then again built-in
dot object shader.vert.spv so this spv
is what contains that uh it's the file
that contains the compiled byte code
of the compiled shader okay and so
uh we are hard coding the name of the
shader here and this is because at least
initially we're only going to have
just sort of this one built-in object
shader
and so there's not really much need to
build
something that's a little bit more fancy
than this i'd rather keep it simple for
now and then
when the need arises later on we can
extend this into
something that's a little bit more fancy
but for now let's go ahead and keep it
simple right
so the other thing that i want to do is
i like to
in cases like this when i'm running
stuff with
echo off i actually like to echo a
special
message of my own to say uh what were
what we're actually doing so that way as
this script is running you can tell what
it's doing
so in this case i'm just saying uh uh
the original name of the file and then
i've got this sort of little arrow that
i'm outputting here and then
the target name of the file okay and i'm
just outputting that
okay so we have that
we have something for the vertex stage
we also need one for the fragment stage
and again we haven't created these files
yet but we will come back to this
um so this is exactly the same
name and location and all of that the
only difference is the stage is frag for
fragment
the name of the file is frag.js glsl
here
and frag.spv here and of course that's
reflected in the echo as well
now should there be problems
with either of these compiling we don't
necessarily want to continue with the
script right we want to sort of echo out
what that problem is
and kill the script so i actually have a
line that i'm going to put in here
after this first shader which is if the
error level
output by this program is not zero
meaning it wasn't successful
uh then we are going to echo the error
the error level
and we are going to bomb the script
basically and we're going to do that
after
both the vertex and the fragment shader
compilation stages
okay and so
once we are done with that then we
are ready to go ahead and move on to our
asset copy
and our asset copy is actually going to
be
pretty straightforward um at least i
think if you've ever used xcopy before
this should look
pretty familiar to you what we're
dealing with here is we're saying
uh we're copying the assets and then
we're copying everything
from the assets directory to the bin
assets directory
and this is recursive and this
is a sequence of
switches that are available for
xcopy if you're interested in that feel
free to look them up
i don't remember what all these are off
the top my head this is just what i've
been using for years
um and so this handles recursion it
handles uh
error checking it handles overwriting
files things of that nature so
we'll want to go ahead and perform this
x copy right so this is a line that just
says what we're doing
which is here and then this is a line
that actually executes it okay
and so that will grab all of our assets
and copy them over and then of course we
will simply say
done at the end of the script and that
is all there is to it right it's a
pretty straightforward script
this will get a little bit complicated
once we actually start adding more
shaders
but for now since we haven't even added
our first shader into this pipeline yet
i'm not too worried about it okay so
this is the basic
this is the basis of our post build
process now
we have a postbuild.bat we should also
have a post build
dot sh for linux so i'm going to go
ahead and create that
[Music]
okay and this time i'm just going to
paste the code right
and this basically does the exact same
thing it just makes sure that the
folders exist
and then it goes ahead and compiles the
vertex shader the fragment shader and
then
goes ahead and copies our assets for us
and says done okay so this is pretty
much the exact same thing
as the windows equivalent great
now that we have that hooked up one last
thing that we need to do
is we need to go ahead and hook this up
in our tasks
okay so if we go into our tasks listing
we have
our build engine build testbed build
everything and then clean everything
and uh we are just going to add a new
entry here
to the end of this list so right
here called post build and all this does
is for windows it calls the batch file
for linux it calls
the sh file and
we're simply just saying that the
current working directory is the
workspace folder
okay and so now if we go ahead and press
f1
we can go ahead and run a task and we
can say that we want to run post build
and when we do that we actually see that
our error catching is working so
it says here compiling shaders says
which one we're compiling
and then it says it cannot open the
input file because it doesn't exist
which is exactly what we expect
because we haven't created it yet okay
so
that is basically preparing us
to move forward to the next part of this
where we're actually going to start
building out
the pipeline so building up the pipeline
is going to be probably a pretty long
video so
i'm going to split this here and next
time we are actually going to start
coding out the pipeline and
i should warn that uh you know this is
another
unfortunate um instance of it's gonna
take a long time to get through this
uh not as long as it's taken us to get
to this point so far but
uh there is a lot to consider here with
the compilation of shaders loading up
those shaders
the graphics pipeline and tying that
into our current system
and ultimately getting something to
show up and render on the screen so um
just be prepared for that
uh and next time we will go ahead and
get started on that so
anyway thank you guys so much for
watching i really appreciate you guys i
hope you enjoy this series so far i hope
you're learning from it
if you like the video please toss it a
thumbs up
if you haven't already consider
subscribing hit the little bell
notification there to
get notified as to when the next video
in this or other series drops
and i will see you guys in the next
video
[Music]