Hello everyone, I'm Wu_Tian. After several days of research on the Minecraft Vanilla Shader through resource packs, I'm excited to share some of the things I've achieved with you.
What is Shader?
In Minecraft, graphical rendering is based on OpenGL(So the programing language is GLSL).In OpenGL,there exists a rendering sequence known as the rendering pipeline.
In a typical OpenGL Program,these shaders are used to control the position of geometry vertices and the color of the corresponding pixels,respectively.However,it's important to note that in the context of Minecraft's post-processing shaders,the meanings of these shaders are totally different,emphasizing the "post-processing" aspect.
Vertex Shader:In Minecraft,the exrtex shader controls the four vertices of the screen.Fragment Shader:The fragment shader controls the pixels of the game screen.
Here is an illustrative image to help understand the role of these two shaders in Minecraft.
First,we need to open a higher version of Minecraft,such as '1.21-pre2.jar'.Inside the '1.21-pre2.jar\assets\minecraft\shaders' directory,you will find all the shader-related content.
Among these,the 'core' folder,introduced in version 1.17,contains the core shaders.These shaders control the actual rendering methods of certain elements in Minecraft and can be compared to the general rendering pipeline of an OpenGL program we discussed eariler.The 'inculde' folder contains include files,which are applicable to core shaders.
As stated by Minecraft Wiki:
If a shader is a core shader, the vertex shader files and fragment shader files can reference specified include shaders to reuse code.
However, our focus to day is on post-processing shaders and special effect animations.So,we won't delve deeply into these two folders.
Next,we can see two other folders:'post' and 'program'.
The program folder contains the logic code for specific vertex and fragment shaders.
The post folder contains the specific 'usable post-processing pipelines'.
In the program folder, we can find many .fsh (fragment shader) and .vsh (vertex shader) files, as well as .json files.
For example:
‘color_convolve.fsh’:A built-in fragment shader used for rendering the Creeper's vision.
'sobel.vsh':A built-in vertex shader used for general scrren projection.
color_convolve.json: The configuration file for the shader program, which binds a vertex shader and a fragment shader, and defines samplers, uniforms, and other information.
In the post folder, we can find several configuration files. For example, creeper.json defines a specific usable rendering pipeline for creeper vision. This configuration file links different JSON files from the program folder, allowing different shaders to render sequentially. Additionally, through this configuration file, you can customize different buffer layer to store different layers of the frame and pass them to the shader program as a sampler.
To explain my understanding of samplers: they are similar to a canvas. Minecraft has a default buffer layer predefined, named minecraft:main in the post JSON files. It contains the current Minecraft information. When this buffer layer is passed into the program as a sampler which is referred to as DiffuseSampler.You can obtain the color information of all pixels from the canvas.
Here is another buffer layer provided by Minecraft,final.It's for glowing entity.
A state by Minecraft Wiki:
It is used to store the post-processing results of entity outlines. This pipeline should not modify the main screen framebuffer, as the game will automatically merge the outline post-processing into the main screen frame buffer after processing.
Here is a picture I make to demonstrate the process of the creeper.json.
following I will show you the specific code of creeper.json:
We can see that the code contains several keys: targets defines custom buffer layers, and passes specifies the order in which shaders are applied and buffer layers are passed in the rendering pipeline. Notably, the global uniform variables must match those defined in the shader configuration files specified in the name field. This allows users to modify different variables in creeper.json to achieve different effects.
Otherwise,there are several other keys for you to choose:
"auxtargets":(Optional) A list of a auxiliary tetures."name":The name of the auxiliary texture,which also serves as the name of the sampler to be passed to the shader"id":The ID of the auxiliary texture corresponding to the names in "targets".The auxiliary texture can be either a buffer layer you defined or a texture image located in "assets/minecraft/pictures/effect" If the auxiliary texture is an image rather than a post-processing frambuffer(buffer layer): width:The width of the texture. height:The height of the texture. bilinear:Whether the texture uses bilinear filtering;otherwise,it will use nearest-neighbor filtering
The effect of bilinear
Deep dig into the shader configuration file.
Let's take color.convolve.json as an example. Here is the source code:
Up to now ,we have already understood the structure and process of the Minecraft Vanilla Shader.
Then we can look at the GLSL file and make a real change of the Minecraft!!
Attention:This Part should some Computer Programming basis.
GLSL is similar to C Language, so if you have any programming experience previously,I think you can enjoy this part.
Since Minecraft's vertex shaders are not commonly used—because no player likes to see their game screen projected at an angle—we directly use Minecraft's built-in sobel.vsh vertex shader.
Before we formally write our shader, it is worth mentioning that Minecraft includes a built-in shader configuration file called blit.json. The function of blit is to copy the contents of our buffer layer to the next buffer layer. This will be very useful in the following steps, because in the rendering pipeline, the input buffer layer cannot be the same as the target buffer layer.
Now,we can try to rewrite the creeper.json and make a shader to replace the creeper vision:
In the program folder, we can write a simple red.fsh fragment shader to envelop the world in a red tint.
#version 150
uniform sampler2D DiffuseSampler;
in vec2 texCoord;
in vec2 oneTexel;
out vec4 fragColor;
void main() {
vec4 color = texture2D(DiffuseSampler, texCoord);
color.r = min(color.r + 0.5, 1.0);
fragColor = color;
}
EXPLANATION
uniform sampler2D DiffuseSampler;: define the game sampler from the rendering pipeline.
in vec2 texCoord;: get the current pos of the texel.(It coordinates to the pos of the output texel)
out vec4 fragColor;: the color of the output texel.
vec4 color = texture(DiffuseSampler, texCoord);:use texture2D Function to retrieve the color of the current clip(the pos of the current clip is from texCoord) from DiffuseSampler.
color.r = min(color.r + 0.5, 1.0);: Increase the intensity of the red channel and ensure that the value is less than 1.0
fragColor = color;: output the modified color.
out and in are GLSL keywords that represent passing this variable to the next stage of the rendering pipeline and receiving a variable from the previous stage of the rendering pipeline, respectively. Colors in GLSL are represented using the vec4 type (a four-dimensional vector), with components representing red intensity, green intensity, blue intensity, and transparency (alpha value). Coordinates are typically represented using vec2 (a two-dimensional vector). Additionally, GLSL supports matrix types, which can be used for convenient vector transformations.
Then we can add a configuration file and rewrite the crepper.json:
Now.Join in the game, spectate the creeper,we can see the effect:
Advanced: How to Customize the Creeper's Red Filter for the Player's View
By exploiting a bug feature in Minecraft, we can allow the player to take on the Creeper's red filter effect. This can be achieved by having the player spectate a Creeper and then killing the player. When the player respawns, they retain the filter effect. By utilizing the game rule doImmediateRespawn, adjusting the player's respawn location, and preserving potion effects using Minecraft 1.21 (24w20a) update, we can apply this filter with minimal disruption to the gameplay experience. The following example commands demonstrate how to achieve this effect:
set_red_screen.mcfunction
# Enable immediate respawn and keep inventory on death
gamerule doImmediateRespawn true
gamerule keepInventory true
# Set player to spectator mode
gamemode spectator @s
# Set delay timer
scoreboard players set @s delay_timer 11
# Summon an armor stand to mark player's position
summon armor_stand ~ ~ ~ {Marker:1b,Invisible:1b,Tags:["marker"]}
execute anchored eyes run summon armor_stand ^ ^ ^0.4 {Marker:1b,Invisible:1b,Invulnerable:1b,CustomName:'"\\uE000"',CustomNameVisible:1b,Fire:1000s,Tags:["shader_trigger"]}
# Summon a bat to record player's effects
summon bat ~ ~ ~ {NoAI:1b,NoGravity:1b,Silent:1b,Tags:["effect_recorder"]}
data modify entity @e[tag=effect_recorder,type=bat,limit=1,sort=nearest] active_effects set from entity @s active_effects
# Teleport the marker armor stand to the player's position
tp @e[type=armor_stand,limit=1,sort=nearest,tag=marker] ~ ~ ~ ~ ~
# Summon a Creeper with invisibility effect as the filter source
summon minecraft:creeper ~ ~ ~ {Silent:1b,NoAI:1b,NoGravity:1b,active_effects:[{id:"invisibility",duration:99999999,show_particles:false}],Tags:["marker"]}
# Assign the filter to the player
scoreboard players operation @e[type=armor_stand,tag=marker,limit=1,sort=nearest] uuid = @s uuid
# Set player to spectate the Creeper and then kill the player
spectate @e[tag=marker,type=creeper,sort=nearest,limit=1]
kill @s
# Restore game rules
gamerule doImmediateRespawn false
execute as @s if score @s KeppInventory matches 0 run gamerule keepInventory false
loot.mcfunction(tick.json loads it):
scoreboard players add @a KeppInventory 0
function blood_night:set_uuid
execute as @a at @s if score @s delay_timer matches 5 run gamemode survival @s
execute as @a at @s if score @s delay_timer matches 5 run function blood_night:find_matching_pos
# After delay, remove markers
execute as @a at @s if score @s delay_timer matches 5 run gamemode survival @s
execute as @a at @s if score @s delay_timer matches 5 run tp @e[tag=marker,sort=nearest,type=armor_stand,limit=1] ~ -1000 ~
execute as @a at @s if score @s delay_timer matches 5 run tp @e[tag=marker,sort=nearest,type=creeper,limit=1] ~ -1000 ~
execute as @a at @s if score @s delay_timer matches 5 run tp @e[tag=effect_recorder,sort=nearest,type=bat,limit=1] ~ -1000 ~
execute as @a at @s if score @s delay_timer matches 4 run tp @e[tag=shader_trigger,sort=nearest,type=armor_stand,limit=1] ~ -1000 ~
# Decrease delay timer
scoreboard players remove @a[scores={delay_timer=1..}] delay_timer
find_maching_pos.mcfunction
[font=helvetica, arial, sans-serif][size=18px]
[/size][/font]
#Find player pos
tp @s @e[tag=marker,type=armor_stand,limit=1,sort=nearest,distance=1..]
execute as @s at @s if score @s uuid = @e[tag=marker,type=armor_stand,limit=1,sort=nearest] uuid run function blood_night:copy_effect
execute as @s at @s unless score @e[tag=marker,type=armor_stand,limit=1,sort=nearest] uuid = @s uuid run function blood_night:find_matching_pos[size=18px][b]
[/b][/size]
It's a good way to apply a shader to the player's screen using this command. However, if the player quits the game or accidentally presses F5, the shader will no longer be available. Therefore, you should inform your players to avoid these actions and provide them with a method to reload the screen. I suggest adding a world_time_tracker variable to track if a connected player has quit the game, ensuring a good immersive experience in multiplayer games.
Advanced: Utilizing Superior Rendering and Screen Information for Dynamic Shader Control
Yes, using the previously mentioned method of leveraging features is not stable and is not suitable for long-term survival maps. Therefore, we can utilize the newly added superior rendering pipeline and dynamically enable or disable a shader by detecting if a specific pixel on the screen has a particular color. This is a very effective method because, unless the player quits the game, it is almost impossible for them to accidentally remove the shader within the game.
However, due to the complexity of the superior rendering pipeline, during the rendering process, we can only pass our shader from minecraft:main to swap, with swap acting as minecraft:main to apply the transparency shader and then pass to final, which is then passed back to minecraft:main. Therefore, we cannot apply the filter to particle effects, the player's arm, etc. Additionally, this operation might cause other vanilla shader resource packs that modify transparency to malfunction. This depends on whether the resource pack only modifies transparency.json in the program folder or also modifies transparency.json in the post folder. This is a limitation and shortcoming of this method.
Regarding the specific implementation of detecting a pixel color at a specific position to activate the filter, the first thing we need is to display a high-purity solid color at the specified position to trigger the shader. There are two methods to achieve this.
Before the introduction of custom fonts in Minecraft, we could achieve this by replacing block models and textures. For example, we could replace a texture with a high-purity red color and disable block shadows. By giving the player night vision, we can obtain a very high-purity red in the game.
The second method involves adding a custom font that displays as a high-purity red color. Then, we simply summon an armor stand to display this font and keep the armor stand on fire (this requires a command loop to prevent the fire from being extinguished by rain). This method also allows us to obtain a very high-purity red in Minecraft.
Obviously, the second method is more practical, so I will focus on explaining the second method next.
Implementing Custom Fonts
Implementing custom fonts is not difficult. You can refer to the attached resource pack for guidance. Here, I will provide a configuration file for custom fonts, where 1.png is pure red [vec4(1.0,0.0,0.0,1.0)] and 2.png is pure blue [vec4(0.0,0.0,1.0,1.0)].
I specifically set the height value to be very large so that the color can cover the entire screen and trigger the shader. Of course, if you need finer control, you can adjust these values yourself, but this would require precise positioning when summoning the armor stand.
Next Steps:Write Your shader
With the high-purity color as a trigger, we can write a simple shader to control the filter transformation. Here is a simple shader I wrote that switches to a red filter when the player sees the high-purity red color.
#version 150
// Declare a uniform sampler2D to access the texture data
uniform sampler2D DiffuseSampler;
// Input texture coordinates and one texel size
in vec2 texCoord;
in vec2 oneTexel;
// Declare additional uniform variables
uniform vec2 InSize; // Input size (not used in this shader)
uniform vec2 OutSize; // Output size (not used in this shader)
uniform float Time; // Time (not used in this shader)
// Output variable for the fragment color
out vec4 fragColor;
// Define the position of the trigger pixel
const vec2 triggerPos = vec2(0.5, 0.5);
// Threshold values for detecting pure red color
const float redThreshold = 0.985; // Threshold for the red channel
const float intensityThreshold = 0.1; // Threshold for color intensity of green and blue channels
// Function to determine if a color is pure red
bool isPureRed(vec4 color, float threshold, float intensity) {
// A color is considered pure red if the red component is above the threshold
// and both green and blue components are below the intensity threshold
return color.r > threshold && color.b < intensity && color.g < intensity;
}
void main() {
// Sample the color at the trigger position
vec4 triggerColor = texture2D(DiffuseSampler, triggerPos);
// Sample the current fragment's color
vec4 currTexel = texture2D(DiffuseSampler, texCoord);
// Check if the trigger color is pure red
if (isPureRed(triggerColor, redThreshold, intensityThreshold)) {
// If the trigger color is pure red, increase the red intensity of the current fragment
fragColor = vec4(min(currTexel.r + 0.5, 1.0), currTexel.g, currTexel.b, 1.0);
} else {
// If the trigger color is not pure red, output the original color
fragColor = currTexel;
}
}
And Don't forget to loop the command that keeps the marker always on fire.
So Now,We have gradually controlled our shader,right?
,
Advance:Creating Looping Animations
We have observed that Minecraft has several uniforms, including one called Time. With this variable, we can modify the shader over time to create animations. Let's create a simple looping animation using the Time variable.
Below is an example of a fragment shader that utilizes the Time variable to create a simple animation effect:
#version 150
uniform sampler2D DiffuseSampler;
in vec2 texCoord;
uniform vec2 InSize;
uniform vec2 OutSize;
uniform float Time;
out vec4 fragColor;
void main() {
// Sample the current fragment's color
vec4 currTexel = texture(DiffuseSampler, texCoord);
// Normalize the coordinates to the range [-1, 1]
vec2 normCoord = (texCoord * 2.0) - 1.0;
// Calculate the time-based scale factor for the ellipse size
float scale = (sin(Time*3.14) + 1.0) / 2.0 * 0.5 + 0.5; // Scale oscillates to large and small
// Define the ellipse parameters
float a = 0.5 * scale; // Semi-major axis
float b = 0.3 * scale; // Semi-minor axis
// Equation of ellipse: (x^2 / a^2) + (y^2 / b^2) <= 1.0
float ellipse = (normCoord.x * normCoord.x) / (a * a) + (normCoord.y * normCoord.y) / (b * b);
// Check if the current coordinate is inside the ellipse
if (ellipse <= 1.0) {
// Inside the ellipse: display the game content
fragColor = currTexel;
} else {
// Outside the ellipse: display red color
fragColor = currTexel * vec4(3.0, 1.0, 1.0, 1.0);
}
}
With this code for varying the size of the ellipse, we can achieve a transforming animation.
Final Advanced Section: Using PreviousSampler and DiffuseSampler for Controllable Trigger Animations
Clearly, PreviousSampler is user-defined and records the contents of DiffuseSampler from the previous frame. By writing the rendering order in the configuration file, this functionality can be achieved. Additionally, by using auxtargets to pass the previous buffer layer as a sampler to the shader, we can implement this feature. Here’s an example of how to set up the rendering sequence in the configuration file:
Next, we can use PreviousSampler to check the color of a specific pixel from the previous frame to determine whether an animation is playing or stopping, and use DiffuseSampler to check whether the conditions for starting or stopping the animation have been triggered.
Below is an example code.
#version 150
// Declare uniform samplers for the current and previous frame textures
uniform sampler2D DiffuseSampler;
uniform sampler2D PrevSampler;
// Input texture coordinates and one texel size
in vec2 texCoord;
in vec2 oneTexel;
// Declare additional uniform variables
uniform vec2 InSize; // Input size (not used in this shader)
uniform vec2 OutSize; // Output size (not used in this shader)
uniform float Time; // Time (not used in this shader)
// Output variable for the fragment color
out vec4 fragColor;
// Define the position of the trigger pixel
const vec2 triggerPos = vec2(0.5, 0.5);
// Threshold values for detecting pure blue color
const float blueThreshold = 0.985; // Threshold for the blue channel
const float intensityThreshold = 0.1; // Threshold for color intensity of red and green channels
// Function to determine if a color is pure blue
bool isPureBlue(vec4 color, float threshold, float intensity) {
// A color is considered pure blue if the blue component is above the threshold
// and both red and green components are below the intensity threshold
return color.b > threshold && color.r < intensity && color.g < intensity;
}
// Function to record the animation state in the output color
void recordAnimationState(bool state, int frame, bool fadingOut, float fadeTimer) {
float stateValue = state ? 1.0 : 0.0; // Animation state as a float
float frameValue = float(frame) / 255.0; // Frame value normalized to [0, 1]
float fadeValue = fadingOut ? 1.0 : 0.0; // Fading out state as a float
float timerValue = fadeTimer / 255.0; // Fade timer normalized to [0, 1]
vec2 epsilon = vec2(oneTexel.x, oneTexel.y); // Small offset for position comparison
// Check if the current fragment is at the trigger position
if (abs(texCoord.x - triggerPos.x) < epsilon.x && abs(texCoord.y - triggerPos.y) < epsilon.y) {
// Encode the animation state, frame, fade state, and timer into the fragment color
fragColor = vec4(stateValue, frameValue, fadeValue, timerValue);
}
}
// Function to get the animation state from the previous frame
bool getAnimationState(sampler2D sampler) {
vec4 stateColor = texture2D(sampler, triggerPos);
return stateColor.r > 0.985;
}
// Function to get the frame number from the previous frame
int getFrame(sampler2D sampler) {
vec4 frameColor = texture2D(sampler, triggerPos);
return int(frameColor.g * 255.0);
}
// Function to get the fading out state from the previous frame
bool getFadingOutState(sampler2D sampler) {
vec4 fadeColor = texture2D(sampler, triggerPos);
return fadeColor.b > 0.985;
}
// Function to get the fade timer from the previous frame
float getFadeTimer(sampler2D sampler) {
vec4 timerColor = texture2D(sampler, triggerPos);
return timerColor.a * 255.0;
}
void main() {
// Sample the current fragment's color
vec4 currTexel = texture2D(DiffuseSampler, texCoord);
// Sample the color at the trigger position
vec4 triggerColor = texture2D(DiffuseSampler, triggerPos);
// Check if the animation should be triggered or ended
bool animationTriggered = (triggerColor.r > 0.985);
bool endAnimation = isPureBlue(triggerColor, blueThreshold, intensityThreshold);
// Retrieve the animation state, frame, fading state, and fade timer from the previous frame
bool playingAnimation = getAnimationState(PrevSampler);
int frame = getFrame(PrevSampler);
bool fadingOut = getFadingOutState(PrevSampler);
float fadeTimer = getFadeTimer(PrevSampler);
// Update animation state based on triggers
if (animationTriggered && !fadingOut) {
playingAnimation = true;
}
if (endAnimation && !fadingOut) {
fadingOut = true;
fadeTimer = 0.0;
}
// Update frame and fade timer based on animation state
if (playingAnimation && !fadingOut) {
if (frame < 255) {
frame++;
}
} else if (fadingOut) {
if (fadeTimer < 255.0) {
fadeTimer++;
} else {
playingAnimation = false;
fadingOut = false;
frame = 0;
fadeTimer = 0.0;
}
}
// Record the updated animation state in the output color
recordAnimationState(playingAnimation, frame, fadingOut, fadeTimer);
// Determine if the current fragment is within the trigger position
vec2 epsilon = vec2(oneTexel.x, oneTexel.y);
if (abs(texCoord.x - triggerPos.x) > epsilon.x || abs(texCoord.y - triggerPos.y) > epsilon.y) {
if (playingAnimation) {
if (fadingOut) {
// Calculate fade progress and apply fade-out effect
float progress = float(fadeTimer) / 255.0;
fragColor = mix(vec4(1.0, 1.0, 1.0, 1.0), vec4(1.0, 0.0, 0.0, 0.0), 1 - progress) * currTexel;
} else {
// Calculate frame progress and apply animation effect
float progress = float(frame) / 255.0;
fragColor = mix(vec4(1.0, 0.0, 0.0, 1.0), vec4(1.0, 1.0, 1.0, 1.0), 1 - progress) * currTexel;
}
} else {
// Output the original color if no animation is playing
fragColor = currTexel;
}
}
}
Yeah,It's a little uneasy.But if I trust your strong will would overcome the difficulty !!!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
That's all what I want to share with you.Thanks for your patient reading.
Hello everyone, I'm Wu_Tian. After several days of research on the Minecraft Vanilla Shader through resource packs, I'm excited to share some of the things I've achieved with you.
What is Shader?
In Minecraft, graphical rendering is based on OpenGL(So the programing language is GLSL).In OpenGL,there exists a rendering sequence known as the rendering pipeline.
In a typical OpenGL Program,these shaders are used to control the position of geometry vertices and the color of the corresponding pixels,respectively.However,it's important to note that in the context of Minecraft's post-processing shaders,the meanings of these shaders are totally different,emphasizing the "post-processing" aspect.
Vertex Shader:In Minecraft,the exrtex shader controls the four vertices of the screen.Fragment Shader:The fragment shader controls the pixels of the game screen.
Here is an illustrative image to help understand the role of these two shaders in Minecraft.
Configuring Shader Files
First,we need to open a higher version of Minecraft,such as '1.21-pre2.jar'.Inside the '1.21-pre2.jar\assets\minecraft\shaders' directory
,you will find all the shader-related content.
Among these,the 'core' folder,introduced in version 1.17,contains the core shaders.These shaders control the actual rendering methods of certain elements in Minecraft and can be compared to the general rendering pipeline of an OpenGL program we discussed eariler.The 'inculde' folder contains include files,which are applicable to core shaders.
As stated by Minecraft Wiki:
However, our focus to day is on post-processing shaders and special effect animations.So,we won't delve deeply into these two folders.
Next,we can see two other folders:'post' and 'program'.
In the program folder, we can find many .fsh (fragment shader) and .vsh (vertex shader) files, as well as .json files.
For example:
In the post folder, we can find several configuration files. For example, creeper.json defines a specific usable rendering pipeline for creeper vision. This configuration file links different JSON files from the program folder, allowing different shaders to render sequentially. Additionally, through this configuration file, you can customize different buffer layer to store different layers of the frame and pass them to the shader program as a sampler.
To explain my understanding of samplers: they are similar to a canvas. Minecraft has a default buffer layer predefined, named minecraft:main in the post JSON files. It contains the current Minecraft information. When this buffer layer is passed into the program as a sampler which is referred to as DiffuseSampler.You can obtain the color information of all pixels from the canvas.
Here is another buffer layer provided by Minecraft,final.It's for glowing entity.
A state by Minecraft Wiki:
Here is a picture I make to demonstrate the process of the creeper.json.
following I will show you the specific code of creeper.json:
We can see that the code contains several keys: targets defines custom buffer layers, and passes specifies the order in which shaders are applied and buffer layers are passed in the rendering pipeline. Notably, the global uniform variables must match those defined in the shader configuration files specified in the name field. This allows users to modify different variables in creeper.json to achieve different effects.
Otherwise,there are several other keys for you to choose:
"auxtargets":(Optional) A list of a auxiliary tetures."name":The name of the auxiliary texture,which also serves as the name of the sampler to be passed to the shader"id":The ID of the auxiliary texture corresponding to the names in "targets".The auxiliary texture can be either a buffer layer you defined or a texture image located in "assets/minecraft/pictures/effect" If the auxiliary texture is an image rather than a post-processing frambuffer(buffer layer): width:The width of the texture. height:The height of the texture. bilinear:Whether the texture uses bilinear filtering;otherwise,it will use nearest-neighbor filtering
The effect of bilinear
Deep dig into the shader configuration file.
Let's take color.convolve.json as an example. Here is the source code:
Here is my explanation:
Blend Section:
The blend section allows the color passed from the buffer layer to be mixed with the final color output by the shader.
There are some ways to blend the color:
add (GL_FUNC_ADD):
Meaning: The source color and destination color are weighted by their respective factors and then added together.
subtract (GL_FUNC_SUBTRACT):
Meaning: The source color, weighted by its factor, subtracts the destination color, weighted by its factor.
reverse_subtract (GL_FUNC_REVERSE_SUBTRACT):
Meaning: The destination color, weighted by its factor, subtracts the source color, weighted by its factor.
min (GL_MIN):
Meaning: Takes the smaller value between the source color and the destination color.
max (GL_MAX):
Meaning: Takes the larger value between the source color and the destination color.
There are some ways to define the srcrgb and dstrgb:
zero (GL_ZERO)
: Fixed value 0, the destination color contributes nothing.
one (GL_ONE)
: Fixed value 1, the destination color contributes fully.
dstcolor (GL_DST_COLOR)
: The destination color itself.
one_minus_dstcolor (GL_ONE_MINUS_DST_COLOR)
: 1 minus the destination color.
dstalpha (GL_DST_ALPHA)
: The alpha component of the destination color.
one_minus_dstalpha (GL_ONE_MINUS_DST_ALPHA)
So essentially, the configuration minecraft wrote here:
can be analyzed to effectively disable blending.
Vertex and Fragment Section:
Attributes Section:
Samplers Section:
Uniforms Section:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Up to now ,we have already understood the structure and process of the Minecraft Vanilla Shader.
Then we can look at the GLSL file and make a real change of the Minecraft!!
Attention:This Part should some Computer Programming basis.
GLSL is similar to C Language, so if you have any programming experience previously,I think you can enjoy this part.
Since Minecraft's vertex shaders are not commonly used—because no player likes to see their game screen projected at an angle—we directly use Minecraft's built-in sobel.vsh vertex shader.
Before we formally write our shader, it is worth mentioning that Minecraft includes a built-in shader configuration file called blit.json. The function of blit is to copy the contents of our buffer layer to the next buffer layer. This will be very useful in the following steps, because in the rendering pipeline, the input buffer layer cannot be the same as the target buffer layer.
Now,we can try to rewrite the creeper.json and make a shader to replace the creeper vision:
EXPLANATION
out and in are GLSL keywords that represent passing this variable to the next stage of the rendering pipeline and receiving a variable from the previous stage of the rendering pipeline, respectively. Colors in GLSL are represented using the vec4 type (a four-dimensional vector), with components representing red intensity, green intensity, blue intensity, and transparency (alpha value). Coordinates are typically represented using vec2 (a two-dimensional vector). Additionally, GLSL supports matrix types, which can be used for convenient vector transformations.
Then we can add a configuration file and rewrite the crepper.json:
red.json:
What should be emphasized is that the uniforms called "InSize" "OutSize" "ProjMat" and "Time" is mandatory to fill out.
Creeper.json:
Now.Join in the game, spectate the creeper,we can see the effect:
Advanced: How to Customize the Creeper's Red Filter for the Player's View
By exploiting a
bugfeature in Minecraft, we can allow the player to take on the Creeper's red filter effect. This can be achieved by having the player spectate a Creeper and then killing the player. When the player respawns, they retain the filter effect. By utilizing the game rule doImmediateRespawn, adjusting the player's respawn location, and preserving potion effects using Minecraft 1.21 (24w20a) update, we can apply this filter with minimal disruption to the gameplay experience. The following example commands demonstrate how to achieve this effect:set_red_screen.mcfunction
loot.mcfunction(tick.json loads it):
find_maching_pos.mcfunction
start.mcfunction(load.json loads it)
set_uuid.mcfunction
It's a good way to apply a shader to the player's screen using this command. However, if the player quits the game or accidentally presses F5, the shader will no longer be available. Therefore, you should inform your players to avoid these actions and provide them with a method to reload the screen. I suggest adding a world_time_tracker variable to track if a connected player has quit the game, ensuring a good immersive experience in multiplayer games.
Advanced: Utilizing Superior Rendering and Screen Information for Dynamic Shader Control
Yes, using the previously mentioned method of leveraging features is not stable and is not suitable for long-term survival maps. Therefore, we can utilize the newly added superior rendering pipeline and dynamically enable or disable a shader by detecting if a specific pixel on the screen has a particular color. This is a very effective method because, unless the player quits the game, it is almost impossible for them to accidentally remove the shader within the game.
However, due to the complexity of the superior rendering pipeline, during the rendering process, we can only pass our shader from minecraft:main to swap, with swap acting as minecraft:main to apply the transparency shader and then pass to final, which is then passed back to minecraft:main. Therefore, we cannot apply the filter to particle effects, the player's arm, etc. Additionally, this operation might cause other vanilla shader resource packs that modify transparency to malfunction. This depends on whether the resource pack only modifies transparency.json in the program folder or also modifies transparency.json in the post folder. This is a limitation and shortcoming of this method.
Regarding the specific implementation of detecting a pixel color at a specific position to activate the filter, the first thing we need is to display a high-purity solid color at the specified position to trigger the shader. There are two methods to achieve this.
Before the introduction of custom fonts in Minecraft, we could achieve this by replacing block models and textures. For example, we could replace a texture with a high-purity red color and disable block shadows. By giving the player night vision, we can obtain a very high-purity red in the game.
The second method involves adding a custom font that displays as a high-purity red color. Then, we simply summon an armor stand to display this font and keep the armor stand on fire (this requires a command loop to prevent the fire from being extinguished by rain). This method also allows us to obtain a very high-purity red in Minecraft.
Obviously, the second method is more practical, so I will focus on explaining the second method next.
Implementing Custom Fonts
Implementing custom fonts is not difficult. You can refer to the attached resource pack for guidance. Here, I will provide a configuration file for custom fonts, where 1.png is pure red [vec4(1.0,0.0,0.0,1.0)] and 2.png is pure blue [vec4(0.0,0.0,1.0,1.0)].
Configuration file default.json:
I specifically set the height value to be very large so that the color can cover the entire screen and trigger the shader. Of course, if you need finer control, you can adjust these values yourself, but this would require precise positioning when summoning the armor stand.
Next Steps:Write Your shader
With the high-purity color as a trigger, we can write a simple shader to control the filter transformation. Here is a simple shader I wrote that switches to a red filter when the player sees the high-purity red color.
Then use a command:
And Don't forget to loop the command that keeps the marker always on fire.
So Now,We have gradually controlled our shader,right?
Advance:Creating Looping Animations
We have observed that Minecraft has several uniforms, including one called Time. With this variable, we can modify the shader over time to create animations. Let's create a simple looping animation using the Time variable.
Below is an example of a fragment shader that utilizes the Time variable to create a simple animation effect:
With this code for varying the size of the ellipse, we can achieve a transforming animation.
Final Advanced Section: Using PreviousSampler and DiffuseSampler for Controllable Trigger Animations
Clearly, PreviousSampler is user-defined and records the contents of DiffuseSampler from the previous frame. By writing the rendering order in the configuration file, this functionality can be achieved. Additionally, by using auxtargets to pass the previous buffer layer as a sampler to the shader, we can implement this feature. Here’s an example of how to set up the rendering sequence in the configuration file:
Next, we can use PreviousSampler to check the color of a specific pixel from the previous frame to determine whether an animation is playing or stopping, and use DiffuseSampler to check whether the conditions for starting or stopping the animation have been triggered.
Below is an example code.
Yeah,It's a little uneasy.But if I trust your strong will would overcome the difficulty !!!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
That's all what I want to share with you.Thanks for your patient reading.
Click to download the resourcepack
Minecraft, My World