This was initially a TAP request, and credit for the idea and initial implementation goes to pecenipicek.
Okay, so what's this about?If you have played Homeworld 2, you may remember that that game allowed you to customize the colour pallette used for your ships in multiplayer. You could choose a base and a stripe colour, and through shader magic, these colours would be applied to your ships.
This is a port of that same feature to FS2.
Great, how does it work?First, you need to make a few adaptations to the textures used by the model you wish to recolor. This feature uses the heretofore unused red and blue channels on the normal map. Areas you wish to carry the base colour should receive a full value in the red channel, while areas that should get the stripe colour should receive a full value in the blue channel.
The models' diffuse map should also be adapted. The areas that should be coloured should receive a base colour of (127, 127, 127) (Or, in other words, a half value in the red, green and blue channels).
Essentially, those parts of the base diffuse texture should be grayscale only.
Now, in order to get a ship to use this, a few table changes are necessary. First, you need to define a set of team colors in colors.tbl (don't worry if you can't find that one, it's pretty damn new).
A team definition looks like this:
#Start Colors
;Because of the way the colors.tbl parser is set up, this section MUST be present, even if it is empty
#End
#Team Colors
$Team Name: Vaygr
$Team Stripe Color: ( 10 240 10 )
$Team Base Color: ( 240 10 10 )
#End
Both the stripe and base colours MUST be defined. If you wish to have one of those to not have an impact, set it to ( 127 127 127 ).
In ships.tbl, you need to set a default team for the ship class that will be using this feature. You can do this by adding a "$Default Team: <name>" entry after the $ND and before the $Show Damage entries. The team name is the name defined in colors.tbl. Setting this property will enable team coloring for that ship class. Not having it means team coloring will be unavailable for that particular class.
Finally, you need an updated main-f.sdr file.
#ifdef FLAG_LIGHT
uniform int n_lights;
#endif
#ifdef FLAG_DIFFUSE_MAP
uniform sampler2D sBasemap;
#endif
#ifdef FLAG_GLOW_MAP
uniform sampler2D sGlowmap;
#endif
#ifdef FLAG_SPEC_MAP
uniform sampler2D sSpecmap;
#endif
#ifdef FLAG_ENV_MAP
uniform samplerCube sEnvmap;
uniform bool alpha_spec;
varying vec3 envReflect;
#endif
#ifdef FLAG_NORMAL_MAP
uniform sampler2D sNormalmap;
varying mat3 tbnMatrix;
#endif
#ifdef FLAG_TEAMCOLOR
uniform vec3 base_color;
uniform vec3 stripe_color;
#endif
#ifdef FLAG_FOG
varying float fogDist;
#endif
#ifdef FLAG_ANIMATED
uniform sampler2D sFramebuffer;
uniform int effect_num;
uniform float anim_timer;
uniform float vpwidth;
uniform float vpheight;
#endif
varying vec4 position;
varying vec3 lNormal;
#if SHADER_MODEL == 2
#define MAX_LIGHTS 2
#else
#define MAX_LIGHTS 8
#endif
#define SPEC_INTENSITY_POINT 5.3 // Point light
#define SPEC_INTENSITY_DIRECTIONAL 3.0 // Directional light
#define SPECULAR_FACTOR 1.75
#define SPECULAR_ALPHA 0.1
#define SPEC_FACTOR_NO_SPEC_MAP 0.6
#define ENV_ALPHA_FACTOR 0.3
#define GLOW_MAP_INTENSITY 1.5
#define AMBIENT_LIGHT_BOOST 1.0
void main()
{
vec3 eyeDir = vec3(normalize(-position).xyz); // Camera is at (0,0,0) in ModelView space
vec4 lightAmbientDiffuse = vec4(0.0, 0.0, 0.0, 1.0);
vec4 lightDiffuse = vec4(0.0, 0.0, 0.0, 1.0);
vec4 lightAmbient = vec4(0.0, 0.0, 0.0, 1.0);
vec4 lightSpecular = vec4(0.0, 0.0, 0.0, 1.0);
vec2 texCoord = gl_TexCoord[0].xy;
#ifdef FLAG_SPEC_MAP
vec4 specColour = texture2D(sSpecmap, texCoord);
#endif
#ifdef FLAG_LIGHT
#ifdef FLAG_NORMAL_MAP
// Normal map - convert from DXT5nm
vec3 normal;
vec4 NormalMap = texture2D(sNormalmap, texCoord);
#ifdef FLAG_TEAMCOLOR
vec2 teamMask = NormalMap.rb;
#endif
normal.rg = (NormalMap.ag * 2.0) - 1.0;
#ifdef FLAG_ENV_MAP
vec3 envOffset = vec3(0.0);
envOffset.xy = normal.xy;
vec3 envReflectNM = envReflect + envOffset;
vec4 envColour = textureCube(sEnvmap, envReflectNM);
#endif
normal.b = sqrt(1.0 - dot(normal.rg, normal.rg));
normal = tbnMatrix * normal;
float norm = length(normal);
// prevent breaking of normal maps
if (length(normal) > 0.0)
normal /= norm;
else
normal = tbnMatrix * vec3(0.0, 0.0, 1.0);
#else
vec3 normal = lNormal;
#ifdef FLAG_ENV_MAP
vec4 envColour = textureCube(sEnvmap, envReflect);
#endif
#endif
vec3 lightDir;
lightAmbient = gl_FrontMaterial.emission + (gl_LightModel.ambient * gl_FrontMaterial.ambient);
float dist;
#pragma optionNV unroll all
for (int i = 0; i < MAX_LIGHTS; ++i) {
#if SHADER_MODEL > 2
if (i > n_lights)
break;
#endif
float specularIntensity = 1.0;
float attenuation = 1.0;
// Attenuation and light direction
#if SHADER_MODEL > 2
if (gl_LightSource[i].position.w == 1.0) {
#else
if (gl_LightSource[i].position.w == 1.0 && i != 0) {
#endif
// Positional light source
dist = distance(gl_LightSource[i].position.xyz, position.xyz);
lightDir = (gl_LightSource[i].position.xyz - position.xyz);
#if SHADER_MODEL > 2
if (gl_LightSource[i].spotCutoff < 91.0) { // Tube light
float beamlength = length(gl_LightSource[i].spotDirection);
vec3 beamDir = normalize(gl_LightSource[i].spotDirection);
// Get nearest point on line
float neardist = dot(position.xyz - gl_LightSource[i].position.xyz , beamDir);
// Move back from the endpoint of the beam along the beam by the distance we calculated
vec3 nearest = gl_LightSource[i].position.xyz - beamDir * abs(neardist);
lightDir = nearest - position.xyz;
dist = length(lightDir);
}
#endif
lightDir = normalize(lightDir);
attenuation = 1.0 / (gl_LightSource[i].constantAttenuation + (gl_LightSource[i].linearAttenuation * dist) + (gl_LightSource[i].quadraticAttenuation * dist * dist));
specularIntensity = SPEC_INTENSITY_POINT;
} else {
// Directional light source
lightDir = normalize(gl_LightSource[i].position.xyz);
specularIntensity = SPEC_INTENSITY_DIRECTIONAL;
}
vec3 half_vec = normalize(lightDir + eyeDir);
// Ambient and Diffuse
lightAmbient += (gl_FrontLightProduct[i].ambient * attenuation);
lightDiffuse += (gl_FrontLightProduct[i].diffuse * (max(dot(normal, lightDir), 0.0)) * attenuation);
// Specular
float NdotHV = clamp(dot(normal, half_vec), 0.0, 1.0);
lightSpecular += ((gl_FrontLightProduct[i].specular * pow(NdotHV, gl_FrontMaterial.shininess)) * attenuation) * specularIntensity;
}
lightAmbientDiffuse = lightAmbient + lightDiffuse;
#else
lightAmbientDiffuse = gl_Color;
lightSpecular = gl_SecondaryColor;
#endif
#ifdef FLAG_ANIMATED
vec2 distort = vec2(cos(position.x*position.w*0.005+anim_timer*20.0)*sin(position.y*position.w*0.005),sin(position.x*position.w*0.005+anim_timer*20.0)*cos(position.y*position.w*0.005))*0.03;
#endif
// Base color
#ifdef FLAG_DIFFUSE_MAP
#ifdef FLAG_ANIMATED
vec4 baseColor;
if (effect_num == 2) {
baseColor = texture2D(sBasemap, texCoord + distort*(1.0-anim_timer));
} else {
baseColor = texture2D(sBasemap, texCoord);
}
#else
vec4 baseColor = texture2D(sBasemap, texCoord);
#endif
#else
vec4 baseColor = gl_Color;
#endif
#ifdef FLAG_TEAMCOLOR
vec3 base = base_color - vec3(0.5);
vec3 stripe = stripe_color - vec3(0.5);
baseColor.rgb += (base * teamMask.x) + (stripe * teamMask.y);
#endif
vec4 fragmentColor;
fragmentColor.rgb = baseColor.rgb * max(lightAmbientDiffuse.rgb * AMBIENT_LIGHT_BOOST, gl_LightModel.ambient.rgb - 0.425);
fragmentColor.a = baseColor.a;
// Spec color
#ifdef FLAG_SPEC_MAP
#ifdef FLAG_TEAMCOLOR
specColour.rgb += ((base * teamMask.x) + (stripe * teamMask.y)) * 0.3;
#endif
fragmentColor.rgb += lightSpecular.rgb * (specColour.rgb * SPECULAR_FACTOR);
fragmentColor.a += (dot(lightSpecular.a, lightSpecular.a) * SPECULAR_ALPHA);
#else
fragmentColor.rgb += lightSpecular.rgb * (baseColor.rgb * SPEC_FACTOR_NO_SPEC_MAP);
#endif
// Env color
#ifdef FLAG_ENV_MAP
vec3 envIntensity = (alpha_spec) ? vec3(specColour.a) : specColour.rgb;
fragmentColor.a += (dot(envColour.rgb, envColour.rgb) * ENV_ALPHA_FACTOR);
fragmentColor.rgb += envColour.rgb * envIntensity;
#endif
// Glow color
#ifdef FLAG_GLOW_MAP
fragmentColor.rgb += texture2D(sGlowmap, texCoord).rgb * GLOW_MAP_INTENSITY;
#endif
#ifdef FLAG_FOG
fragmentColor.rgb = mix(fragmentColor.rgb, gl_Fog.color.rgb, fogDist);
#endif
//Commented out. If HDR makes a comeback, we may need this.
//fragmentColor.a = clamp(fragmentColor.a ,0.0,1.0);
#ifdef FLAG_ANIMATED
if (effect_num == 0) {
float shinefactor = 1.0/(1.0 + pow((fract(abs(gl_TexCoord[0].x))-anim_timer) * 1000.0, 2.0)) * 1000.0;
gl_FragColor.rgb = fragmentColor.rgb + vec3(shinefactor);
gl_FragColor.a = fragmentColor.a * clamp(shinefactor * (fract(abs(gl_TexCoord[0].x))-anim_timer) * -10000.0,0.0,1.0);
}
if (effect_num == 1) {
float shinefactor = 1.0/(1.0 + pow((position.y-anim_timer), 2.0));
gl_FragColor.rgb = fragmentColor.rgb + vec3(shinefactor);
#ifdef FLAG_LIGHT
gl_FragColor.a = fragmentColor.a;
#else
// ATI Wireframe fix *grumble*
gl_FragColor.a = clamp((position.y-anim_timer) * 10000.0,0.0,1.0);
#endif
}
if (effect_num == 2) {
vec2 screenPos = gl_FragCoord.xy * vec2(vpwidth,vpheight);
gl_FragColor.a = fragmentColor.a;
float cloak_interp = (sin(position.x*position.w*0.005+anim_timer*20.0)*sin(position.y*position.w*0.005)*0.5)-0.5;
#ifdef FLAG_LIGHT
gl_FragColor.rgb = mix(texture2D(sFramebuffer, screenPos + distort*anim_timer + anim_timer*0.1*normal.xy).rgb,fragmentColor.rgb,clamp(cloak_interp+anim_timer*2.0,0.0,1.0));
#else
gl_FragColor.rgb = mix(texture2D(sFramebuffer, screenPos + distort*anim_timer + anim_timer*0.1*lNormal.xy).rgb,fragmentColor.rgb,clamp(cloak_interp+anim_timer*2.0,0.0,1.0));
#endif
}
#else
gl_FragColor = fragmentColor;
#endif
}
How does it look in action?Like this:
Team colors off:
Team colors on:
Can I use more than one color set per ship class?Yes. In FRED's initial status editor, you can choose which team colors to use, if team coloring has been enabled for that particular ship.
Are there patches and test builds?Of course.
Patch:
http://blueplanet.fsmods.net/E/stuff/teamcolor.patchExecutables:
http://blueplanet.fsmods.net/E/stuff/teamcolorexe.7zEDIT: Changed shader to let base/stripe colours interact with spec colour.
EDIT2: Fixed a bug where team colors were not applied in the techroom.
EDIT3: Patch and exes have been updated to fix a few issues where team colors were not applied correctly in-mission.