Skip to content

Commit f1e182d

Browse files
committed
Tutorial 2 Added
1 parent 25163d7 commit f1e182d

File tree

14 files changed

+325
-7
lines changed

14 files changed

+325
-7
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
!.gitignore
55
!Introduction
66
!README.md
7-
!LICENSE
7+
!LICENSE

Tutorial 1 - Final Shader Program/shaders/final.fsh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
varying vec2 TexCoords;
44

5-
uniform sampler2D gcolor;
5+
uniform sampler2D colortex0;
66

77
void main() {
8-
vec3 Color = texture2D(gcolor, TexCoords).rgb;
8+
vec3 Color = texture2D(colortex0, TexCoords).rgb;
99
Color = vec3(dot(Color, vec3(0.333f)));
1010
gl_FragColor = vec4(Color, 1.0f);
1111
}

Tutorial 1 - Final Shader Program/tutorial/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,26 +60,26 @@ After the vertex shader comes the fragment shader. We start off by declaring the
6060
varying vec2 TexCoords;
6161
```
6262

63-
Since final is a fullscreen pass, we need to sample the screen's color from somewhere. Since we have not defined any other program besides final, Optifine will use it's internal shader for the missing programs. The internal shader is basically a reimplementation of the vanilla shaders in the shader pipeline. The internal shaders outputs it's color to a texture called `gcolor`.
63+
Since final is a fullscreen pass, we need to sample the screen's color from somewhere. Since we have not defined any other program besides final, Optifine will use it's internal shader for the missing programs. The internal shader is basically a reimplementation of the vanilla shaders in the shader pipeline. The internal shaders outputs it's color to a texture called `colortex0`.
6464

6565
```glsl
66-
uniform sampler2D gcolor;
66+
uniform sampler2D colortex0;
6767
```
6868

6969
Now we enter the `main` function:
7070

7171
```glsl
7272
void main() {
7373
// Sample the color
74-
vec3 Color = texture2D(gcolor, TexCoords).rgb;
74+
vec3 Color = texture2D(colortex0, TexCoords).rgb;
7575
// Convert to grayscale
7676
Color = vec3(dot(Color, vec3(0.333f)));
7777
// Output the color
7878
gl_FragColor = vec4(Color, 1.0f);
7979
}
8080
```
8181

82-
Let's break it down line by line. We first sample `gcolor` using our texture coordinate. We use the function `texture2D` here since that is how you sampled from a 2D texture in old versions of GLSL. More modern versions have replaced this function with `texture`, however, that is not available in GLSL 120. We then convert the color to grayscale using `dot(Color, vec3(0.333f))` which is mathematically equivalent to `Color.r * 0.333f + Color.g * 0.333f + Color.b * 0.333f`. Then we finally output the color to `gl_FragColor`.
82+
Let's break it down line by line. We first sample `colortex0` using our texture coordinate. We use the function `texture2D` here since that is how you sampled from a 2D texture in old versions of GLSL. More modern versions have replaced this function with `texture`, however, that is not available in GLSL 120. We then convert the color to grayscale using `dot(Color, vec3(0.333f))` which is mathematically equivalent to `Color.r * 0.333f + Color.g * 0.333f + Color.b * 0.333f`. Then we finally output the color to `gl_FragColor`.
8383

8484
In the end, your fragment shader should be this:
8585

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
# Tutorial 2
2+
3+
Today we will explore the gbuffers and composite shader programs, and we will use our new knowledge to implement basic diffuse lighting
4+
5+
## What is gbuffers?
6+
7+
The gbuffers programs are the main geometry passes. In gbuffers, you can render entities, particals, blocks, etc from the point of view of the player. There are different types of gbuffers programs. For example, `gbuffers_terrain` renders blocks like stone and dirt, while `gbuffers_entities` renders entities like mobs and chests. In gbuffers you can use them for forward rendering or use them as the gbuffers stage in a deferred rendering pipeline.
8+
9+
## What is compsite?
10+
11+
The composite programs are fullscreen passes that run after all the gbuffers programs have finished executing. There are 15 of them, going from `composite`, `composite1`, `composite2`, `compositeN`, `composite15`. In the composite programs, you can do things like deferred lighting and post processing.
12+
13+
## The color textures and concept of drawbuffers
14+
15+
In the entire part of the shader pipeline from gbuffers to composite, you have 8 color textures to fiddle with. You can sample from them and write to them. Do note that these are the same 8 color textures no matter which part of the pipeline you are in. For example, if I wrote the albedo color to color texture 1 in `gbuffers_terrain`, and then sampled from it in `composite`, I would get the albedo color of the block. You can also use this feature to move data between shader stages. For example, if I wrote the color `vec3(0.5f)` to color texture 2 in `composite`, I would get the same color if I sampled from it in `composite1`. To select which color textures you want to write to, use the following comment somewhere in the fragment shader:
16+
17+
```glsl
18+
/* DRAWBUFFERS:NNNN */
19+
```
20+
21+
Here N coressponds to a buffer index, which is basically which color texture you want to render to. You don't have to have exactly 4 render targets, but you must at least have 1. Here is an example we are going to use in our shader today:
22+
23+
```glsl
24+
/* DRAWBUFFERS:01 */
25+
```
26+
27+
This allows me to write to color textures 0 and 1. To actually write to the color textures from the shader, you can use `gl_FragData`. Here is another example:
28+
29+
```glsl
30+
/* DRAWBUFFERS:37 */
31+
gl_FragData[0] = SomethingToWriteToColorTexture3;
32+
gl_FragData[1] = SomethingToWriteToColorTexture7;
33+
```
34+
35+
You can also specify the format of color textures like so:
36+
37+
```glsl
38+
const int RGBA16 = 1;
39+
const int RGBA32F = 1;
40+
41+
const int colortex2Format = RGBA16;
42+
const int colortex4Format = RGBA32F;
43+
```
44+
45+
See the Optifine docuementation for the full list of availble color texture formats.
46+
47+
### Color texture names
48+
49+
In old shader code, you might see this:
50+
51+
```glsl
52+
// The legacy names
53+
uniform sampler2D gcolor; // color texture 0
54+
uniform sampler2D gdepth; // color texture 1
55+
uniform sampler2D gnormal; // color texture 2
56+
uniform sampler2D composite; // color texture 3
57+
uniform sampler2D gaux1; // color texture 4
58+
uniform sampler2D gaux2; // color texture 5
59+
uniform sampler2D gaux3; // color texture 6
60+
uniform sampler2D gaux4; // color texture 7
61+
```
62+
63+
In more modern shader code you would see this:
64+
65+
```glsl
66+
uniform sampler2D colortex0; // color texture 0
67+
uniform sampler2D colortex1; // color texture 1
68+
uniform sampler2D colortex2; // color texture 2
69+
uniform sampler2D colortex3; // color texture 3
70+
uniform sampler2D colortex4; // color texture 4
71+
uniform sampler2D colortex5; // color texture 5
72+
uniform sampler2D colortex6; // color texture 6
73+
uniform sampler2D colortex7; // color texture 7
74+
```
75+
76+
I will not be using the legacy names in my tutorials.
77+
78+
## Show me the code
79+
80+
Create a new shader pack. We do not want to keep the old grayscale code from the previous tutorial. Let's start off with the vertex shader of `gbuffers_terrain`.
81+
82+
```glsl
83+
#version 120
84+
85+
varying vec2 TexCoords;
86+
varying vec3 Normal;
87+
88+
void main() {
89+
gl_Position = ftransform();
90+
TexCoords = gl_MultiTexCoord0.st;
91+
Normal = gl_NormalMatrix * gl_Normal;
92+
}
93+
```
94+
95+
We first declare 2 `varying` variables, which are our normal vector and our texture coordinates. In the `main` function, we transform our vertex, and assign values to our texture coordinates and normal vector. `gl_Normal` is an in-built `attribute` variable representing the world space normal vector. However, we have to transform our normal from world space to view space since most of our calculations are done in view space. We will see why that is later. We do the normal transformation using an in-built `uniform` variabled called the `gl_NormalMatrix`.
96+
97+
Let's take a look at the fragment shader.
98+
99+
```glsl
100+
#version 120
101+
102+
varying vec2 TexCoords;
103+
varying vec3 Normal;
104+
105+
uniform sampler2D texture;
106+
107+
void main(){
108+
vec4 albedo = texture2D(texture, TexCoords);
109+
/* DRAWBUFFERS:01 */
110+
gl_FragData[0] = albedo;
111+
gl_FragData[1] = vec4(Normal * 0.5f + 0.5f, 1.0f);
112+
}
113+
```
114+
115+
Here, we specify our drawbuffers and write to them. In draw buffer index 0, we write the albedo color, and in index 1 we write the normal. We have to move our normal vector to the [0, 1] range since our color textures are going to have an unsigned integer format. I could use an signed integer format, but that is only availble in newer versions of Optifine, and floating point formats are either going to be way too large or will not have enough precision. If we run our shader as is to make sure everything is working, we run into a problem:
116+
117+
![Broken Vegitation](images/broken_vegitation.png)
118+
119+
This problem occurs because the textures of blocks like grass and leaves are stored as gray scale on the texture atlas. These values are expected to be multipled by a per-vertex color attribute representing biome color. Here are our fixed vertex and fragment shaders.
120+
121+
Vertex shader:
122+
123+
```glsl
124+
#version 120
125+
126+
varying vec2 TexCoords;
127+
varying vec3 Normal;
128+
varying vec4 Color;
129+
130+
void main() {
131+
// Transform the vertex
132+
gl_Position = ftransform();
133+
// Assign values to varying variables
134+
TexCoords = gl_MultiTexCoord0.st;
135+
Normal = gl_NormalMatrix * gl_Normal;
136+
Color = gl_Color;
137+
}
138+
```
139+
140+
Fragment shader:
141+
142+
```glsl
143+
#version 120
144+
145+
varying vec2 TexCoords;
146+
varying vec3 Normal;
147+
varying vec4 Color;
148+
149+
// The texture atlas
150+
uniform sampler2D texture;
151+
152+
void main(){
153+
// Sample from texture atlas and account for biome color + ambien occlusion
154+
vec4 albedo = texture2D(texture, TexCoords) * Color;
155+
/* DRAWBUFFERS:01 */
156+
// Write the values to the color textures
157+
gl_FragData[0] = albedo;
158+
gl_FragData[1] = vec4(Normal * 0.5f + 0.5f, 1.0f);
159+
}
160+
```
161+
162+
`gl_Color` is an in-built attribute variable that, in this case, represents block color. For vegitation blocks, this is the biome color. For non-vegitative blocks, this is just `vec4(1.0f)`. Here is the result of our fix:
163+
164+
![Fixed Vegitation](images/fixed_vegitation.png)
165+
166+
Everything is looking much nicer now. We also get free ambient occlusion by doing this:
167+
168+
![Free Ambient Occlusion](images/free_ao.png)
169+
170+
Look closely at the places where a corner is formed by the intersection of blocks. You will notice that it looks a bit darker. This is because the color ends up getting darkened in occluded spots. One thing to note is that both the block color and texture atlas have display gamma baked into them. That is, if you want to do any lighting calculations on them, you have to convert your albedo from gamma sRGB to linear sRGB. We will do that in `composite`.
171+
172+
Speaking of `composite`, let's look at the vertex shader `composite.vsh:
173+
174+
```glsl
175+
#version 120
176+
177+
varying vec2 TexCoords;
178+
179+
void main() {
180+
gl_Position = ftransform();
181+
TexCoords = gl_MultiTexCoord0.st;
182+
}
183+
```
184+
185+
This is pretty much a copy-paste of our `final.vsh` vertex shader from the previous tutorial. The fragment shader is different however:
186+
187+
```glsl
188+
#version 120
189+
190+
varying vec2 TexCoords;
191+
192+
// Direction of the sun (not normalized!)
193+
uniform vec3 sunPosition;
194+
195+
// The color textures which we wrote to
196+
uniform sampler2D colortex0;
197+
uniform sampler2D colortex1;
198+
199+
/*
200+
const int colortex0Format = RGBA16;
201+
const int colortex1Format = RGBA16;
202+
*/
203+
204+
const float sunPathRotation = -40.0f;
205+
206+
const float Ambient = 0.1f;
207+
208+
void main(){
209+
// Account for gamma correction
210+
vec3 Albedo = pow(texture2D(colortex0, TexCoords).rgb, vec3(2.2f));
211+
// Get the normal
212+
vec3 Normal = normalize(texture2D(colortex1, TexCoords).rgb * 2.0f - 1.0f);
213+
// Compute cos theta between the normal and sun directions
214+
float NdotL = max(dot(Normal, normalize(sunPosition)), 0.0f);
215+
// Do the lighting calculations
216+
vec3 Diffuse = Albedo * (NdotL + Ambient);
217+
/* DRAWBUFFERS:0 */
218+
// Finally write the diffuse color
219+
gl_FragData[0] = vec4(Diffuse, 1.0f);
220+
}
221+
```
222+
223+
`sunPosition` here is the direction of the sun. `colortex0` and `colortex1` are the color textures we wrote to in `gbuffers_terrain`. After that we define out color texture formats. We will be using `RGBA16`. Notice how it was declared as a comment. It still works this way. After that, `sunPathRotation` is a variable Optifine reads. It describes how titled the sun is from an overhead path in degrees. `Ambient` is an ambient lighting factor we will use in our lighting calculations. Now we reach `main`. We first sample the albedo and then account for gamma correction. We also sample the normal and bring it back to teh [-1, 1] range. I normalize it jsut be sure it is a unit vector. Then I compute the dot product between the normal vector and the sun direction. For some reason, `sunPosition` is not a unit vector so I have to normalize it. Then I compute the lighting value and write to drawbuffer 0. Then this value gets read by `final`, and it gets gamma corrected and written to the screen. Here is what our shader looks like now:
224+
225+
![Simple Diffuse Lighting](images/simple_diffuse.png)
226+
227+
Notice how blocks facing away from the sun are lit less that those that are. Also notice how the sky looks completely broken. This is because `composite` is a fullscreen pass and that doesn't mean it does not run for the sky as well. We will fix this bug in a later tutorial. In the next tutorial we will look at using the lightmap to account for both torch and sky lighting.
Loading
Loading
Loading
Loading
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#version 120
2+
3+
varying vec2 TexCoords;
4+
5+
// Direction of the sun (not normalized!)
6+
uniform vec3 sunPosition;
7+
8+
// The color textures which we wrote to
9+
uniform sampler2D colortex0;
10+
uniform sampler2D colortex1;
11+
12+
/*
13+
const int colortex0Format = RGBA16;
14+
const int colortex1Format = RGBA16;
15+
*/
16+
17+
const float sunPathRotation = -40.0f;
18+
19+
const float Ambient = 0.1f;
20+
21+
void main(){
22+
// Account for gamma correction
23+
vec3 Albedo = pow(texture2D(colortex0, TexCoords).rgb, vec3(2.2f));
24+
// Get the normal
25+
vec3 Normal = normalize(texture2D(colortex1, TexCoords).rgb * 2.0f - 1.0f);
26+
// Compute cos theta between the normal and sun directions
27+
float NdotL = max(dot(Normal, normalize(sunPosition)), 0.0f);
28+
// Do the lighting calculations
29+
vec3 Diffuse = Albedo * (NdotL + Ambient);
30+
/* DRAWBUFFERS:0 */
31+
// Finally write the diffuse color
32+
gl_FragData[0] = vec4(Diffuse, 1.0f);
33+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#version 120
2+
3+
varying vec2 TexCoords;
4+
5+
void main() {
6+
gl_Position = ftransform();
7+
TexCoords = gl_MultiTexCoord0.st;
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#version 120
2+
3+
varying vec2 TexCoords;
4+
5+
uniform sampler2D colortex0;
6+
7+
void main() {
8+
// Sample and apply gamma correction
9+
vec3 Color = pow(texture2D(colortex0, TexCoords).rgb, vec3(1.0f / 2.2f));
10+
gl_FragColor = vec4(Color, 1.0f);
11+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#version 120
2+
3+
varying vec2 TexCoords;
4+
5+
void main() {
6+
gl_Position = ftransform();
7+
TexCoords = gl_MultiTexCoord0.st;
8+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#version 120
2+
3+
varying vec2 TexCoords;
4+
varying vec3 Normal;
5+
varying vec4 Color;
6+
7+
// The texture atlas
8+
uniform sampler2D texture;
9+
10+
void main(){
11+
// Sample from texture atlas and account for biome color + ambien occlusion
12+
vec4 albedo = texture2D(texture, TexCoords) * Color;
13+
/* DRAWBUFFERS:01 */
14+
// Write the values to the color textures
15+
gl_FragData[0] = albedo;
16+
gl_FragData[1] = vec4(Normal * 0.5f + 0.5f, 1.0f);
17+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#version 120
2+
3+
varying vec2 TexCoords;
4+
varying vec3 Normal;
5+
varying vec4 Color;
6+
7+
void main() {
8+
// Transform the vertex
9+
gl_Position = ftransform();
10+
// Assign values to varying variables
11+
TexCoords = gl_MultiTexCoord0.st;
12+
Normal = gl_NormalMatrix * gl_Normal;
13+
Color = gl_Color;
14+
}

0 commit comments

Comments
 (0)