Using DepthOfFieldFilter to only blur certain nodes

Hey all! I’m relatively new to JMonkey and am trying to make a simple effect where when I click on a certain node, everything besides that node is blurred in the background. I’ve gotten to the point where I can click on an object and apply the DepthOfFieldFilter to the scene, but am unsure of how to proceed from here, or if a DepthOfFieldFilter is even what I want to be using.

I have this code flow so far:

  • Create spatials, just simple text Box Geometries for now
  • Add those spatials to an ‘interactive’ Node, where things can be clicked via Ray Casting
  • When a spatial is clicked, give it a UserData value of ‘stateFocused = true’
  • Call an AppState I named ‘FocusedAs’ (focused App State)
  • In the AppState, get all nodes with UserData of ‘stateFocused = false’ and remove them from the ‘interactive’ Node, then add them to the ‘non-interactive’ Node, leaving only the one clicked on remaining in the ‘interactive’ Node
  • Create a DepthOfFieldFilter in the ‘FocusedAs’ and add it to the Application class’s FilterPostProcessor

This successfully blurs the entire scene when clicking on a Spatial, and separates the two categories of Spatials into ‘interactive’ Nodes and ‘non-interactive’ Nodes, but I’m unsure of how to apply the blur effect to only the ‘non-interactive’ Nodes. I was reading about render passes, but can’t seem to find any examples of how they work in JMonkey. Any suggestions? Or is there an easy way to only apply a post processing filter to certain nodes?

I’ll post my code below, but it’s written in Kotlin (company standard), so I’m not sure how useful it will be, although Kotlin is very readable! I can convert it to Java if requested.

class MyApp : SimpleApplication(), ActionListener {
    private val iNode = Node(Nodes.INODE.name) //just points to a String enum
    private val niNode = Node(Nodes.NINODE.name) //just points to a String enum
    private val focusedAS = FocusedAs() // my custom AppState class
    val fpp = FilterPostProcessor(assetManager)

    override fun simpleInitApp() {
        viewPort.addProcessor(fpp)
        createBoxes()
    }

    //ommitted some of my other functions like createBoxes()
    //and initializing the input maps due to irrelevancy

    override fun onAction(name: String, isPressed: Boolean, tpf: Float) {
        if (name == Actions.CLICK.name && !isPressed) {
            val results = CollisionResults()
            val click2d = inputManager.cursorPosition.clone()
            val click3d = cam.getWorldCoordinates(click2d, 0f).clone()
            val dir = cam.getWorldCoordinates(click2d, 1f)
               .subtractLocal(click3d).normalizeLocal()
            val ray = Ray(click3d, dir)
            iNode.collideWith(ray, results)

            if (results.size() > 0) {
                val closest = results.closestCollision
                val selectedGeo = closest.geometry
                selectedGeo.setUserData(Actions.FOCUSEDSTATE.name, true) //an enum pointing to a string
                if (stateManager.hasState(focusedAS)) {
                    //already attached
                    val fas: FocusedAs? = stateManager.getState(FocusedAs::class.java)
                    fas?.isEnabled = true //enables the AppState if its already attached
                } else {
                    //attach the appState
                    stateManager.attach(focusedAS)
                }
            }
        }
    }
}

and my AppState…

class FocusedAs : BaseAppState() {
    override fun initialize(app: Application) {
        //called when AppState is initialized
        if (app is MyApp) {
            val iNode = app.rootNode.getChild(MyApp.Nodes.INODE.name) as Node //the interactive Node
            val niNode = app.rootNode.getChild(MyApp.Nodes.NINODE.name) as Node //the non-interactive Node

            //find find the selected geometry
            iNode.children.forEach {
                if (!it.getUserData<Boolean>(MyApp.Actions.FOCUSEDSTATE.name)) {
                    iNode.detachChild(it) //remove spatials from activenode
                    niNode.attachChild(it) //add spatials to inactivenode
                }
            }

            val selectedGeo = iNode.children[0] //only spatial remaining is the selected spatial

            //blur the inactive node
            val dofFilter = DepthOfFieldFilter()
            dofFilter.focusDistance = 0f
            dofFilter.focusRange = 0f
            dofFilter.blurScale = 20f
            app.fpp.addFilter(dofFilter)
            //TODO: figure out how to apply this only to inactive node..
        } else {
            //TODO: Make more specific Exception
            throw Exception("MyApp not found when trying to initialize FocusedAs.")
        }
    }
    //didn't include rest of override functions due to irrelevancy
}

There we go! As I say, it’s working so far as blurring the spatials and moving them all to the right Nodes, but I can’t figure out how to blur only a select number of Nodes. Any help or documentation would be greatly appreciated! Thank you!

Edit: Oh, and if anyone could tell me how to increase the resolution of my blurred spatials, that would be phenomenal. They are very pixelated at the moment.

See how pixelated they are? Not sure how to fix this! I’m thinking I’ll have to do some GLSL shader work, but I’m not sure.

Edit2: I figured out how to make the resolution better– add more DepthOfFieldFilters on top of the original filter. Not sure if this is the best way to go about it, however…

I don’t think you can do it on a per node basis. But I guess with the distance (distance between your cam and the object I guess) and the radius it should be possible, I think that is exactly the idea…

And maybe also play around withe the blurr factor a bit.

There should be several possibilities:

  1. Render the blurred nodes, and then write a small filter to render the not blurred items after the DoF filter has blurred the scene.

  2. Make smart use of the stencil buffer. Might be trickier to implement, but that would be the cleanest solution i think.

After thinking it over i am not sure if stenceling works since it might be impssible to hide the blurred “aura”. But the object itself should be sharp

A post process is applied to the screen as a texture and would be a bit convoluted. Do you want to blur something like in camera footage blurring a license plate? Or are you simply trying to focus onto something like a macro lens would?

Either don’t blur them so much or rewrite the shader to do more sampling (at a great cost in performance).

…or use an entirely different technique not based on depth since a depth effect isn’t what you want.

Probably simplest approach:
write your own post-processing filter that defers to the gaussian blur filters like the bloom filter does… and then re-renders your object unblurred on top of that.

2 Likes

A post process is applied to the screen as a texture and would be a bit convoluted. Do you want to blur something like in camera footage blurring a license plate? Or are you simply trying to focus onto something like a macro lens would?

Very similar to a macro lens! I have a scene with a lot of individual models, and when I click on one I would like the non-focused models to shift towards the background and blur, while the selected one is not blurred in the foreground. That’s why I was thinking maybe a post-processing filter on the nodes in the background, and render the ‘focused’ model in the foreground, if it’s possible. Maybe I’m going about it in the wrong way, though…

Ah! I’m reading about stencil buffers now, and that seems like it would work. It’s essentially just a type of image mask, if what I’m reading is correct? So I could blur out the background and mask over the selected model, potentially. But yeah, the blur “aura” from the other objects might be a problem; also the blur “aura” from the main object distorts the model going inward, so.

A filter might be what I end up going with after all, just because I don’t think I can really blur the focused model and then mask it without some distortion. The complicated bit will probably be switching between blurred and not-blurred states in a smooth animation. :pensive: