Page 5 of 32

Posted: Tue Oct 09, 2007 9:58 pm
by JensAyton
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:
Image

and apply this highly original decal to the bottom:
Image

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:
Image

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.

Posted: Tue Oct 09, 2007 10:00 pm
by JensAyton
On an unrelated note, the animation example contains a texture that’s 256 by 1280 pixels. This is a Naughty, Bad, Non-power-of-two size which will be rescaled when it is loaded. In this one instance, do not follow my example. (256 by 1024 would be fine.)

Posted: Tue Oct 09, 2007 10:16 pm
by Arexack_Heretic
That is a very nice tutorial/explanation Ahruman, thanks.

Also I saw that entity_personality used before in a shader, is this a random seed number chosen on spawning (like number of seconds on clock)?

'vec' could you explain this term to a fool (me).


---
I've been trying to combine the animation shader on top of a diffuse layer all evening. (without the cutt-off <0.5 colour code)

Probably when I have digested your previous tutorial I'll have more success. ;)

Posted: Tue Oct 09, 2007 10:56 pm
by JensAyton
Arexack_Heretic wrote:
That is a very nice tutorial/explanation Ahruman, thanks.

Also I saw that entity_personality used before in a shader, is this a random seed number chosen on spawning (like number of seconds on clock)?
It’s a value that’s chosen randomly when an entity is created.
Arexack_Heretic wrote:
'vec' could you explain this term to a fool (me).
“Vec” is short for vector. A vector in GLSL is a series of 2, 3 or 4 numbers (vec2, vec3 and vec4 respectively). The numbers are identified by letter. There are three sets of letters that can be used, with conventional meanings:
  • x, y, z and w: spacial co-ordinates (i.e., points) and normals.
  • r, g, b and a: colour components.
  • s, t, p and q: texture co-ordinates. Since only 2D textures are used in Oolite, s and t are the interesting ones.
To access a component, you use a dot and the name of a component: myVec.r (which is equivalent to myVec.x or myVec.s). A single component accessed this way is a float, that is, a number with a fractional part. You can also access more than one component using component names from the same set; for instance, myVec.rgb extracts a vec3, and works on either a vec3 or a vec4, but not a vec2 since it doesn’t have a b component. In this usage, components can be specified in any order, for instance: myVec.zx. Components can be rearranged and copied this way: myVec.spq = myVec.rrs. This is called swizzling.

Vectors can be constructed from a list of integers or vectors using syntax like:

Code: Select all

vec2 a = vec2(1.0, 2.0); // [1.0, 2.0]
vec3 b = vec3(3.0); // [3.0, 3.0, 3.0]
vec4 c = vec4(a, 4.0, 5.0); // [1.0, 2.0, 4.0, 5.0]
The arithmetic operators +, -, * and / can be applied to vectors of the same size, in which case they work on an element-by-element basis. The can also be applied with a float and a vector, in which case the float acts like an appropriately-sized vector:

Code: Select all

vec4 d = c + 2.0; // [3.0, 4.0, 6.0, 7.0]
For more information, written even more pedantically and technically than my stuff, see the language specification, chapter 5. Like the JavaScript class references on the wiki, it’s not a great teacher, but it’s the right place for questions like “what does smoothstep(0.3, 0.7, x) mean?”. The GLSL Quick Reference Guide is also useful, mostly for questions like “how do I spell gl_ModelViewProjectionMatrixInverseTranspose?” or (more commonly) “what order are the parameters to smoothstep() supposed to be in?”
Arexack_Heretic wrote:
I've been trying to combine the animation shader on top of a diffuse layer all evening. (without the cutt-off <0.5 colour code)
The very first code block from the decal example simply composites one texture on top of another using the alpha channel of the top image.

Posted: Wed Oct 10, 2007 7:23 am
by Captain Hesperus
Would this method solve the idea regarding the Dream Team Anaconda with randomly determined haulage company skins/decals?

Captain Hesperus

Posted: Wed Oct 10, 2007 9:00 am
by JensAyton
For decals, yes. For entire skins, multiple shipdata.plist entries would be simpler (and work without shader support).

Posted: Wed Oct 10, 2007 2:40 pm
by Arexack_Heretic
Another one:
could one in theory give a subentity a seperate set of shaders, or should all used shaders be defined in the main-entity?

Posted: Wed Oct 10, 2007 2:54 pm
by JensAyton
Arexack_Heretic wrote:
could one in theory give a subentity a seperate set of shaders
Yes, trivially. See the Shady Griff Krait.

Posted: Wed Oct 10, 2007 3:21 pm
by Arexack_Heretic
Oh, right. added link to first post.

Ooh! just had a small idea that could have big affect.

-use the ship_personality number to choose a random colour on a colourgradient and add as a lowalpha additive shader.
This would give each ship it's own 'personality'.

(Maybe using a mask would be better, but much more laboursome.)

edit:
I could use this idea to colour 'default racers' in individual colours.
Combine this with a roles-system (racer-1, racer-2, racer-n, etc), each distinct model would get all available roles.
Populating race: Each role-number needs to be called for each contestant once and uniquely.
[variable] contestants : while [variable]>0: addShipAtPrecicely: role-[variable], decrement: variable. (I know: not working code)
The shader could refer to the 'active role' (i.e. the one used to call it) of the entity to select 'number decal' etc?
identity, shipID etc could maybe be used for seperate decal-shaders for comercial adds etc.

This way, I'd need to use only one shipdata and one model.file and one diffuse tex in greyscale.
Plus all the decals artwork and one big-ass complicated shader-complex. Complex to me in any case. ;)
Sorry this might belong more in the scripters' cove. :roll:

Extending to multiple decals

Posted: Wed Oct 10, 2007 11:07 pm
by JensAyton
Extending to multiple decals
First, we need a uniform variable to select the decal, and a constant specifying the number of decals in the decal texture (equivalent to frames in the animation):

Code: Select all

uniform float uDecalSelect;
...
const float kDecalCount = 4.0;
Oh yeah, a decal texture might be useful, too. I decided to lay it out horizontally this time, for variation. Oh, and also because it’s easier to work with wide pictures than tall ones on a landscape-mode screen.
Image
The code changes turned out to be simpler than I thought. We want to stretch the texture horizontally by a factor of

Code: Select all

kDecalCount
. We’re already compressing the texture to shrink it to scale, so we combine this into one operation.

Code: Select all

    vec2 decalTexCoord = baseTexCoord;
    decalTexCoord -= vec2(kDecalS, 0.5 - (0.5 / kDecalSize));
    decalTexCoord *= vec2(kDecalSize / kDecalCount, kDecalSize);
All that’s left is to select the “frame” from the decal texture we wish to use, in the same way as with the animation:

Code: Select all

    // Select the desired decal from the set.
    float offset = floor(uDecalSelect * kDecalCount) / kDecalCount;
    decalTexCoord.s += offset;
and modify the clipping to exclude the other frames:

Code: Select all

    decalTex *= step(offset, decalTexCoord.s) *
                step(0.0, decalTexCoord.t) *
                step(-1.0 / kDecalCount - offset, -decalTexCoord.s) *
                step(-1.0, -decalTexCoord.t);
This will work as-is for 0 ≤ uDecalSelect < 1. However, entityPersonality can be exactly 1, in which case no decal will be drawn. This can be rectified by specifying the repeat_s attribute in the texture specifier in the shaders dictionary. If repeat_s is true, an uDecalSelect of 1 will work like an uDecalSelect of 0, showing the leftmost decal. In fact, any value will work; if you bind uDecalSelect to universalTime, it will cycle through the decals once per second.

Complete code:

Code: Select all

uniform sampler2D uBaseTexture;
uniform sampler2D uDecalTexture;
uniform float uDecalSelect;

const float kDecalSize = 9.0;
const float kDecalS = 0.35;
const float kDecalCount = 4.0;


vec4 diffuseColor()
{
    vec2 baseTexCoord = gl_TexCoord[0].st;
    
    // Calculate decal texture co-ordinates.
    vec2 decalTexCoord = baseTexCoord;
    decalTexCoord -= vec2(kDecalS, 0.5 - (0.5 / kDecalSize));
    decalTexCoord *= vec2(kDecalSize / kDecalCount, kDecalSize);
    
    // Select the desired decal from the set.
    float offset = floor(uDecalSelect * kDecalCount) / kDecalCount;
    decalTexCoord.s += offset;
    
    // Get texture values.
    vec4 baseTex = texture2D(uBaseTexture, baseTexCoord);
    vec4 decalTex = texture2D(uDecalTexture, decalTexCoord);
    decalTex *= step(offset, decalTexCoord.s) *
                step(0.0, decalTexCoord.t) *
                step(-1.0 / kDecalCount - offset, -decalTexCoord.s) *
                step(-1.0, -decalTexCoord.t);
    
    // Composite decal over base.
    float alpha = decalTex.a;
    return baseTex * (1.0 - alpha) + decalTex * alpha;
}


void main()
{
    vec4 color = diffuseColor();
    // Insert lighting calculations here
    
    gl_FragColor = color;
}

Posted: Thu Oct 11, 2007 3:14 pm
by Arexack_Heretic
okay. great progress. :D

Are we going to standardise decals?
if so
we need a specifier for orientation (rotation) of their projection.
Not all ships have the same texture maps.

(Unless you plan on having seperate sets for each ship that may use the same decal.)

Posted: Thu Oct 11, 2007 3:57 pm
by JensAyton
“Unless I plan…?” I’m not planning anything here.

In the first version, the decal is rotated around its top corner by kOrientation radians clockwise. In the second version, it is rotated around its own centre, at the cost of an additional two-component addition.

Code: Select all

uniform sampler2D uBaseTexture;
uniform sampler2D uDecalTexture;
uniform float uDecalSelect;

const float kDecalSize = 9.0;
const float kDecalS = 0.35;
const float kDecalCount = 4.0;
const float kOrientation = 0.7853981634; // pi / 4


vec4 diffuseColor()
{
    vec2 baseTexCoord = gl_TexCoord[0].st;
    

    // Calculate decal texture co-ordinates.
    vec2 decalTexCoord = baseTexCoord;
    decalTexCoord -= vec2(kDecalS, 0.5 - (0.5 / kDecalSize));
    float s = sin(kOrientation);
    float c = cos(kOrientation);
    decalTexCoord *= mat2(c, s, -s, c);
    decalTexCoord *= vec2(kDecalSize / kDecalCount, kDecalSize);
    
    // Select the desired decal from the set.
    float offset = floor(uDecalSelect * kDecalCount) / kDecalCount;
    decalTexCoord.s += offset;
    
    // Get texture values.
    vec4 baseTex = texture2D(uBaseTexture, baseTexCoord);
    vec4 decalTex = texture2D(uDecalTexture, decalTexCoord);
    decalTex *= step(offset, decalTexCoord.s) *
                step(0.0, decalTexCoord.t) *
                step(-1.0 / kDecalCount - offset, -decalTexCoord.s) *
                step(-1.0, -decalTexCoord.t);
    
    // Composite decal over base.
    float alpha = decalTex.a;
    return baseTex * (1.0 - alpha) + decalTex * alpha;
}


void main()
{
    vec4 color = diffuseColor();
    // Insert lighting calculations here
    
    gl_FragColor = color;
}

Code: Select all

uniform sampler2D uBaseTexture;
uniform sampler2D uDecalTexture;
uniform float uDecalSelect;

const float kDecalSize = 9.0;
const float kDecalS = 0.35;
const float kDecalCount = 4.0;
const float kOrientation = 0.7853981634; // pi / 4


vec4 diffuseColor()
{
    vec2 baseTexCoord = gl_TexCoord[0].st;
    

    // Calculate decal texture co-ordinates.
    vec2 decalTexCoord = baseTexCoord;
    decalTexCoord -= vec2(kDecalS + (0.5 / kDecalSize), 0.5);
    float s = sin(kOrientation);
    float c = cos(kOrientation);
    decalTexCoord *= mat2(c, s, -s, c);
    decalTexCoord += vec2(0.5 / kDecalSize);
    decalTexCoord *= vec2(kDecalSize / kDecalCount, kDecalSize);
    
    // Select the desired decal from the set.
    float offset = floor(uDecalSelect * kDecalCount) / kDecalCount;
    decalTexCoord.s += offset;
    
    // Get texture values.
    vec4 baseTex = texture2D(uBaseTexture, baseTexCoord);
    vec4 decalTex = texture2D(uDecalTexture, decalTexCoord);
    decalTex *= step(offset, decalTexCoord.s) *
                step(0.0, decalTexCoord.t) *
                step(-1.0 / kDecalCount - offset, -decalTexCoord.s) *
                step(-1.0, -decalTexCoord.t);
    
    // Composite decal over base.
    float alpha = decalTex.a;
    return baseTex * (1.0 - alpha) + decalTex * alpha;
}


void main()
{
    vec4 color = diffuseColor();
    // Insert lighting calculations here
    
    gl_FragColor = color;
}
Oh, you want to understand how it works? Well, study some linear algebra, then. :-)

Posted: Thu Oct 11, 2007 4:41 pm
by Arexack_Heretic
I meant 'you' in the general sense, as in 'one must do it'. ;)

Algebra is not really the big problem here
(...although I must admit it's not my strongest talent.)
'tis more that I just do not know the methods, command and conventions in GL (or JS) yet.

Sorry if I sound ungratefull. :p

present for Griff: ;)

TheWormingHole

Posted: Sun Oct 14, 2007 2:32 am
by Griff
Awesome stuff A_H! I spent ages trying to draw something like that and just gave up in the end, but that animation you uploaded is exactly what i was trying to do! thanks so much for uploading it, i've added it to the spacebar.oxp and uploaded it all here:-
Edit2: updated the .oxp to include Ahrumans fix for the .plist that stopped the neon sign animating, fixed the problem with solar panel flickering horribly, changed the docking bay shader to stop it inheriting romantic mood lighting from the system sun and remembered to flip the UV's on one side of the neon sign so it looked correct from the front & back!
http://www.box.net/shared/85jm82oz5r

i *think* the non-animated-ness of the A_H neon sign at the moment is something to do with a bug that Ahruman has already fixed in his current work in progess build of Oolite - something about sub-entities not inheriting shader bindings if i remember correctly although i can't find the post where he worked it out, i think it was the griff_boa one where i was moaning that the engines weren't glowing.
How are you getting on with rendermonkey A_H? you seem to be getting the jist of this glsl lark pretty damn fast!
edit: uh, just realised that the oxp i uploaded might change most of the coriolis stations into spacebars instead of what they should be, um.. best to treat it as an experimental oxp!

Posted: Sun Oct 14, 2007 6:48 am
by Dr. Nil
A lot of good work and interesting experiments being done here.

Perhaps one day we'll see random ads in the main stations' docking bays. :D

Btw: I have a couple of brand logos and decals from some OXPs in higher resolution than released. Perhaps there could be a sticky thread for logos and decals somewhere?