You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/content/docs/current/Guides/Your First Shaderpack/1_composite.mdx
+9-2Lines changed: 9 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,7 +6,12 @@ sidebar:
6
6
order: 2
7
7
---
8
8
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
+
9
13
## Setting Up the File Structure
14
+
10
15
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.
11
16
12
17
```
@@ -31,6 +36,7 @@ It is always important to respect the license associated with code when you use
31
36
When you select the shader in the shader selection screen, you should not see any errors in the logs.
32
37
33
38
## The `composite` Pass
39
+
34
40
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.
35
41
36
42
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.
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.
84
90
85
91
:::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>).
87
93
:::
88
94
89
95
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() {
107
113
108
114
Let's analyse this as well a bit.
109
115
110
-
111
116
```glsl
112
117
uniform sampler2D colortex0
113
118
```
@@ -139,13 +144,15 @@ color = texture(colortex0, texcoord);
139
144
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).
140
145
141
146
## Making It Grayscale
147
+
142
148
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.
143
149
144
150
:::tip[What's a dot product?]
145
151
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).
Copy file name to clipboardExpand all lines: src/content/docs/current/Guides/Your First Shaderpack/2_gbuffers.mdx
+22-5Lines changed: 22 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,6 +6,10 @@ sidebar:
6
6
order: 2
7
7
---
8
8
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
+
9
13
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`.
10
14
11
15
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
27
31
:::
28
32
29
33
## The Vertex Shader
34
+
30
35
Let's open `gbuffers_terrain.vsh` and take a peek inside.
31
36
32
37
```glsl
@@ -47,6 +52,7 @@ void main() {
47
52
You will notice that this time, there are three `out` variables. Let's go over each one.
48
53
49
54
### `texcoord`
55
+
50
56
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.
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.
61
67
62
68
### `glcolor`
69
+
63
70
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.
Let's undo that, since having color is quite nice.
68
75
69
76
### Normals
77
+
70
78
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.
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.
95
104
96
105
### `gtexture`
106
+
97
107
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.
This is an example of what the texture atlas looks like, however it can vary.
101
111
102
112
### `lightmap`
113
+
103
114
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.
104
115
105
116
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
109
120
:::
110
121
111
122
## Normals
123
+
112
124
Next, let's add that new `in` declaration for the normal.
113
125
114
126
```glsl
@@ -122,30 +134,34 @@ To do this, we can just add a new line at the end of `main`:
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.
127
140
128
141
:::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!
130
143
:::
131
144
132
145
Again, let's undo this, as it is not how we want our shader to look.
133
146
134
147
## Colors
148
+
135
149
As you can see, `main` contains the following code (with comments added here for clarification).
150
+
136
151
```glsl
137
152
color = texture(gtexture, texcoord) * glcolor; // biome tint
138
153
color *= texture(lightmap, lmcoord); // lightmap lighting [REMOVE ME!]
139
-
if (color.a < 0.1) { // alpha test
154
+
if (color.a < alphaTestRef) { // alpha test
140
155
discard; // don't bother writing
141
156
}
142
157
```
143
158
144
159
- The first step multiplies the color by `glcolor` to get the biome tint.
145
160
- 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.
147
162
148
163
## Writing Extra Data
164
+
149
165
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.
150
166
151
167
```glsl
@@ -157,12 +173,13 @@ layout(location = 2) out vec4 encodedNormal;
157
173
158
174
:::caution[Warning]
159
175
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
+
160
177
```glsl
161
178
layout(location = 0) out vec4 someData; // writes to colortex5
162
179
layout(location = 1) out vec4 someMoreData; // writes to colortex3
163
180
```
164
-
:::
165
181
182
+
:::
166
183
167
184
:::note[Note]
168
185
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)
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.
179
196
180
197
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.
Copy file name to clipboardExpand all lines: src/content/docs/current/Guides/Your First Shaderpack/3_deferred_lighting.mdx
+28-8Lines changed: 28 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,11 +1,15 @@
1
1
---
2
-
title: Lighting in Composites
2
+
title: Deferred Lighting
3
3
description: Implement basic diffuse shading in a composite pass
4
4
sidebar:
5
-
label: Lighting in Composites
5
+
label: Deferred Lighting
6
6
order: 3
7
7
---
8
8
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
+
9
13
In this section we will implement basic diffuse shading in the composite pass. As such, we will be primarily editing `composite.fsh`.
10
14
11
15
## Gamma Correction
@@ -33,26 +37,28 @@ const int colortex0Format = RGB16;
33
37
```
34
38
35
39
:::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.
37
41
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.
39
43
40
44
```glsl
41
45
// const int colortex0Format = RGB16;
42
46
```
47
+
43
48
:::
44
49
45
50
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!
46
51
47
52
:::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.
49
54
:::
50
55
51
56
For more information on gamma correction, check out the [LearnOpenGL article](https://learnopengl.com/Advanced-Lighting/Gamma-Correction).
52
57
53
-
54
58
## Decoding Data
59
+
55
60
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
+
56
62
```glsl
57
63
uniform sampler2D colortex1;
58
64
uniform sampler2D colortex2;
@@ -67,21 +73,26 @@ vec3 normal = normalize((encodedNormal - 0.5) * 2.0); // we normalize to make su
67
73
```
68
74
69
75
Next, let's verify everything is being decoded correctly.
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.
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.
82
91
83
92
## Lighting
93
+
84
94
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
+
85
96
- Ambient (some constant factor, so you can see in caves)
86
97
- Skylight (blue in the day, uses the lightmap). This is our **indirect lighting**.
87
98
- 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
90
101
So, to apply our lighting, we would then do something like `color.rgb *= ambient + skylight + blocklight + sunlight`.
91
102
92
103
First, let's define some colors/brightnesses for each term. I chose the following on a whim.
@@ -118,6 +130,7 @@ Don't worry if your sky isn't the same color. We'll fix that later!
118
130
:::
119
131
120
132
## Sunlight
133
+
121
134
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.
122
135
123
136
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.
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.
152
172
153
173
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.
0 commit comments