Skip to content

Commit d3c84de

Browse files
committed
Added files
1 parent eb4dffa commit d3c84de

38 files changed

+951
-0
lines changed

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Ignore all other shaderpacks
2+
#*
3+
# Add the files relating to the tutorial
4+
!.gitignore
5+
!Introduction
6+
!README.md
7+
!LICENSE

Introduction/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Overview
2+
3+
Here I will explain a few basics of the graphics in Minecraft and the shader pipeline and how Minecraft shaders work in general.
4+
5+
## Graphics in Minecraft
6+
7+
Minecraft is written in a really old version of OpenGL, and shaders are only available via OpenGL extensions. Since Minecraft is written in such an old OpenGL version, many (old) shaders are written in GLSL version 120. For simplicity's sake, I will be also writing my shaders in version 120. Many things very different between version 120 and more mordern versions of GLSL. Here are a few examples:
8+
9+
- You no longer specify vertex attributes with `layout(location = N) in genType var`. Instead you do `attribute genType var`. The shaders mod knows the locations of the attributes by using `glGetAttribLocation`.
10+
- You can't use `in` or `out` to transfer variables between the vertex and fragment shader. You must use a keyword called `varying`. `varying` was replaced with `in` and `out` in newer OpenGL versions because other shaders besides the vertex and fragment shader were added.
11+
- GLSL version 120 had a lot more in-built variables than modern ones. Some examples include `gl_Vertex` and `gl_Normal`. I will explain how these are used in the later tutorials I suggest you check out the [GLSL Version 120 Specifcation](https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.1.20.pdf) for the full list.
12+
- If you want to write to a render target you must use `gl_FragColor` and `gl_FragData`. `gl_FragData` is an array of `vec4` with size of `GL_MAX_COLOR_ATTACHMENTS` (typically 8, you can check the [OpenGL GPU database](https://opengl.gpuinfo.org/displaycapability.php?name=GL_MAX_COLOR_ATTACHMENTS) for more details). Each element of `gl_FragData` corresponds to a color attachment. For example, the first color attachment is `gl_FragData[0]`, the second one is `gl_FragData[1]`, and so on. `gl_FragColor` is like `gl_FragData` except it writes to all color attachments.
13+
14+
### Rendering the blocks
15+
16+
Now with that out of the way, we can focus on how Minecraft actually does it's rendering. Minecraft is a voxel game, and therefore it does not follow the normal style of rendering that is present in most games.First of all, Minecraft has to render a large amount of blocks, which could be different types of blocks. Rendering each block as it's own draw call is a really bad idea for performance. Intancing could work, but it has it's own limitations, besides not being present in ancient version of OpenGL at all. Instead, what Minecraft does it it batches verticies into a chunks of verticies, so each chunk becomes it's own draw call. To texture each block, Minecraft uses a texture atlas.
17+
18+
### Lighting
19+
20+
Lighting in Minecraft is a bit different from how it is done in other games. Minecraft needs to support an arbitrary number of light sources, with the features of old OpenGL versions, and have decent performance on slow hardware like Intel iGPUs and Apple Macs. There also needs to be occlusion detection for the lights, that is, a light behind a wall cannot light up what is in front of the the wall. Doing this the "normal" way would require storing all lights in a texture and having a texture atlas of shadow maps for each light. This doesn't support area lighting, so lighting from blocks like glowstone up close will look bad, and this would be insanely costly. Imagine how slow rendering the nether would be, since each lava block in the nether needs to be processed. Minecraft needs a different approach from this.
21+
22+
Some of you who play Minecraft will know that each block has a lighting level, which comes from both torches and how exposed a block is to the sky. Minecraft reuses this information for lighting the blocks. Each vertex has a `vec2` attribute known as the "lightmap coordinates". The `x` value represents lighting from blocks like torches and glowstone, while the `y` value represents how much the vertex is exposed to the sky. These values in older versions of Minecraft are from 0 to 15, but in newer versions it can be up to the 200s.
23+
24+
The lightmap alone is not enough to light the block. It somehow has to be converted to a lighting color which then has to be multiplied by the block color to obtain the final color that gets displayed on your screen. Minecraft by default uses the light map coordinates (after doing math to move them to the [0, 1] range) as texture coordinates to look up a lighting color value from a lightmap texture in the fragment shader. The lighting color value gets multiplied by the block color and then displayed on your screen. See the [Optifine documentation](https://github.com/sp614x/optifine/blob/master/OptiFineDoc/doc/custom_lightmaps.txt) on this for more details. We won't be using the light map coordinates to look up from the lightmap texture. We will be doing our own math to calculate the lighting value. This is what most other shaders do anyway (and when I mean most I mean 99.9%, if it doesn't, then it's probably a really bad or super old no-name shader)
25+
26+
## How Shaders Work
27+
28+
To understand how shaders work, lets understand how the shader pipeline works. The shader pipeline is comprised of a bunch of fullscreen passes, a few block and entity rendering passes, and a shadow pass. What shader packs do is define what goes on in each pass of the pipeline. To give an example, let's say I wanted to do a blur of what I see on my screen. I could write a fullscreen pass that does that. If this sounds confusing, don't worry, it will become much more easier to understand in the coming tutorials. (note: if any exprienced shader dev has a better explanation of this part, please contact me with the better explanation so I can update this section with it)

LICENSE

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright 2021 fuzdex
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Minecraft Shader Programming
2+
3+
This repository contains information and tutorials on how to write custom shaders for Minecraft use the Optifine mod. The tutorials start at the "Introduction" folder and then the "Tutorial N - ABCD" folders contain information on how to add a certain effect to your shader. The "Introduction" folder is not a shader pack, but the "Tutorial N - ABCD" is. For these tutorials the only requirement is a basic knowledge of graphics programming (you need to know everything up to the concept of rendering to a texture), although I plan to add another tutorial that explains all the basics in detail.
4+
5+
I originally created this project because when I was learning how to write Minecraft shaders, there was little to no information I could find on how to write them. These set of tutorials aims to get rid of that issue for those who want to learn how to write a Minecraft shader.
6+
7+
If you want to contribute your own tutorial, create a pull request. One of my end goals with this project is the turn this into a community effort.
8+
9+
## How to download
10+
11+
First make sure you have Optifine installed and find your `shaderpacks` folder.
12+
13+
If you have `git` installed, first move every shader in your `shaderpacks` folder to a temporary directory, so you can clone this repo into your `shaderpacks` folder and move everything back in.
14+
15+
If you do not have `git` installed, download this repo and move it's files into your `shaderpacks` folder.
16+
17+
## Acknowledgements
18+
19+
- The Continuum Minecraft shader tutorial, which helped me initially get started on shader programming.
20+
- The [shaderLabs Discord server](https://discord.gg/RpzWN9S) for helping me with my own shader programming.
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+
vec3 Color = texture2D(colortex0, TexCoords).rgb;
9+
Color = vec3(dot(Color, vec3(0.333f)));
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: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Tutorial 1
2+
3+
In this tutorial we explore the most basic fullscreen pass: The final pass.
4+
5+
## Basic file structure of a shader
6+
7+
Within a shader pack, there is a folder called "shaders". This is where all the shader code goes. All shader files have the extension `.vsh` for vertex shaders or `.fsh` for fragment shaders. The shaders with the same file name will become part of the same program. For example, in this case, `final.vsh` and `final.fsh` get linked toghter to form the `final` program.
8+
9+
## What is final?
10+
11+
final is a fullscreen pass. It is the last pass in the shader pipeline. Whatever color final outputs is the color that gets displayed on your screen. Here, you can do certain post processing effects like bluring, tone mapping, or gamma correction.
12+
13+
## What other fullscreen passes are there?
14+
15+
In Optifine, there are 3 types of fullscreen passes. They are called `deferred`, `composite`, and `final`. `final` is the fullscreen pass we just covered. `deferred` and `compsite` are fullscreen passes we will cover in later tutorials.
16+
17+
## Show me the code
18+
19+
We will be implementing a basic shader that converts the colors on your screen to grayscale. Let's start off with the vertex shader. We first start with the version declratation. We will be using GLSL version 120
20+
21+
```glsl
22+
#version 120
23+
```
24+
25+
Since final is a fullscreen pass, we need to pass a texture coordinate into the fragment shader.
26+
27+
```glsl
28+
varying vec2 TexCoords;
29+
```
30+
31+
Now we head into the `main` function of the vertex shader.
32+
33+
```glsl
34+
void main() {
35+
gl_Position = ftransform();
36+
TexCoords = gl_MultiTexCoord0.st;
37+
}
38+
```
39+
40+
If you have never seen `ftransform` or `gl_MultiTexCoord0` before, don't worry. They are part of the old versions of the GLSL shading language. `ftransform` basically expands to `gl_ModelViewProjectionMatrix * gl_Vertex`. `gl_Vertex` is the in-built vertex attribute.`gl_ModelViewProjectionMatrix` is the in-build model view projection matrix. Since `gl_Vertex` is probably in clip space already, `gl_ModelViewProjectionMatrix` is the identity matrix. `gl_MultiTexCoord0` is the in-built texture coordinate attribute. If you are wondering, yes, there is `gl_MultiTexCoord1`, `gl_MultiTexCoord2`, etc. We will look into using `gl_MultiTexCoord1` later. `gl_MultiTexCoord2` and higher are usually not used. In built texture coordinates are `vec4`, which is why we have to add `.st` at the end.
41+
42+
End the end your vertex shader looks like this:
43+
44+
```glsl
45+
#version 120
46+
47+
varying vec2 TexCoords;
48+
49+
void main() {
50+
gl_Position = ftransform();
51+
TexCoords = gl_MultiTexCoord0.st;
52+
}
53+
```
54+
55+
After the vertex shader comes the fragment shader. We start off by declaring the GLSL version and accepting the texture coordinate output from the vertex shader.
56+
57+
```glsl
58+
#version 120
59+
60+
varying vec2 TexCoords;
61+
```
62+
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`.
64+
65+
```glsl
66+
uniform sampler2D colortex0;
67+
```
68+
69+
Now we enter the `main` function:
70+
71+
```glsl
72+
void main() {
73+
// Sample the color
74+
vec3 Color = texture2D(colortex0, TexCoords).rgb;
75+
// Convert to grayscale
76+
Color = vec3(dot(Color, vec3(0.333f)));
77+
// Output the color
78+
gl_FragColor = vec4(Color, 1.0f);
79+
}
80+
```
81+
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`.
83+
84+
In the end, your fragment shader should be this:
85+
86+
```glsl
87+
#version 120
88+
89+
varying vec2 TexCoords;
90+
91+
uniform sampler2D gcolor;
92+
93+
void main() {
94+
vec3 Color = texture2D(gcolor, TexCoords).rgb;
95+
Color = vec3(dot(Color, vec3(0.333f)));
96+
gl_FragColor = vec4(Color, 1.0f);
97+
}
98+
```
99+
100+
Here is the results of the shader:
101+
102+
![Image of the Grayscale Shader](images/demo.png)
103+
104+
Although it is not much, it's definitely a start on your shader programming journey!
Loading

0 commit comments

Comments
 (0)