Page 2 of 2

Re: New method proposal: ISceneNode::getScreenSpace()

Posted: Wed Dec 07, 2016 3:15 pm
by Mel
I think it is kinda nice to know the screen quad that uses a certain node, perhaps it would be possible (with all due respect to the change on the video driver of stuff...) to render certain objects using a clipping rectangle that englobed them and that enabled effects only on that region of the screen

Unreal uses this for the LOD selection? firts news i had O.o I knew the Torque Game Engine used the size of an object on screen to pick the LOD, but i never knew the exact details.

Re: New method proposal: ISceneNode::getScreenSpace()

Posted: Wed Dec 07, 2016 4:16 pm
by CuteAlien
Ah - that way for LOD. Kinda didn't get it before (and was really talking about transparency sorting).
Thought couldn't allow to clip it when using it for LOD that way I guess.

Re: New method proposal: ISceneNode::getScreenSpace()

Posted: Wed Dec 07, 2016 4:47 pm
by REDDemon
In doom III each object was rendered multiple times (1 for each light affecting it) but setting scissor only to the enclosing rectangle for discarding light-less pixels of a mesh, would be cool to have something that gives the enclosing screen rectangle of lights.

Re: New method proposal: ISceneNode::getScreenSpace()

Posted: Wed Dec 07, 2016 6:43 pm
by Mel
Okay, take a look at this. This code is stand alone.

main.cpp

Code: Select all

#include <irrlicht.h>
#include "CScreenSpaceQuadsNode.h"
 
using namespace irr;
 
#define MAX_NODES 7
 
int main(int argc, char* argv[])
{
    irr::SIrrlichtCreationParameters prm;
    prm.DriverType = video::EDT_OPENGL;
    prm.WindowSize = core::dimension2du(1280, 720);
 
    IrrlichtDevice* device = createDeviceEx(prm);
 
    //No device? UPS!
    if (!device)
        return 1;
 
    video::IVideoDriver* driver = device->getVideoDriver();
    scene::ISceneManager* manager = device->getSceneManager();
 
    manager->addCameraSceneNodeFPS();
 
    //Create a sphere mesh
    scene::IMesh* sphereMesh = manager->addSphereMesh("Sphere", 10.0f);
 
    //Add the quad renderer...
    scene::CScreenQuadsSceneNode* quads = new scene::CScreenQuadsSceneNode(manager->getRootSceneNode(), manager, -32);
 
    //Add some nodes...
    for (u32 j = 1; j < 4;++j)
        for (u32 i = 0; i < MAX_NODES; ++i)
        {
            scene::IMeshSceneNode* node = manager->addMeshSceneNode(sphereMesh);
            node->setPosition(core::vector3df(j*300 * sin(2 * core::PI*i / MAX_NODES), 0, j*300 * cos(2 * core::PI*i / MAX_NODES)));
            node->setScale(core::vector3df(j,j,j));
            node->getMaterial(0).DiffuseColor = video::SColor(0xFFFFFFFF);
            node->getMaterial(0).NormalizeNormals = true;
            quads->registerNode(node);
        }
 
    //Add a light
    scene::ILightSceneNode* light = manager->addLightSceneNode(manager->getRootSceneNode(), core::vector3df(500, 500, 500), video::SColorf(1, 1, 1, 1), 1000.0f);
 
    quads->drop();
    quads = 0;
 
    while (device->run())
    {
        driver->beginScene();
        driver->draw2DRectangle(core::recti(0, 0, 1280, 720), video::SColor(0xFF00007F), video::SColor(0xFF00007F), video::SColor(0xFF007F7F), video::SColor(0xFF007F7F));
        manager->drawAll();
        driver->endScene();
    }
    device->drop();
 
    return 0;
}
"CScreenSpaceQuadsNode.h"

Code: Select all

 
#ifndef _CSCREENQUADSSCENENODE_H_
#define _CSCREENQUADSSCENENODE_H_
 
#include <irrlicht.h>
#include <map>
 
//This is a scene node that will draw quads around registered scene nodes covering their screen space, also, you can retrieve the quads given a scene node via a irr::map
 
namespace irr
{
    namespace scene
    {
        typedef std::map<ISceneNode*, core::rectf> nodeRectMap;
 
        class CScreenQuadsSceneNode : public ISceneNode
        {
            //Nodes whose screenspace we're going to calculate
            nodeRectMap nodes;
 
            //Dummy box
            core::aabbox3df box;
 
            //Video driver
            video::IVideoDriver* driver;
 
            //Active camera
            scene::ICameraSceneNode* camera;
 
            //Current resolution
            core::dimension2du resolution;
 
        public:
            //CTOR
            CScreenQuadsSceneNode(ISceneNode* parent, ISceneManager* manager, s32 id);
 
            //DTOR
            ~CScreenQuadsSceneNode();
 
            //Register scene nodes
            void registerNode(ISceneNode* node);
 
            //Clears the node map
            void clearNodes();
 
            //Render method
            void render();
 
            //Get Bounding box. Nothing too fancy here
            const core::aabbox3d<f32>& getBoundingBox() const;
 
            //Get the quad belonging to a given scene node
            core::rectf getScreenSpace(ISceneNode* node);
 
            //Animates this node. Updates the screen quads positions but there is a frame delay... Is okay, produces a cool "tracking" like effect :)
            void OnAnimate(u32 ms);
 
            //Registers this node for rendering
            void OnRegisterSceneNode();
 
        private:
            //Renders the rectangles
            void renderQuad(core::rectf& rect);
 
            //Calculates all the visible quads
            void calculateScreenQuads();
 
            //Caluculates a quad
            core::rectf calculateQuad(ISceneNode* node, ICameraSceneNode* camera);
        };
    }
}
 
#endif
 
"CScreenSpaceQuadsNode.cpp"

Code: Select all

 
#include "CScreenSpaceQuadsNode.h"
 
namespace irr
{
    namespace scene
    {
        //CTOR
        CScreenQuadsSceneNode::CScreenQuadsSceneNode(ISceneNode* parent, ISceneManager* manager, s32 id)
            :ISceneNode(parent,manager,id)
        {
            driver = getSceneManager()->getVideoDriver();
            resolution = driver->getScreenSize();
            printf("Node Tracker Ready for operation at %d x %d\n",resolution.Width,resolution.Height);
            setAutomaticCulling(EAC_OFF);
        }
 
        //DTOR
        CScreenQuadsSceneNode::~CScreenQuadsSceneNode()
        {
            nodes.clear();
        }
 
        //Register scene nodes
        void CScreenQuadsSceneNode::registerNode(ISceneNode* node)
        {
            //Not that it wouldn't be possible to do it, but it is kinda pointless to do this to a potential screen sized render XD
            if (node == this)
                return;
            
            if (node == nullptr)
                return;
 
            nodes[node] = core::rectf();
        }
 
        //Clears the node map
        void CScreenQuadsSceneNode::clearNodes()
        {
            nodes.clear();
        }
 
        //Get the quad belonging to a given scene node. If not found, returns an empty rectangle.
        core::rectf CScreenQuadsSceneNode::getScreenSpace(ISceneNode* node)
        {
            if (nodes.find(node) != nodes.end())
                return nodes[node];
            else
                return core::rectf();
        }
 
        //Render method
        void CScreenQuadsSceneNode::render()
        {
            //nothing to do here :)
            if (camera == nullptr)
                return;
 
            //No quads? no work
            if (nodes.size() == 0)
                return;
 
            nodeRectMap::iterator it;
 
            for (it = nodes.begin(); it != nodes.end(); ++it)
            {
                renderQuad((*it).second);
            }
        }
 
        //Get Bounding box. Nothing too fancy here
        const core::aabbox3d<f32>& CScreenQuadsSceneNode::getBoundingBox() const
        {
            return box;
        }
 
        //Animates this node. Updates the screen quads positions but there is a frame delay... Is okay, produces a cool "tracking" like effect :)
        void CScreenQuadsSceneNode::OnAnimate(u32 ms)
        {
            //Is there any active camera? if not, it is very easy to know where to continue after this...
            camera = getSceneManager()->getActiveCamera();
 
            if (camera == nullptr)
            {
                ISceneNode::OnAnimate(ms);
                return;
            }
 
            //Update all the rectangles
 
            //Using the standard map which implements iterative access
            nodeRectMap::iterator it;
 
            for (it = nodes.begin(); it != nodes.end(); ++it)
                (*it).second = calculateQuad((*it).first, camera);
 
            ISceneNode::OnAnimate(ms);
        }
 
        //Renders a quad
        void CScreenQuadsSceneNode::renderQuad(core::rectf& rect)
        {
            core::recti r = core::recti(rect.UpperLeftCorner.X, rect.UpperLeftCorner.Y, rect.LowerRightCorner.X, rect.LowerRightCorner.Y);
 
            u8 red, green;
            red = pow(abs((rect.getArea() / resolution.getArea())),1.0f/6.0f) * 255; //gets redder the bigger the quad is on screen ;) hint for LOD? :P
            green = 255 - red;
 
            driver->draw2DRectangleOutline(r, video::SColor(255, red, green, 0));
        }
 
        //Calculates the bounding quad
        core::rectf CScreenQuadsSceneNode::calculateQuad(ISceneNode* node, ICameraSceneNode* camera)
        {
            const scene::SViewFrustum* fr = camera->getViewFrustum();
            core::aabbox3df box, viewBox; //Necesary to have a box in view space and then in projection space
            core::matrix4 view, proj;//Necesary to obtain the box in view space and then in projection space
            f32 near, far;
            f32 vec[4]; //Irr won't do the homogeinization step for us... in any form! O.o
 
            //Get the node's aabb.
            box = node->getTransformedBoundingBox();
            //If the node is invisible, it has no screen space
            if (!node->isVisible())
                return core::rectf();
            //if the node is completely outside the view frustum, it has no screen space
            bool outside = false;
            for (u32 i = 0; i < 6 && !outside; ++i)
                outside = box.classifyPlaneRelation(fr->planes[i]) == core::ISREL3D_FRONT;
            if (outside)
                return core::rectf();
 
            //At this point the box is either completely inside the view volume, or clipped somewhere, in any case, it is worth the attempt.
 
            core::array<core::vector3df> points;//Box corners
            points.reallocate(8);
            node->getTransformedBoundingBoxEdges(points);
 
            //Get the AABB in view space.
            view = camera->getViewMatrix();
 
            //Transform into view space
            view.transformVect(vec, points[0]);
            viewBox.reset(vec[0] / vec[3], vec[1] / vec[3], vec[2] / vec[3]);
 
            for (u32 i = 1; i < 8; ++i)
            {
                view.transformVect(vec, points[i]);
                viewBox.addInternalPoint(vec[0] / vec[3], vec[1] / vec[3], vec[2] / vec[3]);
            }
 
            //Viewbox is aligned to the Z near and far planes
            near = camera->getNearValue();
            far = camera->getFarValue();
 
            //Clamp to minimum view Z
            if (viewBox.MinEdge.Z < near)
                viewBox.MinEdge.Z = near;
 
            //Clamp to maximum view Z
            if (viewBox.MaxEdge.Z > far)
                viewBox.MaxEdge.Z = far;
 
            //Copy the points again so we can transform them into projection space
            viewBox.getEdges(points.pointer());
            
            proj = camera->getProjectionMatrix();
 
            //Transform into projection space
            proj.transformVect(vec, points[0]);
            box.reset(vec[0] / vec[3], vec[1] / vec[3], vec[2] / vec[3]);
 
            for (u32 i = 1; i < 8; ++i)
            {
                proj.transformVect(vec, points[i]);
                box.addInternalPoint(vec[0] / vec[3], vec[1] / vec[3], vec[2] / vec[3]);
            }
 
            //The box is in projection space, clamp it to -1,1
            if (box.MinEdge.X < -1)
                box.MinEdge.X = -1;
 
            if (box.MaxEdge.X > 1)
                box.MaxEdge.X = 1;
            
            //Clamp the Y coordinate...
            if (box.MinEdge.Y < -1)
                box.MinEdge.Y = -1;
 
            if (box.MaxEdge.Y > 1)
                box.MaxEdge.Y = 1;
 
 
            //Transform into 0-range and swap Y to be in screen coordinates
            box.MinEdge.X = box.MinEdge.X*0.5f + 0.5f;
            box.MaxEdge.X = box.MaxEdge.X*0.5f + 0.5f;
 
            box.MinEdge.Y = -box.MinEdge.Y*0.5f + 0.5f;
            box.MaxEdge.Y = -box.MaxEdge.Y*0.5f + 0.5f;
 
            //Return this box in screen coordinates
            return core::rectf(box.MinEdge.X*resolution.Width, box.MinEdge.Y*resolution.Height, box.MaxEdge.X*resolution.Width, box.MaxEdge.Y*resolution.Height);
        }
 
        //Registers this node for rendering
        void CScreenQuadsSceneNode::OnRegisterSceneNode()
        {
            //Register in the last pass of the render
            getSceneManager()->registerNodeForRendering(this, ESNRP_TRANSPARENT_EFFECT);
            ISceneNode::OnRegisterSceneNode();
        }
    }
}
This should provide the following effect:

Image

Which is a bit conservative in the side that it is always larger than the actual bounding box, but it is because it is the bounding box of the viewspace bounding volume. I hope you can test it for yourselves! and if you find it useful, go ahead and use it :)

Just in case you want to use it for lights... Irr won't calculate the lights bounding volume, but it can calculate the screen space of ANY SCENE NODE that has a bounding box, which is every of them, as the scene nodes have in their interface a "getBoundingBox" method :)

I've intentionally made the tint of the quad become redder the bigger it is on the screen ;) (uses the area to calculate it) So using the quad area / screen area proportion you may have a cabal idea of how large is a node compared to the screen. And it works both in GL and DirectX alike.

Re: New method proposal: ISceneNode::getScreenSpace()

Posted: Wed Dec 07, 2016 7:14 pm
by Virion
It seems like epic is still continue to use this method in UE4, as described in this page (the last sentence) http://docs.speedtree.com/doku.php?id=w ... els_in_ue4

Also, I managed to found the screenshot which shows the "screen size" settings in UE4 to confirm this:

Image Image

Re: New method proposal: ISceneNode::getScreenSpace()

Posted: Wed Dec 07, 2016 8:58 pm
by CuteAlien
@Mel: Thanks! edit: Have to test (thought first there was a bug, but probably not...).

Re: New method proposal: ISceneNode::getScreenSpace()

Posted: Thu Dec 08, 2016 12:27 am
by Mel
I haven't tested it thoroughly, and for instance if you remove a node, the program will crash, and I would like to have a tighter bound on the objects, to but the functionality is there, and bears with anything that can be englobed by a bounding box, which is... everything :)