Jump to content

Recommended Posts

Sorry, it's been 20 years since I used to work with glsl, and my shaders were top of the world in realism, but monolithic in structure;  I was heavily criticized for my style while highly praised for the results...  Anyways, I was trying to introduce one little mod in model_common.fs, just to get my feet wet... See near the bottom:

#version 120

#include "common/fog.h"
#include "common/los_fragment.h"
#include "common/shadows_fragment.h"

uniform sampler2D baseTex;
uniform sampler2D aoTex;
uniform sampler2D normTex;
uniform sampler2D specTex;

#if USE_OBJECTCOLOR
  uniform vec3 objectColor;
#else
#if USE_PLAYERCOLOR
  uniform vec3 playerColor;
#endif
#endif

uniform vec3 shadingColor;
uniform vec3 ambient;
uniform vec3 sunColor;
uniform vec3 sunDir;

varying vec4 v_lighting;
varying vec2 v_tex;

#if (USE_INSTANCING || USE_GPU_SKINNING) && USE_AO
  varying vec2 v_tex2;
#endif

#if USE_SPECULAR
  uniform float specularPower;
  uniform vec3 specularColor;
#endif

#if USE_NORMAL_MAP || USE_SPECULAR_MAP || USE_PARALLAX || USE_AO
  uniform vec4 effectSettings;
#endif

#if USE_SPECULAR || USE_NORMAL_MAP || USE_SPECULAR_MAP || USE_PARALLAX
  varying vec4 v_normal;
  #if (USE_INSTANCING || USE_GPU_SKINNING) && (USE_NORMAL_MAP || USE_PARALLAX)
    varying vec4 v_tangent;
    //varying vec3 v_bitangent;
  #endif
  #if USE_SPECULAR || USE_SPECULAR_MAP
    varying vec3 v_half;
  #endif
  #if (USE_INSTANCING || USE_GPU_SKINNING) && USE_PARALLAX
    varying vec3 v_eyeVec;
  #endif
#endif

void main()
{
  vec2 coord = v_tex;

  #if (USE_INSTANCING || USE_GPU_SKINNING) && (USE_PARALLAX || USE_NORMAL_MAP)
    vec3 bitangent = vec3(v_normal.w, v_tangent.w, v_lighting.w);
    mat3 tbn = mat3(v_tangent.xyz, bitangent, v_normal.xyz);
  #endif

  #if (USE_INSTANCING || USE_GPU_SKINNING) && USE_PARALLAX
  {
    float h = texture2D(normTex, coord).a;

    vec3 eyeDir = normalize(v_eyeVec * tbn);
    float dist = length(v_eyeVec);

    vec2 move;
    float height = 1.0;
    float scale = effectSettings.z;

    int iter = int(min(20.0, 25.0 - dist/10.0));

	if (iter > 0)
	{
		float s = 1.0/float(iter);
		float t = s;
		move = vec2(-eyeDir.x, eyeDir.y) * scale / (eyeDir.z * float(iter));
		vec2 nil = vec2(0.0);

		for (int i = 0; i < iter; ++i) {
		  height -= t;
		  t = (h < height) ? s : 0.0;
		  vec2 temp = (h < height) ? move : nil;
		  coord += temp;
		  h = texture2D(normTex, coord).a;
		}

		// Move back to where we collided with the surface.
		// This assumes the surface is linear between the sample point before we
		// intersect the surface and after we intersect the surface
		float hp = texture2D(normTex, coord - move).a;
		coord -= move * ((h - height) / (s + h - hp));
	}
  }
  #endif

  vec4 tex = texture2D(baseTex, coord);

  // Alpha-test as early as possible
  #ifdef REQUIRE_ALPHA_GEQUAL
    if (tex.a < REQUIRE_ALPHA_GEQUAL)
      discard;
  #endif

  #if USE_TRANSPARENT
    gl_FragColor.a = tex.a;
  #else
    gl_FragColor.a = 1.0;
  #endif

  vec3 texdiffuse = tex.rgb;

  // Apply-coloring based on texture alpha
  #if USE_OBJECTCOLOR
    texdiffuse *= mix(objectColor, vec3(1.0, 1.0, 1.0), tex.a);
  #else
  #if USE_PLAYERCOLOR
    texdiffuse *= mix(playerColor, vec3(1.0, 1.0, 1.0), tex.a);
  #endif
  #endif

  #if USE_SPECULAR || USE_SPECULAR_MAP || USE_NORMAL_MAP
    vec3 normal = v_normal.xyz;
  #endif

  #if (USE_INSTANCING || USE_GPU_SKINNING) && USE_NORMAL_MAP
    vec3 ntex = texture2D(normTex, coord).rgb * 2.0 - 1.0;
    ntex.y = -ntex.y;
    normal = normalize(tbn * ntex);
    vec3 bumplight = max(dot(-sunDir, normal), 0.0) * sunColor;
    vec3 sundiffuse = (bumplight - v_lighting.rgb) * effectSettings.x + v_lighting.rgb;
  #else
    vec3 sundiffuse = v_lighting.rgb;
  #endif

  vec4 specular = vec4(0.0);
  #if USE_SPECULAR || USE_SPECULAR_MAP
    vec3 specCol;
    float specPow;
    #if USE_SPECULAR_MAP
      vec4 s = texture2D(specTex, coord);
      specCol = s.rgb;
      specular.a = s.a;
      specPow = effectSettings.y;
    #else
      specCol = specularColor;
      specPow = specularPower;
    #endif
    specular.rgb = sunColor * specCol * pow(max(0.0, dot(normalize(normal), v_half)), specPow);
  #endif

  vec3 color = (texdiffuse * sundiffuse + specular.rgb) * getShadow();
  vec3 ambColor = texdiffuse * ambient;

  #if (USE_INSTANCING || USE_GPU_SKINNING) && USE_AO
    vec3 ao = texture2D(aoTex, v_tex2).rrr;
    ao = mix(vec3(1.0), ao * 2.0, effectSettings.w);
    ambColor *= ao;
  #endif
  
/******************************************************************/
  float up_bias;
  #if USE_SPECULAR || USE_SPECULAR_MAP || USE_NORMAL_MAP
    up_bias = 0.4 * (1.5 + normal.z);
  #else
    up_bias = 0.4 * (1.5 + v_normal.z);
  #endif
  ambColor.rgb *= up_bias;
/******************************************************************/

  color += ambColor;

  #if USE_SPECULAR_MAP && USE_SELF_LIGHT
    color = mix(texdiffuse, color, specular.a);
  #endif

  color = applyFog(color);

  color *= getLOS();

  color *= shadingColor;

  gl_FragColor.rgb = color;
}

Description:

"Ambient Light", the way OpenGL defines it, (light coming from all directions equally) is not even a first approximation of real ambient light.  A second approximation, if you call that a first, is where light comes from all directions, but with a "sky-bias" or "up-bias";  i.e. more light coming from above than from below.  I calculated it as 0.4 * (1.5 + normal.z), where normal.z wishes to be the UP component of the normal vector,  like "normal dot UP";  I don't know that it is;  it was a wild guess;  I'm very rusty on all this.

Consider that 0.5 * (1.0+("norm dot UP")) would produce a bit too much bias, as it would make light from below equal zero.  With 0.4 and 1.5 I get a range of 0.2 from below to 1.0 from top.  But subject to experimentation, of course.

The problem is it doesn't even compile.  Initially I tried using 'normal' and it told me "undefined variable 'normal'".

So I added the #if and v_normal, and now it tells me "undefined variable 'v_normal'".

But I see the declarations, albeit in wild nests of conditionals...  Which by the way I don't understand why there's so many conditions.  In the settings there are no choices for whether to have or not to use specularity or normal maps, and my "shaders" setting, whatever it means, is maxed out.

EDIT: And if you could tell me how to get the UP dot normal quantity, if z is not it, I'd appreciate.

EDIT2: Another question I have is in regards to what shaders are used for what, as I notice that when this shader fails to compile the trees disappear, but other things stay.

 

Edited by DanW58
Link to post
Share on other sites
20 minutes ago, DanW58 said:

A second approximation, if you call that a first, is where light comes from all directions, but with a "sky-bias" or "up-bias";  i.e. more light coming from above than from below.

That might happen only in case you have no directional light (but even in that case there are cases when the up light isn't so bright as horizontal ones). But a usual scene has a sun (or something like that), which light is pretty well scattered and lightens different areas.

20 minutes ago, DanW58 said:

I calculated it as 0.4 * (1.5 + normal.z), where normal.z wishes to be the UP component of the normal vector,  like "normal dot UP";  I don't know that it is;  it was a wild guess;  I'm very rusty on all this.

Consider that 0.5 * (1.0+("norm dot UP")) would produce a bit too much bias, as it would make light from below equal zero.  With 0.4 and 1.5 I get a range of 0.2 from below to 1.0 from top.  But subject to experimentation, of course.

To check that it works correctly you need to check many maps with different ambient color values.

20 minutes ago, DanW58 said:

The problem is it doesn't even compile.  Initially I tried using 'normal' and it told me "undefined variable 'normal'".

v_normal is declared only if (USE_SPECULAR || USE_NORMAL_MAP || USE_SPECULAR_MAP || USE_PARALLAX).

20 minutes ago, DanW58 said:

But I see the declarations, albeit in wild nests of conditionals...  Which by the way I don't understand why there's so many conditions.  In the settings there are no choices for whether to have or not to use specularity or normal maps, and my "shaders" setting, whatever it means, is maxed out.

It's a reducing number of shaders by combining them. These conditions are set by a material and model settings which were set by an artist/modder.

20 minutes ago, DanW58 said:

EDIT: And if you could tell me how to get the UP dot normal quantity, if z is not it, I'd appreciate.

It should be Y, Y axis looks up.

20 minutes ago, DanW58 said:

EDIT2: Another question I have is in regards to what shaders are used for what, as I notice that when this shader fails to compile the trees disappear, but other things stay.

As I said above different models have different materials, different materials mean different conditions.

Link to post
Share on other sites

Thanks!  That gives me something to chew on.

Re...

17 minutes ago, vladislavbelov said:
36 minutes ago, DanW58 said:

A second approximation, if you call that a first, is where light comes from all directions, but with a "sky-bias" or "up-bias";  i.e. more light coming from above than from below.

That might happen only case you have no directional light. But a usual scene has a sun (or something like that), which light is pretty well scattered and lightens different areas.

I do take directional light into account.  What I'm saying is this:  Light from the Sun is scattered by the atmosphere, taking some of the blue end of the spectrum out of direct path, making the Sun look yellowish, but then reaching us from other directions in the sky as it re-scatters.  However, there's no sky below our feet;  there's a ground instead, which reflets some, but not all, of the sky-scattered blue light.  So the bluish ambient light has a bias towards the sky.  The green component could be represented better by a bias towards the mid vector between UP and the Sun, maybe squared.  This is a very rough, almost contemptible, approximation, but orders of magnitude better than OGL's definition of ambient illumination.  And I do think it would improve realism with any map;  and it is super-simple.

Regarding ambient light specifications, I once advocated (while working with another engine) that the ambient light specification be removed entirely, and that the engine compute it on the fly by averaging the cube-map.  This was done and it improved realism considerably.  There's no reason why the ambient color and the cubemap used for specular reflections should ever disagree, and therefore no need to burden artists with determining and writing down what ambient color should be.  Ambient (diffuse) reflections have the same origin as specular reflections;  same environment, just different light-bounce type.

Edited by DanW58
Link to post
Share on other sites

Problem with doing that other than stripping mappers like @wowgetoffyourcellphone from their freedom is that you need a lot of new skyboxes. So far those textures have mostly been left unchanged for 10 years and we have no way to create new ones. The code there isn't that great as the dds are hardcoded.

Link to post
Share on other sites
2 minutes ago, DanW58 said:

I do take directional light into account.  What I'm saying is this:  Light from the Sun is scattered by the atmosphere, taking some of the blue end of the spectrum out of direct path, making the Sun look yellowish, but then reaching us from other directions in the sky as it re-scatters.  However, there's no sky below our feet;  there's a ground instead, which reflets some, but not all, of the sky-scattered blue light.  So the bluish ambient light has a bias towards the sky.  The green component could be represented better by a bias towards the mid vector between UP and the Sun, maybe squared.  This is a very rough, almost contemptible, approximation, but orders of magnitude better than OGL's definition of ambient illumination.  And I do think it would improve realism with any map;  and it is super-simple.

In games a single ambient light doesn't mean only skies. It's just an old tool to set color of objects without sun. So it's not guaranteed that the value of ambient color will be bluish on all our maps, it might be yellowish or whatever an artist want.

4 minutes ago, DanW58 said:

that the ambient light specification be removed entirely, and that the engine compute it on the fly by averaging the cube-map.  This was done and it improved realism considerably.  There's no reason why the ambient color and the cubemap used for specular reflections should ever disagree, and therefor no need to burden people with determining and writing down what ambient color should be.

Yes, that's a way to make a light looks more realistic. Usually it's called radiance environment map, and indeed it might use cubemaps, but in some cases harmonics. And as @Stan` pointed, it requires a lot of artist work. And checking that everything works fine. Also without using physically correct lighting it won't give a lot better results.

Link to post
Share on other sites

Guys, I sense nothing but resistance to ANYTHING I want to help improve, no matter how obviously adventageous.

Is there any point whatsoever for me sticking around here?

I'm accused of trying to take away artistic freedom... Heck, I was an artist before I became a programmer, and as an artist being told I have to set a color for ambient light which I don't know what it should be might not be what I consider "freedom".

And how does my proposition require "a lot of artist work"?!?!?!  What?  Where?  I was merely suggesting that the engine compute ambien light color from the environment map, by averaging it.  How does that increase the workload of artists?

The resistance and hostility to any changes whatsoever around here are off the charts!

Link to post
Share on other sites
13 minutes ago, DanW58 said:

I'm accused of trying to take away artistic freedom...

Actually I'm all for reducing such freedom which breaks introducing general algorithms like you suggested. But AFAIK there is the only one way is to move to PBR.

I've already removed separated ambient colors for units and terrain, for what I was kind of "blamed".

13 minutes ago, DanW58 said:

And how does my proposition require "a lot of artist work"?!?!?!  What?  Where?  I was merely suggesting that the engine compute ambien light color from the environment map, by averaging it.  How does that increase the workload of artists?

Every modification of a general algorithm is applied to the custom map settings can significantly change a visual style of a map. So it's not our goal or wish to make a resistance, it's just a pretty old project that have own workaround and not ideal solutions.

13 minutes ago, DanW58 said:

The resistance and hostility to any changes whatsoever around here are off the charts!

Nobody wants to make resistance and hostility.

6 minutes ago, DanW58 said:

it might also be INCORRECT.

For sure it might be incorrect. But the problem is that there is a map that uses an incorrect color and which is compensated by other settings and it looks kind of "nice". After changing the algorithm it might become look different. And that "different" might be much less nice. And for that cases you need to find all maps with the incorrect lighting, change algorithm and test that it looks ok, and artists should say that it looks ok.

Link to post
Share on other sites

Alright, gottcha.

2 minutes ago, vladislavbelov said:

Actually I'm all for reducing such freedom which breaks introducing general algorithms like you suggested. But AFAIK there is the only one way is to move to PBR.

LOL, we are brothers in pain, then.

What's PBR?  Oh, physics based rendering.  Well, if by that you mean that system that uses like nine texture layers to describe a material, none of that is necessary to have a pretty good looking rendering pipeline.  Just a bit of common sense here and there adds up over time, such as not having separate ambients for units... (OMG!)...

 

Link to post
Share on other sites
20 minutes ago, DanW58 said:

Oh, physics based rendering.

Yeah.

21 minutes ago, DanW58 said:

Well, if by that you mean that system that uses like nine texture layers to describe a material

Usually you'd have (normal map + ao) + (diffuse + (glossiness + specular) | (metallic + roughness)) = 5 textures.

24 minutes ago, DanW58 said:

none of that is necessary to have a pretty good looking rendering pipeline.

Sure, you don't have to use it in a general game. In our case using these textures allows to describe real world material. Which means relatively predictable results for different map settings and physically correct lighting.

Link to post
Share on other sites

Okay, that stack looks reasonable.  It can still be optimized.

Alpha, for example, is useless 99.999% of the time;  worth having a separate shader for things like windows, as they require fresnel anyways.

So, use the alpha channels of diffuse and specular for ao and roughness respectively.

The system I co-authored 20 years ago had 3 textures, not 5:

Diffuse (RGB) + ao (A)

Specular color (RGB) + roughness (A)

Glossiness and roughness are the same thing.

The distinction between using metallic specularity (colored, unmodulated), and non-metallic specularity (white, Fresnel modulated) was deduced per fragment by the shader, based on the diffuse and specular textures.  If diffuse and specular agreed more or less in hue and saturation, it applied metallic reflectivity; but if the saturation of the specular color was far less than that of the diffuse color, it went for a plastic to paint reflectivity.

The third texture was for detail texturing.

Link to post
Share on other sites

Bah, sorry;  I forgot here you use a separate mapping for AO.

But that's even better, you can use diffuse alpha channel to distinguish between metals and dielectric materials.

Of course, it would have to ship with simple tools to convert from existing stack, but that's writable in a day.

EDIT, by "glossiness and roughness are the same thing" I mean the microbumpiness.. If by glossiness you mean the index of refraction, that could also be conveyed by the diffuse alpha channel.  Above 75%, say, means metallic.  0~50% encodes dielectric constant.

EDIT2:  By now I have looked at all of the fragment shaders, and I'm still confused.  It seems to me if particular effects such as bumpmapping are not on, then there is no normal to work with!!  Isn't there an interpolated normal coming in, regardless of effects?

Edited by DanW58
Link to post
Share on other sites
25 minutes ago, DanW58 said:

So, use the alpha channels of diffuse and specular for ao and roughness respectively.

The system I co-authored 20 years ago had 3 textures, not 5:

Diffuse (RGB) + ao (A)

Specular color (RGB) + roughness (A)

Glossiness and roughness are the same thing.

I'm talking about how many textures an artist should create. You're talking about technical implementation on the engine. And indeed it might be packed. As a normal map might store only 2 bytes per pixel. And another 2 bytes are for something else.

19 minutes ago, DanW58 said:

EDIT, by "glossiness and roughness are the same thing" I mean the microbumpiness.. If by glossiness you mean the index of refraction, that could also be conveyed by the diffuse alpha channel.  Above 75%, say, means metallic.  0~50% encodes dielectric constant.

I don't mean something special, just a general approach. We can define it as we want. The main thing to make it work for us and for artists.

And another problem for adding PBR is that we still have old hardware, which has a pretty limited amount of resources. And we need a plan what to do with that too.

Link to post
Share on other sites
22 minutes ago, DanW58 said:

EDIT2:  By now I have looked at all of the fragment shaders, and I'm still confused.  It seems to me if particular effects such as bumpmapping are not on, then there is no normal to work with!!  Isn't there an interpolated normal coming in, regardless of effects?

It doesn't use normal texture if it doesn't have one. Just uses primitive normal.

Link to post
Share on other sites
1 hour ago, DanW58 said:

I'm accused of trying to take away artistic freedom... Heck, I was an artist before I became a programmer, and as an artist being told I have to set a color for ambient light which I don't know what it should be might not be what I consider "freedom".

I'm sorry it felt that way. You shouldn't feel attacked by me :)  I believe it's a side effect of text based communication :) 

 

 

 

Link to post
Share on other sites
5 minutes ago, vladislavbelov said:

And another problem for adding PBR is that we still have old hardware, which has a pretty limited amount of resources.

Hahaha, older than 20 years?  That's how long ago I achieved realism like you still don't see nowadays.  And even then I didn't have the latest videocard...

Just to give you an idea of what I was able to do:  You know when car manufacturers mix metal and plastic parts, and they try to match the color and the specularity, but you can still tell the difference between the plastic and the paint?  With my shaders I could represent that difference.  Even exaggerate it, if I wanted to.

Yes, the primitive normal is what I'm looking for.  Both normal and v_normal are declared inside conditionals.  Looking for the normal that always comes in.

 

Link to post
Share on other sites
6 minutes ago, Stan` said:

I'm sorry it felt that way. You shouldn't feel attacked by me :)  I believe it's a side effect of text based communication :) 

Don't worry, Stan, I've short fuse sensitivities, but I know you're a good guy.  But seriously, "take artistic freedom away" just because I'd remove an absurd parameter?

Most artists are not as unreasonable as to say "I want my model to ALWAYS be seen in this light, regardless of lighting conditions!"

Edited by DanW58
Link to post
Share on other sites
6 minutes ago, DanW58 said:

Hahaha, older than 20 years? 

Haha, yes :( I've just removed ability to use GL1 with fixed function pipeline where you can't write shaders at all. And we have about 100 players which support at most GL1.5. Also people (especially from open-source) sometimes are asking do we support low-end hardware.

8 minutes ago, DanW58 said:

That's how long ago I achieved realism like you still don't see nowadays.  And even then I didn't have the latest videocard...

That's cool! Though I don't know it can be achieved in our case on old hardware with all our limitations .

10 minutes ago, DanW58 said:

es, the primitive normal is what I'm looking for.  Both normal and v_normal are declared inside conditionals.  Looking for the normal that always comes in.

You might add your code under conditions as well :)

Link to post
Share on other sites
1 minute ago, vladislavbelov said:

Haha, yes :( I've just removed ability to use GL1 with fixed function pipeline where you can't write shaders at all. And we have about 100 players which support at most GL1.5. Also people (especially from open-source) sometimes are asking do we support low-end hardware.

https://feedback.wildfiregames.com/results/ (For stats, hopefully more soon, with CPU caps and supported resolutions)

GL1 1992 FTW :D

 

Link to post
Share on other sites
3 minutes ago, Stan` said:

Well... just see the slightly snarky answer there :)

EDIT: There too https://code.wildfiregames.com/D3237

Hmmmm...  How do you know the comment is snarky?  I'd probably say the same thing without snarkiness.  As an artist I used to hate to have to set thing I had no idea how to set properly, such as ambient light or specular power, and various others.

Link to post
Share on other sites
3 minutes ago, Stan` said:

https://feedback.wildfiregames.com/results/ (For stats, hopefully more soon, with CPU caps and supported resolutions)

GL1 1992 FTW :D

 

Ouch!

Intel GPU owners should be sent to labor camps on the moon, though.

Well, when I was stuck with XP, Blender did not go out of their way to support me;  they just said go back to 2.73 or whatever it was...

Link to post
Share on other sites
4 minutes ago, DanW58 said:

Hmmmm...  How do you know the comment is snarky?  I'd probably say the same thing without snarkiness.  As an artist I used to hate to have to set thing I had no idea how to set properly, such as ambient light or specular power, and various others

Cause I spoke with him :)

1 minute ago, DanW58 said:

Well, when I was stuck with XP, Blender did not go out of their way to support me;  they just said go back to 2.73 or whatever it was...

Well we only dropped XP this year :)  And yeah Intel GL drivers are crap. Their GPU work much better on linux

Link to post
Share on other sites

Here it is:  Old on top, new at bottom;  notice the increased contrast between ground facing and upward facing surfaces.

It is subtle, but this is how you get to high realism;  one subtle improvement at a time.

Some places to look at are the stairs, see how the vertical planes look a bit darker;  and those spherical lingams left and right, showing more contrast between the down-facing and up-facing parts.  Half way between the horse and the elephant, the wide edge of the curvey wall also shows more contrast.  And you might say, "we can increase contrast with a filter";  yes, but this adds contrast where it matters more to 3D shape perception.

The only flaw I now see is that I darkened ambient light slightly over-all, but I can fix that easily.

Though I admit this is a hack that will work 99% of the time in open grounds;  not for my space game;  not for interiors;  but for 2 lines of glsl code I think it is worth it in lieu of a more general approach to lighting.

old.jpg

new.jpg

model_common_new.fs terrain_common_new.fs

Edited by DanW58
  • Like 2
Link to post
Share on other sites

You notice the difference more in-game;  elephants look more real;  everything looks more real.  The only problem is that it makes the lack of ambient shadowing more noticeable.  But I guess every unit would have to include a prop for terrain shadowing, to do that...

Here are the files with the loss of ambient brightness problem fixed:

EDIT:  Tried it with four different maps.  The only map that has a bit of a problem with it is the Acropolis, in the sense that the ground is too white to begin with, and it gets even whiter with skyward bias.  I point out this is an artistic problem, in the sense that no ground could be as white as that even if we tried to.  The best telescope mirrors reflect only 92% of the light or so.  Diffuse materials reflect less by their very nature.  Just because it is possible to make a ground almost pure white doesn't mean it is correct.  And I'm willing to bet that the diffuse and specular add to more than white...  I'd be willing to fix that texture, rather than tweak the sky bias formula.

For those wanting to try it, throw these files into your  ~/0ad/binaries/data/mods/public/shaders/glsl  folder.  To change back, you can go to the folder and type svn revert <name of the file>.

model_common.fs terrain_common.fs

Edited by DanW58
Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...