Dev 143 – JSR184 – Using 3D in J2ME – part 3 of 3

We have come to touch the most interesting topic of this series, something that will allow us to enrich 3D objects on our phones with colors and lights . With a minimum effort it is possible to obtain really satisfying results.

The best video games that use 3D offers the most advanced texturing, lighting projection, special effects techniques such as particle management and so on.

As we have already mentioned before the set of features available in M3G is not very large, but they are sufficient to create rather complex and realistic scenes and animations.

I don’t know if all the devices currently in circulation can run applications that take full advantage of all these features. It is often necessary to compromise with the mini-machine trying to save calculations and memory where possible.

A very used “trick” is paint the effects of an imaginary light source directly onto the textures. This avoids the burdensome calculations necessary for the projection of the lighting.

It is not excluded that in the near future mobile phones with very powerful hardware acceleration chipsets will go on the market. We are talking about introducing a light version of OpenGL that is already supported by Nokia’s N-GAGE on the Symbian platform.

Add some color

In the example of the previous article we saw a completely white cube rotate inside the display. The “flat” coloring is determined by the absence, in the VertexBuffer of the cube, of information relating to the color of each vertex.  

Let’s organize an array like the following

private static final byte[] COLORS_VERTICES = {
    0, (byte) 255, 0, 0, (byte) 255, (byte) 255,   
    (byte) 255, 0, 0, (byte) 255, 0, (byte) 255,  
    (byte) 255, (byte) 255, 0, (byte) 255, (byte) 255, (byte) 255,   
     0, 0, (byte) 128, 0, 0, (byte) 255   
};

in which have been specified the colors in RGB format that each vertex must assume. Note that only 8 colors (8 x 3 components = 24 bytes) have been written because 8 are the vertices of the cube that must be colored.

We add the following class variables

private Appearance appearanceCube;

which declares a new variable of type Appearance. This class defines the aspect that will have the drawn object by the Render() method. As shown by Box1 it maintains information about various properties of the appearance of an object.

Box 1 – Here are the properties that can be managed by the Appearance class

Store object instances about

  • Material which is used – as we will see later – to manage lighting 
  • Fog which indicates how the object must fade when the fog effect is activated.
  • Texture2D which contains information about how to apply textures to the object
  • PolygonMode we are going to study immediately

in fact we declare the variable

private PolygonMode polygonMode;

of type PolygonMode, a class by which polygon drawing modes can be set. These methods are related to

  • The type of Culling to use. You can choose between CULL_BACK, CULL_FRONT, CULL_NONE.
  • The type of Winding: WINDING_CCW or WINDING_CW
  • The Shading type, chosen between SHADE_FLAT and SHADE_SMOOTH

When a polygon is drawn in space has not one but two faces. Each of the two sides of a polygon can have different properties in terms of color, texture, etc. Obviously depending on the position of the observation point it will be possible to see only one of the two faces at a time.

Often one of the faces that make up an object may never be visible; think as – an example – to a sphere. The inner faces will never be visible.

Selecting the type of culling tells the API which face to “not” draw, thus speeding up the rendering process.

However, one problem is distinguish the front face from the back face. The convention used is to discriminate the order in which the vertices are specified.

If a CW winding is used the vertices specified following the clockwise direction represent the front face; the opposite happens if CCW winding is used.

Finally the Shading type permits to select the filling mode and the color, of the triangles. The SHADE_SMOOTH option generates an interpolation of color values ​​between vertices. By appropriately associating the colors to the vertices of a triangle, it is possible to obtain splendid effects of intermediate shades between them. Conversely setting SHADE_FLAT it will use the color of the last vertex to fill the triangle.

Just before the camera declaration, in the usual Init() method , we write the following lines of code

cubeAppearance = new Appearance();
polygonMode = new PolygonMode();

VertexArray verticesColors = new VertexArray(VERTICES_COLORS.length / 3, 3, 1);
verticesColors(0, VERTICES_COLORS.length / 3, VERTICES_COLORS);
cubeVerticesData.setColors(verticesColors);

After the creation of the Appearance and PolygonMode objects a new VertexArray was created to contain the color data which was inserted into the VertexBuffer using the SetColors() method.

To complete the changes to the previous code it is necessary to change the old line

g3d.render(cubeVerticesData, cubeTriangles, new Appearance(), cubeTransformation);

within the Draw3D () method, with the

g3d.render(cubeVerticesData, cubeTriangles, cubeAppearance, cubeTransformation);

where we pass our Appearance object with the default settings.

By default the SHADE_SMOOT option is active; therefore, we launch the program to see the changes made. In Figure 1 it is shown the output

Figure 1 – How the cube looks after the vertex colors have been applied

while the Listato1 presents the changes made in the method Init().

public void Init() {                 
          
    // Create a new instance of the VertexBuffer object
    cubeVerticesData = new VertexBuffer();
            
    // Create a new VertexArray to store 
    // the coordinates of the cube's vertices
    VertexArray verticesPositions = new VertexArray(VERTICES_POSITIONS.length / 3, 3, 1);
            
    // Store the positions of the vertices in the array
    verticesPositions.set(0, VERTICES_POSITIONS.length / 3, VERTICES_POSITIONS);
            
    // Pass the VertexArray reference to the VertexBuffer
    cubeVerticesData(verticesPositions, 1.0f, null);
            
    // Create a new instance of the object  
    TriangleStripArray cubeTriangles = new TriangleStripArray(TRIANGLES_INDICES, new int[] {TRIANGLES_INDICES.length});
            
    // Create a new instance of the Camera object
    Camera camera = new Camera();
            
    // Calculate the value of the AspectRatio
    float aspectRatio = (float) DISPLAY_WIDTH / (float) DISPLAY_HEIGHT;

    // Create a new instance of the Appearance object
    cubeAppearance = new Appearance();
            
    // Create a new instance of the PolygonMode object
    polygonMode = new PolygonMode();

    cubeAppearance.setPolygonMode(polygonMode);
            
    // Create a VertexArray to store vertex colors
    VertexArray vertexColors = new VertexArray(VERTEX_COLORS.length / 3, 3, 1);
            
    // Memorize the vertex colors
    vertexColors.set(0, VERTEX_COLORS.length / 3, VERTEX_COLORS);
            
    // Inserts the VertexArray into the VertexBuffer
    cubeVerticesData.setColors(vertexColors);                        
            
    // Set the perspective correction
    camera.setPerspective(30.0f, aspectRatio, 1.0f, 1000.0f);            
            
    // Create a transformation object
    Transform cameraTransform = new Transform();
            
    // Set a translation
    cameraTransform.postTranslate(0.0f, 0.0f, 10.0f);
            
    // Activate the camera
    g3d.setCamera(camera, cameraTransform);            
            
    initDone = true;

}

Lights

To recreate the natural phenomenon of lighting M3G also has classes for managing various types of light sources:

  • ambient light; a type of lighting that does not have a precise source, but comes from all directions
  • unidirectional light; which is made up of parallel rays of light as if they came from an infinite distance. Unlike the previous one, this type of lighting comes from a very specific direction.
  • spotlight; a type of light that comes from a point in space and can possess a certain angular amplitude and draws a beam on the object that strikes.

Activating a light source is very simple. After creating the normal data

private static final byte[] VERTICES_NORMALS = {
    0, 0, 127, 0, 0, 127, 0, 0, 127, 0, 0, 127, // front
    0, 0, -128, 0, 0, -128, 0, 0, -128, 0, 0, -128, // retro
    127, 0, 0, 127, 0, 0, 127, 0, 0, 127, 0, 0, // right
    -128, 0, 0, -128, 0, 0, -128, 0, 0, -128, 0, 0, // left
    0, 127, 0, 0, 127, 0, 0, 127, 0, 0, 127, 0, // high
     0 , -128, 0, 0, -128, 0, 0, -128, 0, 0, -128, 0 low //  
}

and setting the normals in the VertexBuffer with the usual method

VertexArray cubeNormals = new VertexArray(VERTICES_NORMALS.length / 3, 3, 1);
cubeNormals.set(0, VERTICES_NORMALS.length / 3, NORMALI_VERTICI);
cubeVerticesData.setNormals(cubeNormals);

the Light object is created

Light light = new Light();

then a transformation to be applied to the lighting source

Transform lightTransformation = new Transform();

we move the point of some units to distance it from the object

lightTransformation.postTranslate(0.0f, 0.0f, 5.0f);

we set the type of light to be created

luce.setMode(Light.OMNI);

and the intensity of the same

luce.setIntensity(3.0f);

at the end we signal the object Graphics3D to clear any lights and set the one just created to perform the rendering operations.

g3d.resetLights();
g3d.addLight(light, light transformation);

To experiment with the other settings try changing the type of light with the values ​​Ligt.DIRECTIONAL, Light.SPOT and Light.AMBIENT.

Using Light.SPOT it is also necessary to define the angular aperture – in degrees – that the light beam should have

luce.setSpotAngle(30.0f); 

Materials

When lighting is used it must also be taken into account that – as in nature – the apparent color of each object is determined by the absorption/reflection of the luminous radiations of the materials.

To see then the effects of a light source it is necessary to associate to the VertexBuffer the properties of the material that composes it. There are several parameters that can be chosen

  • Environment reflection; the reflected light of ambient lighting
  • Diffuse reflection; light that is reflected equally in all directions
  • Specular reflection; it’s the effect that creates an object with a shiny surface
  • Emissivity; that is the possibility for the object to emit light

To set up a material follow this procedure

Material cubeMaterial = new Material();

then after creating the object we use the setColor() method in this way

cubeMaterial.setColor(Material.DIFFUSE, 0x00FF0000);

the first parameter can take one of the following values AMBIENTDIFFUSEEMISSIVE and MIRROR. In the case of SPECULAR we must also indicate the reflexivity index by the method

setShininess()

The material settings must be passed to the Appearance object

cubeAppearance.setMaterial(cubeMaterial);

Now you can see the results of the changes, as in Figure 2, by launching the application.

Figure 2 – Now the lighting is applied

The Listing 2 shows the complete code of the Init() method.

public void Init() {                 
            
    // Create a new instance of the VertexBuffer object
    cubeVerticesData = new VertexBuffer();
            
    // Create a new VertexArray to store 
    // the coordinates of the cube's vertices
    VertexArray verticesPositions = new VertexArray(VERTICES_POSITIONS.length / 3, 3, 1);
            
    // Store the positions of the vertices in the array
    verticesPositions.set(0, VERTICES_POSITIONS.length / 3, VERTICES_POSITIONS);
            
    // Pass the VertexArray reference to the VertexBuffer
    cubeVerticesData(verticesPositions, 1.0f, null);
            
    // Create a new instance of the object  
    TriangleStripArray cubeTriangles = new TriangleStripArray(TRIANGLES_INDICES, new int[] {TRIANGLES_INDICES.length});
            
    // Create a new instance of the Camera object
    Camera camera = new Camera();
            
    // Calculate the value of the AspectRatio
    float aspectRatio = (float) DISPLAY_WIDTH / (float) DISPLAY_HEIGHT;

    // Create a new instance of the Appearance object
    cubeAppearance = new Appearance();
            
    // Create a new instance of the PolygonMode object
    polygonMode = new PolygonMode();

    cubeAppearance.setPolygonMode(polygonMode);
            
    // Create a VertexArray to store vertex colors
    VertexArray vertexColors = new VertexArray(VERTEX_COLORS.length / 3, 3, 1);
            
    // Memorize the vertex colors
    vertexColors.set(0, VERTEX_COLORS.length / 3, VERTEX_COLORS);
            
    // Inserts the VertexArray into the VertexBuffer
    cubeVerticesData.setColors(vertexColors);                        
            
    // Set the perspective correction
    camera.setPerspective(30.0f, aspectRatio, 1.0f, 1000.0f);            
            
    // Create a transformation object
    Transform cameraTransform = new Transform();
            
    // Set a translation
    cameraTransform.postTranslate(0.0f, 0.0f, 10.0f);
            
    // Activate the camera
    g3d.setCamera(camera, cameraTransform);            
            
    // Create a new VertexArray to store the normals
    VertexArray cubeNormals = new VertexArray(VERTICES_NORMALS.length / 3, 3, 1);
            
    // Stores the normals in the VertexArray
    cubeNormals.set(0, VERTICES_NORMALS.length / 3, VERTICES_NORMALS);
            
    // Store the VertexArray to the VertexBuffer
    cubeVerticesData.setNormals(cubeNormals);
            
    // Create a new Material object
    Material cubeMaterial = new Material();
            
    // Set material values
    cubeMaterial.setColor(Material.AMBIENT, 0x00FF0000);
            
    // Memorize the object in Appearance
    cubeAppearance.setMaterial(cubeMaterial);

    // Create a new Light object
    Light light = new Light();
            
    // Set the type of lighting source
    light.setMode(Light.DIRECTIONAL);
            
    // Set the intensity of the light
    light.setIntensity(5.0f);
            
    // Create a new transformation for light
    Transform lightTransform = new Transform();
            
    // Set the translation value
    lightTransform.postTranslate(0.0f, 0.0f, 3.0f);          
            
    // Reset all lights
    g3d.resetLights();
            
    // Adds light to the scene including transformation
    g3d.addLight(light, lightTransform);
            
    initDone = true;

}

Texture mapping

We have just seen some techniques for changing the appearance of objects. However these methods produce somewhat artificial results. We could add more realism by using images to fill the polygons.

The procedure for specifying the coordinates on the textures is the usual one

private static final byte[] TEXTURE_COORDINATES = {
    0, 1, 1, 1, 0, 0, 1, 0, // front
    0, 1, 1, 1, 0, 0, 1, 0, // retro
    0, 1, 1, 1, 0, 0, 1, 0, // right
    0, 1, 1, 1, 0, 0, 1, 0, // left
    0, 1, 1, 1, 0, 0, 1, 0, // high
    0, 1, 1, 1, 0, 0, 1, 0  // low
};

obviously we must refer to an image file containing the texture

private static final String FILE_TEXTURE = "/texture.png";

it is useless to remember to create the VertexArray

VertexArray textureData = new VertexArray(TEXTURE_COORDINATES.length / 2 , 2 , 1);

store the array data

textureData.set(0, TEXTURE_COORDINATES.length / 2 , TEXTURE_COORDINATES);

and insert the coordinates in the new texture

cubeVerticesData.setTexCoords(0, textureData, 2.0f, null);

At this point we load the image “/texture.png” into memory

try {
     Image2D imgTexture = (Image2D) Loader.load(FILE_TEXTURE)[0];
} catch(Exception e) {
     // Error handling
}

and if the loading of the image is successful we pass to the creation of the texture

Texture2D cubeTexture = new Texture2D(imgTexture);

When you “link” a texture on a polygon normally you want to somehow preserve the color values generated by the lighting. This in fact does not work directly on the texture, but on a color assigned to the polygon (or object).

The result of the given color illumination is then added to the texture image according to various functions, as happens for the OpenGL. The color of the object is set by the method

cubeVerticesData.setDefaultColor (0x00FFFFFF);

the texture is associated with Appearance with

cubeVerticesData.setTexture (0, textureCubo);

where 0 represents the texture number. The blending function is activated via setBlending() of the Texture2D class

cubeTexture.setBlending(Texture2D.FUNC_DECAL);

The Figure3 shows the cube with the textures created

Figure 3 – Here a 64×64 pixel texture is applied to the cube

while the Listing 3 the complete code for reference purposes.

ublic void Init() {                 
            
    // Create a new instance of the VertexBuffer object
    cubeVerticesData = new VertexBuffer();
            
    // Create a new VertexArray to store 
    // the coordinates of the cube's vertices
    VertexArray verticesPositions = new VertexArray(VERTICES_POSITIONS.length / 3, 3, 1);
            
    // Store the positions of the vertices in the array
    verticesPositions.set(0, VERTICES_POSITIONS.length / 3, VERTICES_POSITIONS);
            
    // Pass the VertexArray reference to the VertexBuffer
    cubeVerticesData(verticesPositions, 1.0f, null);
            
    // Create a new instance of the object  
    TriangleStripArray cubeTriangles = new TriangleStripArray(TRIANGLES_INDICES, new int[] {TRIANGLES_INDICES.length});
            
    // Create a new instance of the Camera object
    Camera camera = new Camera();
            
    // Calculate the value of the AspectRatio
    float aspectRatio = (float) DISPLAY_WIDTH / (float) DISPLAY_HEIGHT;

    // Create a new instance of the Appearance object
    cubeAppearance = new Appearance();
            
    // Create a new instance of the PolygonMode object
    polygonMode = new PolygonMode();

    cubeAppearance.setPolygonMode(polygonMode);
            
    // Create a VertexArray to store vertex colors
    VertexArray vertexColors = new VertexArray(VERTEX_COLORS.length / 3, 3, 1);
            
    // Memorize the vertex colors
    vertexColors.set(0, VERTEX_COLORS.length / 3, VERTEX_COLORS);
            
    // Inserts the VertexArray into the VertexBuffer
    cubeVerticesData.setColors(vertexColors);                        
            
    // Set the perspective correction
    camera.setPerspective(30.0f, aspectRatio, 1.0f, 1000.0f);            
            
    // Create a transformation object
    Transform cameraTransform = new Transform();
            
    // Set a translation
    cameraTransform.postTranslate(0.0f, 0.0f, 10.0f);
            
    // Activate the camera
    g3d.setCamera(camera, cameraTransform);            
            
    // Create a new VertexArray to store the normals
    VertexArray cubeNormals = new VertexArray(VERTICES_NORMALS.length / 3, 3, 1);
            
    // Stores the normals in the VertexArray
    cubeNormals.set(0, VERTICES_NORMALS.length / 3, VERTICES_NORMALS);
            
    // Store the VertexArray to the VertexBuffer
    cubeVerticesData.setNormals(cubeNormals);
            
    // Create a new Material object
    Material cubeMaterial = new Material();
            
    // Set material values
    cubeMaterial.setColor(Material.AMBIENT, 0x00FF0000);
            
    // Memorize the object in Appearance
    cubeAppearance.setMaterial(cubeMaterial);

    // Create a new Light object
    Light light = new Light();
            
    // Set the type of lighting source
    light.setMode(Light.DIRECTIONAL);
            
    // Set the intensity of the light
    light.setIntensity(5.0f);
            
    // Create a new transformation for light
    Transform lightTransform = new Transform();
            
    // Set the translation value
    lightTransform.postTranslate(0.0f, 0.0f, 3.0f);          
            
    // Reset all lights
    g3d.resetLights();
            
    // Adds light to the scene including transformation
    g3d.addLight(light, lightTransform);
            
    VertexArray textureData = new VertexArray(TEXTURE_COORDINATES.length / 2, 2, 1);

    textureData.set(0, TEXTURE_COORDINATES.length / 2, TEXTURE_COORDINATES);

    cubeVerticesData.setTexCoords(0, textureData, 1.0f, null);
            
    try {
       Image2D imgTexture = (Image2D) Loader.load(FILE_TEXTURE)[0];
       Texture2D cubeTexture = new Texture2D(imgTexture);
                    
       cubeTexture.setBlending(Texture2D.FUNC_DECAL);
                    cubeApeearance.setTexture(0, textureCubo);
    } catch(Exception e) {
        // Gestione dellíerrore
    }                                    
                        
    initDone = true;                        
        
}

Conclusions

Of course the articles you have read does not claim to be a complete guide to the API, but I am sure that from this point on you will be able to move by yourself taking as a reference only the javadoc documentation.

In the immediate future we will surely have the opportunity to deepen the discussion through more complete and complex examples, such as the creation of a real 3D video game.

I also hope that the subject presented is a reason for interest and stimulus to give life to interesting ideas in the world of mobile video games. For this I invite all readers to contact us for any advice and idea you wish to submit.

Bibliography

  1. Sun Corporation, “Mobile 3D Graphics API Specification “, www.sun.com
  2. DEV No. 132 year XIII September 2005, “Using OpenGL”

Downloads

Leave a Reply

Your email address will not be published. Required fields are marked *