Shaders’ Outpost

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

Moderators: winston, another_commander

Post Reply
User avatar
Frame
---- E L I T E ----
---- E L I T E ----
Posts: 1477
Joined: Fri Mar 30, 2007 8:32 am
Location: Witchspace

Post by Frame »

Arexack_Heretic wrote:
How do you calculate these coordinates actually?

I have been surfing through wiki, wikipedia and openGL tutorials, but I'm not finding much sense out of it all. :oops:

DLing rendermonkey, maybe that will do it for me. :)


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... ?

Just coming to grips with the texcoord calculus now.
I can see how you could use the method you used in the animation-example to apply a given frame on a model.

Not sure how exactly tex1 is mapped on tex0 (which is AFAIK the model-base-texture.) I'll figure it out...someday
i´m not sure if this is what you mean... but here goes..

Take A Imaginary Image with Width and Height of 256 by 256....

X range 0-255 (remember we count in ZERO)
Y range 0-255

coordinates in paint program x=255,y=255 would be in OpenGL terms

X 255/255 = 1.0 = OpenGL coordinate
y 255/255 = 1.0 = OpenGL coordinate

likewise coordinates in paint program x=127, y=127

X 127/255 = 0,498039 = OpenGL coordinate
Y 127/255 = 0,498039 = OpenGL coordinate

yes you could use 0.5 instead... ;-).

The reason for this is that we can change the image size to what we want, which is usefull for example Mipmapping,
without actually having to write code to recalculate the Actual coordinates...
Bounty Scanner
Number 935
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 »

Ah..yes. I think so.
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:
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!
Post Reply