-
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.
-
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.
-
Thank you for the help!
-
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.
-
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
-
Thank you for the video link, it is an exact replica of the kind of 3D rendering I am doing.
-
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);
:
-
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.
-
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.
-
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
-
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.
-
I like this idea; it's always interesting to see another technique of giving the 'feel' of 3D without actual 3D stuff.