« Previous: Starter Tutorial 3 - Hello TriMesh
Next: Starter Tutorial 5 - Hello KeyInput »
(Tip: Up-to-date source files for the tutorials are always in the repository)
This example introduces MaterialState, TextureState, and LightState. You learn how to assign States to objects that give the surface a pattern and color. For example, I want to create a cube with a jMonkey picture on it.
import java.net.URL; import com.jme.app.SimpleGame; import com.jme.bounding.BoundingBox; import com.jme.bounding.BoundingSphere; import com.jme.image.Texture; import com.jme.light.PointLight; import com.jme.math.Vector3f; import com.jme.renderer.ColorRGBA; import com.jme.scene.Node; import com.jme.scene.shape.Box; import com.jme.scene.shape.Sphere; import com.jme.scene.state.LightState; import com.jme.scene.state.MaterialState; import com.jme.scene.state.TextureState; import com.jme.util.TextureManager; /** * Started Date: Jul 20, 2004<br> * <br> * * Demonstrates using RenderStates with jME. * * @author Jack Lindamood */ public class HelloStates extends SimpleGame { public static void main(String[] args) { HelloStates app = new HelloStates(); app.setConfigShowMode(ConfigShowMode.AlwaysShow); app.start(); } protected void simpleInitGame() { // Create our objects. Nothing new here. Box b = new Box("My box", new Vector3f(1, 1, 1), new Vector3f(2, 2, 2)); b.setModelBound(new BoundingBox()); b.updateModelBound(); Sphere s = new Sphere("My sphere", 15, 15, 1); s.setModelBound(new BoundingSphere()); s.updateModelBound(); Node n = new Node("My root node"); // Get a URL that points to the texture we're going to load URL monkeyLoc; monkeyLoc = HelloStates.class.getClassLoader().getResource( "jmetest/data/images/Monkey.jpg"); // Get a TextureState TextureState ts = display.getRenderer().createTextureState(); // Use the TextureManager to load a texture Texture t = TextureManager.loadTexture(monkeyLoc, Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear); // Assign the texture to the TextureState ts.setTexture(t); // Get a MaterialState MaterialState ms = display.getRenderer().createMaterialState(); // Give the MaterialState an emissive tint ms.setEmissive(new ColorRGBA(0f, .2f, 0f, 1)); // Create a point light PointLight l = new PointLight(); // Give it a location l.setLocation(new Vector3f(0, 10, 5)); // Make it a red light l.setDiffuse(ColorRGBA.red.clone()); // Enable it l.setEnabled(true); // Create a LightState to put my light in LightState ls = display.getRenderer().createLightState(); // Attach the light ls.attach(l); // Signal that b should use renderstate ts b.setRenderState(ts); // Signal that n should use renderstate ms n.setRenderState(ms); // Detach all the default lights made by SimpleGame lightState.detachAll(); // Make my light effect everything below node n n.setRenderState(ls); // Attach b and s to n, and n to rootNode. n.attachChild(b); n.attachChild(s); rootNode.attachChild(n); } }
Every object in jME has several states. Here we learn more about TextureState, MaterialState, and LightState: These States define how an object is rendered. This is why they are dependent on the renderer.
Did you note the object display? It always refers to whichever renderer I’m using. This variable is already defined for me in SimpleGame as a reference to DisplaySystem.getDisplaySystem(). This way your code stays the same, no matter whether your user decides to use LWJGL or JOGL. This is jME’s abstraction, the game code is independent of the rendering environment, very handy!
First we use a ClassLoader to locate the image that we want to put on the box: The Monkey.jpg image is stored in the sources directory (src) under jmetest/data/images/.
// Get a URL that points to the texture we're going to load URL monkeyLoc; monkeyLoc=HelloStates.class.getClassLoader().getResource( "jmetest/data/images/Monkey.jpg" );
Second we create a Texture (jME API) object from the image in the monkeyLoc URL.
// Use the TextureManager to load a texture Texture t=TextureManager.loadTexture( monkeyLoc, Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear);
To create a Texture, we have used a class called Texture Manager (jME API). It manages loading of textures so that all we need to do is supply the URL and it will load correctly. The constants BilinearNearestMipMap and Bilinear just let jME know how to filter the texture, I won’t get into any details here. For now, just use these two filters all the time.
To assign a texture to an object we need a TextureState (jME API). A TextureState is created by the DisplaySystem.
// Get a TextureState TextureState ts=display.getRenderer().createTextureState();
Our TextureState is ready to accept our previously created Texture object.
// Assign the texture to the TextureState ts.setTexture(t);
TextureState maintains a texture state for a given node and it's children. The number of states that a TextureState can maintain at one time is equal to the number of texture units available on the GPU.
To assign a color to an object we need a MaterialState (jME API). A MaterialState is created by the DisplaySystem.
// Get a MaterialState MaterialState ms=display.getRenderer().createMaterialState();
Now that I have the MaterialState, I give it a color:
// Give the MaterialState an emissive tint ms.setEmissive(new ColorRGBA(0f,.2f,0f,1));
The Emissive Color setting makes the object look a tiny bit green, which is why you see a green tint at the bottom. Material is defined by the emissive quality of the object, the ambient color, diffuse color and specular color. The material also defines the shininess of the object and the alpha value of the object. MatrialState has methods such as setEmissive, setSpecular, setDiffuse, setAmbient, setShininess. These work just like any other modeling environment.
Did you know? The difference between MaterialStates and the previously discussed per-vertex coloring is that the MaterialState uses lighting and shadows, so it looks much better. You can use either one or the other.
The appearance of both the MaterialState and the TextureState depend on a light source. A light source is made up of a light object and a LightState (jME API).
// Create a point light PointLight l=new PointLight(); // Give it a location l.setLocation(new Vector3f(0,10,5));
This creates a point light object. Think of a PointLight (jME API) as a lightbulb. It’s a light, at a point. Which point, you ask? Well, that’s why I use setLocation. It moves my light to (x=0,y=10,z=5). Lights can have colors of their own, and I give my light a red color:
// Make it a red light l.setDiffuse(ColorRGBA.red.clone());
After creating the PointLight object, I have to attach the light to a LightState. The LightState is what makes the MaterialState's color visible. A LightState (jME API) is created by the DisplaySystem.
// Create a LightState to put my light in LightState ls=display.getRenderer().createLightState(); // Attach the light ls.attach(l);
After I have created all my states, I have to assign them to the nodes that I want them to affect. Node n is the parent of the box b and the sphere s. I want everything child of Node n to have a greenish tint from the MaterialState ms. I additionally want the reddish light ls to shine on node n. I also want to apply the monkey TextureState ts on the box.
// Detach all the default lights made by SimpleGame lightState.detachAll(); // Make my light affect everything below Node n n.setRenderState(ls); // Signal that Node ''n'' should use renderstate ''ms'' n.setRenderState(ms); // Signal that the Box ''b'' should use renderstate ''ts'' b.setRenderState(ts);
Notice I have to call detachAll() on an object called lightState. That is because SimpleGame makes a light for us and puts it in lightState. I only want to use my lights (the red light), so I detach all of the default lightState lights.
This is what the scene graph looks like now:
| rootNode | |
| “My root node” n red lightstate, green materialstate | |
| “my box” b monkey texturestate | “My sphere” s |
Again, the scene graph inheritance comes into play. The box “my box” is affected by a red lightstate (the red light) as well as the green MaterialState (the green tint), because it is a child of n. The same with s.
Compare it with this example scene graph:
| rootNode | ||
| “My root node” n red lightstate, green materialstate | “Another sphere” q | |
| “My sphere” s | “my box” b monkey texturestate | |
If my SceneGraph had a Sphere q attached to the rootNode, Sphere q would not be affected by Node n’s light at all – no matter how close it was to the lights location! That’s simply because q is not a child of n. This allows you to control precisely what your lights are lighting. If an object appears to dark, too bright, or colorless, check the SceneGraph whether you got the inheritance of States right.