Skip to content

Instantly share code, notes, and snippets.

@EtiTheSpirit
Last active December 18, 2023 19:56
Show Gist options
  • Save EtiTheSpirit/97dfdc63f667e19acb6314dc8c1e2d18 to your computer and use it in GitHub Desktop.
Save EtiTheSpirit/97dfdc63f667e19acb6314dc8c1e2d18 to your computer and use it in GitHub Desktop.
Rain World Level File Color Key

Rain World Level File Color Key

Level image files contain highly specific data that has a range of meanings both to the game during runtime (on the CPU thread) and to the level shader itself. The colors seen in levels can be slightly confusing, so this document relays them all.

Preliminary Information / Tips Before Reading

  1. All colors are represented in 8 bit format (that is, I will use the range 0 to 255 like most people are used to, not 0.0 to 1.0 as seen in shaders). This way it's easier for everyone to understand.
  2. Any mention of a color (especially red, green, and blue) should be considered on a per-channel basis. In Layman's terms, think of it like the color wheel in your favorite image editor, how it shows you three sliders for R, G, and B. I'm talking about those sliders individually, basically.
  3. Since this term is used throughout the document, "iff" is a shortening of the phrase "if and only if". Thought I'd mention it. It's not a typo!
  4. A "palette row" is one of the long gradients you will see in color palettes. In total there are six rows. The top three are for sunlit areas, and the bottom three are for shade. See the modding wiki page for pictures of what this means, but hopefully you are already familiar with this.
    1. If I say "sun palette", this means the first three rows of gradients.
    2. Similarly, if I mention "shade palette", this means the last three.
  5. DEPTH IS ONE-INDEXED. While in the program depth starts at 0 and ends at 29, here it will be 1 to 30 like people are used to.

In the LevelColor Shader

The red channel controls:

  1. red > 90 means that pixel is lit by the sun, otherwise it is in shadow. Iff red is > 90, then 90 is subtracted away from it.
  2. The palette row is selected based on floor((red - 1) / 30), clamped so that it's always 0, 1, or 2. // Floor is easy. Literally just erase all the decimals. 4.2069 becomes 4).
    In effect...
    1. Red from 1 to 30 is the undertone palette row (0)
    2. Red from 31 to 60 is the forefacing color / middle palette row (1)
    3. Red from 61 to 90 is the highlight palette row (2)
    4. To be clear: UVs are sampled from the bottom up internally; a palette color of 0 will actually sample the bottom row of the palette texture rather than the top. If you are a shader author, see Unity Built-in Macros (UNITY_UV_STARTS_AT_TOP) when writing functions to get pixels by coordinate, if you want 0 to always unambiguously be the top of your image.
    5. This is affected by green, see Green #1.
  3. From this point on, red is now equal to: fmod(red - 1, 30) // If you don't know what this means, this "wraps around". Here is an example of fmod on Desmos. Basically, if red is more than 30, keep subtracting 30 over and over again until its not more than 30.
  4. Iff sunlight affects this color a red value of 5 or more instructs the system to look for the angle of the sun's light. This angle creates an offset (to create the shadow in the distance), and then reads the color of all objects rendered before the level. If the pixel at that location is not black, this pixel will be overridden and shrouded in a shadow. This is how it draws shadows while the game is running.
  5. Iff grime is enabled, the red value affects the offset of the rainbow stripe in the palette, which is where grime gets its colors (this way the rainbow is different at different depths and it doesnt look weirdly the same everywhere).
  6. If red is 10 or more fog will affect the pixel completely, otherwise the strength of fog is interpolated by the shade of red (smooth). This may not be the fog effect necessarily, check the green category below. Note that this strength is affected by the fog devtools effect.

The green channel controls:

  1. If green is greater than or equal to 16/255, 16 is subtracted away. This has a number of effects.
    Iff green was greater than or equal to 16, the following things happen...
    1. The sunlight color will always use the darkest available tone on the palette (darkest highlight, darkest forefacing, darkest undertone, depending on the value of red).
    2. Fog color is limited to half its normal intensity on the foreground layer (layer 1), from depths 1 to 10. 11 or more will still always be full intensity.
    3. Level editors may know of this as "platform coloring". When you make custom tiles and use 150 instead of 255 for red, green, or blue, the level editor will render that tile with this feature.
  2. If green is greater than or equal to 8/255, 8 is subtracted away, and then decal colors/prop colors are enabled. The math for custom colors is complicated and clutters this list when put here. Check the bottom of the document for information.
  3. Iff grime is enabled, green determines whether or not the grime actually displays (a value greater than or equal to 4 means to display grime, less than 4 hides it).
  4. If green is 1, 2, or 3, the color of the pixel will be blended towards being the effect color (1=A, 2=B, 3="C"). It is affected by shadows.
    1. See Blue #1 for how this blending is applied.
    2. "Effect Color C" is not a true effect color. It's just an easy way to categorize it for this document. It is always white (cannot be customized), and only renders in swarm rooms (where batflies spawn). It is the reason the tops of batfly hives have a white gradient (and is also why this white gradient disappears if you don't mark it as a swarm room, visually indicating the hive is inactive/dead).

The blue channel controls:

  1. Iff green is 1, 2, or 3, the blue channel in its entirety determines how strong the effect color (A or B respectively) should display in place of the current pixel. A value of 0/255 means to have no effect color, and a value of 255/255 means to fully use only that effect color.
    1. Again, 3 refers to the swarm room special color (see Green #4.ii)
  2. Iff decals are enabled (refer to Green, #2) this determines how intense the decal color is.
  3. In all other cases, blue is ignored.

The alpha channel controls:

Nothing! But, it is still sent anyway and stored. Handle this information however you please (and please limit any custom shaders to your region only. Thing of etiquette.)

To see how custom prop/decal colors work, open this (see Green #2).

NOTE: This code is sampled from my version of the LevelColor shader. Certain macros (i.e. SUNLIGHT_EFFECTIVELY_ENABLED) do not actually exist in native RW code. They are optimizations to trim out certain if statements and extra math when it is known ahead of time that a value will remain constant.

#ifdef UNITY_UV_STARTS_AT_TOP
	const half customPropColorY = 799.5 / 800.0;
#else
	const half customPropColorY = 0.5 / 800.0;
#endif
	const half levelImageWidth = 1400;
	half customPropColorX = F32_TO_I8_F(1 - texcol.b) + 0.5; 
	// Convert the blue channel to a byte color. 
	// RW inverts this though, so B=0.0f (0) means to use the 255th color
	// and B=1.0f (255) means to use the 0th color.

	// To be punctual: The actual prop color that gets used (as you can see below)
	// is in a strip of color spanning the top of the level texture starting at (0, 0)

	half4 customPropColor = tex2D(
		_MainTex,
		float2(
			customPropColorX / levelImageWidth,
			customPropColorY
		)
	);
						
	if (paletteColor == 2) {
        // Highlight color.
#ifdef SUNLIGHT_EFFECTIVELY_ON
		customPropColor = lerp(customPropColor, 1, 0.2 - shadow * 0.1);
#else
		// Lerp factor is always 0.1 (0.2 - 1 * 0.1)
		customPropColor = lerp(customPropColor, 1, 0.1);
#endif
	}
						
						
#ifdef SUNLIGHT_EFFECTIVELY_ON
	half customPropColorShadowFactor = 0.3 + 0.4 * shadow;
#else
	const half customPropColorShadowFactor = 0.7;
#endif
	const half ONE_OVER_60 = 1.0 / 60.0;
	customPropColor = lerp(customPropColor, SAMPLE_PALETTE(PALETTE_DECAL_COLOR_MOD), red * ONE_OVER_60);
						
						
	half4 fadedPropColor = lerp(setColor, customPropColor, 0.7);		// Makes the prop color appear translucent, by taking 30% of the original color up.
	half4 multipliedPropColor = setColor * customPropColor * 1.5;		// Makes the prop color appear groggy and worn.
						
	half propGroggynessFactor = saturate((red - 3.5) * 0.3);			
	// Here's what this results in.
	// The first range (left side) is depth.
	// The second range (right side) is the mix factor.
	// [0,  3] => 0.00
	// [4,  7] => [0.15, 1.00]
	// [8, 30] => 1.00
	half propGroggyness = lerp(0.9, customPropColorShadowFactor, propGroggynessFactor);
						
	// Set the rendered result color now.
	setColor = lerp(fadedPropColor, multipliedPropColor, propGroggyness);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment