mirror of
https://github.com/patriciogonzalezvivo/thebookofshaders
synced 2024-11-09 19:10:24 +00:00
449 lines
22 KiB
Markdown
449 lines
22 KiB
Markdown
## An introduction for those coming from JS
|
|
by [Nicolas Barradeau](http://www.barradeau.com/)
|
|
|
|
|
|
If you're a JavaScript developer, chances are you'll be a bit puzzled when reading the book.
|
|
Indeed, there are many differences between manipulating high-level JS and getting down and dirty with shaders.
|
|
Yet, as opposed to the underlying assembly language, GLSL is human readable and I'm sure that, once you acknowledge its specificities, you'll quickly be up and running.
|
|
|
|
I assume you have a prior (be it shallow) knowledge of JavaScript of course, but also of the Canvas API.
|
|
If not, don't worry, you'll still be able to get most of this section.
|
|
|
|
Also, I won't go too much into details and some things may be _half true_, don't expect a "definitive guide" but rather
|
|
|
|
### A BIG HUG
|
|
|
|
JavaScript is great at quick prototyping ; you throw a bunch of random, untyped variables and methods, you can dynamically add and remove class members, refresh the page and see if it works,
|
|
make changes accordingly, refresh the page, repeat, life is easy.
|
|
So you may wonder what is the difference between JavaScript and GLSL.
|
|
After all, both run in the browser, both are used to draw a bunch of funky stuff on a screen and to that extent, JS is easier to use.
|
|
|
|
Well, the main difference is that Javascript is an **interpreted** language while GLSL is a **compiled** language.
|
|
A **compiled** program is executed natively on the OS, it is low level and generally fast.
|
|
An **interpreted** program requires a [Virtual Machine](https://en.wikipedia.org/wiki/Virtual_machine) (VM) to be executed, it is high level and generally slow.
|
|
|
|
|
|
When a browser (the _JavaScript **VM**_) **executes** or **interprets** a piece of JS, it has no clue about which variable is what and which function does what (with the notable exception of **TypedArrays**).
|
|
Therefore it can't optimize anything _upfront_, so it takes some time to read your code, to **infer** (deduce from the usage) the types of your variables and methods
|
|
and when possible, it will convert _some_ of your code into assembly code that will execute much faster.
|
|
|
|
It's a slow, painstaking and insanely complex process, if you're interested in the details, I'd recommend watching how [Chrome's V8 engine works](https://developers.google.com/v8/).
|
|
The worst is that every browser optimizes JS its way and the process is _hidden_ from you ; you are powerless.
|
|
|
|
A **compiled** program is not interpreted ; the OS runs it, if the program is valid, the program is executed.
|
|
That's a big change ; if you forget a semicolon at the end of line, your code is invalid, it will not compile: your code won't turn into a program at all.
|
|
|
|
That's cold but that's what a **shader** is: _a compiled program executed on the GPU_.
|
|
Fear not! a **compiler**, the piece of program that makes sure your code is valid, will become your best friend.
|
|
The examples of this book and the [companion editor](http://editor.thebookofshaders.com/) are very user friendly.
|
|
They'll tell you where and why your program failed to compile, then you'll have to fix things and whenever the shader is ready to compile, it will be displayed instantly.
|
|
That's a great way of learning as it's very visual and you can't really break anything.
|
|
|
|
Last note, a **shader** is made of 2 programs, the **vertex shader** and the **fragment shader**.
|
|
In a nutshell, the **vertex shader**, the first program, receives a *geometry* as an input and turns it into series of **pixels** (or *fragments*) then hands them over to the
|
|
**fragment shader**, the second program, that will decide which color to paint the pixels.
|
|
This book is mostly focused on the latter, in all the examples, the geometry is a simple quadrilateral that covers the whole screen.
|
|
|
|
SO! ready?
|
|
|
|
off we go!
|
|
|
|
### strong types
|
|
![first search result for 'strong type' on Google Image, on the 2016/05/20](strong_type.jpg)
|
|
|
|
When you come from JS or any untyped language, **typing** your variables is an alien concept, making **typing** the hardest step to take towards GLSL.
|
|
**Typing**, as the name suggests, means that you'll give a **type** to your variables (and functions of course).
|
|
This basically means that the word **`var`** doesn't exist anymore.
|
|
The GLSL thought-police erased it from the common tongue and you're not able to speak it because, well... it doesn't exist.
|
|
|
|
Instead of using the magic word **`var`**, you'll have to _explicitly specify the type of each variable_ you use, then the compiler will only see objects and primitives it knows how to handle efficiently.
|
|
The downside when you can't use the **`var`** keyword and must _specify everything_, is that you'll have to know the type of all the variables and know them well.
|
|
Rest assured, there are few and they're fairly simple (GLSL is not a Java framework).
|
|
|
|
Might sound scary but all in all, it's not very different from what you're doing when you code JavaScript ; if a variable is a `boolean`, you'll expect it to store `true` or `false` and nothing else.
|
|
If a variable is called `var uid = XXX;`, chances are that you'll store an integer value in there and a `var y = YYY;` _might_ be a reference to a floating point value.
|
|
Even better, with **strong types**, you won't waste time wondering if `X == Y` (or was it `typeof X == typeof Y` ? .. or `typeof X !== null && Y...` ... anyway) ; you'll just *know* it and if you don't, the compiler will.
|
|
|
|
Here are the **scalar types** (a **scalar** describes a quantity) you can use in GLSL: `bool` (Boolean), `int`(Integer), `float`(floating point Number).
|
|
There are other types but let's take it easy, the following snippet shows how to declare **`vars`** (yes, I spoke the forbidden word) in GLSL:
|
|
```glsl
|
|
//a Boolean value:
|
|
JS: var b = true; GLSL: bool b = true;
|
|
|
|
//an Integer value
|
|
JS: var i = 1; GLSL: int i = 1;
|
|
|
|
//a Float value (a Number)
|
|
JS: var f = 3.14159; GLSL: float f = 3.14159;
|
|
```
|
|
Not that hard right? as mentioned above, it even makes things easier when it comes to coding as you don't waste your time checking the type of a given variable.
|
|
When in doubt, remember that you're doing this for your program to run immensely faster than in JS.
|
|
|
|
#### void
|
|
There is a `void` type that roughly corresponds to `null`, it is used as the return type of a method that doesn't return anything.
|
|
you can't assign it to a variable.
|
|
|
|
#### boolean
|
|
As you know, Booleans are mostly used in conditional tests ; `if( myBoolean == true ){}else{}`.
|
|
If the conditional branching is a valid option on the CPU, [the parallel nature](http://thebookofshaders/01/) of GLSL makes it less true.
|
|
Using conditionals is even discouraged most of the time, the book explains a couple of alternative techniques to solve this.
|
|
|
|
#### type casting
|
|
As [Aragorn](https://en.wikipedia.org/wiki/Aragorn) put it, "One does not simply combine Typed primitives". Unlike JavaScript, GLSL will not allow you to perform operations between variables of different types.
|
|
|
|
This for instance:
|
|
```glsl
|
|
int i = 2;
|
|
float f = 3.14159;
|
|
|
|
//trying to multiply an integer by a float value
|
|
float r = i * f;
|
|
```
|
|
will not play nice because you're trying to crossbreed a **_cat_** and a **_giraffe_**.
|
|
The solution to this is to use **type casting** ; it will _make the compiler believe_ that *`i`* is of type `float` without actually changing the type of *`i`*.
|
|
```glsl
|
|
//casting the type of the integer variable 'i' into float
|
|
float r = float( i ) * f;
|
|
```
|
|
|
|
Which is strictly equivalent to dressing up a **_cat_** in a **_giraffe_ outfit** and will work as expected ( `r` will store the result of `i` x `f`).
|
|
|
|
It is possible to **cast** any of the above types into any other type, note that casting a `float` to `int` will behave like a `Math.floor()` as it will remove the values behind the floating point.
|
|
Casting a `float` or a `int` to `bool` will return `true` if the variable is not equal to zero.
|
|
|
|
#### constructor
|
|
The variable **types** are also their own **class constructor** ; in fact a `float` variable can be thought of as an _`instance`_ of a _`Float`_ class.
|
|
|
|
This declarations are equally valid:
|
|
|
|
```glsl
|
|
int i = 1;
|
|
int i = int( 1 );
|
|
int i = int( 1.9995 );
|
|
int i = int( true );
|
|
```
|
|
This may not sound like much for `scalar` types, it's not very different from **casting**, but it will make sense when addressing the *overload* section.
|
|
|
|
Ok, so these three are the `primitive types`, things you can't live without but of course, GLSL has more to offer.
|
|
|
|
### Vectors
|
|
![first search result for 'vector villain' on Google Image, on the 2016/05/20](vector.jpg)
|
|
|
|
In Javascript like in GLSL, you'll need more sophisticated ways of handling data, that's where **`vectors`** come in handy.
|
|
I suppose that you've already coded a `Point` class in JavaScript to hold together a `x` and a `y` value, the code for this would go like:
|
|
```glsl
|
|
// 'class' definition:
|
|
var Point = function( x, y ){
|
|
this.x = x || 0;
|
|
this.y = y || 0;
|
|
}
|
|
|
|
//and you would instantiate it like:
|
|
var p = new Point( 100,100 );
|
|
```
|
|
|
|
As we've just seen, this is SO wrong at SO many levels! That **`var`** keyword for one, then the horrendous **`this`**, then again **untyped** `x` and `y` values...
|
|
No, this is not going to work in shaderland.
|
|
|
|
Instead, GLSL exposes built-in data structures to hold data together, namely:
|
|
|
|
* `bvec2`: a 2D Boolean vector, `bvec3`: a 3D Boolean vector, `bvec4`: a 4D Boolean vector
|
|
* `ivec2`: a 2D Integer vector, `ivec3`: a 3D Integer vector, `ivec4`: a 4D Integer vector
|
|
* `vec2`: a 2D Float vector, `vec3`: a 3D Float vector, `ivec4`: a 4D Float vector
|
|
|
|
You immediately noticed that there's a type of **vector** for each primitive type, clever bunny.
|
|
From what we just saw, you can deduce that a `bvec2` will hold two values of type `bool` and a `vec4` will hold four `float` values.
|
|
|
|
Another thing introduced by vectors is a number of **dimensions**, it doesn't mean that a 2D vector is used when you render 2D graphics and a 3D vector when you do 3D.
|
|
What would a 4D vector represent then? (well, actually it is called a tesseract or hypercube)
|
|
|
|
No, the **dimensions** represent the number and the type of **components** or **variables** stored into the **vector**:
|
|
```glsl
|
|
// let's create a 2D Boolean vector
|
|
bvec2 b2 = bvec2 ( true, false );
|
|
|
|
// let's create a 3D Integer vector
|
|
ivec3 i3 = ivec3( 0,0,1 );
|
|
|
|
// let's create a 4D Float vector
|
|
vec4 v4 = vec4( 0.0, 1.0, 2.0, 1. );
|
|
```
|
|
`b2` stores two different boolean values, `i3` stores 3 different integer values and `v4` stores 4 different float values.
|
|
|
|
but how to retrieve those values?
|
|
in the case of `scalars`, the answer is obvious ; with `float f = 1.2;`, the variable `f` holds the value `1.2`.
|
|
With **vectors** it's a bit different and quite beautiful.
|
|
|
|
#### accessors
|
|
There are different ways of accessing the values
|
|
```glsl
|
|
// let's create a 4D Float vector
|
|
vec4 v4 = vec4( 0.0, 1.0, 2.0, 3.0 );
|
|
```
|
|
to retrieve the 4 values, you can do the following:
|
|
```glsl
|
|
float x = v4.x; // x = 0.0
|
|
float y = v4.y; // y = 1.0
|
|
float z = v4.z; // z = 2.0
|
|
float w = v4.w; // w = 3.0
|
|
```
|
|
nice and easy ; but the following are equally valid ways of accessing your data:
|
|
```glsl
|
|
float x = v4.x = v4.r = v4.s = v4[0]; // x = 0.0
|
|
float y = v4.y = v4.g = v4.t = v4[1]; // y = 1.0
|
|
float z = v4.z = v4.b = v4.p = v4[2]; // z = 2.0
|
|
float w = v4.w = v4.a = v4.q = v4[3]; // w = 3.0
|
|
```
|
|
|
|
And the clever bunny you are already noticed three things:
|
|
* `X`, `Y`, `Z` & `W` are used in 3D programs to represent 3D vectors
|
|
* `R`, `G`, `B` & `A` are used to encode colors and alpha
|
|
* `[0]`, `[1]`, `[2]` & `[3]` mean that we have a random access array of values
|
|
|
|
So depending on whether you're manipulating 2D or 3D coordinates, a color with or without an alpha value or simply some random variables, you can pick the most suited **vector** type and size.
|
|
Typically 2D coordinates and vectors (in the geometric sense) are stored as a `vec2`, `vec3` or `vec4`, colors as `vec3` or `vec4` if you need opacity but there is no restriction on how to use the vectors.
|
|
For instance, if you want to store only one boolean value in a `bvce4`, it's possible, it's just a waste of memory.
|
|
|
|
**note**: in a shader, color values (`R`, `G`, `B` & `A`) are normalised, they range from 0 to 1 and not from 0 to 0xFF, so you'd rather use a Float `vec4` than an Integer `ivec4` to store them.
|
|
|
|
Nice already, but there's more!
|
|
|
|
#### swizzle
|
|
|
|
It is possible to return more than one value at once ; say you need only the `X` and `Y` values of a `vec4`, in JavaScript, you'd have to write something like:
|
|
```glsl
|
|
var needles = [0, 1]; // location of 'x' & 'y' in our data structure
|
|
var a = [ 0,1,2,3 ]; // our 'vec4' data structure
|
|
var b = a.filter( function( val, i, array ) {
|
|
return needles.indexOf( array.indexOf( val ) ) != -1;
|
|
});
|
|
// b = [ 0, 1 ]
|
|
|
|
//or more literally:
|
|
var needles = [0, 1];
|
|
var a = [ 0,1,2,3 ]; // our 'vec4' data structure
|
|
var b = [ a[ needles[ 0 ] ], a[ needles[ 1 ] ] ]; // b = [ 0, 1 ]
|
|
```
|
|
Ugly. In GLSL you can retrieve them like so:
|
|
```glsl
|
|
// create a 4D Float vector
|
|
vec4 v4 = vec4( 0.0, 1.0, 2.0, 3.0 );
|
|
|
|
//and retrieve only the X & Y components
|
|
vec2 xy = v4.xy; // xy = vec2( 0.0, 1.0 );
|
|
```
|
|
What just happened?! when you **concatenate accessors**, GLSL gracefully returns a subset of the values you asked for, in the best suited **vector** format.
|
|
Indeed, the vector is a **random access** data structure, like an array in JavaScript if you want.
|
|
So not only can you retrieve a subset of your data, but you can also specify the **order** in which you need it, this will invert the values of the components of a vector:
|
|
```glsl
|
|
// create a 4D Float vector: R,G,B,A
|
|
vec4 color = vec4( 0.2, 0.8, 0.0, 1.0 );
|
|
|
|
//and retrieve the color components in the A,B,G,R order
|
|
vec4 backwards = v4.abgr; // backwards = vec4( 1.0, 0.0, 0.8, 0.2 );
|
|
```
|
|
And of course, you can ask the same component multiple times:
|
|
```glsl
|
|
// create a 4D Float vector: R,G,B,A
|
|
vec4 color = vec4( 0.2, 0.8, 0.0, 1.0 );
|
|
|
|
//and retrieve a GAG vec3 based on the G & A channels of the color
|
|
vec3 GAG = v4.gag; // GAG = vec4( 0.8, 1.0, 0.8 );
|
|
```
|
|
|
|
This is extremely handy to combine parts of vectors together, extract only the rgb channels of a RGBA color etc.
|
|
|
|
|
|
#### overload everything!
|
|
|
|
In the types section, I mentioned something about the **constructor** and that's yet again a great feature of GLSL ; **overloading**.
|
|
For those who don't know, **overloading** an operator or a function roughly means: _'changing the behaviour of said operator or function depending on the operands/arguments'_.
|
|
Overloading is not allowed in JavaScript, so this may be a bit strange at first but I'm sure that once you get used to it, you'll wonder why it is not implemented in JS (short answer, *typing*).
|
|
|
|
the most basic example of operator overloading goes as follow:
|
|
|
|
```glsl
|
|
vec2 a = vec2( 1.0, 1.0 );
|
|
vec2 b = vec2( 1.0, 1.0 );
|
|
//overloaded addition
|
|
vec2 c = a + b; // c = vec2( 2.0, 2.0 );
|
|
```
|
|
WHAT? so you can add things that are not numbers?!
|
|
|
|
Yes, precisely. Of course this applies to all operators (`+`, `-`, `*` & `/`) but that's only the beginning.
|
|
Consider the following snippet:
|
|
```glsl
|
|
vec2 a = vec2( 0.0, 0.0 );
|
|
vec2 b = vec2( 1.0, 1.0 );
|
|
//overloaded constructor
|
|
vec4 c = vec4( a , b ); // c = vec4( 0.0, 0.0, 1.0, 1.0 );
|
|
```
|
|
We built a `vec4` out of two `vec2`, by doing so, the new `vec4` used the `a.x` and `a.y` as the `X`, `Y` components of `c`.
|
|
Then it took `b.x` and `b.y` and used them as the `Z` and `W` components of `c`.
|
|
|
|
This is what happens when a **function** is overloaded to accept different arguments, in this case, the `vec4` **constructor**.
|
|
It means that many **versions** of the same method with a different signature can coexist in the same program, for instance the following declarations are all valid:
|
|
```glsl
|
|
vec4 a = vec4(1.0, 1.0, 1.0, 1.0);
|
|
vec4 a = vec4(1.0);// x, y, z, w all equal 1.0
|
|
vec4 a = vec4( v2, float, v4 );// vec4( v2.x, v2.y, float, v4.x );
|
|
vec4 a = vec4( v3, float );// vec4( v3.x, v3.y, v3.z, float );
|
|
etc.
|
|
```
|
|
the only thing you should make sure of is to provide enough arguments to feed your **vector**.
|
|
|
|
Last thing, you are allowed to overload the built-in functions in your program so they can take arguments they were not designed for (this shouldn't happen too often though).
|
|
|
|
#### more types
|
|
Vectors are fun, they're the meat of your shader.
|
|
There are other primitives such as Matrices and Texture samplers which will be covered later in the book.
|
|
|
|
We can also use Arrays. Of course they have to be typed and there are *twists*:
|
|
* they have a fixed size
|
|
* you can't push(), pop(), splice() etc. and there is no ```length``` property
|
|
* you can't initialize them immediately with values
|
|
* you have to set the values individually
|
|
|
|
this won't work:
|
|
```glsl
|
|
int values[3] = [0,0,0];
|
|
```
|
|
but this will:
|
|
```glsl
|
|
int values[3];
|
|
values[0] = 0;
|
|
values[1] = 0;
|
|
values[2] = 0;
|
|
```
|
|
This is fine when you know your data or have small arrays of values.
|
|
If you want a more expressive way of declaring a variable,
|
|
there is also a ```struct``` type. These are like _objects_ without methods ;
|
|
they allow to store and access multiple variables inside the same object
|
|
```glsl
|
|
struct ColorStruct {
|
|
vec3 color0;
|
|
vec3 color1;
|
|
vec3 color2;
|
|
}
|
|
```
|
|
then you can set and retrieve the values of _colors_ by doing:
|
|
```glsl
|
|
//initialize the struct with some values
|
|
ColorStruct sandy = ColorStruct( vec3(0.92,0.83,0.60),
|
|
vec3(1.,0.94,0.69),
|
|
vec3(0.95,0.86,0.69) );
|
|
|
|
//access a values from the struct
|
|
sandy.color0 // vec3(0.92,0.83,0.60)
|
|
```
|
|
This is syntactic sugar but it can help you write cleaner code, at least code you're more familiar with.
|
|
|
|
#### statements & conditions
|
|
|
|
Data structures are nice as such but we _might_ need to iterate or perform conditional tests at some point.
|
|
Fortunately for us, the syntax is very close to the JavaScript.
|
|
A condition is like:
|
|
```glsl
|
|
if( condition ){
|
|
//true
|
|
}else{
|
|
//false
|
|
}
|
|
```
|
|
A for loop is usually:
|
|
```glsl
|
|
const int count = 10;
|
|
for( int i = 0; i <= count; i++){
|
|
//do something
|
|
}
|
|
```
|
|
or with a float iterator:
|
|
```glsl
|
|
const float count = 10.;
|
|
for( float i = 0.0; i <= count; i+= 1.0 ){
|
|
//do something
|
|
}
|
|
```
|
|
Note that ```count``` will have to be defined as a ```constant```.
|
|
This means prefixing the type with a ```const``` **qualifier**, we'll cover this in a second.
|
|
|
|
we also have the ```break``` and ```continue``` statements:
|
|
```glsl
|
|
const float count = 10.;
|
|
for( float i = 0.0; i <= count; i+= 1.0 ){
|
|
if( i < 5. )continue;
|
|
if( i >= 8. )break;
|
|
}
|
|
```
|
|
note that on some hardware, ```break``` does not work as expected and the loop doesn't bail out early.
|
|
|
|
In general, you'll want to keep the iteration count as low as possible and avoid the loops and the conditionals as often as you can.
|
|
|
|
|
|
#### qualifiers
|
|
|
|
On top of the variable types, GLSL uses **qualifiers**.
|
|
Long story short, qualifiers help the compiler know which variable is what.
|
|
For instance some data can only be provided by the CPU to the GPU, those are called **attributes** and **uniforms**.
|
|
The **attributes** are reserved for the vertex shaders, the **uniforms** can be used in both the vertex and the fragment shaders.
|
|
There's also a ```varying``` qualifier used to pass variables between the vertex and the fragment shader.
|
|
|
|
I won't go too much into details here as we're mostly focused on the **fragment shader** but later in the book, you'll see something like:
|
|
```glsl
|
|
uniform vec2 u_resolution;
|
|
```
|
|
See what we did here? we stuck a ```uniform``` qualifier before the type of the variable
|
|
This means that the resolution of the canvas we're working on is passed to the shader from the CPU.
|
|
The width of the canvas is stored in the x and the height in the y component of the 2D vector.
|
|
|
|
When the compiler sees a variable preceded by this qualifier, it will make sure that you can't *set* those values at runtime.
|
|
|
|
The same applied to our ```count``` variable which was the limit of our ```for``` loop:
|
|
```glsl
|
|
const float count = 10.;
|
|
for( ... )
|
|
```
|
|
When we use a ```const``` qualifier, the compiler will make sure that we set the variable's value only once, otherwise it's not a constant.
|
|
|
|
There are 3 extra qualifiers that are used in the functions signatures : ```in```, ```out``` and ```inout```.
|
|
In JavaScript, when you pass scalar arguments to a function, their value is read-only and if you change their values inside the function,
|
|
the changes are not applied to the variable outside the function.
|
|
```glsl
|
|
function banana( a ){
|
|
a += 1;
|
|
}
|
|
var value = 0;
|
|
banana( value );
|
|
console.log( value );// > 0 ; the changes are not taken into account outside the function
|
|
```
|
|
|
|
With arguments qualifiers, you can specify the behaviour of the the arguments:
|
|
* ```in``` will be read-only ( default )
|
|
* ```out``` write-only: you can't read the value of this argument but you can set it
|
|
* ```inout``` read-write: you can both get and set the value of this variable
|
|
|
|
rewriting the banana method in GLSL would look like
|
|
```glsl
|
|
void banana( inout float a ){
|
|
a += 1.;
|
|
}
|
|
float A = 0.;
|
|
banana( A ); //now A = 1.;
|
|
```
|
|
This is very different from JS and quite powerful too but you don't have to specify the signature qualifiers (the default is read-only).
|
|
|
|
#### space & coordinates
|
|
|
|
Final note, in the DOM and the Canvas 2D, we're used to have the Y axis pointing 'down'.
|
|
This makes sense in the context of a DOM as it follows the way a web page unrolls ; the navbar at the top, content expanding towards the bottom.
|
|
In a WebGL canvas, the Y axis is flipped: Y points 'up'.
|
|
|
|
This means that the origin, the point (0,0), is located at the bottom left corner of a WebGL context, not at the top left corner like in a 2D Canvas.
|
|
The textures coordinates follow this rule which might be counter-intuitive at first.
|
|
|
|
## And we're done!
|
|
Of course we could have gone deeper into the various concepts but as mentioned earlier, this is meant to give a BIG HUG to the newcomers.
|
|
It's a quite a lot to ingest but with patience and practice, this will become more and more natural.
|
|
|
|
I hope you found some of this useful, now [what about starting your journey through the book?]("https://www.thebookofshaders.com/")
|