[Solved] Fading shininess of material TerrainLighting at runtime doesn’t work

Hi guys,

I’m still working on my project. Now I have dynamic sky, dynamic weather, terrain grid, etc. I’m very happy with my results until now!

But I have a problem with within my Weather Manager. I want to modify the shininess of the terrain when it rains. I want the terrain to look wet, so it should be shinier than normal. I have a way to fade between different weather states (like heavy or medium or light rain, and same for snow, etc), so I did the same with the terrain material shininess value. But if I fade the value of the shininess of the material at runtime, the material doesn’t care at all, it always uses the initial shininess value (or so it would seem).

I uploaded 2 videos on youtube to demonstrate what is happening. In the first video, I fade the value of the shininess over the course of 2 or 3 in game time hours. I recorded the jMonkey window and the console output. You can see that the game starts around 1am and finishes around 5am. The shininess value starts at 1.0f and finishes at 20,0f when it’s around 3am. The sun rises around 5 am and the terrain should be really shiny by then. But it’s not. It’s like the terrain still considers the shininess value is 1.0f. (By the way, you can also see the default weather fade to the heavy rain weather over the same time.)

example if which I fade the shininess:


In the second video, I do not fade the shininess. I initially set it at 20.0f and let the time run. The game still begins at 1am and finishes a little after 5am after the sunrise. As you can see the terrain is very shiny: we see the sun light almost glow on the terrain and even the night light dmakes the terrain shine.
example in which only initial shininess value is set:

I looked at the TerrainLighting material definition and the shaders and I see that shininess is passed as a uniform parameter. So I guess changing the value at runtime should update the final aspect of the terrain. I change uniform values at runtime in my DynamicSky color shader and it works well.

I wonder, was this material designed like this purposefully? Is there a reason I shouldn't fade the shininess value at runtime? What would be the best way to achieve this? I guess cloning the material definition with a new shininess value is out of the question performance wise. Is there a way to tell the shader to consider the new shininess value?

I would really like to get this working, it's probably one of my last steps to wrap up my weather manager. I would like to solve in a clean fashion. :)


It would appear that you have a hold of the wrong material, or are setting some other value. You can change any material’s settings at runtime.

Can you pose your code where you make the change and get the material?

1 Like

Hi Sploreg!

First off, thanks for giving the most important information: it should work. So there must something I’m doing wrong…

I wanted to post snippets of my code, but it’s split up across many wrapper classes so I decided to make a test project based on the TerrainGridTest project, with very few changes except for the material. Run the project to demonstrate the problem. Change the field “defaultShininess” from values 1.0f to 20.0f to make both tests.

Here is the code:


package jme3test.terrain;

import com.jme3.app.SimpleApplication;

import com.jme3.app.state.ScreenshotAppState;

import com.jme3.asset.plugins.HttpZipLocator;

import com.jme3.asset.plugins.ZipLocator;

import com.jme3.bullet.BulletAppState;

import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;

import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;

import com.jme3.bullet.control.CharacterControl;

import com.jme3.bullet.control.RigidBodyControl;

import com.jme3.input.KeyInput;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.KeyTrigger;

import com.jme3.light.DirectionalLight;

import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.math.Vector3f;

import com.jme3.shader.VarType;

import com.jme3.terrain.Terrain;

import com.jme3.terrain.geomipmap.TerrainGrid;

import com.jme3.terrain.geomipmap.TerrainGridListener;

import com.jme3.terrain.geomipmap.TerrainGridLodControl;

import com.jme3.terrain.geomipmap.TerrainQuad;

import com.jme3.terrain.geomipmap.grid.ImageTileLoader;

import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;

import com.jme3.terrain.heightmap.Namer;

import com.jme3.texture.Texture;

import com.jme3.texture.Texture.WrapMode;

import java.io.File;

import java.util.logging.Level;

import java.util.logging.Logger;

public class ShininessChangeTest extends SimpleApplication {

private float defaultShininess = 1.0f; // or 20.0f for no fade test

private Material mat_terrain;

private TerrainGrid terrain;

private float grassScale = 64;

private float dirtScale = 16;

private float rockScale = 128;

private boolean usePhysics = false;

public static void main(final String[] args) {

ShininessChangeTest app = new ShininessChangeTest();



private CharacterControl player3;


public void simpleInitApp() {

File file = new File(“TerrainGridTestData.zip”);

if (!file.exists()) {

assetManager.registerLocator(“http://jmonkeyengine.googlecode.com/files/TerrainGridTestData.zip”, HttpZipLocator.class);

} else {

assetManager.registerLocator(“TerrainGridTestData.zip”, ZipLocator.class);



ScreenshotAppState state = new ScreenshotAppState();


mat_terrain = new Material(assetManager, “Common/MatDefs/Terrain/TerrainLighting.j3md”);

mat_terrain.setBoolean(“useTriPlanarMapping”, false);

mat_terrain.setFloat(“Shininess”, defaultShininess);

//The line below is to eliminate black spots on the terrain

mat_terrain.setBoolean(“WardIso”, true);

//default config… use unknown

mat_terrain.setBoolean(“isTerrainGrid”, true);



mat_terrain.setColor(“Diffuse”, ColorRGBA.White);

mat_terrain.setColor(“Specular”, ColorRGBA.White);

//Create Splat Textures

// GRASS texture

Texture grass = assetManager.loadTexture(“Textures/Terrain/splat/grass.jpg”);


mat_terrain.setTexture(“DiffuseMap”, grass);

mat_terrain.setFloat(“DiffuseMap_0_scale”, grassScale);

// DIRT texture

Texture dirt = assetManager.loadTexture(“Textures/Terrain/splat/dirt.jpg”);


mat_terrain.setTexture(“DiffuseMap_1”, dirt);

mat_terrain.setFloat(“DiffuseMap_1_scale”, dirtScale);

// ROCK texture

Texture rock = assetManager.loadTexture(“Textures/Terrain/Rock2/rock.jpg”);


mat_terrain.setTexture(“DiffuseMap_2”, rock);

mat_terrain.setFloat(“DiffuseMap_2_scale”, rockScale);

this.terrain = new TerrainGrid(“terrain”, 65, 257, new ImageTileLoader(assetManager, new Namer() {

public String getName(int x, int y) {

return “Scenes/TerrainMountains/terrain_” + x + “_” + y + “.png”;




this.terrain.setLocalTranslation(0, 0, 0);

this.terrain.setLocalScale(1f, 1f, 1f);


TerrainGridLodControl control = new TerrainGridLodControl((Terrain)this.terrain, getCamera());

control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier


final BulletAppState bulletAppState = new BulletAppState();


this.getCamera().setLocation(new Vector3f(0, 200, 0));

this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));

DirectionalLight light = new DirectionalLight();

light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize());


if (usePhysics) {

CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(0.5f, 1.8f, 1);

player3 = new CharacterControl(capsuleShape, 0.5f);




player3.setPhysicsLocation(new Vector3f(cam.getLocation().x, 256, cam.getLocation().z));


terrain.addListener(new TerrainGridListener() {

public void gridMoved(Vector3f newCenter) {


public Material tileLoaded(Material material, Vector3f cell) {

return material;


public void tileAttached(Vector3f cell, TerrainQuad quad) {




quad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(quad.getHeightMap(), terrain.getLocalScale()), 0));



public void tileDetached(Vector3f cell, TerrainQuad quad) {








private void initKeys() {

// You can map one or several inputs to one named action

this.inputManager.addMapping(“Lefts”, new KeyTrigger(KeyInput.KEY_A));

this.inputManager.addMapping(“Rights”, new KeyTrigger(KeyInput.KEY_D));

this.inputManager.addMapping(“Ups”, new KeyTrigger(KeyInput.KEY_W));

this.inputManager.addMapping(“Downs”, new KeyTrigger(KeyInput.KEY_S));

this.inputManager.addMapping(“Jumps”, new KeyTrigger(KeyInput.KEY_SPACE));

this.inputManager.addListener(this.actionListener, “Lefts”);

this.inputManager.addListener(this.actionListener, “Rights”);

this.inputManager.addListener(this.actionListener, “Ups”);

this.inputManager.addListener(this.actionListener, “Downs”);

this.inputManager.addListener(this.actionListener, “Jumps”);


private boolean left;

private boolean right;

private boolean up;

private boolean down;

private final ActionListener actionListener = new ActionListener() {


public void onAction(final String name, final boolean keyPressed, final float tpf) {

if (name.equals(“Lefts”)) {

if (keyPressed) {

ShininessChangeTest.this.left = true;

} else {

ShininessChangeTest.this.left = false;


} else if (name.equals(“Rights”)) {

if (keyPressed) {

ShininessChangeTest.this.right = true;

} else {

ShininessChangeTest.this.right = false;


} else if (name.equals(“Ups”)) {

if (keyPressed) {

ShininessChangeTest.this.up = true;

} else {

ShininessChangeTest.this.up = false;


} else if (name.equals(“Downs”)) {

if (keyPressed) {

ShininessChangeTest.this.down = true;

} else {

ShininessChangeTest.this.down = false;


} else if (name.equals(“Jumps”)) {





private final Vector3f walkDirection = new Vector3f();


public void simpleUpdate(final float tpf) {

Vector3f camDir = this.cam.getDirection().clone().multLocal(0.6f);

Vector3f camLeft = this.cam.getLeft().clone().multLocal(0.4f);

this.walkDirection.set(0, 0, 0);

if (this.left) {



if (this.right) {



if (this.up) {



if (this.down) {



if (usePhysics) {




Float mat_Shininess = ((Float)mat_terrain.getParam(“Shininess”).getValue());

if(mat_Shininess < 20f) {

mat_Shininess = mat_Shininess + 0.01f;

mat_terrain.setParam(“Shininess”, VarType.Float, mat_Shininess);


Logger.getAnonymousLogger().log(Level.SEVERE, "Shininess: " + mat_Shininess.toString());




Well well well… I found out something interesting with this test that I didn’t see in my project. Here the tiles are much smaller than in my project. It seems the new shininess is applied to the new tiles as they are loaded but it is not applied to the tiles that are already loaded… Is it because each tile gets a copy of the material as they are loaded rather than a reference? Huh!!! That a look a that, it seems I found out the problem. If I add these lines of code under the line “mat_terrain.setParam(“Shininess”, VarType.Float, mat_Shininess);” in the test project, it works just fine:


for(Spatial spatial: terrain.getChildren()) {

if(spatial instanceof TerrainQuad) {

TerrainQuad terrainQuad = (TerrainQuad)spatial;

terrainQuad.getMaterial().setParam(“Shininess”, VarType.Float, mat_Shininess);




Alright, problem solved!

Thanks Sploreg for putting me on the right track. :slight_smile:

1 Like

Ah yes with terrain grid it is a different story. It has to clone the material as each grid tile might have a different alpha map. So if you just change the shininess of the original reference of the material, then none of the terrain tiles will see the change. So what you did with the loop there is correct.

Glad you got it working :slight_smile:

1 Like