Graphics Pipeline Overview - Vulkan Game Engine Tutorial 02

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so how does a computer draw well we can break down the difficult problem of trying to have a computer draw anything into just having it draw a series of triangles and being clever about how we color them of course this is a bit of an oversimplification but not by as much as you may think so let's start by solving how to draw a single triangle a triangle can be defined by three vertices one for each corner in a simple two dimensional system the position of each vertex can be represented with two numbers an x and a y-coordinate so we have our six values as an input and as an output we want an image that represents our triangle therefore all we need to do is find out what pixels are mostly contained by our triangle and set each one to a color this is exactly what the graphics pipeline in vulcan solves for us the graphics pipeline is a linear sequence of stages where each stage takes as input some data performs some operations on it and outputs the transform data as input into the next stage we can think of our graphics pipeline like a factory assembly line and at each stage our raw data is refined into something closer to resembling our final image the first stage is the input assembler which takes as input a list of numbers and groups them together into geometry for our triangle example the first six numbers may be used to form the first triangle and the next six a second triangle and so on next the vertex shader processes each vertex individually and performs transformations such as rotations and translations the rasterization stage breaks up our geometry into fragments for each pixel our triangle overlaps the fragment shader processes each fragment individually and outputs values such as the color by using interpolated data from things like textures normals and lighting and finally the color blending stage applies operations to mix the values from multiple fragments that correspond to the same pixel in the final image this is a simplified overview of the graphics pipeline and don't worry if you're having trouble understanding what each stage does right now as we'll go into much more detail in the coming tutorials this is just to give you a high level overview of how things work so that you're able to keep the bigger picture in mind there are two types of stages fixed function and programmable the input assembler rasterization and color blending stages are all fixed function as programmers we have less control over what operations these stages perform we can configure each stage by setting variables that modify the stage's behavior for the programmable stages we have the ability to upload our own code to be executed by the gpu these little programs that live on the gpu are called shaders and can be written in a c like language called glsl in this video we will code a basic vertex and fragment shader to draw our first triangle note that in this diagram i am not showing the tessellation or geometry shader stages these stages are programmable and occur between the vertex shader and the rasterization stage but for our purposes we do not need to use this functionality anytime soon now one thing you may be wondering is why is all of this necessary wouldn't it be simpler to code a program in c plus that draws a triangle for us and you'd be right it would be simpler but it would be much much slower what gpus are specifically made for is doing parallel computing so unlike a cpu which would have to process one vertex at a time modern gpus are capable of processing thousands of vertices concurrently even top of the line multi-core cpus cannot come close to that processing power but for all this power there's a trade-off when programming for gpus we are much more limited in what we can do and we can't write code for them the same way we would right for a cpu since the hardware is fundamentally different and that's why we need to work within the constructs of the predefined graphics pipeline so now let's get started we're going to create our first vertex shader and fragment shader i'm going to create a folder called shaders and in this folder add a file simple underscore shader.vert the first thing we need to do is specify which version of glsl we are using we do this with hashtag version 450. this corresponds to glsl version 4.5 next we need a main function this main function is going to be executed once for each vertex we have so thinking back to our graphics pipeline as input our vertex shader will get each vertex from the input assembler stage and then needs to output a position rather than doing this in the return value of our main function there's a special variable called gl underscore position that we assign a value to act as our output notice the capital p here as well that can be a common mistake this is a four dimensional vector that maps to our output frame buffer image the top left corner corresponds to negative one negative 1 and the bottom right to 1 1 so this means that the center is at 0 0 if you have used opengl before take notice that the sign of the y coordinates is flipped next let's hard code vertices for our triangle vect2 is a built-in type in the glsl lang that contains two floating point values we're going to initialize a positions array of length 3 for each corner of the triangle use whatever values you would like that are in the range negative one to one we need to add in three vec twos to initialize this properly i'll be using zero zero negative zero point five let's copy this and do two more our second vertex will be 0.5 0.5 and our final vertex will be negative 0.5 0.5 these six values will end up looking something like this hard-coding your values in a vertex shader isn't something you typically do usually we pass in our values using a vertex buffer but for now it's all right finally let's assign gl position a value it's a vect4 and our x and y comes from our positions array and we can index into it with the built-in gl underscore vertex index variable which contains the index of the current vertex for each time our main function is run the third component is the z value which ranges from zero to one and if you're familiar with digital art programs this is kind of like having a layer with z equals zero being the front most layer now the reason for a fourth component is that in subsequent graphics pipeline stages the gl position value is turned into a normalized device coordinate by dividing the whole vector list by its last component so in this case we divide everything by 1.0 i.e nothing changes for now but i'll cover why this can be useful at a later time so now let's create a simple underscore shader.frag file for our fragment shader same as before we start with hashtag version 450 and also have a void main function unlike the vertex shader there isn't a built-in output variable so we need to declare it ourselves we do so with layout bracket location equals zero closing bracket out vect4 and then our variable name which in this case we'll just call something like out color okay let's break this down first we have the layout qualifier which takes a location value based on how we set up the graphics pipeline a fragment shader is capable of outputting to multiple different locations for now we're only using location zero next we specify that this variable is to be used as an output with the out qualifier and finally we declare the variable's name and what type it is then in our main function we need to assign this variable a value so out color equals we're using a four component vector with each component being the red green blue and alpha channels in that order the value of each component needs to be in the zero to one range so in this case i have red at max value no green no blue and alpha at one to be fully opaque now don't get confused and think this means we're painting the entire image red the fragment shader runs on a per fragment basis which are determined by what pixels are geometry mostly contains during the rasterization stage similar to how c plus code needs to be compiled before being executed we need to compile our shader code into an intermediate binary format known as standard portable intermediate representation 5. you've actually already downloaded this compiler when you downloaded the vulkan sdk how you will do this will differ based on if you're working on windows linux or mac for mac os and linux navigate to where you installed the vulkan sdk and locate the file glslc and copy the file path to this program for linux it will likely be in the subdirectory of vulkan version number slash x8664 bin and on mac os it will likely be in the vulkan sdk mac os bin folder okay so once you've copied the file path to the glsl compiler create a new bash scripting file compile.sh in your main directory then paste the path you just copied make sure you have glslc at the end of your path the first argument is the file path to our shader so shaders slash simple underscore shader dot vert then dash o to specify our compiled output file's name in this case we use the same name and then add spv file extension at the end then copy this entire line and change the dot vert file extension to dot frag save this file and then in terminal in your main directory so same directory as your compile script run chmod plus x compile.sh this modifies the compile script to be executable then finally run the script and now in your shaders directory you should be able to see compiled shader code on a mac you may get an error that this program cannot be trusted the easiest way i found to get the compiler working is to install it separately using homebrew homebrew can be found at the following link after installing it run brew install gl slang use the command where gl slc to get the path to the compiler copy and use this path instead for windows open file explorer and navigate to the directory you installed the vulkan sdk then open the folder for your version number then bin 32 and locate the glslc file if you hold shift and right click you should see an option to copy as path once you have the file path copied create a new file compile.bat in your main directory then paste the path you just copied and removed the quotations the first argument is the file path to our shader so shaders backslash simple underscore shader.vert then dash o to specify our compiled output's file name in this case use the same name and add the dot spv file extension then copy this entire line and change the dot vert file extension to dot frag then add pause as the final line so we will be able to view the output after running save this file and then in your file explorer double-click your compile.bat file in your shader folder you should now see two more files with the dot spv extensions this is our compiled shader code so now that we have compiled shader code we need to read these files into our program we're going to start by creating a new header file lve underscore pipeline dot hpp add a header guard and your namespace we're going to have a new class lv pipeline and in this class a public constructor and it's going to take a const string ref to our vertex file path similarly copy this and paste it because we'll have a second parameter which goes to our fragment file path and also don't forget to include string next declare two private functions the first is going to be a static function that returns a vector of characters called read file and this is going to take a con string ref to a file path like in our constructors we also need to include vector and then our second function is going to be a helper function void create graphics pipeline and it's going to take the exact same arguments as our constructor all right now let's add an implementation for our pipeline add a new file lve underscore pipeline dot cpp include our pipeline header and add your namespace now in your header file grab the read file function signature and paste that in remove static and add your class name before your function name okay so to read our shader files we're going to use an input file stream object so from the standard library include fstream then let's initialize a new input file stream variable so std if stream file then put your file path as the first argument and as the second argument we are going to put some bit flags std ios 8 and std ios binary the first bit flag 8 means that when the files open we seek to the end immediately this makes getting the size a bit more convenient and we read it in as a binary to avoid any unwanted text transformations from occurring next let's check that the file was successfully opened so if not file dot is open then in this case if we fail to open the file we'll just throw an error include std accept and then throw a runtime error failed to open file plus file path if you're getting this error it's most likely because your file path is incorrect or you don't have permissions to open that file so now we need the size of the file so size t file size equals static cast size t file dot tell g so because of the 8-bit flag we're already at the end of the file so when we use tel g we get the last position which is the file size next create a vector of characters this is our character buffer and we're going to initialize with the size of the file the next thing is to seek to the start of the file because now we actually want to read the data and to read this data into our buffer.data and the number of bytes we read is equal to file size now close your file with file.close and return your buffer so that's going to be our read file function next let's implement our create graphics pipeline similarly let's go back to our header and grab our create graphics pipeline function signature add your class name so now inside this function all we're going to do is read in our vertex and fragment code we're not going to do anything with it until the next tutorial but just to make sure our read file function is working correctly let's output the size of our vertex shader code to our console duplicate this line and do the same for fragment shader and i need to also include i o stream now the last function to implement is our constructor go back to our header to grab our function signature and paste it add lve pipeline and all this is going to do is call our create graphics pipeline helper function and pass through our parameters now finally let's instantiate an instance of our lve pipeline so back in our first app header under window well first include lve pipeline.hpp then under window lve pipeline lve pipeline and pass the file paths to our vertex and fragment shaders so this file path is relative to wherever our executable file ends up so when i make my code in my main directory is where a dot ends up so our file path will be shader slash simple underscore shader dot vert and we can copy this and change the file extension to frag so a common mistake don't forget we want to read in our compiled code not our original code so make sure to add the dot spv file extension to the file paths so save and compile and in our console we can see vertex shader code size 1164 characters which sounds about right and fragment shader code size characters perfect okay so i think this is where we're going to leave things for now we're about halfway to seeing our first triangle on screen thank you for watching if you're enjoying this series so far please like and subscribe it really means a lot bye for now
Info
Channel: Brendan Galea
Views: 32,079
Rating: 4.9739537 out of 5
Keywords: Vulkan, vulkan api, vulkan tutorial, 3d game engine, coding tutorial, vulkan coding, vulkan graphics, 3d graphics, programming, gpu, vulkan programming, vulkan game engine, vulkan game engine tutorial, vulkan engine tutorial, learn vulkan, how to code vulkan, Vulkan beginner, graphics beginner, vulkan noob, vulkan from scratch
Id: _riranMmtvI
Channel Id: undefined
Length: 20min 24sec (1224 seconds)
Published: Fri Nov 27 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.