Page 1 of 1

Shader Tutorial thread, let's learn shaders C3D style.

Posted: Sun Mar 12, 2017 2:31 am
by GaborD
In this thread I will build a C3D shader step by step, starting with very simple things and then adding more as we go. Hopefully it helps people coming from non-shader engines to make own shaders.
That said, C3D does not require you to write shaders, the engine comes with a versatile PBR shader from the getgo.
It is fun though to write your own, so let't get started :)


First, the absolute basics.
Let's look at what a very minimal GLSL shader looks like for C3D. We are going to be talking about simple shader setups here. Also, please keep in mind I am not an expert, some of this may be very very wrong, please feel free to correct it if it is.


A shader needs two parts, a vertex shader and a pixel (or fragment) shader.

Think of them as functions that get called when rendering.

The vertex shader function will be called once for each vertex in your drawn geometry.
It's purpose is to
a) write out the correct vertex position in camera perspective space (where the vertex will actually be on the screen after the camera perspective is applied)
b) Feed the necessary information to the pixel shader for it to be able to do it's thing.

The pixel shader function will be called for every drawn pixel on the screen.
This means it will be called a lot of times for every rendered frame. At higher resolutions we are talking millions of calls each frame. So better optimize it well. :)

The purpose of each pixel shader instance is to
a) calculate one single color that it will draw. Because the pixel shader is called once for every drawn pixel, it also only has to draw one single pixel, thus it only needs to calculate the one color for this pixel and doesn't need to worry about anything else.


So let's write both of them.

First the vertex shader. Here is the code with every line explained. Save this as yourshadername.vert
And with yourshadername I mean choose a name. Whatever you want.

Code: Select all

// First, we need to tell the compiler for which version of GLSL to compile. I chose a quite old version here to ensure compability. We do not need very modern features anyway.

#version 330


// Model info. This is data that is given to us by the engine. All the needed information of our model will be coming in here. For now, we only need the very basic things like the position of the vertex and the uv position of the vertex.

layout(location = 0) in vec3 pos;
layout(location = 1) in vec2 uv;


// Output to pixel shader. We can create variables that will be sent to each instance of the pixel shader that is lying in a polygon connected to our vetrex. Keep in mind, 3 vertices will be sending their info to each pixel in one polygon, so how will that work? The information gets interpolated based on how far the pixel is from each of it's parent vertices. For now, we only need to send the uv position. Simple enough.

out vec2 uvs;


// Matrices
// These will be filled for you with the necessary information to transfer your vertex positions between the different spaces. (model space, world space, camera space, camera perspective space)
// Once we get to lighting we will need to think about these, for now we don't need them for our own data. We will only use them to transfer the vertex position from model space (that is how it is handed to us) to camera perspective space (which kinda just means the vertex position will be the vertex's screen position after the transformation, as seen through the perspective of the camera)

uniform mat4 view;
uniform mat4 model;
uniform mat4 proj;


// Vertex shader
void main(){
   // we transform the vertex position here. For now, let's not worry about it. This will be explained in detail in a later tutorial when we need to transfer more data between spaces.

   gl_Position =  proj * view * model * vec4(pos, 1);
   
   // Now all we have left to do is to write the incoming uv position into our handy own variable that will go out to all the pixel shader instances of the connected polygons.

   uvs = uv;
}


Here is a clean version of the same vertex shader without all the comments, in order to have a clean short version.

Code: Select all

#version 330
layout(location = 0) in vec3 pos;
layout(location = 1) in vec2 uv;
out vec2 uvs;

uniform mat4 view;
uniform mat4 model;
uniform mat4 proj;

void main(){
   gl_Position =  proj * view * model * vec4(pos, 1);
   uvs = uv;
}



Onward to the pixel shader. Save this as yourshadername.frag
Here is the heavily commented code:

Code: Select all

// Once again, we tell the compiler which version we are gunning for

#version 330


// Now let's decare a variable for the incoming information from vertex shader. You have to define the same variables here that you have defined as "out" in the vertex shader and they will receive the information.
// Keep in mind, each pixel shader instance will receive information from 3 vertex shader instances, the three corners of it's poly. As described in the vertex shader, this info will be interpolated based on the pixel's position.

in vec2 uvs;


// Textures
// Here we define which textures we will need.
// For now, we only need a simple 2D texture, thus we need to define a 2D sampler for it. A sampler will just fetch the texture information for us based on the uv position we give it. We can name this in whichever way we want, we will just have to use the same name when assigning the texture in our C3D program. I will just name it tD, short for textureDiffuse.

uniform sampler2D tD;


// Output
// This will be the variable we write out at the end. It has to be a vec4 for RGBA. A vec4 is just a group of 4 floats, in this case they will correspond to red, green, blue and alpha. Anything we put into this variable will be sent out when the shader instance is finished.

out vec4 color;


// Main shader
//This is the function that will be called for each drawn pixel.

void main() {
   // In this case we can keep it very simple, we have the uv position coming in from the vertex shader and we have defined a texture sampler above, so let's just get the texture color for this uv position and write it out.
   
   color = vec4(texture(tD, uvs).rgb, 1);
   
   // Let's pick this line apart a bit.
   // The color from the texture will be just red green and blue, so we have to add an alpha to it to fill our outgoing vec4 with it's four components.
   // We can easily do this by manually building it with vec4();
   // We just have to ensure we fill our structure with 4 floats.
   
   // We could do this by going vec4(float, float, float, float);
   // or vec4( vec2, float, float); or  vec4( float, vec2, float); or vec4( float, float, vec2);
   // or like in this case vec4( vec3, float); because our color is a vec3 and thus has three floats contained (RGB)
   // We fill the fourth float with a 1 (alpha = 1 means solid, opaque color).
   
   // The texture command is the command that will activate our texture sampler from above.
   // We just have to tell it the sampler name and the uv it should do the lookup at.
   // The texture fetch alone looks like this: texture(sD, uvs);
   
   // In this case, because we only need RGB, it's good practice to make sure it only fetches 3 components even if the texture applied has an alpha channel, thus we add a .rgb to it, which tells it to only fetch the red, green and blue components.
   // So our finalized texture lookup alone would look like this: texture(sD, uvs).rgb;
   
   // It could be filled into a vec3, which would not require the vec4() around it. vec3 color = texture(sD, uvs).rgb;
   // If we had a setup that also uses the alpha channel of the diffuse texture, we would just use vec4 color = texture(sD, uvs).rgba;
}


That was it. Quite short actually, here is the pixel shader without all the comments:

Code: Select all

#version 330
in vec2 uvs;
uniform sampler2D tD;

out vec4 color;
void main() {
   color = vec4(texture(tD, uvs).rgb, 1);
}



And some simple C3D example code to load and use this:

Code: Select all

Init()
Function Init()
    CreateC3DCore()
    ForceGL()

    Global mainwindow = CreateWindow(1920,1080,16, "mywindow", 0,0)
    Global camera = CreateCamera(1)
   
    TurnEntity(camera, Vec3(20, -25, 0))
    MoveEntity(camera, Vec3(0, 1, 7.0))
   
    // Load our shader. Here we reference the two files we created above.
    Global shader = LoadShader("Data/Shaders/yourshadername.vert", "Data/Shaders/yourshadername.frag","","","")
   
    // Load a cube and a texture for it. Change the filenames to match your model and texture :)
    Global ctex = LoadTexture("Data/Textures/cubetexture.dds")
    Global cmod = LoadMesh("Data/Meshes/cubemodel.fbx")
    Global cmat = CreateMaterial(shader)
    AddTexture(cmat, "tD", ctex)
    SetMeshMaterial(cmod, cmat)
    SetMaterialShader(cmat, shader)
   
    MainLoop()
    EnterMainLoop()
End Function

Function MainLoop()
    While(1 = 1)
        UpdateAll()
    Wend
End Function


Image


Hope this helps some people who are just getting started with shaders. If not, yell at me.

Re: Shader Tutorial thread, let's learn shaders C3D style.

Posted: Sun Mar 12, 2017 7:13 pm
by Yue
Hi, excellent information, thank you very much. When I come home, I will have something new to learn. :-)