Purpose of SpecularMap in a TerrainLighting?

Good evening community,

I am in the process of using the TerrainLighting material on a Terrain in my editor.
I came across the SpecularMap property and was wondering, why is this there?

Is the specular map even used on a terrain material?

Could anyone clarify?

I took a quick glance at the shader, and it looks like the SpecularMap is not used in the .frag shader at all.

Mg best guess is that it was just left there by mistake when the original author was creating the terrain shader based on the standard Lighting.frag shader.

They probably planned to make 12 SpecularMap params (1 for each terrain texture slot) and then likely never got around to it (possibly intentionally never went back to add it, since a normal map is much more useful than a specular map anyways, and its easy to surpass the texture limit with a terrain so adding them would not be very useful for big. terrains using that shader anyways)

2 Likes

Cool, thanks for the reply. This was exactly my thought.

For my use case, it would be awesome to paint the reflection sports on a terrain by making use of a specular texture map. Similar to how we paint textures on a terrain.

Do you think that would be possible on the TerrainLighting material?

Yeah that should be possible. I don’t use the old lighting anymore (I only use pbr in my current projects) so I hope I am remembering how specular works properly.

But it looks like TerrainLighting already has a uniform shininess param for the entire terrain:

So you could just add code to read a gray-scale SpecularMap and then use that as the final shininess value that gets input into this lighting equation at the end of the shader:

Or, alternatively, you could add a bunch of float uniforms called Shiniess_0, Shininess_1, and so on for all 12 texture slots, and then you can assign a unique shiniess value to each tetxture slot, so that way you wouldn’t have to paint a specular map to match your terrain.

Do you by any chance know how to do this?
My shader skills are really limited.

Could I do something like this, which is in the normal Lighting.frag shader?

#ifdef SPECULARMAP
      vec4 specularColor = texture2D(m_SpecularMap, newTexCoord);
    #else
      vec4 specularColor = vec4(1.0);
    #endif

Yeah that should work in the terrain shader.

Here’s a quick adjustment to that .frag shader that would use the specular map as a gray-scale reflectivity map: (and it looks like the .j3md already has a Define set for Specular map, so that file should be okay as is)

But in this use case, I think it would no longer be called “specular map” and would instead be called something like a “shininess map”, since Specular color is usually a full rgb color, wheras in this case we are just trying to add reflectivity/shininess.

For simplicity I just made a quick PR with the minimal-necessary adjustments to get it to work how you describe. If anyone else finds this useful and thinks its worth merging the PR, I could work on finalizing it and making it more clean, but this should hopefully atleast do what you described.

1 Like

Thank you so much.
Let me have a look at this and incorporate it into my editor.

Overall, I think this would benefit the community in any ways.

1 Like

I have tested this change, however it does not seem to be working.

I am getting this error.

Uncaught exception thrown in Thread[jME3 Main,5,main]
RendererException: compile error in: ShaderSource[name=Shaders/TerrainLighting.frag, defines, type=Fragment, language=GLSL150]
0:848(7): error: `specularColor' redeclared

After this I just tried this code:

    #ifdef SPECULARMAP
      vec4 specularColor = texture2D(m_SpecularMap, texCoord);
      float finalShininessValue = specularColor.r; //assumes that specularMap is a gray-scale reflectivity/shininess map)
    #else
      vec4 specularColor = vec4(1.0);
      float finalShininessValue = m_Shininess;
    #endif

However, still no success.

1 Like

I overlooked the fact that the “specularColor” variable is later used in that shader as a static input to the lighting equation.

But it should work if you rename it to something else like “specularMapColor”. I updated the PR to fix it, sorry for the mistake.

I also added a “UseSpecularMapAsReflectivity” define to approach this in a cleaner way, and to allow the specularMap to be used as a standardSpecular color, or for reflectivity if this new define is set to true.

So it should work now, but make sure that you set the useSpecularMapAsShininess true for your use case with a gray-scale shininess map.

1 Like

Thanks, however I still don’t see any effect of shininess.
What is an example of how a gray-scale image should look like?
Can you help?

I don’t think I ever tried using the shininess param with the standard Lighting shader, so I’m not entirely sure how noticeable it should be.

Maybe it wants values higher than the 0.0 - 1.0 range (which is the range you will get from a grayScale texture), so maybe try multiplying the finalShininess float by 10 or 20 and see what happens.

In this case, it would just be any image where you paint the red, green, and blue channel as the same value, very similar to how an ambientOcclusion texture would look.

This would be what your final grayScale texture would look like, where the black spots indicate no shininess (0.0), and the white spots are max shininess (1.0)

image

1 Like

I made a little change on how it works.
Please validate. It looks like this new way works correctly.

    vec4 specularColor = vec4(1.0);
    float finalShininessValue = m_Shininess;
    #ifdef SPECULARMAP      
      #ifdef USE_SPECULARMAP_AS_SHININESS
        vec4 specularMapColor = texture2D(m_SpecularMap, texCoord);
        specularColor = specularMapColor;
        finalShininessValue = specularMapColor.r*m_Shininess;
      #endif      
    #endif
1 Like

Yeah that looks like it is probably the best way to handle this.

I’ll update the PR to use this code with the original m_Shininess value being used as a scalar.

The only thing I can think that could be worth changing is this line here:

It could be better to change it to :
finalShininessValue = specularMapColor.a*m_Shininess;

so this change would make it use the the alpha channel as a shiniess multiplier (rather than the red channel which is standard for grayScale textures) and that way you could theoretically still use the red, green, and blue channel as non-gray scale specular color if you want to add colored reflections, or you can leave it as a grascale texture and it will work like it does now.

So specular color would be packed in the RGB channel of the SpecularMap, and the shininess Multiplier would be in the A channel of the SpecularMap. You would just need to update your editor’s terrain painting tool to ensure that you are writing the shininess to the alpha channel instead of the red channel

Cool. I think that could work.
Talking of reflection, would one be able to implement reflection on the terrain?

You should be able to add realistic real-time reflection, I’ve never done that personally, but I think there are some reflection shader examples out there for jme that you could base it on.

But I think that would end up being very resource intensive if it is indeed real-time reflections you are looking for, especially for a big terrain, so that’s usually why PBR is typically considered better for shininess/reflectivity with its “fake” reflections from the light probe, and then real-time reflection shades are used more sparingly for the occasional model that needs clear perfect reflections in real-time (like if you want the player to be able to see their own reflection when they move while looking at things like ice, a mirror, or water).

I know for apps targeting mobile devices, PBR is usually too resource intensive and that is often a reason that jme users choose to use the old phong lighting instead of PBR. So in that case I think implementing reflections into the phong lighting would probably also be too resource intensive as well.

But if you are targeting desktop devices then implementing them into TerrainLighting could potentially be manageable without lowering the framerate too much.

Okay interesting.
I will try this on my own to see if it could be possible and performent enough.

I had to make a small change again and also use the alpha now. I had to create a new vec4 to get a white color for specularColor.

    vec4 specularColor = vec4(1.0);
    float finalShininessValue = m_Shininess;
    #ifdef SPECULARMAP      
      #ifdef USE_SPECULARMAP_AS_SHININESS
        vec4 specularMapColor = texture2D(m_SpecularMap, texCoord);
        specularColor = vec4(specularMapColor.a);
        finalShininessValue = specularMapColor.a*m_Shininess;
      #endif      
    #endif

In terms of PBRTerrain, it does not seem to work on linux. This is my result with PBRTerrain.

1 Like

That is unfortunate, I was not aware of that issue with running my pbr terrain shaders on linux, but it looks like it is important to fix and I’ll try to work on it.

Have you confirmed that it doesn’t work on more than 1 linux device (no other jme linux users have used the PBR terrain shader that I’m aware of, so I don’t have any information on whether or not it is affecting all linux users, or f it could potentially be related to your specific GPU).

What are your full OS and device specs?
(I should probably also make another thread about this issue and see if I can get any other jme users to test the shader if they are using linux or a similar GPU as yours).

I’m also curious to know if the AdvancedPBRTerrain also has the same issue for you (I assume it does, but it could still be worthwhile to check in order to rule out a few other potential reasons for the issue, particuarily the fact that I only use the advanced one myslef so I may have been more likely to make a mistake on the older pbr terrain)

I am not sure if other people on linux experience the same issues. Also, I haven’t tested the advancedpbrterrrain.

Here are my settings:

INFO: OpenGL Renderer Information

  • Vendor: Intel
  • Renderer: Mesa Intel(R) Xe Graphics (TGL GT2)
  • OpenGL Version: 4.6 (Compatibility Profile) Mesa 23.2.1-1ubuntu3.1~22.04.2
  • GLSL Version: 4.60
  • Profile: Compatibility

Here is a screenshot of the PBRTerrainTest.java:

This is what the PBRTerrainAdvanceTest looks like:
Triplinar off:

And this one is with triplinar on:

1 Like

So my other question is, should I rather use the AdvancedPBRTerrain?
And what is actually the difference?