Skip to content

Commit d214453

Browse files
committed
improve tutorial
1 parent 1ab524e commit d214453

8 files changed

Lines changed: 134 additions & 70 deletions

File tree

src/content/docs/current/Guides/Your First Shader/0_intro.mdx renamed to src/content/docs/current/Guides/Your First Shaderpack/0_intro.mdx

File renamed without changes.

src/content/docs/current/Guides/Your First Shader/1_composite.mdx renamed to src/content/docs/current/Guides/Your First Shaderpack/1_composite.mdx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ sidebar:
66
order: 2
77
---
88

9+
:::note
10+
Full code for this stage of the tutorial is available for reference [on GitHub](https://github.com/IrisShaders/tutorial-code/tree/main/Your%20First%20Shader/1.%20Your%20First%20Shader%20Effect).
11+
:::
12+
913
## Setting Up the File Structure
14+
1015
Minecraft shaders require a specific structure of files in the right places to load code. While it's important to understand this structure, to save time, we will be working with the Base 330 pack from shaderLABS. Download it from [here](https://github.com/shaderLABS/Base-330), and extract it into your `shaderpacks` folder. You should have the following structure.
1116

1217
```
@@ -31,6 +36,7 @@ It is always important to respect the license associated with code when you use
3136
When you select the shader in the shader selection screen, you should not see any errors in the logs.
3237

3338
## The `composite` Pass
39+
3440
For this shader, we will be using the first `composite` pass. This is a full screen pass which runs just after all gbuffers programs have rendered.
3541

3642
First, let's open `composite.vsh`. This is the **vertex shader** for the `composite` program. Since `composite` is a fullscreen pass, this actually just renders a singular quad (a rectangular polygon) to the screen which exactly covers it. This means that your fullscreen passes are technically actually running on 3D geometry! Specifically, the vertex shader will run four times, one for each corner of this quad.
@@ -83,7 +89,7 @@ texcoord = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy;
8389
This gives us the 'texture coordinate' of the current vertex. This is more commonly known as the 'UV', and it is used so that the fragment shader knows where on the screen it is. These texture coordinates range from (0, 0) at the bottom left of the texture to (1, 1) at the top right.
8490

8591
:::tip[Swizzling]
86-
In the previous line of code, you might have noticed some weird syntax: `.xy`. This is an operation unique to shading languages known as **swizzling**. You can read more about swizzling [here](https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Swizzling).
92+
In the previous line of code, you might have noticed some weird syntax: `.xy`. This is an operation unique to shading languages known as **swizzling**. You can read more about swizzling [here](<https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Swizzling>).
8793
:::
8894

8995
Let's open `composite.fsh`. This is the **fragment shader**, and since `composite` is a full screen pass, it runs for every pixel on the screen.
@@ -107,7 +113,6 @@ void main() {
107113

108114
Let's analyse this as well a bit.
109115

110-
111116
```glsl
112117
uniform sampler2D colortex0
113118
```
@@ -139,13 +144,15 @@ color = texture(colortex0, texcoord);
139144
This reads the value in `colortex0` at position `texcoord` and stores it in `color`. For more info, see [the OpenGL docs](https://registry.khronos.org/OpenGL-Refpages/gl4/html/texture.xhtml).
140145

141146
## Making It Grayscale
147+
142148
A color is grayscale when the r, g, and b components all have the same value. We can compute this value by taking the dot product of the original color and a `vec3(1.0/3.0)`. This is mathematically equivalent to multiplying each channel by 1/3 and then summing the products together (averaging them). If we set the r, g, and b channels of `color` to this value, the resulting image will be converted to grayscale.
143149

144150
:::tip[What's a dot product?]
145151
If you don't know what a dot product is, you're probably new to linear algebra as well. We recommend studying up on it (or at least learning about unfamiliar concepts throughout the chapters on your own) as from this point on everything you do will be in relation to it somehow. A great place to get started is [3Blue1Brown's series of tutorials](https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab).
146152
:::
147153

148154
So, after we get the value of `color`, we can do:
155+
149156
```glsl
150157
float grayscale = dot(color.rgb, vec3(1.0 / 3.0));
151158
color.rgb = vec3(grayscale);

src/content/docs/current/Guides/Your First Shader/2_gbuffers.mdx renamed to src/content/docs/current/Guides/Your First Shaderpack/2_gbuffers.mdx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ sidebar:
66
order: 2
77
---
88

9+
:::note
10+
Full code for this stage of the tutorial is available for reference [on GitHub](https://github.com/IrisShaders/tutorial-code/tree/main/Your%20First%20Shader/2.%20The%20Gbuffers).
11+
:::
12+
913
In this next section, we will start with a fresh copy of the Base-330 pack, instead of using the grayscale version we made in the previous step. You could also just remove the lines you added to `composite.fsh`.
1014

1115
For the purposes of this tutorial, we will only be covering the shading of terrain. Therefore, it is worth deleting all other files starting with `gbuffers_`. You can also get rid of the `deferred` files. You should now just have
@@ -27,6 +31,7 @@ When a gbuffers program is not present, Iris will fall back to using the most si
2731
:::
2832

2933
## The Vertex Shader
34+
3035
Let's open `gbuffers_terrain.vsh` and take a peek inside.
3136

3237
```glsl
@@ -47,6 +52,7 @@ void main() {
4752
You will notice that this time, there are three `out` variables. Let's go over each one.
4853

4954
### `texcoord`
55+
5056
In the previous tutorial, we had a variable called `texcoord`, which gave us the texture coordinate onscreen of the given fragment. In the gbuffers, the texture coordinate instead represents the coordinate in the **texture atlas**. The texture atlas contains the textures of every block. We will cover this in the next section.
5157

5258
### `lmcoord`
@@ -60,13 +66,15 @@ lmcoord / (30.0 / 32.0) - (1.0 / 32.0);
6066
This now gets us the correct light level. You will notice now that if you reload the shader, lighting is now broken. We will fix this later on.
6167

6268
### `glcolor`
69+
6370
Some blocks, like grass, have a tint based on their biome, provided in the form of `gl_Color`. If you look in the files for the grass block texture, it is actually grey. To demonstrate this, let's set `glcolor` to `vec4(1.0)`. Since the color is multiplied by glcolor in the fragment shader, and multiplying by 1 does nothing, this will remove the tint.
6471

6572
![](../../../../../assets/beginner_tutorial/notint.webp)
6673

6774
Let's undo that, since having color is quite nice.
6875

6976
### Normals
77+
7078
Before we move onto the fragment shader, there's one more value we'll want to use later - the **normal**. This is a 3 dimensional vector representing the direction the current vertex is facing in. Let's add a new `out` declaration for the normal.
7179

7280
```glsl
@@ -91,15 +99,18 @@ uniform mat4 gbufferModelViewInverse;
9199
```
92100

93101
## The Fragment Shader
102+
94103
With those values being passed through, let's move onto the fragment shader (`gbuffers_terrain.fsh`). You'll notice that two textures are being sampled.
95104

96105
### `gtexture`
106+
97107
This is the texture atlas we mentioned earlier. It contains the textures of all the blocks onscreen, and `texcoord` tells us where in the atlas the current fragment texture is.
98108

99109
![](../../../../../assets/beginner_tutorial/textureatlas.webp)
100110
This is an example of what the texture atlas looks like, however it can vary.
101111

102112
### `lightmap`
113+
103114
Remember how by default, Minecraft uses a texture with colors for each light level? This texture contains those colors. Since we aren't using the texture anymore, we can get rid of this line.
104115

105116
Next, you'll notice that we are writing to `colortex0`. This is generally where most shaders store the main image, with supplementary info in the other buffers. For example, you could store the light levels in `colortex1`, and use them for lighting in `composite`. In fact, we are going to do exactly that.
@@ -109,6 +120,7 @@ You might be wondering why you'd do lighting in a fullscreen pass, when you coul
109120
:::
110121

111122
## Normals
123+
112124
Next, let's add that new `in` declaration for the normal.
113125

114126
```glsl
@@ -122,30 +134,34 @@ To do this, we can just add a new line at the end of `main`:
122134
```glsl
123135
color.rgb = normal;
124136
```
137+
125138
![](../../../../../assets/beginner_tutorial/normals.webp)
126139
You can see that faces that face upwards are green. Colors are stored in the `rgba` format and vectors/coordinates in the `xyzw` format. Since both of these are stored in the same `vec4` format, this means that the `r` component represents the `x` component, and so on. Since `g` represents `y`, this means that if the face is green, then the normal must only have a value in the `y` component, and hence is facing upwards.
127140

128141
:::tip[Negative Normals]
129-
You'll notice some faces are black. This occurs when the normals are negative, since you cannot have a negative color!
142+
You'll notice some faces are black. This occurs when the normals are negative, since you cannot have a negative color!
130143
:::
131144

132145
Again, let's undo this, as it is not how we want our shader to look.
133146

134147
## Colors
148+
135149
As you can see, `main` contains the following code (with comments added here for clarification).
150+
136151
```glsl
137152
color = texture(gtexture, texcoord) * glcolor; // biome tint
138153
color *= texture(lightmap, lmcoord); // lightmap lighting [REMOVE ME!]
139-
if (color.a < 0.1) { // alpha test
154+
if (color.a < alphaTestRef) { // alpha test
140155
discard; // don't bother writing
141156
}
142157
```
143158

144159
- The first step multiplies the color by `glcolor` to get the biome tint.
145160
- The second step multiplies the color by Minecraft's defualt lighting color. You should remove this, as we are going to do our own lighting.
146-
- Finally, if the color's alpha (transparency) is less than 0.1, we `discard`, which tells the shader program to return and not write anything. This potentially saves us some texture writes.
161+
- Finally, if the color's alpha (transparency) is less than `alphaTestRef` (this is set by Iris, but usually 0.1), we `discard`, which tells the shader program to return and not write anything. This potentially saves us some texture writes.
147162

148163
## Writing Extra Data
164+
149165
So, since we are going to do lighting in `composite`, we need to send the normal and the lightmap data to it. We do this by storing it in a texture. Let's append our `RENDERTARGETS` so we can write to more textures, and bind some variables to these other two textures.
150166

151167
```glsl
@@ -157,12 +173,13 @@ layout(location = 2) out vec4 encodedNormal;
157173

158174
:::caution[Warning]
159175
It is important to note that `location = 0` is not because we are writing to `colortex0`, but because the first element in the `RENDERTARGETS` directive is at index 0. For example, if we had `/* RENDERTARGETS: 5,3 */`, then we would have
176+
160177
```glsl
161178
layout(location = 0) out vec4 someData; // writes to colortex5
162179
layout(location = 1) out vec4 someMoreData; // writes to colortex3
163180
```
164-
:::
165181

182+
:::
166183

167184
:::note[Note]
168185
Iris implements by default something known as 'buffer flipping'. This means that the texture you write to is not actually the same one you read from. This is because different instances of the shader might run at different times and if multiple of them try to access the same pixel, the output could be different depending on which one gets to it first. For more information, see [the docs](/current/reference/shadersproperties/rendering/#flip)
@@ -175,7 +192,7 @@ lightmapData = vec4(lmcoord, 0.0, 1.0);
175192
encodedNormal = vec4(normal * 0.5 + 0.5, 1.0);
176193
```
177194

178-
There are a couple of things here to note. First of all, we *always set the alpha to 1.0*. This is to ensure that the data always gets written, because if the alpha is 0, Iris may decide that it shouldn't be written anyway. This is because of alpha blending. See [LearnOpenGL's page on blending](https://learnopengl.com/Advanced-OpenGL/Blending) for more info.
195+
There are a couple of things here to note. First of all, we _must set the alpha to 1.0_. This is to ensure that the data always gets written correctly, as otherwise your GPU may treat the values you are writing as transparent, and either blend or not write them at all. In most cases, a better option is to simply [stop the pass from blending to that buffer](/current/reference/shadersproperties/rendering/#blend) See [LearnOpenGL's page on blending](https://learnopengl.com/Advanced-OpenGL/Blending) for more info.
179196

180197
Secondly, since textures by default can only store numbers between 0.0 and 1.0, we must convert our components - which can range from -1.0 to 1.0 - into the [0, 1] range.
181198

src/content/docs/current/Guides/Your First Shader/3_composite_lighting.mdx renamed to src/content/docs/current/Guides/Your First Shaderpack/3_deferred_lighting.mdx

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
---
2-
title: Lighting in Composites
2+
title: Deferred Lighting
33
description: Implement basic diffuse shading in a composite pass
44
sidebar:
5-
label: Lighting in Composites
5+
label: Deferred Lighting
66
order: 3
77
---
88

9+
:::note
10+
Full code for this stage of the tutorial is available for reference [on GitHub](https://github.com/IrisShaders/tutorial-code/tree/main/Your%20First%20Shader/3.%20Deferred%20Lighting).
11+
:::
12+
913
In this section we will implement basic diffuse shading in the composite pass. As such, we will be primarily editing `composite.fsh`.
1014

1115
## Gamma Correction
@@ -33,26 +37,28 @@ const int colortex0Format = RGB16;
3337
```
3438

3539
:::caution[Warning]
36-
Observe that the declaration for the format is in a *multi-line comment*. This is because this code does not actually need to make it onto the GPU. Instead, Iris reads it, and knows to set the format of the buffer as we defined it. That means we can place this code anywhere in the shaderpack.
40+
Observe that the declaration for the format is in a _multi-line comment_. This is because this code does not actually need to make it onto the GPU. Instead, Iris reads it, and knows to set the format of the buffer as we defined it. That means we can place this code anywhere in the shaderpack.
3741

38-
The following is *not* valid! The declaration must be on its own line.
42+
The following is _not_ valid! The declaration must be on its own line.
3943

4044
```glsl
4145
// const int colortex0Format = RGB16;
4246
```
47+
4348
:::
4449

4550
By default, `colortex` buffers are RGBA8, meaning they store 4 channels of 8-bit integers which can each encode 256 values, for a total of 16,777,216 colors and 256 alpha values. By increasing the precision to 16-bit, each channel can now encode 65535 color values -- over 280 trillion colors in total!
4651

4752
:::tip[VRAM Costs]
48-
Just like your CPU, GPUs have access to a limited amount of memory, know as Video RAM (VRAM). Increasing the precision of your buffers also increases their VRAM cost. While modern GPUs have more than enough VRAM to support one higher-precision buffer, it is always important to consider how much precision you actually need. While this tutorial will increase the precision for simplicity, it would be more memory efficient to stick to using RGBA8 and convert colors to and from sRGB whenever writing them to the buffer.
53+
Just like your CPU, GPUs have access to a limited amount of memory, know as Video RAM (VRAM). Increasing the precision of your buffers also increases their VRAM cost. While modern GPUs have more than enough VRAM to support one higher-precision buffer, it is always important to consider how much precision you actually need.
4954
:::
5055

5156
For more information on gamma correction, check out the [LearnOpenGL article](https://learnopengl.com/Advanced-Lighting/Gamma-Correction).
5257

53-
5458
## Decoding Data
59+
5560
With all that out of the way, let's read back in the lightmap and normal data we stored in `colortex1` and `colortex2` in the previous tutorial. First, we need to be able to sample the textures.
61+
5662
```glsl
5763
uniform sampler2D colortex1;
5864
uniform sampler2D colortex2;
@@ -67,21 +73,26 @@ vec3 normal = normalize((encodedNormal - 0.5) * 2.0); // we normalize to make su
6773
```
6874

6975
Next, let's verify everything is being decoded correctly.
76+
7077
```glsl
7178
color.rgb = vec3(lightmap, 0.0);
7279
```
80+
7381
![](../../../../../assets/beginner_tutorial/lightmap.webp)
7482

7583
You can see that where there is skylight, only the green component is set. However, where there is blocklight (by the torch), the color is yellow. Since red and green make yellow in RGB, we know that blocklight is stored in the red component and sunlight is stored in the green component.
7684

7785
```glsl
7886
color.rgb = normal;
7987
```
88+
8089
![](../../../../../assets/beginner_tutorial/normals.webp)
81-
Yep, that looks pretty *normal*. You'll notice that the sky is black here. This is because the `gbuffers_terrain` program does not run for the sky, so no data is stored for these pixels. We will resolve this later on.
90+
Yep, that looks pretty _normal_. You'll notice that the sky is black here. This is because the `gbuffers_terrain` program does not run for the sky, so no data is stored for these pixels. We will resolve this later on.
8291

8392
## Lighting
93+
8494
To apply lighting to our color, we want to multiply it by the color of the light hitting it. Of course, with multiple light sources, there are multiple types of light hitting it. For our shader, we will have the following types of light
95+
8596
- Ambient (some constant factor, so you can see in caves)
8697
- Skylight (blue in the day, uses the lightmap). This is our **indirect lighting**.
8798
- Blocklight (some warm color that matches torches, uses the lightmap)
@@ -90,6 +101,7 @@ To apply lighting to our color, we want to multiply it by the color of the light
90101
So, to apply our lighting, we would then do something like `color.rgb *= ambient + skylight + blocklight + sunlight`.
91102

92103
First, let's define some colors/brightnesses for each term. I chose the following on a whim.
104+
93105
```glsl
94106
const vec3 blocklightColor = vec3(1.0, 0.5, 0.08);
95107
const vec3 skylightColor = vec3(0.05, 0.15, 0.3);
@@ -118,6 +130,7 @@ Don't worry if your sky isn't the same color. We'll fix that later!
118130
:::
119131

120132
## Sunlight
133+
121134
Now, what about that sunlight? Well, if something is facing directly towards the sun, then we want it to be fully sunlit. On the other hand, if something is facing away from the sun, we want there to be no sunlight. So, we need a function that returns 1.0 if two vectors are facing in the same direction, and 0.0 if they are facing away from each other. Happily, we can use a dot product for this.
122135

123136
Now, the first thing we need to know is where the sun (or moon) is. We can do this with `uniform vec3 shadowLightPosition`. In the daytime this returns the sun's position, in the night, this returns the moon's position.
@@ -148,7 +161,14 @@ With that change, your lighting should seem a bit more realistic.
148161
![](../../../../../assets/beginner_tutorial/lighting2.webp)
149162

150163
## Fixing the Sky
151-
At this point, it may appear as if your Minecraft world is currently undergoing an apocalypse. This is because we are also applying lighting to the sky, despite the fact we do not have normal or lightmap data for it. So, how do we tell if a pixel is the sky or not? Well, we have access to something called the depth buffer, which tells you how far away a pixel is. If the pixel is at the maximum view distance, the depth buffer will store 1.0.
164+
165+
At this point, it may appear as if your Minecraft world is currently undergoing an apocalypse. This is because we are also applying lighting to the sky, despite the fact we do not have normal or lightmap data for it.
166+
167+
:::note[Note]
168+
If your sky does not appear broken, that is still fine. Reading from a buffer we have not written to is _undefined behavior_, so it may act differently on different hardware.
169+
:::
170+
171+
So, how do we tell if a pixel is the sky or not? Well, we have access to something called the depth buffer, which tells you how far away a pixel is. If the pixel is at the maximum view distance, the depth buffer will store 1.0.
152172

153173
To access the depth buffer, we use `uniform sampler2D depthtex0`. In between the code which reads the color from `colortex0` and the code which applies lighting, we can check if the pixel is the sky, and if it is, we can `return` from the `main` function.
154174

0 commit comments

Comments
 (0)