Multi-Texturing

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

Multi-Texturing

Hugo Lizzi
I just updated by project to Java3D 1.7.  I was surprised at how easy it was.  
I would like to say how much I appreciate that Java3D is being maintained.
I don't see any other UI that is as easy.

Now for my problem.  
I am trying to do multi-texturing.  From what I can tell of the "Getting Started with Java3D API" tutorial there are 2 types of multi-texturing.

The first type is called multi-level texturing where 2 or more texture images overlay each other (also called mipmaps).  
Instructions for doing this type of multi-texturing are given in "Getting Started" chapter 7.6.  

The second type is just called multi-texturing where 2 or more texture images are applied to different parts of the same Visual Object.
Instructions for doing this type of multi-texturing are given in "Getting Started" chapter 7.8.
 
I am trying to do the second.  
I created a simple zero thickness 3D rectangle with the intention of applying one texture on the front and one on the back.
But no matter how I write the code, the second texture gets put on the back OK,
but the front just gets color from the edge of the second texture.  
It seems like the second TextureUnitState is applied to the whole Visual Object overwriting the first.

If anyone can point out an error I would be very appreciative.  
Here is my code after 100 hours of trial and error (mostly error).  

/**
 * Uses JDK 12.0.1 and Java3D version 1.7
 */
public class MultiTexTest extends JFrame {
        static final long serialVersionUID = 1;
               
        public static void main( String[] args ) { new MultiTexTest(); }
       
        public MultiTexTest() {
               setPreferredSize( new Dimension( 600, 400 ) );
               
                GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
                Canvas3D canvas3D = new Canvas3D( config );
                canvas3D.setBounds( 0, 0, 1000, 1000 );
                add( canvas3D );

                MultiTexRectangle multiTexRectangle = new MultiTexRectangle( canvas3D );

                TransformGroup rectangleTrans = new TransformGroup();
                rectangleTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
                rectangleTrans.addChild( multiTexRectangle );

                Transform3D yAxis = new Transform3D();
                Alpha rotationAlpha = new Alpha(-1, 8000);

                RotationInterpolator rotator = new RotationInterpolator(
                rotationAlpha, rectangleTrans, yAxis, 0f, (float) Math.PI*2f );
                BoundingSphere bounds = new BoundingSphere( new Point3d(0, 0, 0), 600 );
                rotator.setSchedulingBounds( bounds );
 
                BranchGroup branchGroupRoot = new BranchGroup();  
                branchGroupRoot.addChild( rectangleTrans );  
                branchGroupRoot.addChild( rotator );
                branchGroupRoot.compile();
       
                SimpleUniverse simpleU = new SimpleUniverse( canvas3D );
                simpleU.getViewingPlatform().setNominalViewingTransform();
                simpleU.addBranchGraph( branchGroupRoot );

                pack();
               setVisible( true );
        }
}

import java.io.IOException;
import java.net.URL;

import org.jogamp.java3d.Appearance;
import org.jogamp.java3d.Canvas3D;
import org.jogamp.java3d.GeometryArray;
import org.jogamp.java3d.Shape3D;
import org.jogamp.java3d.Texture;
import org.jogamp.java3d.TextureAttributes;
import org.jogamp.java3d.TextureUnitState;
import org.jogamp.java3d.TriangleArray;
import org.jogamp.java3d.utils.image.TextureLoader;
import org.jogamp.vecmath.Point3f;
import org.jogamp.vecmath.TexCoord2f;
import org.jogamp.vecmath.Vector3f;

public class MultiTexRectangle extends Shape3D {
        GeometryArray geometryArray;
        Appearance appearance = new Appearance();
   
        int[] texCoordSetMap = new int[2];
        Point3f[] vertexArray = new Point3f[12];         // Vertex Coords for 2 sided rectangle
        Vector3f[] normalArray = new Vector3f[12];
        TexCoord2f[] textureArray0 = new TexCoord2f[6];  // Texture Coords for front of rectangle
        TexCoord2f[] textureArray1 = new TexCoord2f[6];  // Texture Coords for back of rectangle  
   
        public MultiTexRectangle( Canvas3D canvas3D ) {
                 vertexArray[0] = new Point3f(.6f, .4f, 0);      
                 normalArray[0] = new Vector3f(0, 0, 1f);      
                 textureArray0[0] = new TexCoord2f(1f, 1f);

                 vertexArray[1] = new Point3f(0, .4f, 0);      
                 normalArray[1] = new Vector3f(0, 0, 1f);      
                 textureArray0[1] = new TexCoord2f(0f, 1f);

                 vertexArray[2] = new Point3f(0, 0, 0);      
                 normalArray[2] = new Vector3f(0, 0, 1f);      
                 textureArray0[2] = new TexCoord2f(0f, 0f);

                 vertexArray[3] = new Point3f(0, 0, 0);      
                 normalArray[3] = new Vector3f(0, 0, 1f);      
                 textureArray0[3] = new TexCoord2f(0f, 0f);

                 vertexArray[4] = new Point3f(.6f, 0, 0);      
                 normalArray[4] = new Vector3f(0, 0, 1f);      
                 textureArray0[4] = new TexCoord2f(1f, 0f);

                 vertexArray[5] = new Point3f(.6f, .4f, 0);      
                 normalArray[5] = new Vector3f(0, 0, 1f);      
                 textureArray0[5] = new TexCoord2f(1f, 1f);
       
                 vertexArray[6] = new Point3f(0, .4f, 0);      
                 normalArray[6] = new Vector3f(0, 0, -1f);      
                 textureArray1[0] = new TexCoord2f(0f, 1f);
   
                 vertexArray[7] = new Point3f(.6f, .4f, 0);      
                 normalArray[7] = new Vector3f(0, 0, -1f);      
                 textureArray1[1] = new TexCoord2f(1f, 1f);
   
                 vertexArray[8] = new Point3f(.6f, 0, 0);      
                 normalArray[8] = new Vector3f(0, 0, -1f);      
                 textureArray1[2] = new TexCoord2f(1f, 0f);
   
                 vertexArray[9] = new Point3f(.6f, 0, 0);      
                 normalArray[9] = new Vector3f(0, 0, -1f);      
                 textureArray1[3] = new TexCoord2f(1f, 0f);
   
                 vertexArray[10] = new Point3f(0, 0, 0);      
                 normalArray[10] = new Vector3f(0, 0, -1f);      
                 textureArray1[4] = new TexCoord2f(0f, 0f);
   
                 vertexArray[11] = new Point3f(0, .4f, 0);      
                 normalArray[11] = new Vector3f(0, 0, -1f);      
                 textureArray1[5] = new TexCoord2f(0f, 1f);
     
                 URL image0, image1;
                 try {
                       image0 = new URL( "http://www.relativitysimulation.com/Images/Soup-Label.jpg" );
                       image1 = new URL( "http://www.relativitysimulation.com/Images/Sky-Ground.jpg" );
                 }
                 catch ( IOException e ) {
                        System.out.println("FAILURE trying to load image files from URLs.  Can't apply multi-texture.");
                        System.out.println( e );
                        return;
                 }
                TextureLoader texLoader0 = new TextureLoader( image0, canvas3D );
                Texture texture0 = texLoader0.getTexture();
                TextureAttributes texAttrib0 = new TextureAttributes();
                TextureUnitState unitState0 = new TextureUnitState( texture0, texAttrib0, null );
           
                TextureLoader texLoader1 = new TextureLoader( image1, canvas3D );
                Texture texture1 = texLoader1.getTexture();        
                TextureAttributes texAttrib1 = new TextureAttributes();
                TextureUnitState unitState1 = new TextureUnitState( texture1, texAttrib1, null );
   
                TextureUnitState[] stateArray = new TextureUnitState[2];
                stateArray[0] = unitState0;  // First element in TextureUnitState array gets Soup-Label texture
                stateArray[1] = unitState1;  // Second element in TextureUnitState array gets Sky-Ground texture
   
                texCoordSetMap[0] = 0;      // First element in Texture Coord Set gets first Texture Unit State
                texCoordSetMap[1] = 1;      // Second element in Texture Coord Set gets second Texture Unit State
       
                geometryArray = new TriangleArray( 12, TriangleArray.COORDINATES | TriangleArray.NORMALS  
                                  | TriangleArray.TEXTURE_COORDINATE_2, 2, texCoordSetMap );
   
                geometryArray.setCoordinates( 0, vertexArray );
                geometryArray.setNormals( 0, normalArray );
                geometryArray.setTextureCoordinates( texCoordSetMap[0], 0, textureArray0 ); // Soup-Label goes on rectangle front
                geometryArray.setTextureCoordinates( texCoordSetMap[1], 6, textureArray1 ); // Sky-Ground goes on rectangle back        
                setGeometry( geometryArray );  
           
                appearance.setTextureUnitState( stateArray );
                setAppearance( appearance );    
        }
}
Reply | Threaded
Open this post in threaded view
|

Re: Multi-Texturing

Hugo Lizzi
Just a follow up to my post above.

I studied/ran the example MultiTextureTest.java provided in the Examples Folder.  
It gave me a Multi-Level Texture Box.  That is, both textures overlay the entire box.
I did not see any attempt to assign the 2 textures to 2 different areas of the box.  
I assume that the name of the example should have been MultiLevelTextureTest.
Reply | Threaded
Open this post in threaded view
|

Re: Multi-Texturing

philjord
Hi,
Thanks very much for submitting a easily understood problem.


The root of your problem is to do with the intent and usage of textures and TextureUnitStates, and generally the basic use of multi-texturing.

Multi-texturing is the term used for layering textures one on top of another with some sort of interesting operation happening between them. So it is a way to for example, make a brick wall have a lovely translucent "dirt" texture over it to make it look a bit down at heel.
So a TextureUnitState can have a TextureAttribute object attached, so that the dirt layer on top of the brick layer can be made a bit interesting by having a combine function applied (like inverting the colors or whatever).
We would also want layers where we have decals, so the brick wall could have bullet marks all over it. Decals tend to have lots of transparent texture to show the texture underneath.
Dot3Demo shows layers interacting

In the case where we have different textures applied to different areas of a big geometry the concept is one of grouping each chunk of geometry with an appearance, and the appearance simply has the texture we want attached to it.

3D design tools and rendering engines all use different names for this same concept, so an Obj file would call these Groups, Java3D calls them Shape3D.

So to do the two sides with a different texture we make 2 Shape3D objects, give each one a chunk of the single geometry data (but telling them where to start and stop in the geometry data array), and assign the texture coordinates separately and then assign the texture.

So below I've slightly modified your code to use 2 Shape3D in a single BranchGroup, but used your data exactly as it was. Because I've chopped out the 2 texture units the mapping is gone, so the constructors are slightly simpler (apart from the slightly odd array used in setTextureUnitState method call).


Because we now aren't layering textures on top of each other (with a operation to define how they merge) we actually don't need texture unit states at all, so all the calls below could go directly into the Appearance object, which supports a single texture nicely.


Finally, if you actually do want to do a layer texture with some crazy color operations I do suggest strongly that you use the gl2es2 pipeline, which supports programmable shaders, simply put
System.setProperty("j3d.rend", "jogl2es2");
at the start of your code.
That way you could call the same code as below with the Appearance class swapped to SimpleShaderAppearance and then call getVertexShaderSource() and getFragmentShaderSource() you can see what a programmable equivilent to the fixed function code you are calling is.



EnvironmentMappingGLSL.java
Shows a use of TextureUnitState with shaders









package test;


import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.io.IOException;
import java.net.URL;

import javax.swing.JFrame;

import org.jogamp.java3d.Alpha;
import org.jogamp.java3d.Appearance;
import org.jogamp.java3d.BoundingSphere;
import org.jogamp.java3d.BranchGroup;
import org.jogamp.java3d.Canvas3D;
import org.jogamp.java3d.GeometryArray;
import org.jogamp.java3d.RotationInterpolator;
import org.jogamp.java3d.Shape3D;
import org.jogamp.java3d.Texture;
import org.jogamp.java3d.TextureAttributes;
import org.jogamp.java3d.TextureUnitState;
import org.jogamp.java3d.Transform3D;
import org.jogamp.java3d.TransformGroup;
import org.jogamp.java3d.TriangleArray;
import org.jogamp.java3d.utils.image.TextureLoader;
import org.jogamp.java3d.utils.universe.SimpleUniverse;
import org.jogamp.vecmath.Point3d;
import org.jogamp.vecmath.Point3f;
import org.jogamp.vecmath.TexCoord2f;
import org.jogamp.vecmath.Vector3f;

/**
 * Uses JDK 12.0.1 and Java3D version 1.7
 */
public class MultiTexTest2 extends JFrame {
        static final long serialVersionUID = 1;
               
        public static void main( String[] args ) { new MultiTexTest2(); }
       
        public MultiTexTest2() {
               setPreferredSize( new Dimension( 600, 400 ) );
               
                GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
                Canvas3D canvas3D = new Canvas3D( config );
                canvas3D.setBounds( 0, 0, 1000, 1000 );
                add( canvas3D );

                MultiTexRectangle multiTexRectangle = new MultiTexRectangle( canvas3D );

                TransformGroup rectangleTrans = new TransformGroup();
                rectangleTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
                rectangleTrans.addChild( multiTexRectangle );

                Transform3D yAxis = new Transform3D();
                Alpha rotationAlpha = new Alpha(-1, 8000);

                RotationInterpolator rotator = new RotationInterpolator(
                rotationAlpha, rectangleTrans, yAxis, 0f, (float) Math.PI*2f );
                BoundingSphere bounds = new BoundingSphere( new Point3d(0, 0, 0), 600 );
                rotator.setSchedulingBounds( bounds );
 
                BranchGroup branchGroupRoot = new BranchGroup();  
                branchGroupRoot.addChild( rectangleTrans );  
                branchGroupRoot.addChild( rotator );
                branchGroupRoot.compile();
       
                SimpleUniverse simpleU = new SimpleUniverse( canvas3D );
                simpleU.getViewingPlatform().setNominalViewingTransform();
                simpleU.addBranchGraph( branchGroupRoot );

                pack();
               setVisible( true );
        }
 



  class MultiTexRectangle extends BranchGroup {
          Shape3D shape0;
          Shape3D shape1;
       
   
        Point3f[] vertexArray = new Point3f[12];         // Vertex Coords for 2 sided rectangle
        Vector3f[] normalArray = new Vector3f[12];
        TexCoord2f[] textureArray0 = new TexCoord2f[6];  // Texture Coords for front of rectangle
        TexCoord2f[] textureArray1 = new TexCoord2f[6];  // Texture Coords for back of rectangle  
   
        public MultiTexRectangle( Canvas3D canvas3D ) {
       
        // data for first shape and geometry
                 vertexArray[0] = new Point3f(.6f, .4f, 0);      
                 normalArray[0] = new Vector3f(0, 0, 1f);      
                 textureArray0[0] = new TexCoord2f(1f, 1f);

                 vertexArray[1] = new Point3f(0, .4f, 0);      
                 normalArray[1] = new Vector3f(0, 0, 1f);      
                 textureArray0[1] = new TexCoord2f(0f, 1f);

                 vertexArray[2] = new Point3f(0, 0, 0);      
                 normalArray[2] = new Vector3f(0, 0, 1f);      
                 textureArray0[2] = new TexCoord2f(0f, 0f);

                 vertexArray[3] = new Point3f(0, 0, 0);      
                 normalArray[3] = new Vector3f(0, 0, 1f);      
                 textureArray0[3] = new TexCoord2f(0f, 0f);

                 vertexArray[4] = new Point3f(.6f, 0, 0);      
                 normalArray[4] = new Vector3f(0, 0, 1f);      
                 textureArray0[4] = new TexCoord2f(1f, 0f);

                 vertexArray[5] = new Point3f(.6f, .4f, 0);      
                 normalArray[5] = new Vector3f(0, 0, 1f);      
                 textureArray0[5] = new TexCoord2f(1f, 1f);
       
                 // data for second shape and geometry
                 vertexArray[6] = new Point3f(0, .4f, 0);      
                 normalArray[6] = new Vector3f(0, 0, -1f);      
                 textureArray1[0] = new TexCoord2f(0f, 1f);
   
                 vertexArray[7] = new Point3f(.6f, .4f, 0);      
                 normalArray[7] = new Vector3f(0, 0, -1f);      
                 textureArray1[1] = new TexCoord2f(1f, 1f);
   
                 vertexArray[8] = new Point3f(.6f, 0, 0);      
                 normalArray[8] = new Vector3f(0, 0, -1f);      
                 textureArray1[2] = new TexCoord2f(1f, 0f);
   
                 vertexArray[9] = new Point3f(.6f, 0, 0);      
                 normalArray[9] = new Vector3f(0, 0, -1f);      
                 textureArray1[3] = new TexCoord2f(1f, 0f);
   
                 vertexArray[10] = new Point3f(0, 0, 0);      
                 normalArray[10] = new Vector3f(0, 0, -1f);      
                 textureArray1[4] = new TexCoord2f(0f, 0f);
   
                 vertexArray[11] = new Point3f(0, .4f, 0);      
                 normalArray[11] = new Vector3f(0, 0, -1f);      
                 textureArray1[5] = new TexCoord2f(0f, 1f);
     
                 URL image0, image1;
                 try {
                       image0 = new URL( "http://www.relativitysimulation.com/Images/Soup-Label.jpg" );
                       image1 = new URL( "http://www.relativitysimulation.com/Images/Sky-Ground.jpg" );
                 }
                 catch ( IOException e ) {
                        System.out.println("FAILURE trying to load image files from URLs.  Can't apply multi-texture.");
                        System.out.println( e );
                        return;
                 }
                TextureLoader texLoader0 = new TextureLoader( image0, canvas3D );
                Texture texture0 = texLoader0.getTexture();
                TextureAttributes texAttrib0 = new TextureAttributes();
                TextureUnitState unitState0 = new TextureUnitState( texture0, texAttrib0, null );
           
                TextureLoader texLoader1 = new TextureLoader( image1, canvas3D );
                Texture texture1 = texLoader1.getTexture();        
                TextureAttributes texAttrib1 = new TextureAttributes();
                TextureUnitState unitState1 = new TextureUnitState( texture1, texAttrib1, null );            
           
                Appearance appearance0 = new Appearance();
                Shape3D shape0 = new Shape3D();
                GeometryArray geometryArray0 = new TriangleArray( 6, TriangleArray.COORDINATES | TriangleArray.NORMALS  
                                  | TriangleArray.TEXTURE_COORDINATE_2 );
   
                geometryArray0.setCoordinates( 0, vertexArray, 0, 6 );
                geometryArray0.setNormals( 0, normalArray, 0, 6 );
                geometryArray0.setTextureCoordinates( 0, 0, textureArray0 ); // Soup-Label goes on rectangle front                
                shape0.setGeometry( geometryArray0 );  
           
                appearance0.setTextureUnitState( new TextureUnitState[] { unitState0 } );
                shape0.setAppearance( appearance0 );
               
                addChild(shape0);
               
                Appearance appearance1 = new Appearance();
                Shape3D shape1 = new Shape3D();
                GeometryArray geometryArray1 = new TriangleArray( 6, TriangleArray.COORDINATES | TriangleArray.NORMALS  
                                  | TriangleArray.TEXTURE_COORDINATE_2 );
   
                geometryArray1.setCoordinates( 0, vertexArray, 6, 6 );
                geometryArray1.setNormals( 0, normalArray, 6, 6 );
                geometryArray1.setTextureCoordinates(0, 0, textureArray1 ); // Sky-Ground goes on rectangle back        
                shape1.setGeometry( geometryArray1 );  
           
                appearance1.setTextureUnitState(new TextureUnitState[] { unitState1 });
                shape1.setAppearance( appearance1 );
               
                addChild(shape1);
               
        }
  }
}
Reply | Threaded
Open this post in threaded view
|

Re: Multi-Texturing

Hugo Lizzi
Thank you philjord for your clear and accurate reply.  
Your suggested code modifications produced exactly what I wanted.  
Wow.  I just misunderstood the whole multitexture concept.  
Now I can move on to realistic models.  I think I understand how the format of *.obj files can produce their results too.
Reply | Threaded
Open this post in threaded view
|

Re: Multi-Texturing

Sven Gothel
Administrator
In reply to this post by philjord
On 2/1/20 6:38 AM, philjord [via jogamp] wrote:
> Hi,
> Thanks very much for submitting a easily understood problem.
>
>
> The root of your problem is to do with the intent and usage of textures and
> TextureUnitStates, and generally the basic use of multi-texturing.
>
> Multi-texturing is the term used for layering texture one on top of another
> with some sort of interesting operation happening between them.

Great explanation Phil.

I would even go so far as to (simply) say n-textures are provided as
an (input) data source to be used via the texture unit (in shader)
for whatever you desire ..

You may stream the data into the GPU (or CPU/GPU shared memory)
off-thread using a shared GL context or the old pbuffer concept
and act upon them as desired when available.

This is a shared domain with compute and earlier GPU accel tasks
loved to use multiple RED only textures (1 color, maybe even 1 dimension).

And so on and so forth .. (sorry, I always elaborate too much).

In our JOGL Texture util class,
I elaborated about the texture unit concept a while ago:
<https://jogamp.org/deployment/jogamp-next/javadoc/jogl/javadoc/com/jogamp/opengl/util/texture/Texture.html#textureCallOrder>.

Using multiple texture units in parallel is multitexture usage.

~Sven