Multicanvas rendering



Screenshot: using two contexts to display the same scene from different angles,

this is the code that uses the system to do it:


package com.gibbon.mfkarpg.core;

import com.gibbon.mfkarpg.core.context.*;
import com.gibbon.mfkarpg.core.context.lwjgl.*;

import com.jme.image.Texture;
import com.jme.light.PointLight;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.shape.RoundedBox;
import com.jme.scene.state.LightState;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.PreferencesGameSettings;
import com.jme.util.TextureManager;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.util.prefs.Preferences;
import javax.swing.JFrame;

public class View3D extends javax.swing.JDialog {
   
    private int width, height;
    private JmeContext context;
   
    public View3D(JFrame parent, int width, int height) {
        super(parent, false);
        this.width=width;
        this.height=height;
        initComponents();
    }
   
    public JmeContext getContext(){
        return context;
    }
   
    private static class RotatingBox extends RenderPass {
       
        private float angle = 0;
        private Node root;
       
        private ColorRGBA background;
        private boolean rotateScene = false;
       
        public RotatingBox(Node root, ColorRGBA bgColor, boolean rotateScene){
            super();
            background = bgColor;
            this.rotateScene = rotateScene;
            this.root = root;
            add(root);
        }
       
        public void initPass(JmeContext context){
            super.initPass(context);
           
            context.getRenderer().setBackgroundColor(background);
           
            Camera cam = context.getRenderer().getCamera();
            if (rotateScene){
                cam.setLocation(new Vector3f(5,5,5));
            }else{
                // use a different angle if we are the 2nd canvas
                cam.setLocation(new Vector3f(5,7,2));
            }
           
            cam.lookAt(Vector3f.ZERO,Vector3f.UNIT_Y);
        }
        public void doUpdate(JmeContext context){
            super.doUpdate(context);
           
            if (rotateScene){
                angle += FastMath.PI * context.getPassManager().getTPF();
                if (angle > FastMath.TWO_PI){
                    angle -= FastMath.TWO_PI;
                }
                root.getLocalRotation().fromAngles(angle,0,0);
            }
        }
    }
   
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc=" Generated Code ">                         
    private void initComponents() {
        gl = createCanvas();

        setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
        setFocusable(false);
        setFocusableWindowState(false);
        setName("view3d");
        setResizable(false);
        setUndecorated(true);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(gl, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(gl, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)
        );
        pack();
    }// </editor-fold>                       
   
    protected LWJGLCanvas canvas;

    protected LWJGLCanvas createCanvas(){
        try {
            if (JmeContext.get()!=null){
                // share with the previous context
                context = JmeContext.create(JmeContext.getDefaultImplementorClassName(),
                                            JmeContext.CONTEXT_CANVAS,
                                            JmeContext.get());
            }else{
                context = JmeContext.create(JmeContext.getDefaultImplementorClassName(),
                                            JmeContext.CONTEXT_CANVAS);
            }
        } catch (InstantiationException ex) {
            ex.printStackTrace();
            return null;
        }
       
        context.start();
        canvas = (LWJGLCanvas) context.getCanvas();
        return canvas;
    }
   
    public static void main(String[] args){
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();

        View3D view1 = new View3D(null,320,240);
        View3D view2 = new View3D(null,320,240);
       
        view1.setLocation((int)(d.getWidth() / 2 - view1.getWidth()) - 120, (int)(d.getHeight() / 2 - view1.getHeight() / 2));
        view1.setVisible(true);

        view2.setLocation((int)(d.getWidth() / 2) + 120, (int)(d.getHeight() / 2 - view2.getHeight() / 2));
        view2.setVisible(true);
       
        JmeContext context = JmeContext.get();
        try {
            // makes sure DisplaySystem.getDisplaySystem().getRenderer() is not null
            context.waitFor();
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
       
        Node root = new Node("root");
       
        RoundedBox box = new RoundedBox("Box");
        box.setLocalScale(3);
       
        root.attachChild(box);
       
        LightState ls = context.getRenderer().createLightState();
        PointLight pl = new PointLight();
        pl.setSpecular(ColorRGBA.white);
        pl.setDiffuse(ColorRGBA.yellow);
        pl.setAmbient(ColorRGBA.darkGray);
        pl.setEnabled(true);
        pl.setLocation(new Vector3f(-5,5,5));
        ls.attach(pl);
        root.setRenderState(ls);

        ZBufferState buf = context.getRenderer().createZBufferState();
        buf.setFunction(ZBufferState.CF_LEQUAL);
        buf.setWritable(true);
        root.setRenderState(buf);

        TextureState ts = JmeContext.get().getRenderer().createTextureState();
        Texture t = TextureManager.loadTexture(
                        View3D.class.getClassLoader().getResource("jmetest/data/images/Monkey.jpg"));
        ts.setTexture(t);
        root.setRenderState(ts);

        root.updateRenderState();
        root.updateGeometricState(0,true);
       
        view1.getContext().getPassManager().add(new RotatingBox(root,ColorRGBA.red,true));
        view2.getContext().getPassManager().add(new RotatingBox(root,ColorRGBA.green,false));
    }
   
    // Variables declaration - do not modify                    
    private java.awt.Canvas gl;
    // End of variables declaration                  
   
}

Nice job… this has been asked in the forums several times before, perhaps you could put it in the Wiki for later quick reference.  :smiley:



Cheers

Well actualy jME doesn't support it without modifications to the source, and I am using a custom display system to make it easier to control (see the code I provided in my first post).

damn thats really cool…

with that you could make a game like portal …



which would be awesome in a multiplayer mode…



… come on … someone should do that :0



i dont have time and not enough knowledge in 3D stuff … :frowning:



gerch

Hey, this is pretty cool.  I was hoping to do something similar to simulate stereo vision.  How extensive were the JME code modifications?

I just recently found out that no modifications are actualy required to the source; after looking at DummyDisplaySystem. I might post this soon if I get the time.

How are you planning to simulate stereo vision? So far I managed to do it for the anaglyph method.

Well personally I don't mind crossing my eyes a bit… (btw this is also a great bar trick for winning at those "find the difference between these pictures" video games)  :smiley:



But the end purpose is to feed data to a computer vision training system.



Is it possible to just render two different cameras to two different parts of the canvas?

Is this also how one would do splitscreen, or could that be achieved some other way with multiple cameras or rendering to texture onto screen-aligned quads?

Yeah I finally got it by creating a doubly large display and changing the camera sizes and aspect ratios and rendering to a texture which I moved to the side and rendered parallel mode.  And then I had to play around with the backbuffer and add some other objects to my scene because the second camera only wanted to render shadows without them there for some reason. 



It was a huge pain in the ass, and after all that trial and error I still don't have support for the renderpasses I wanted, because those are hardcoded to use the display size (and yeah, I could probably fix that, but I'm afraid it's a rabit hole I don't want to go down yet, especially when my solution for the immediate problem is already a hacky mess) :confused:



I'd really rather do whatever momoko_fan figured out!  :smiley:



You can check out my demo here:  http://www.cs.utexas.edu/users/aharp/vg



If you cross your eyes after hitting "cycle rendermode" once, you can see it in 3d!  I realize this is backwards from how it works in real life, but I've always done the magic eye pics that way too, though then I get an inverted image, since they expect you to relax your eyes, which I could never master.  I always wondered why they didn't just invert them to begin with so you could just cross your eyes :stuck_out_tongue:





Anyway, here are some assorted pieces of code (I found inspiration from the spatiallookattest):




  Quad quad;
  private void setupTextureCam()
  {
    tRenderer = display.createTextureRenderer(320, 240, TextureRenderer.RENDER_TEXTURE_2D);
    tCam = tRenderer.getCamera();
   
    monitorNode = new Node("Monitor Node");
    monitorNode.setRenderQueueMode(Renderer.QUEUE_ORTHO);
    quad = new Quad("Monitor");
    quad.getBatch(0).setZOrder(1);
    quad.initialize(width, height);
    quad.setLocalTranslation(new Vector3f(width * 1.5f, height * 0.5f, 0));
    monitorNode.attachChild(quad);

    // Ok, now lets create the Texture object that our scene will be
    // rendered to.
    tRenderer.setBackgroundColor(new ColorRGBA(0, 0, 0, 1f));
    fakeTex = new Texture();
    fakeTex.setRTTSource(Texture.RTT_SOURCE_RGBA);
    tRenderer.setupTexture(fakeTex);
    TextureState screen = display.getRenderer().createTextureState();
    screen.setTexture(fakeTex);
    screen.setEnabled(true);
    quad.setRenderState(screen);
    monitorNode.updateGeometricState(0.0f, true);
    monitorNode.updateRenderState();
    //camNode.lookAt(m_character.getWorldTranslation(), Vector3f.UNIT_Y);
   
    quad.setLocalTranslation(new Vector3f(width * .75f, height * 0.5f, 0));
    quad.initialize(width/2, height);
    tCam.resize(width/2, height);
    tCam.setFrustumPerspective(55.0f, width/2f / height, 1, 1000);
  }



public final void simpleRender()
  {
    if(fakeTex != null)
    {
      if (bStereo)
      {
          Vector3f loc = tCam.getLocation();
          tCam.setLocation(loc.add(tCam.getLeft().mult(-1.0f)));
          tRenderer.render(rootNode, fakeTex);
          lastRend = 0;
          display.getRenderer().draw(monitorNode);
      }
     
    }
  }




  
 public void doRender() {
      synchronized(this)
      {
        if(m_bRenderEnabled || m_bImageRequested)
        {
          renderer.clearBuffers();
  //        renderer.draw(rootNode);
          cam.resize(width/2, height);
          renderer.clearBuffers();
         //renderer.draw(rootNode);
          pManager.renderPasses(renderer);
//          renderer.displayBackBuffer();
         
          cam.resize(width, height);
          simpleRender();
         
          renderer.displayBackBuffer();

        }
      }
    }




See, told you it was a mess!  But it works!

It's not neccecary to use render-to-texture, that just causes uneccessary performance loss.

You need to change camera parameters to limit the viewport to some part of the screen, render scene A, then repeat for another part of the screen and render scene B.

I just tested your demo and clicking cycle render mode caused a white box to appear to the right side of the canvas…

Weird, it works for me and everyone else who has tested it.



Can you please explain how to translate the camera position on the canvas?  I was only able to scale the size–but it always seemed to be anchored at 0,0 no matter what I did.  I'm hoping it's just a function call that I overlooked, but nothing I tried seemed to work.

To answer my own question, I had to do the following:


 
  public final void simpleRender()
  {
    renderer.clearBuffers();
   
    // First pass draws on the left.
    cam.resize(width/2, height);
    cam.setViewPort(0.0f, 1.0f, 0.0f, 1.0f);

    boolean bUseRenderPasses = true;

    if( bUseRenderPasses )
      pManager.renderPasses(renderer);
    else
      renderer.draw(rootNode);

    if (bStereo)
    {
      // Second pass on the right.
      cam.resize(width/2, height);
      cam.setViewPort(1.0f, 2.0f, 0.0f, 1.0f);

      // Shift the camera over a little.
      cam.setLocation(cam.getLocation().add(cam.getLeft().mult(-1.0f)));

      if( bUseRenderPasses )
        pManager.renderPasses(renderer);
      else
        renderer.draw(rootNode);
    }

    renderer.displayBackBuffer();
  }



This is so much cleaner!

My main misconception before was not realizing that setviewport treats 1.0 as the width of the camera.  So when I passed in pixel values, of course it didn't work.  It seems to default to normal size if you give it bad values.  You can of course go above 1.0 if your camera is smaller than the canvas.

Now would it be more efficient to use multiple cameras?  That is, is it worse to move a camera attached to the renderer, or change the camera that is attached to the renderer?

Oh, and I'm still not sure why I had to resize the second camera render to the same size again.  Why couldn't I just move the viewport, since it was already right size?

Oh, and I'm still not sure why I had to resize the second camera render to the same size again.  Why couldn't I just move the viewport, since it was already right size?

There's no function to move the viewport, only to limit it to a certain rectangle on the screen.

Now would it be more efficient to use multiple cameras?  That is, is it worse to move a camera attached to the renderer, or change the camera that is attached to the renderer?

It would be more efficient to use an existing camera, but it might be cleaner to use a new one- this is purely a design choice.
Momoko_Fan said:

There's no function to move the viewport, only to limit it to a certain rectangle on the screen.


By moving the viewport I meant setFrustumPerspective

My question is, why do I constantly have to keep resizing the camera if it's already the right size?  If I don't both images appear in the same spot, even though I have set the viewport differently before rendering each of them.

Wow, nice work, congratulations !!



Any change you could post your version that requires no modification on jME (or a preview of your custom display system)?



TIA

I'm still waiting for this too, it seems cleaner than what I came up with :slight_smile:

He released it here:

http://www.jmonkeyengine.com/jmeforum/index.php?topic=7168.0

Sweet – I guess I haven't been keeping up with the forums closely enough this year  :stuck_out_tongue: