SFML community forums

Help => Graphics => Topic started by: FleshyOverlord on April 20, 2019, 06:39:41 am

Title: Creating simple 3D objects SFML
Post by: FleshyOverlord on April 20, 2019, 06:39:41 am
For anyone who wants to create simple, rough, and fast 3D models I found a method from Scratch (thanks to kevin11) that works perfectly in SFML. If you separate a 3D object into 2D layers you can stack these layers to create a 3D effects and you can even rotate the objects along the y-axis. This can and will, however, be a bottleneck in your program. If there are any ideas on how to optimize this process they would be very helpful.
Title: Re: Creating simple 3D objects SFML
Post by: fallahn on April 20, 2019, 09:55:41 am
Hey this looks like a nice idea for a unique style! If by bottleneck you mean drawing so many extra sprites you could improve performance by drawing all the parts with a single vertex array or vertex buffer.
Title: Re: Creating simple 3D objects SFML
Post by: FleshyOverlord on April 20, 2019, 08:37:11 pm
Thank you for the help!
Title: Re: Creating simple 3D objects SFML
Post by: fallahn on April 20, 2019, 10:49:00 pm
I believe that, if you're using sf::Quads, a vertex array will draw them in the order in which they were added. I don't know if this is official OpenGL spec or just how it happens to work though (ie there might be some corner cases where it doesn't work). You'll also have to put all your images into a single texture.
Title: Re: Creating simple 3D objects SFML
Post by: FRex on April 21, 2019, 12:10:41 am
Not sure if what is presented here (e.g. at 1:50) is the same thing or not, it looks similar: https://www.youtube.com/watch?v=kjFlIxGzABY
Title: Re: Creating simple 3D objects SFML
Post by: FleshyOverlord on April 21, 2019, 02:01:07 am
Thank you for the video link, it is an exact replica of the kind of 3D rendering I am doing.
Title: Re: Creating simple 3D objects SFML
Post by: FleshyOverlord on April 21, 2019, 03:13:41 am
Is it possible to have multiple sf::Transforms  (rotations and scales) for one renderstate? Each transform would have to correspond with one quad in a vertex array and each vertex quad would have a unique center point based on its texture. I am asking this because I need to rotate, scale, (and possibly translate though that is not as important) each vertex quad in a VertexArray around its own center point instead of around one center point.


In the code below I was able to resize my robot (the red figure in the image below) but not rotate the 22 layers that make up the robot around their own center point. Instead, they were rotated around the head of the robot as you can see below.
        sf::Texture* tex = new sf::Texture;
        tex->loadFromFile("spritesheet.png");
        sf::RenderStates rendState;

//tile Width and Height
//all the tiles are the same size
        float tileWH = tex->getSize().y;
        rendState.texture = tex;
                for (int j = 0; j < 22; j++) {
                        int i = (22 - j);
                        float yVal = (350.0f + (i*0.8f));
                        float left = xVal;
                        float top = yVal;

                        sf::Vertex * quad = &verticies[j * 4];

                        quad[0].position = sf::Vector2f(left, top + tileWH);
                        quad[1].position = sf::Vector2f(left, top);
                        quad[2].position = sf::Vector2f(left + tileWH, top);
                        quad[3].position = sf::Vector2f(left + tileWH, top + tileWH);
                       
                        quad[0].texCoords = sf::Vector2f(tileWH*i, tileWH);
                        quad[1].texCoords = sf::Vector2f(tileWH*i, 0.0f);
                        quad[2].texCoords = sf::Vector2f(tileWH*i + tileWH, 0.0f);
                        quad[3].texCoords = sf::Vector2f(tileWH*i + tileWH, tileWH);
                        sf::Transform transform;
                        sf::Transform secondTransfrom;
                        sf::Vector2f center = sf::Vector2f((tileWH / (2.0f)) + left, (tileWH / (2.0f)) + top);
                       
//rotation is only around the head of the robot instead of each image layer
                        secondTransfrom.rotate(angle, center);
//scaling is working
                        transform.scale(sf::Vector2f(5.0f, 5.0f), center);
                        rendState.transform *= secondTransfrom;
                        rendState.transform *= transform;
                        */

                }
                window->draw(verticies, rendState);

 
:
Title: Re: Creating simple 3D objects SFML
Post by: fallahn on April 21, 2019, 10:09:09 am
Technically no, although you can pre-transform vertices when you build your vertex array. In theory it could be done a bit like this (untested code):


struct BodyPart final : public sf::Transformable
{
    std::array<sf::Vector2f, 4u>  quadPoints = {}; //set these to the default positions of a quad used for a body part
};

enum PartID
{
    Legs, Body, Head, Count //start with legs because presumably you want to draw from the bottom up
};

class Robot final : public sf::Drawable, public sf::Transformable
{
public:
    Robot()
    {
        //for each body part set the initial quad point positions
        //eg {-10.f, -10.f} , {10.f, -10.f}, {10.f, 10.f}, {-10.f, 10.f} for a 20 unit quad that rotates around the centre
    }

    void update()
    {
        //update the transform for each body part
        m_bodyParts[PartID::Head].setRotation(20.f);
        m_bodyParts[PartID::Body].setScale(1.2f, 1.2f);
        //...etc

        //build the vertex array by creating a transformed quad for each part
        m_vertices.clear();
        for(const auto& part : m_bodyParts)
        {
            auto quadPoints = part.quadPoints; //make sure this is a copy so as not to modify the orignal part
            const auto& transform = part.getTransform();
            for(auto& point : quadPoints)
            {
                point = transform.transformPoint(point); //the point is now rotated/scaled by the bodypart transform
            }

            for(auto i = 0; i < SliceCount; ++i) //slice count is number of quads in this body part
            {
                //create a new slice by using the quadPoints array as positions
                //and adding any texture coordinates. Emplace the vertex into m_vertices
                //don't forget to decrement the Y value of the position by the thickness of a slice.
                for(auto& point : quadPoints)
                {
                    m_vertices.emplace_back(point, someTexCoord);
                    point.y -= sliceThickness;
                }
            }
    }
private:
    std::array<BodyPart, PartID::Count> m_bodyParts;
    std::vector<sf::Vertex> m_vertices;

    void draw(sf::RenderTarget& rt, sf::RenderStates) const override
    {
        states.transform *= getTransform(); //this now controls the world transform of the entire robot
        rt.draw(m_vertices.data(), m_vertices.size(), sf::Quads, states);
    }
};

 

To summarise:
Keep the positions for each part separate along with their own transform
When rebuilding the vertex array apply the transform of that part directly to *a copy* of the quad base position
Draw the vertex array using a transform which controls the position/rotation/scale of the robot in world coordinates.
Title: Re: Creating simple 3D objects SFML
Post by: FleshyOverlord on April 21, 2019, 05:29:29 pm
Thank you so much for the in-depth response fallahn! I found all I needed to do was apply the sf::Transform to each point of the quad using sf::Transform::transformPoint which I was unsure of how to use until now. To draw 100 of the models only requires about 30% of my GPU (GTX1060 on a lenovo laptop) and requires minimal CPU usage(10% max) on a core i7(laptop);


sf::Transform transform;
sf::Vector2f center = sf::Vector2f((tileWH / (2.0f)) + left, (tileWH / (2.0f)) + top);

transform.scale(sf::Vector2f(5.0f, 5.0f), center);
transform.rotate(angle, center);

quad[0].position = transform.transformPoint(sf::Vector2f(left, top + tileWH));
quad[1].position = transform.transformPoint(sf::Vector2f(left, top));
quad[2].position = transform.transformPoint(sf::Vector2f(left + tileWH, top));
quad[3].position = transform.transformPoint(sf::Vector2f(left + tileWH, top + tileWH));
                       
 

A working image of the code.
Title: Re: Creating simple 3D objects SFML
Post by: fallahn on April 21, 2019, 09:52:41 pm
You're welcome, it's looking good! It sounds like an interesting technique, I'm sure I shall be experimenting with it myself sometime :D
Title: Re: Creating simple 3D objects SFML
Post by: FleshyOverlord on April 23, 2019, 12:55:35 am
I am just spitting ideas out now but to rotate objects along their pitch axis you could stretch all the quads that make up each vertex array and change the order of rendering for each layer when you exceed 180 degrees.
Title: Re: Creating simple 3D objects SFML
Post by: Hapax on April 28, 2019, 09:19:02 pm
I like this idea; it's always interesting to see another technique of giving the 'feel' of 3D without actual 3D stuff.