Shaders’ Outpost

Discussion and information relevant to creating special missions, new ships, skins etc.

Moderators: winston, another_commander

Post Reply
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post 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.
Last edited by JensAyton on Wed Oct 22, 2008 9:11 pm, edited 3 times in total.
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post 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.)
User avatar
Arexack_Heretic
Dangerous Subversive Element
Dangerous Subversive Element
Posts: 1876
Joined: Tue Jun 07, 2005 7:32 pm
Location: [%H] = Earth surface, Lattitude 52°10'58.19"N, longtitude 4°30'0.25"E.
Contact:

Post 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. ;)
Riding the Rocket!
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post 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.
User avatar
Captain Hesperus
Grand High Clock-Tower Poobah
Grand High Clock-Tower Poobah
Posts: 2310
Joined: Tue Sep 19, 2006 1:10 pm
Location: Anywhere I can sell Trumbles.....

Post by Captain Hesperus »

Would this method solve the idea regarding the Dream Team Anaconda with randomly determined haulage company skins/decals?

Captain Hesperus
The truth, revealed!!
Image
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post by JensAyton »

For decals, yes. For entire skins, multiple shipdata.plist entries would be simpler (and work without shader support).
User avatar
Arexack_Heretic
Dangerous Subversive Element
Dangerous Subversive Element
Posts: 1876
Joined: Tue Jun 07, 2005 7:32 pm
Location: [%H] = Earth surface, Lattitude 52°10'58.19"N, longtitude 4°30'0.25"E.
Contact:

Post 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?
Riding the Rocket!
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post by JensAyton »

Arexack_Heretic wrote:
could one in theory give a subentity a seperate set of shaders
Yes, trivially. See the Shady Griff Krait.
User avatar
Arexack_Heretic
Dangerous Subversive Element
Dangerous Subversive Element
Posts: 1876
Joined: Tue Jun 07, 2005 7:32 pm
Location: [%H] = Earth surface, Lattitude 52°10'58.19"N, longtitude 4°30'0.25"E.
Contact:

Post 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:
Riding the Rocket!
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Extending to multiple decals

Post 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;
}
User avatar
Arexack_Heretic
Dangerous Subversive Element
Dangerous Subversive Element
Posts: 1876
Joined: Tue Jun 07, 2005 7:32 pm
Location: [%H] = Earth surface, Lattitude 52°10'58.19"N, longtitude 4°30'0.25"E.
Contact:

Post 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.)
Riding the Rocket!
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post 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. :-)
User avatar
Arexack_Heretic
Dangerous Subversive Element
Dangerous Subversive Element
Posts: 1876
Joined: Tue Jun 07, 2005 7:32 pm
Location: [%H] = Earth surface, Lattitude 52°10'58.19"N, longtitude 4°30'0.25"E.
Contact:

Post 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
Riding the Rocket!
User avatar
Griff
Oolite 2 Art Director
Oolite 2 Art Director
Posts: 2479
Joined: Fri Jul 14, 2006 12:29 pm
Location: Probably hugging his Air Fryer

Post 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!
Last edited by Griff on Thu Oct 18, 2007 7:49 pm, edited 5 times in total.
User avatar
Dr. Nil
---- E L I T E ----
---- E L I T E ----
Posts: 983
Joined: Thu Sep 28, 2006 5:11 pm
Location: Nearest Hoopy Casino
Contact:

Post 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?
Image

300 billboards in Your Ad Here!
Astromines and more in Commies.
AVAILABLE HERE along with other Oolite eXpansion Packs.
Post Reply