mrkite77
Terrarian
Someone asked me how the XNB shaders worked, so I figured I'd post here, just to help anyone else who needed it.
A while ago I wrote a quick little command-line program that can disassemble xnb shaders. You can download the source here.
If you disassemble the TileShaders.xnb, you'll see a lot of definitions that describe techniques and passes.
If you scroll down you'll see multiple Resources. Each Resource is a shader for a different paint color.
Near the bottom, you'll see the shader for Pass2, which is the Red paint. We'll use that one as an example.
It's small, so I'll put it here:
This shader (like all pixel shaders) is called repeatedly for each pixel in the texture.
The first 3 lines define variables passed in from the program. v is most likely the lighting color (we'll ignore it),
t.xy is the XY coordinate for the current pixel we're shading in the texture, s is the texture.
texld sets the vector 'r' to be the RGBA value of the current pixel in the texture.
Now the rest of the code refers to components of 'r' as 'r.x' and 'r.y' and 'r.z' but since we know that r is actually an RGB value,
those are misleading. r.x = red, r.y = green, r.z = blue (and r.w = alpha if the shader actually cared about the alpha channel)...
so in all future references I'll translate "r.x" into "red"... and so on.
Converting the rest into pseudocode, we basically have:
r1.x = max(red, green)
r1.y = min(red, green)
r1.w = r1.x - blue
r1.z = blue
if (r1.w >= 0) r2.x = r1.x else r2.x = r1.z
if (r1.w >= 0) r2.y = r1.z else r2.y = r1.x
blue = green = min(r2.y, r1.y)
red = r2.x
r *= v // apply the lighting.. we're ignoring this, but i translated it for you anyway
outputPixel = r // oC = the final RGB for this pixel
Shader code always assumes that red/green/blue are values from 0.0 to 1.0.
Which is why you can apply the lighting by multiplying two vectors together.
It's then best to go back through that pseudocode and clean up the variable names to make it clearer what is actually happening.
You might then realize that those if statements basically just check to see if blue is greater than the max or lower than the min
and then assign the appropriate output.
The whole thing boils down to:
out_red = max(red, green, blue)
out_green = out_blue = min(red, green, blue)
and that's how red paint is applied to each pixel of a tile.
A while ago I wrote a quick little command-line program that can disassemble xnb shaders. You can download the source here.
If you disassemble the TileShaders.xnb, you'll see a lot of definitions that describe techniques and passes.
If you scroll down you'll see multiple Resources. Each Resource is a shader for a different paint color.
Near the bottom, you'll see the shader for Pass2, which is the Red paint. We'll use that one as an example.
It's small, so I'll put it here:
dcl v
dcl t.xy
dcl_2d s
texld r, t, s
max r1.x, r.x, r.y
min r1.y, r.y, r.x
add r1.w, -r.z, r1.x
mov r1.z, r.z
cmp r2.x, r1.w, r1.x, r1.z
cmp r2.y, r1.w, r1.z, r1.x
min r.yz, r2.y, r1.y
mov r.x, r2.x
mul r, r, v
mov oC, r
This shader (like all pixel shaders) is called repeatedly for each pixel in the texture.
The first 3 lines define variables passed in from the program. v is most likely the lighting color (we'll ignore it),
t.xy is the XY coordinate for the current pixel we're shading in the texture, s is the texture.
texld sets the vector 'r' to be the RGBA value of the current pixel in the texture.
Now the rest of the code refers to components of 'r' as 'r.x' and 'r.y' and 'r.z' but since we know that r is actually an RGB value,
those are misleading. r.x = red, r.y = green, r.z = blue (and r.w = alpha if the shader actually cared about the alpha channel)...
so in all future references I'll translate "r.x" into "red"... and so on.
Converting the rest into pseudocode, we basically have:
r1.x = max(red, green)
r1.y = min(red, green)
r1.w = r1.x - blue
r1.z = blue
if (r1.w >= 0) r2.x = r1.x else r2.x = r1.z
if (r1.w >= 0) r2.y = r1.z else r2.y = r1.x
blue = green = min(r2.y, r1.y)
red = r2.x
r *= v // apply the lighting.. we're ignoring this, but i translated it for you anyway
outputPixel = r // oC = the final RGB for this pixel
Shader code always assumes that red/green/blue are values from 0.0 to 1.0.
Which is why you can apply the lighting by multiplying two vectors together.
It's then best to go back through that pseudocode and clean up the variable names to make it clearer what is actually happening.
You might then realize that those if statements basically just check to see if blue is greater than the max or lower than the min
and then assign the appropriate output.
The whole thing boils down to:
out_red = max(red, green, blue)
out_green = out_blue = min(red, green, blue)
and that's how red paint is applied to each pixel of a tile.
Last edited: