- [Instructor] Previously, we looked at
building a particle system.
In this video, we'll learn about programmable pipeline
and shaders.
We will then code a fragment and a vortex shader.
We will also add shaders in our game and load them.
Finally, we will update and draw a shader in each frame.
Open Graphics Library, OpenGL, is a programming library
that handles 2D and 3D graphics.
OpenGL works on all major desktop operating systems.
And there's also a version, OpenGL ES,
that works on mobile devices.
OpenGL was originally released in 1992.
It has been refined and improved over more than 20 years.
Furthermore, graphics cards manufacturers
design their hardware to make it work well with OpenGL.
The point of telling you this is not for the history lesson,
but to explain that it would be a fools errand
to try and improve upon OpenGL,
and using it in 2D and 3D games on the desktop,
especially if you want your game
to run on more than just windows, is the obvious choice.
We are already using OpenGL because SFML uses OpenGL.
Shaders are programs that run on the GPU itself,
so let's find out more about them next.
The programmable pipeline in shaders through OpenGL
we have access to what is called a programmable pipeline.
We can send our graphics off to be drawn, each frame,
with the RenderWindow draw function.
We can also write code that runs on the GPU,
which is capable of manipulating
each and every pixel independently, after the call to draw.
This is a very powerful feature.
This extra code that runs on the GPU
is called a shader program.
We can write code to manipulate the geometry
position of our graphics in what is called a Vertex shader.
We can also write code that manipulates the appearance
of each and every pixel individually,
in code called a Fragment shader.
Although we will not be exploring shaders
in any great depth,
we will write some shader code using GLSL.
And we'll get a glimpse of the possibilities offered.
In OpenGL, everything is a point, a line, or a triangle.
In addition, we can attach colors and textures
to this basic geometry.
And we can also combine these elements
to make the complex graphics that we see
in today's modern games.
These are collectively known as primitives.
We have access to OpenGL primitives
through the SFML primitives and vertex array,
as well as the sprite and shape classes we have seen.
In addition to primitives, OpenGL uses matrices.
Matrices are a method and structure
for performing arithmetic.
This arithmetic can range
from extremely simple high school level calculations,
to move, translate a coordinate, or it can be quite complex,
to perform more advanced mathematics,
such as converting our game world coordinates
into OpenGL screen coordinates that the GPU can use.
Fortunately, it is this complexity
that SFML handles for us behind the scenes.
SFML also allows us to handle OpenGL directly.
If you want to find out more about OpenGL,
you can get started here,
learnopengl.com.
If you want to use OpenGL directly alongside SFML,
you can read the article at this link.
An application can have many shaders.
We can then attach different shaders
to different game objects to create the desired effects.
We will only have one vertex
and one framentshader in this game.
We will apply it, each frame, to the background.
However, when you see how to attach a shader
to a draw call, it will be plain that it is trivial
to add more shaders.
We will follow these steps.
First of all, we need the code for the shader
that will be executed on the GPU.
Then we need to compile that code.
Finally, we need to attach the shader
to the appropriate draw call
in the draw function of our game engine.
GLSL is a language in its own right.
And it also has its own types,
and variables of those types can be declared and utilized.
Furthermore, we can interact
with the shader programs variables from our C++ code.
As we will see, GLSL has some syntax similarities to C++.
Coding a fragment shader.
Here is the code from the rippleShader.frag file
in the shaders folder.
You don't need to code this,
as it is available in the assets we added back
while learning abstraction and code management.
The first four lines, excluding comments,
are the variables that the fragment shader will use.
However, they are not ordinary variables.
The first type we see varying.
These are variables which are in scope between both shaders.
Next, we have the uniform variables.
These variables can be manipulated
directly from our C++ code.
We will see how we do this soon.
In addition to the varying and uniform types,
each of the variables also has a more conventional type,
which defines the actual data.
Vec4 is a vector with four values.
Vec2 is a vector with two values.
Sampler2D will hold a texture float
is just like a float in C++.
The code inside the main function
is what is actually executed.
If you look closely at the code in main,
you will see each of the variables in use.
Exactly what this code does
is beyond the scope of this course.
In summary, however, the texture coordinates, vTexCoord,
and the color of the pixels, fragments gl_FragColor,
are manipulated by a number
of mathematical functions and operations.
Remember that this executes for each and every pixel
involved in the draw call
on each and every frame of our game.
Furthermore, be aware that uTime
is passed in as a different value each and every frame.
The result, as we will soon see, will be a rippling effect.
Now let's code for a vertical shader.
Here is the code from the vertShader.vert file.
You don't need to code this,
as it was in the assets we added
back in abstraction and code management section.
First of all, notice the two varying variables.
These are the very same variables
that we manipulated back in the fragment shader.
In the main function,
the code manipulates the position of each and every vertex.
How the code works is beyond the scope of this book.
But there is some quite in depth math
going on behind the scenes, and if it interests you,
then exploring GLSL will be fascinating.
Now we have two shaders, one fragment and one vertex.
We can use them in our game.
Adding shaders to the engine class.
First you have to open the Engine.h file.
Add this code, which adds an SFML shader instance
called m_RippleShader to the engine class.
We will add this instance after texture here.
The engine object and all its functions
now have access to m_RippleShader.
Note that an SFML shader object
will be comprised of both shader code files.
Open the Engine.cpp file
and add this code to the constructor.
Loading the shaders.
Add some code which checks
whether the players GPU can handle shaders.
The game will quit if it can't.
Next we will add an else clause
that actually loads the shaders,
if the system can handle them.
Now we are nearly ready to see our ripple effect in action.
Updating and drawing the shader in each frame.
Open the Draw.cpp file.
As we discussed when we coded the shaders,
we will update the uTime variable,
directly from our C++ code, each frame.
We do so with the uniform function.
Add the highlighted code
to update the shaders uTime variable.
And change the call draw for m_BackgroundSprite
in each of the possible drawing scenarios.
We have to also change the call here.
Include the lines of code being written here.
It would be best to actually delete the lines of code
that I have shown commented out.
I just did it this way to make it clear
which lines of code are being replaced.
Run the game
and you get an eerie kind of molten rock effect.
Experience with changing the background image
if you want to have some fun.
That's it.
Our third and final game is done.
In this video, we have learned about OpenGL,
shaders, and GLSL.
In the grand finale, we explored the concepts
of particle systems in shaders.
Although we looked at probably
the simplest possible case for each,
we still managed to create a simple explosion
and an eerie molten rock effect.
This was really a big journey.
And, hopefully, you have a good insight
in how to build games in C++.
I wish you all the best, keep gaming.
(typing)