Arexack_Heretic wrote:I guess decals would only be practical for vehicles that all use the same texture/model and are expected to have decals? Racers, police etc.
As each different model would need to have a different decal-shader...in order to apply the effect on the right place on the model... ?
...
Not sure how exactly tex1 is mapped on tex0 (which is AFAIK the model-base-texture.) I'll figure it out...someday
Right. Oolite’s model format only allows for one set of texture co-ordinates, so if you want to work with multiple textures you need to work out the relationships between the textures in the GLSL code. For things like specular maps and glow maps, you generally want all the textures to have the same co-ordinates anyway. For a decal, you’ll need to apply a transformation to the texture co-ordinates.
For a concrete example, let’s take a Cobra 3:
and apply this highly original decal to the bottom:
First, lets have the base texture with the decal composited on top:
Code: Select all
uniform sampler2D uBaseTexture;
uniform sampler2D uDecalTexture;
void main()
{
vec2 baseTexCoord = gl_TexCoord[0].st;
// Calculate decal texture co-ordinates.
vec2 decalTexCoord = baseTexCoord;
// Get texture values.
vec4 baseTex = texture2D(uBaseTexture, baseTexCoord);
vec4 decalTex = texture2D(uDecalTexture, decalTexCoord);
// Composite decal over base.
float alpha = decalTex.a;
vec4 color = baseTex * (1.0 - alpha) + decalTex * alpha;
// Insert lighting calculations here
gl_FragColor = color;
}
Next, we need to scale it down.
Code: Select all
const float kDecalSize = 1.0/9.0;
...
vec2 decalTexCoord = baseTexCoord;
decalTexCoord /= kDecalSize;
In GLSLEditorSample, this covers the base with a 9x9 grid of decals. This wouldn’t happen in Oolite, because it defaults to using the “clamp to edge” texture mode rather than “repeat”. In principle, you could make do with using a decal texture whose edge is fully transparent. However, this won’t work with mip-mapping and is generally not robust, so I’ll clamp the texture explicitly in the code:
Code: Select all
vec4 decalTex = texture2D(uDecalTexture, decalTexCoord);
decalTex *= step(0.0, decalTexCoord.s) *
step(0.0, decalTexCoord.t) *
step(-1.0, -decalTexCoord.s) *
step(-1.0, -decalTexCoord.t);
Digression: in a traditional programming language, this would probably be written if (decalTexCoord.s < 0.0 || ...) decalTex = vec4(0.0);. However, conditional expressions are generally to be avoided in GLSL, even though they work fine in this case. Getting used to using things like step() instead where possible is a good idea. step(
edge,
x) returns 0.0 if
x <
edge, otherwise 1.0.
It is especially important to avoid writing code like:
Code: Select all
if (/* coords are inside texture */)
{
// do alpha blending here
}
else
{
// just use the base texture
}
This type of code will be handled by first doing the body of the if, then the body of the else, then blending the results together based on the condition – in other words, instead of increasing efficiency by reducing the work for fragments outside the decal, it increases work for all fragments. This is a necessary consequence of the way graphics hardware works, by performing the same action in parallel on lots of fragments. Parallelism is also a major part of what makes graphics hardware fast, so it’s not really avoidable.
</digression>
Anyway, we now have a clipped decal in the top-left corner of the base texture. All that remains is to move it to the right place, which is a simple matter of adding the required offset to the texture co-ordinates… or rather, of subtracting it. (If you want the decal’s left edge to be at 0.2, you want
decalTexCoord.s to be 0 when
baseTex.s is 0.2, so you subtract 0.2.)
This can be done either before or after scaling the texture co-ordinates; I chose before, so we’re working in units of the base texture co-ordinate system. We want to offset the
s co-ordinate some amount to the right, and we want the
t co-ordinate to be right in the middle. To get it in the middle, we use an offset of 0.5 minus half the height of the decal.
Code: Select all
vec2 decalTexCoord = baseTexCoord;
decalTexCoord -= vec2(kDecalS, 0.5 - (0.5 * kDecalSize));
decalTexCoord *= kDecalSize;
Through experimentation I’ve determined that a
kDecalS value of about 0.35 works well in this case.
Complete shader:
Code: Select all
uniform sampler2D uBaseTexture;
uniform sampler2D uDecalTexture;
const float kDecalSize = 1.0/9.0;
const float kDecalS = 0.35;
void main()
{
vec2 baseTexCoord = gl_TexCoord[0].st;
// Calculate decal texture co-ordinates.
vec2 decalTexCoord = baseTexCoord;
decalTexCoord -= vec2(kDecalS, 0.5 - (0.5 * kDecalSize));
decalTexCoord /= kDecalSize;
// Get texture values.
vec4 baseTex = texture2D(uBaseTexture, baseTexCoord);
vec4 decalTex = texture2D(uDecalTexture, decalTexCoord);
decalTex *= step(0.0, decalTexCoord.s) *
step(0.0, decalTexCoord.t) *
step(-1.0, -decalTexCoord.s) *
step(-1.0, -decalTexCoord.t);
// Composite decal over base.
float alpha = decalTex.a;
vec4 color = baseTex * (1.0 - alpha) + decalTex * alpha;
// Insert lighting calculations here
gl_FragColor = color;
}
Output:
This shader allows you to add a static decal that’s more detailed than the base texture, but generally you’ll want to select a decal from a set. The technique used in the animation shader should be easy to adapt. Instead of
universalTime, bind the controlling uniform to
entityPersonality to select a random decal for each ship, or use a Behemoth-like technique with multiple shipdata.plist entries with different constant uniforms. (To specify a constant float uniform, use { uDecalOffset = { type = "float"; value = "0.25"; }} in the uniforms list.)
Oh, yeah… on the topic of the Behemoth technique, I should probably point out that you can replace textures on a model using the materials dictionary, without requiring shaders at all. But you can’t select a part of a texture this way, so the name signs would have to be put in separate files.