YesNoOk
avatar

Primer to GLSL in Mugen 1.1 (Read 10164 times)

Started by Bronko, August 15, 2013, 11:18:47 PM
Share this topic:
Primer to GLSL in Mugen 1.1
#1  August 15, 2013, 11:18:47 PM
  • ******
  • Just a butcher on a mission.
    • www.justnopoint.com/lbends
*Disclaimer*
This guide is not for people who have no concept of programming.  If am not obligated to answer questions which I consider too basic.


What is GLSL?
Beginning with Mugen 1.1, we now have limited access to OpenGL shader programming.  OpenGL shaders are written in a language called GLSL.

How does it work?
Simple shaders have two components, a vertex shader and a fragment shader.  The vertex shader determines where a pixel needs to be drawn and the fragment shader determines what color it will be.
If you look in /data/ you will find four of these shader files.  They are

indexed.frag
indexed.vert
rgba.frag
rgba.vert

Indexed is used when rendering paletted sprites, while rgba is used when rendering rgba images.  These are all written in plaintext so you can open them with your favorite text editor.

Data Types
Like any other programming language, GLSL has defined data types.  There are quite a few but we'll only be looking at the basic ones.
For single variables we have
int, float, bool

For vectors we have
vec2, vec3, vec4, ivec2, ivec3, ivec4

And a couple of special ones
sampler1d, sampler2d

If you already know another programming language the first three are fairly obvious.  Integers are just whole numbers, floats have decimal points, and booleans are either true or false.
Vectors are a special type of array.  GLSL supports standard arrays the way other programming languages do using brackets, but vectors have a specific purpose in defining coordinates, colors, or whatever else requires vector math.  vec2 has two values, while vec3 and vec4 have 3 and 4 respectively.  ivec simply means the values are integers not floats.  Vectors have a special property in that you can reference each value inside by name, for convenience.  These names are rgba, xyzw, and stpq.  For example, to reference the second value of a vector, vec2.g, vec2.y, and vec2.t all work.  You can even get a subset of a vector in this way, vec4.yz for example will result in a vec2 containing the second and third elements.
Samplers cannot be defined in your code, they have to be passed in from somewhere else.  Samplers are typically raster data like textures.  A sampler1d is more or less a straight line, while a sampler2d has a width and height.

Looking at the vertex shader
Mugen currently does not use a full screen shader, so any changes we make to the vertex shader will probably have disastrous results.
Code:
#version 120

/*
 * Vertex shader that does nothing special.
 */
void main()
{
  gl_TexCoord[0] = gl_MultiTexCoord0;
 
  gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
Both indexed.vert and rgba.vert look like this.  Let's briefly go over each line of code.
Code:
#version 120
This is a preprocessor directive that tells us that this file is written using version 1.20 of GLSL.  A preprocessor directive is a line of code that is executed BEFORE the code is compiled, more on that later.  Versions are important in GLSL because the language changes so much from version to version.  If you're into that sort of thing, the complete documentation for every version can be found here.
Code:
void main()
Every shader has a line like this, this is a function definition.  Void is the type of data the function returns, in this case it doesn't.  Some functions will say Int or Float or Vec3 etc.  The name of this function is main, you can name a function anything you want, but main will always be executed automatically.  Inside the parenthesis is space for parameters.  main does not use parameters, but other functions might.
Code:
gl_TexCoord[0] = gl_MultiTexCoord0;
One of the key characteristics of GLSL is the presence of global and reserved variables.  Global variables are ones that you always have access to without defining them yourself.  Reserved variables in this case, are any that start with gl_ , you are not allowed to create these.  There are three important global variables for the vertex shader but in 2D we only need two.  They are gl_TexCoord and gl_Position.

gl_TexCoord is an array variable that tells the graphics card where the pixel on the current texture(s) is.  The \[0\] simply means we are accessing the first part of the array.  In this case, the array doesn't go past 0, but we still have to do it this way.  gl_MultiTexCoord0 is a global variable that is more or less the exact same thing, but the fragment shader does not have access to this.  The fragment and vertex shaders do not have access to the same global variables.
gl_Position is a variable that tells the graphics card where to draw the current pixel.  Here we are calculating the position based on two other globals, the transformation matrix gl_ModelViewProjectionMatrix which has already been calculated for us, and gl_Vertex which is a coordinate within the sprite itself.

Looking at the fragment shader
This is where things get interesting.
Spoiler, click to toggle visibilty
For the sake of space I removed all the commented lines.
Let's start at the bottom with our main() function.
Code:
vec4 color = nearest();
Here we create a vec4 variable called color, and set its value to the function called nearest().
Code:
palfx(color);
Then we call a function that applies various pal fx to the color.  There is a preprocessor directive around this line of code that checks first if palfx are disabled.  If palfx IS disabled, it's as if this line of code doesn't even exist.
Code:
gl_FragColor = color;
The most important line of code.  gl_FragColor is a global variable that tells the graphics card what color pixel to draw.

Now let's back up and look at nearest()
Code:
vec4 nearest()
Notice that nearest is a vec4, not a void.  This means that the function returns a value of vec4, which is why we can set our color to the result. 
Code:
#if defined(PERSPECTIVE_NONE)
  vec4 idxv = texture2D(texunit0, gl_TexCoord[0].st);
#elif defined(PERSPECTIVE_STANDARD)
  vec4 idxv = texture2D(texunit0, gl_TexCoord[0].st / gl_TexCoord[0].q);
#else // PERSPECTIVE_EVEN
  vec4 idxv = texture2D(texunit0, vec2(gl_TexCoord[0].s / gl_TexCoord[0].q, gl_TexCoord[0].t));
#endif
Again we have a preprocessor directive which means only one of these three lines of code are actually present depending on circumstance.  texture2D is a built-in function for GLSL that allows us to get data from a sampler2D based on a set of coordinates we feed into it.  If we scroll further up we can see the definition for texunit0, our sampler2D.
Code:
uniform sampler2D texunit0;
Notice how this line is not inside of any defined function.  This means the variable has a global scope, any function can use it.  It also has a uniform qualifier, this means that the data here is being provided from somewhere outside of the code.  In this case, mugen feeds in the sprite raster data.
Code:
  float idx = idxv.r;
  vec4 color = texture1D(texunit1, index_correction(idx));
Here's where the magic happens.  The value we got before is actually an index that points us to a color in the palette.  Here we access idxv.r (idxv.x and idxv.s would also work) because the image only has one color channel (it's 8 bit remember?), the indexed color.  We take that value, do some dirty math with index_correction() (elecbyte hacked up something that could probably be fixed some other way) and find the correct color from texunit1, our sampler1D which is actually the palette that mugen loaded here.
Code:
  if (!mask)
    color.a = 1.0;

  if (color.a == 0.0)
    discard;

#if !defined(PREMUL)
  color.a *= src_alphamul;
#else
  color *= src_alphamul;
#endif
More dirty hacks by elecbyte.  The discard command actually stops the shader immediately and tells the graphics card to skip this pixel entirely.  This provides a huge speed increase when you know a particular pixel is going to be invisible.  Down at the preprocessor block, we see two different lines of code that do roughly the same thing.  One is a formula to use when the color values are not premultiplied (in which case we only need to fix the alpha value) and when they are (in which case we can multiply every value in the vector at once).
Code:
return color;
Like in any other programming language, this line simply sends a value back from the function.  If the variable color is not the same type as what we declared in the name of the function, this will error.


Challenge: Figure out on your own how palfx() works.
Re: Primer to GLSL in Mugen 1.1
#2  August 15, 2013, 11:41:45 PM
  • ******
  • [E]
    • Mexico
thanks, but I won't do the homework, instead I am trying to figure out what can be done with this knowledge, the only thngs that come to mind are skewing the screen and maybe some weird zoom/rotation effects ( all that is too eyecandy-ish for what I use to do).
Re: Primer to GLSL in Mugen 1.1
#3  September 02, 2013, 08:47:41 AM
  • avatar
  • *
It's obvious that shaders will mainly be used to enhance sprite display in higher resolution than the one intended for those sprites.
There are a lot of stages developed to look nice in FHD or HD Ready, but most characters are still developed in very low resolution. So applying a shader would easily enhance them in those higher resolution, as mugen 1.0 doesn't have the graphic filter option of WinMugen anymore (diagonal edge detection, bilinear filtering and horizontal scanlines).
Re: Primer to GLSL in Mugen 1.1
#4  September 02, 2013, 09:29:13 AM
  • avatar
  • ******
    • USA
I have no idea what's going on but there are many numbers so it must be a pretty cool thingy.

Gibe exampl pics pls. I want to see some results you can get with the shaders (Already saw the one that detects the edges of characters and paints them black, that ought to be useful for some stylish games).
Re: Primer to GLSL in Mugen 1.1
#5  September 02, 2013, 09:33:48 AM
  • ******
  • You did better than good... you did gooder
    • USA
    • network.mugenguild.com/jmorphman