Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Object Rendering by sf::VertexArray (and assorted questions)  (Read 1954 times)

0 Members and 1 Guest are viewing this topic.

Prometheus

  • Newbie
  • *
  • Posts: 5
    • View Profile
Object Rendering by sf::VertexArray (and assorted questions)
« on: March 04, 2014, 09:23:18 am »
Hey, guys.

I've been working on a side project of mine for a while now, and I've really taken to SFML -- it's lightweight and convenient to use. The project is, at its most fundamental level, a simple 2D top-down game based in space. It's fairly standard fare: the player controls a ship and flies around completing objectives.

I've got a few questions on methodology, however, that I would appreciate feedback on from the SFML community.

  • Object Rendering
    There's a bit of history to this, so bear with me. The game's background consists of several objects: stars, nebulas, and the like -- all procedurally generated. Objects are assigned parallax depths and are moved correspondingly slowly compared to objects nearer the top layer. Each area of the game is of fixed size and loops around itself.

    The stars were the first background element that I implemented, and so they have gone through several iterations:

      Version History:
      • Version 1:
        This was nothing more than a proof-of-concept naive implementation. Stars were objects that contained a position (sf::Vector2f) and its own sf::Circleshape. The star vector was looped through twice: once for moving each star, and again for drawing. It worked, but its performance was understandably horrid.
      • Version 1.5:
        This version removed all the sf::Circleshape copies floating around. There were only 20-something star variations, so I constructed that many const sf::Circleshape objects and stored pointers to them in the stars. No change to performance, but memory usage was significantly improved.
      • Version 2:
        This version included an implementation of a quadtree. The star vector was looped through once in its entirety to check whether they were on screen and, if so, added to the quadtree. Once done, all stars in the quadtree were individually drawn. This version saw performance gains over the naive approach.
      • Version 3 (current):
        This version scrapped the sf::Circleshape/quadtree implementation (and, indeed, the whole star object) in favor of an sf::VertexArray implementation. Every "star" contains between 5 and 11 points, depending on its size. Instead of looping through an array of objects to determine which are on screen and drawing only those, all the stars are drawn regardless of position. This implementation is faster than previous versions on some machines, and is about equal on others.

      I am looking for suggestions regarding two key points in Version 3:
      • While the rendering of these star objects is faster in the latest iteration due to the GPU-friendly sf::VertexArrays, I am finding it difficult to efficiently implement the background looping. Previously, I would move each star as necessary, check whether their new position exists outside the fixed game world (and loop them around if so), then add them to the quadtree for eventual rendering. Now, however, there are significantly more vertices than there were sf::Circleshapes, so it is unreasonable to loop through each to check their position (and since stars come in different sizes, I cannot conveniently use pointer arithmetic to check the center vertices).
      • Similarly, it doesn't appear feasible to perform bounds-checking against the screen for all of the vertices (if I were to implement another quadtree, for instance).

      Do you guys have any ideas as to how I may either further optimize the current iteration or efficiently reimplement the background looping/wrapping? Code at bottom.
  • More Rendering
    At one point very early on, I rendered all of my stars to an sf::RenderTexture. I have since switched to rendering to an sf::RenderWindow instead, but I have noticed one distinct visual difference: with the sf::RenderTexture, stars looked consistent and maintained their forms, whereas with the sf::RenderWindow their internal geometry appears to reconfigure every cycle they are moved. That is, their constituent elements appear to jump around, as opposed to moving synchronously with one another.

    This reorganization is very noticeable with larger star sizes (radius 2.f to 2.5f), and manifests as flickering for smaller star sizes. This occurs for both the sf::Circleshape and sf::VertexArray implementations, and appears to be unaffected by the number of constituent points for a given star. Do you guys have any ideas as to why this is occurring, as well as suggestions for avoiding this behavior?

Code (suggestions and criticism welcome):

How stars are created:

void Starfield::CreateStar(const StarParameters& star_parameters) {
    ColorName color = FilterColor(star_parameters.color_value);
   
    unsigned int num_points = 0;
    sf::Vector2f center_position(star_parameters.position);
    center_position *= kLevelFactors[level_]; // parallax
    center_position += screen_size_ / 2.f;
   
    // determine the number of points by star size
    if (star_size_ == kSmall)
        num_points = 6U;
    else if (star_size_ == kMedium)
        num_points = 8U;
    else
        num_points = 11U;
   
    // center point for circle (combintion of triangles)
    sf::Vertex center(center_position, kStarColors[color]);
    // points along circumference
    sf::VertexArray outliers(sf::Points, num_points - 1);
   
    // figure out where the points lie along the circumference
    for (int index = 0; index < num_points - 1; ++index) {
        float rotation = to_Radians(to_SFMLDegrees((index + 1) * 360.f / (num_points - 1)));
        sf::Vector2f position(center_position.x + star_parameters.radius * cos(rotation),
                              center_position.y + star_parameters.radius * sin(rotation));
        outliers[index] = sf::Vertex(position, kStarColors[color]);
    }
   
    // append vertices in groups of three (triangles)
    for (int index = 0; index < num_points - 1; ++index) {
        stars_.append(center);
        stars_.append(outliers[index]);
        if (index == num_points - 2)
            stars_.append(outliers[1]);
        else
            stars_.append(outliers[index + 1]);
    }
   
    return;
}

How stars were moved prior to Version 3:

void Starfield::MoveStars(const sf::Vector2f& screen_offset) {
    quadtree_->Clear();
    // parallax
    sf::Vector2f scaled_offset = screen_offset * kLevelFactors[level_];

    // world bounds (really should use an sf::FloatRect here)
    float left_bound = half_screen_size_.x - half_field_size_;
    float right_bound = half_screen_size_.x + half_field_size_;
    float top_bound = half_screen_size_.y - half_field_size_;
    float bottom_bound = half_screen_size_.y + half_field_size_;
   
    for (Star* star : stars_) {
        // move star
        star->MoveStar(scaled_offset);
       
        sf::Vector2f loop_offset(0.f, 0.f);
       
        // check if star left world bounds
        if (star->position().x < left_bound)
            loop_offset.x += field_size_;
        else if (star->position().x > right_bound)
            loop_offset.x -= field_size_;
           
        if (star->position().y > bottom_bound)
            loop_offset.y -= field_size_;
        else if (star->position().y < top_bound)
            loop_offset.y += field_size_;
       
        // loop the star if needed
        if (loop_offset.x || loop_offset.y)
            star->MoveStar(loop_offset);

        // if the star is on screen, insert it in the quadtree for later rendering
        // (screen bounds check performed by Insert function)
        quadtree_->Insert(star);
    }
   
    return;
}

How stars are moved and drawn now:

void Starfield::MoveStars(const sf::Vector2f& screen_offset) {
    // no screen or world bounds check
    offset_.translate(screen_offset * kLevelFactors[level_]);

    return;
}

void Starfield::draw(sf::RenderTarget& target, sf::RenderStates states) const {
    states.transform = offset_;
    target.draw(stars_, states);
   
    return;
}



A final addendum: I'm aware of the existence of sf::View, and that it can be used for both scrolling the game world as well as looping the background. I'm not quite sure how it works -- I hadn't really considered it until just recently -- but I suppose I'm not opposed to its use. I'll give it a shot if there's general consensus that it would be a valuable addition. That said, I'm still looking for ideas on how to better utilize sf::VertexArrays and the like.

Thanks for taking the time to read through glance at my perhaps excessively long post.

Rhimlock

  • Jr. Member
  • **
  • Posts: 73
    • View Profile
Re: Object Rendering by sf::VertexArray (and assorted questions)
« Reply #1 on: March 04, 2014, 12:18:02 pm »
If you want to learn more about Views, you should look in the Tutorials:
http://www.sfml-dev.org/tutorials/2.1/graphics-view.php

But since you use a single VertexArray and use a Translate to move the Stars, I'm not really sure if you would gain any advantage in using a View.

Prometheus

  • Newbie
  • *
  • Posts: 5
    • View Profile
Re: Object Rendering by sf::VertexArray (and assorted questions)
« Reply #2 on: March 06, 2014, 04:47:09 am »
But since you use a single VertexArray and use a Translate to move the Stars, I'm not really sure if you would gain any advantage in using a View.

Thanks for the comment. I was thinking that views may be a convenient method of wrapping the background/world around itself, but that's really just an addendum to the main topic at hand.

I'm looking forward to hearing from more of the community.

Prometheus

  • Newbie
  • *
  • Posts: 5
    • View Profile
Re: Object Rendering by sf::VertexArray (and assorted questions)
« Reply #3 on: March 10, 2014, 09:02:48 pm »
Perhaps bumping this thread is a bit premature, but I am still looking for feedback from the SFML community. (The thread was about to fall off the first page.)

Thanks.