Line versus Triangle drawing order

I have been creating a simple chart gui element using raylib and soon noticed that lines always draw behind everything else.

I am trying to fill the background of my gui element with a solid color and then draw my charts on top of it. However my background rectangle always ends up being drawn over on top of the lines of my chart. The only solution i could find is drawing all the lines in to a framebuffer and then drawing the frame buffer on top of the rectangle. This sounds like a bad resource hungry way to do it.

Is there a better way of getting lines to draw in front of polygons?

Comments

  • Also i have noticed that if you try to draw too many line sections in a single frame raylib crashes with an "ERROR: MAX_LINES_BATCH overflow"
  • Hi Berni,

    Yes, you're right, I've been trying to find the best approach to this issue since raylib first version, here it is some more details on that: https://github.com/raysan5/raylib/wiki/2D-vs-3D-development-with-raylib

    Right now, for upcoming raylib v1.7, I implemented DrawLineEx() that is a QUAD-based lines drawing; you can also use DrawRectangle() with with/height of 1px for straight lines. Those two solutions work but clearly I need to change that internal behaviour (LINES-TRIANGLES-QUADS buffers) to a single QUAD buffer system. Just designed that way for the OpenGL 1.1 style layer (rlgl.c).

    If you are just doing simple 2D graphics, you can also just recompile the library for OpenGL 1.1 (https://github.com/raysan5/raylib/wiki/FAQ) and let the driver manage that internally (it should work as expected).
  • MAX_LINES_BATCH controls the size of lines buffer, by default it's set to 8K lines (https://github.com/raysan5/raylib/blob/develop/src/rlgl.h#L109), you can increase that limit... are you drawing more than 8K lines on screen? are you drawing lines outside screen?
  • I had tried to use 1 pixel wide rectangles and that worked well but really gave it a hit in performance once you draw a lot of them.

    I had started digging in the source files of raylib and indeed found the define for the maximum number of lines. However since i am running it on a Raspberry Pi 3 this limit is only 1K lines. I quite quickly used that up by drawing complicated poly lines on graphs. With dense data on the graph a 500 pixel wide graph can have 500 line segments to draw the trace. Add two more such traces and you are at 1500 line segments. In the end i recompiled raylib with this limit tweaked, helped a lot, however the RPi GPU seams to have some additional limit to it as lines stop drawing if you try to draw more than 2000 lines(I set the limit to 8K), there is no error or anything, just all lines disappear. But it does make a lot of sense now why lines always draw behind triangles.

    The idea i was trying to implement is a live scrolling graph. New data gets drawn on the right of the graph while the whole graph constantly moves left. When running at 60 fps this looked really neat and fancy. Just that making the graph very large in pixel size brought me to these issues. Lines seam to be able to be drawn incredibly fast as a Poly Line tho, even when drawing thousands of them, there are no issues holding 60fps.

    I still have one idea to try by creating a framebuffer in the background and then drawing that to the screen, but instead of redrawing the whole graph i would just draw it shifted over by 1 pixel every time and draw in just the one new data point. I know its a rather processing hungry thing to draw at 60fps no matter how its implemented, i just thought it looked really fancy when i got a early test of it working.

    If you are curious i am using this to visualize data from a inertial module(gyroscope, accelerometer, compass) to play around with fusing the various sensors together in to a accurate angle tracking system. So far i was able to use raylib to draw the information to the screen with great responsiveness. So fast in fact that i can't see any delay at all between moving the inetral module and its motion represented on the screen.

    Sorry for torturing your library in to doing things it was never designed for, but it did some really cool things for me.
  • edited April 29
    Hi Berni! Sorry, limits for RPI are a bit lower, I tested it with the first RPI and decided to set a low limit for any RPI system.

    Yeah, GPU can set their own limits, usually related to maximum vertex data processed by shader in a single draw call pass (it happens the same in PC with big QUADs batches at around 20K QUADs). Solution to that is just dispatch a draw call and start filling the buffers again... unfortunately, that can not be controlled manually right now from main code, rlglDraw() should be called internally to do that. I just take note on that to be added to the library.

    About using a framebuffer (RenderTexture2D), that's a good solution, performance hit shouldn't be that big.

    Wow! Your project sounds very interesting! Actually, I'm an Electronic Engineer and I always love to know about that kind of projects... and much more if raylib is used for visualization! Please, keep me updated with your progress, some image would be great! :)

    You can torture raylib as much as you want, actually, that's the pourpose of the library, to be used in real projects of any kind! Porting it to RPI was done with that kind of projects in mind!

    By the way, are you using latest version of the library from develop branch? (https://github.com/raysan5/raylib/tree/develop)

  • I tried calling rlglDraw() and that worked nicely. Tho i still see that drawing a large amount of lines does make the RPi start to strugle to maintain 60fps (To be expected really) I haven't got around to doing the framebufer being drawn over by 1 pixel approach.

    I noticed that sometimes simply sidestepping raylib completely and calling a opengl function directly is a perfectly good was of doing things when you need to do something special. For example raylib does not provide any way ot adjusting line thickness, but i have taken care of that by calling glLineWidth(int). Tho it affects the entire draw call so the rlglDraw needs to be used multiple times to create multiple line thicknesses in one scene. Not saying raylib needs this built in, but a good example where sidestepping raylib for doing just one specific thing is actually a fairly elegant solution.

    I am using 1.7.0 that i pulled down using git clone.

    Here is a peek of what i been using it for:


    Along the way i found your raymath.h very useful for its quaternion math as those confusing mathematical thingys seamed to deal the best with the heap of angle calculations needed to process the senor data.
  • Awesome, thanks for sharing.
  • Wow! Truly amazing! :D

    Actually raylib is quite a thin layer over OpenGL to make things easier, you can use directly OpenGL if you require some extra functionality not exposed by raylib. It's true that line thick (for LINES drawing) is not exposed... I take note on that.

    On the other side, if using raylib v1.7, you can use:
    void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); 
    It's a new version of DrawLine() that uses QUADS to draw lines of any thick.

    raylib already includes an example to use only the rlgl module in standalone form, independently of raylib (https://github.com/raysan5/raylib/blob/master/examples/others/rlgl_standalone.c) but I'm also reviewing that module...

    In any case, congrats for your app, looks great! :)
  • Thanks, it probably wouldn't have contained 3d if it wasn't for raylib cause i would likely find it too much work for what its worth.

    I didn't need thick lines that badly, i was mostly using it to draw 3d vectors inside a sphere to visualize them better for debug but wanted the vectors to stand out from the wireframe sphere, so i made them thicker.

    Using raylib to rotate a 3D model actually did help in tracking down some tracking issues that occurred in weird angles near that dreaded Eulers gimbal lock. If its one thing i learned doing this is that angles are super annoying to work with, especially when you get to 3d space.

    I think there should be an example that also demonstrates sidestepping raylib by calling a thing or two in rlgl or opengl directly. I know its a bit spitting in your own bowl as its sort of saying "Here is what raylib does badly so just do it manualy" but i usually assume libraries like these would get upset and maybe crash in weird ways if i sneakily mess with what its doing while its running. It also encourages you to go dig inside the source code to see how raylib does it, so that you can copy paste out a snippet in to your own program, but modify it to do the thing you wanted. I found the underlying code pretty simple. Its the setting up OpenGL from scratch that's tedious and confusing, the actual drawing part is easy to understand without even reading its documentation.

    One thing i didn't get running on the RPi is lighting in 3d. I did do a quick swap of shaders for GL 1.1 but then i also found things regarding lighting changed and i seen somewhere that the default shader can have issues with the RPi video hardware so i left it there. Its not a big deal, just thought it makes models look nicer from what i was playing around with under Windows.

    Nothing all that special is done with raylib with this demo, but one thing that might deserve getting an example included with raylib is manipulating models with matrices.

    Matrix Transformation;
    //Move objects origin
    Transformation = MatrixIdentity();
    Transformation = MatrixMultiply(Transformation,MatrixTranslate(250, -500, -500));
    Transformation = MatrixMultiply(Transformation,MatrixRotateX(0.3));
    //Scale the model
    Transformation = MatrixMultiply(Transformation,MatrixScale(0.003, 0.003, 0.003));
    //Rotate to desired orientation
    Transformation = MatrixMultiply(Transformation,MatrixRotateZ(DegToRad(Roll)));
    Transformation = MatrixMultiply(Transformation,MatrixRotateX(DegToRad(Pitch)));
    Transformation = MatrixMultiply(Transformation,MatrixRotateY(DegToRad(Yaw)));

    Begin3dMode(*camera);

    rlglDrawMesh(ObjModel.mesh, ObjModel.material, Transformation);

    End3dMode();
    This is what i use to rotate my plane model along each of the axis separately. Before the actual rotation i also use them to scale it and move the origin of the model to the correct location as the origin is originally in the tail but for more sensible rotation i needed to rotate it around the midpoint of the wings, along the way i also tweak the rotation of the origin to make an angle of 0 correspond to the plane being perfectly horizontal.
  • I know, angles in 3d space are a lot of fun, best solution is using Quaternions for rotations, that way you avoid Euler gimbal lock and matrix rounding deviations.

    Most of the examples are intended for beginners, not advance users... but yes, some more advance examples could useful... just need time...

    About lighting, current lighting system included with raylib, didn't work well on RPI due to GPU shader compiling mechanism (https://github.com/raysan5/raylib/issues/167), it needed to be redesigned so after some thinking I decided to take lighting out from raylib and move it to an advance example (https://github.com/raysan5/raylib/blob/master/examples/others/standard_lighting.c)... but it also needs review on RPI...

    One of my students also implemented an advance lighting system that I would like to incorporate to raylib at some point: https://github.com/victorfisac/rPBR

    About matrix transformations, that's a good sample, just note that you can accumulate transformations in the Model structure:

    Model model = LoadModel("resources/plane.obj");
    model.material.texDiffuse = LoadTexture("resources/plane_diffuse.png");
    model.transform = MatrixTranslate(250, -500, -500);
    model.transform = MatrixMultiply(model.transform, MatrixRotateX(0.3));
    model.transform = MatrixMultiply(model.transform, MatrixScale(0.003, 0.003, 0.003));
    model.transform = MatrixMultiply(model.transform, QuaternionToMatrix(QuaternionFromEuler(roll, pitch, yaw));

    BeginDrawing();
    Begin3dMode(*camera);

    // NOTE: Internal model transform is combined with transform generated from funtion parameters
    DrawModel(model, VectorZero(), 1.0f, WHITE);

    End3dMode();
    EndDrawing();
    Seeing your example, I decided to add QuaternionFromEuler() and QuaternionToEuler() functions to raymath... not tested yet but... :)
  • edited May 4
    I kept using rlglDrawMesh() because the older versions of raylib would have the DrawModel() and DrawModelEx() functions overwrite the "model.transform" element with there own matrix. The version i was using on RPi turned out had a extra matrix multiply in there to combine it with the existing mode.transform but was commented out (presumably for compatibility). Not a big deal, but nice to see matrix transformations can now be done without resorting to rlgl.

    Not that lighting on the RPi is that important since there is proabobly not a whole lot of GPU horsepower to render large scenes anyway. Its just something i gave a shot since it was easy to get working on the PC while making the model look nicer. I do have to say that lighting system that Victor did looks rather impressive. I would guess most of the magic is in the shader scripts anyway.

    I already made in my own functions for going between Euler and Qaternion since all of my IMU calculations happened in euler angles, just the gyroscope had to be done with quaternions to get it to work properly, so i had to convert back and forth. Here is what mine looks like, it has been tested quite a bit, especially at the weird angles that give Euler trouble.

    Quaternion QuaternionFromEuler(double roll, double pitch, double yaw)
    {
    Quaternion q;
    double t0 = std::cos(DegToRad(yaw) * 0.5);
    double t1 = std::sin(DegToRad(yaw) * 0.5);
    double t2 = std::cos(DegToRad(roll) * 0.5);
    double t3 = std::sin(DegToRad(roll) * 0.5);
    double t4 = std::cos(DegToRad(pitch) * 0.5);
    double t5 = std::sin(DegToRad(pitch) * 0.5);

    q.w = t0 * t2 * t4 + t1 * t3 * t5;
    q.x = t0 * t3 * t4 - t1 * t2 * t5;
    q.y = t0 * t2 * t5 + t1 * t3 * t4;
    q.z = t1 * t2 * t4 - t0 * t3 * t5;
    return q;
    };

    void QuaternionToEuler(Quaternion q, float* roll, float* pitch, float* yaw)
    {
    double ysqr = q.y * q.y;

    // roll (x-axis rotation)
    double t0 = +2.0 * (q.w * q.x + q.y * q.z);
    double t1 = +1.0 - 2.0 * (q.x * q.x + ysqr);
    *roll = RadToDeg(std::atan2(t0, t1));

    // pitch (y-axis rotation)
    double t2 = +2.0 * (q.w * q.y - q.z * q.x);
    t2 = t2 > 1.0 ? 1.0 : t2;
    t2 = t2 < -1.0 ? -1.0 : t2;
    *pitch = RadToDeg(std::asin(t2));

    // yaw (z-axis rotation)
    double t3 = +2.0 * (q.w * q.z + q.x * q.y);
    double t4 = +1.0 - 2.0 * (ysqr + q.z * q.z);
    *yaw = RadToDeg(std::atan2(t3, t4));
    };
    Its mostly code i copy pasted from some website. These particular functions use Euler angles using standard aviation ordering (XYZ or RPY), this means gimbal lock occurs when pitch points straight up or straight down. This is usualy the way that makes the most sense since most people agree that if you have an arrow thats pointing forwards but you then start tilting it up and continue going until 180 degrees you then get an arrow that's flat but pointing backwards (Yaw was flipped, but pitch is zero again). Or for an aircraft that would be the equivalent of being at the top of a looping where its heading in reverse of what it used to and is rolled upside down.

    The euler part of my IMU math had to be specifically told to be careful when pitch approaches 90° in order to avoid glitches due to gimbal lock.
  • Hey Berni! Just used the same code you used, adapted to raymath: https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles

    :)
Sign In or Register to comment.