Im3d is a small, self-contained library for immediate mode rendering of basic primitives

Im3d is a small, self-contained library for immediate mode rendering of basic primitives (points, lines, triangles), plus an immediate mode UI which provides 3d manipulation 'gizmos' and other tools. It is platform and graphics API agnostic and designed to be compatible with VR.

Im3d outputs vertex buffers for rendering by the application. Im3d does not affect the system graphics state directly, therefore Im3d calls can be made from anywhere in the application code. This is useful for graphics debugging, 3d data visualization, writing CAD & game development tools, etc.

Demo Screenshot 1 Demo Screenshot 2 Demo Screenshot 3

The API design follows OpenGL immediate mode in that it functions as a state machine:

Im3d::PushDrawState();
Im3d::SetSize(2.0f);
Im3d::BeginLineLoop();
	Im3d::Vertex(0.0f, 0.0f, 0.0f, Im3d::Color_Magenta);
	Im3d::Vertex(1.0f, 1.0f, 0.0f, Im3d::Color_Yellow);
	Im3d::Vertex(2.0f, 2.0f, 0.0f, Im3d::Color_Cyan);
Im3d::End();
Im3d::PopDrawState();

A key point to note is that there is no view-projection matrix here - the requirement for VR support precludes this. Instead, all vertices are specified in world space and the view-projection transform is applied at draw time (in the shader).

The UI system follows the immediate mode paradigm in that no UI state is retained; you can create gizmos from anywhere in the code:

static mat4 transform;
if (Im3d::Gizmo("UnifiedGizmo", &transform)) {
	// transform was modified, do something with the matrix
}

Translation Gizmo Rotation Gizmo Scale Gizmo

See here for more complete examples.

Integration

Im3d has no dependencies other than the C standard lib. A C++11 compatible compiler is required.

Integration is fairly straightforward:

  • Copy the files from the root of this repo and add them to the application project.
  • Modify im3d_config.h if necessary (provide a custom malloc/free, set the vertex data alignment, matrix layout, etc.).

At runtime, the application should then proceed as follows:

  • At startup, load the graphics resources (shaders, etc.) required to actually draw the Im3d vertex buffers.
  • Each frame, fill the Im3d::AppData struct, providing user input and other context data, then call Im3d::NewFrame().
  • Towards the end of the frame, call Im3d::Draw() once for each view/projection to be rendered. Im3d calls an application-defined callback to actually execute rendering commands and draw the points/lines/triangles pushed during the frame.

More detailed and API-specific integration examples are available in examples/.

Frequently Asked Questions (FAQ)

Where is the documentation?

  • im3d.h contains the main API documentation.
  • examples/common/main.cpp contains usage examples for most features, especially how to use the Gizmo*() API.
  • examples/ contain reference implementations with lots of comments.

Are geometry shaders required?

No, the application is free to render the vertex data in any conceivable manner. Geometry shaders are the easiest way to expand points/lines into triangle strips for rendering, but there are alternatives:

  • Use instanced rendering to draw 1 triangle strip per point/line primitive and do point/line expansion in the vertex shader, manually fetching the vertex data from a constant buffer. See examples/OpenGL31 for a reference implementation.
  • Rasterize points/lines directly. If the target graphics API doesn't support per-vertex point size/line width this won't draw as nicely but may be good enough.
  • Expand points/lines manually on the CPU and draw the converted vertex data as a single large triangle strip. This would obviate the need for shaders entirely, however the performance may be suboptimal.

How can I update to the latest version?

  • Check which version you currently have; IM3D_VERSION is defined at the top of im3d.h.
  • Examine the change log in the latest version of im3d.cpp to see if there have been any API-breaking changes which require modifications on the application side.
  • Overwrite everything except im3d_config.h if you have made any changes to your copy.

Is Im3d thread safe?

Im3d provides no thread safety mechanism per se, however per-thread contexts are fully supported and can be used to achieve thread safety.

Comments
  • Question:  Weird plane orientation

    Question: Weird plane orientation

    Hi, I setupped the gizmo in osg drawImplementation but got a problem, the orientation of planes are not correct and I didnt know why and what caused that, appreciating for any help. The wird orientation planes are showed like below:
    planes

  • im3d default Camera model does not suppot aspect ratio on height

    im3d default Camera model does not suppot aspect ratio on height

    Hi!

    The default Im3d camera model does not support "height" (aspect ratio) influence onto its projection matrix. If you change view width the pixel size (as a square) is kept, when height changes projection matrix "scales" the output.

    Here is the snippet for camera projection matrix that handles that:

    	float a = viewport_res_x / viewport_res_y;
    	fov_rad_ = Im3d::Radians(fov_deg_);
    
    	const float n = 0.1f;
    	const float f = 500.0f;
    	
    	float scale = std::tanf(fov_rad_ * 0.5f) * n;	
    	
    	float r = scale; //no aspect correction here
    	float l = -scale; //no aspect correction here
    	
    	float t = scale;
    	float b = -scale;
    
    	//-----
    	if (a > 1.0f)
    	{
    		b /= a;
    		t /= a;
    	}
    	else
    	{
    		l *= a;
    		r *= a;
    	}
    	//-----
    
    	const float viewZ = -1.0f;
    	
    	proj_ = Im3d::Mat4
    	(
    		2.0f * n / (r - l),
    		0.0f,
    		-viewZ * (r + l) / (r - l), //unnecessary for infinite cam (lim -> 0.0f)
    		0.0f,
    	
    		0.0f,
    		2.0f * n / (t - b),
    		-viewZ * (t + b) / (t - b), //unnecessary for infinite cam (lim -> 0.0f)
    		0.0f,
    	
    		0.0f,
    		0.0f,
    		viewZ,
    		-n,
    	
    		0.0f,
    		0.0f,
    		viewZ,
    		0.0f
    	); 
    
  • Depth testing for Triangle primitives

    Depth testing for Triangle primitives

    I use im3d to draw some 3d overlays over a 3d scene in an entirely separate render pass, after the scene.

    Sometimes, logically, the overlays are meant to traverse some entities in the scene, but visually, the overlays are always rendered on top.

    image

    In the example image above, the blue quad (drawn using DrawQuadFilled) is supposed to traverse the cube, but it's hard to see without moving the camera around to understand where the quad is located.

    I was wondering if anyone has experimented with depth testing for im3d, at least for the triangle primitives. I was able to get a ID3D11DepthStencilView* from my 3d scene, and provide it to my D3D11 im3d implementation, and bind it using OMSetRenderTargets, while binding a depth-stencil state with depth testing enabled.

    However, I could not get results that made sense for the im3d primitives. The overlays would have parts that randomly appeared and disappeared as the camera moved in closer or farther.

    I kind of expected point and line primitives to have issues, but even the blue quad in the screenshot above would have nonsensical results.

  • Direct access to draw data outside of callback.

    Direct access to draw data outside of callback.

    Hello, I've been enjoying this library immensely and recommended it to multiple people for use in their own projects a little while back. It is a really useful self-contained library. During my tweet, I also pointed out in a reply on how I would go about getting the project working with newer deferred APIs. I feel like I did a pretty lousy job at explaining myself and why I thought the limited lifespan of draw data presented issues, so I thought I would elaborate on my points here.

    The current examples suggest to use this process as a callback:

    Callback

    1. SetupViewportScissors
    2. Upload array to Vertex Buffer
    3. Set Shaders/Push Constants
    4. Draw

    This works well if Draw executes immediately but in a more modern API like Vulkan, it basically adds it to a TODO list. We would end up overwriting the vertex data needed by all of the earlier draw commands. You would have to manually execute all of the commands leading up to it which would incur a massive performance penalty.

    So you would probably want to make sure all of the vertex data lasts until the end of all render commands, to do this you could load all of the data into a global vertex buffer. So here would be your' callback would do: (this is what my program deals with Im3d draw data at the moment)

    Callback

    1. Setup Viewport/Scissors
    2. Reallocate Vertex Buffer to fit additional vertex data if necessary (Executes Immediately)
    3. Upload array to the END of the Vertex Buffer (Executes Immediately)
    4. Set Shaders/Push Constants
    5. Issue Draw Command with END as an offset into the buffer
    6. Offset the END of the Vertex buffer by the number of verts to draw.

    Now this works but is hardly efficient since it requires us to reallocate the vertex buffer if there is not enough room and since this is performed every time the callback is issued, having to resize the buffer is incredibly wasteful. Keep in mind you don't need to resize all of the time or even every frame if you offset what is the END of the vertex buffer back to the beginning and simply overwrite the last frame's data. But if one of your earlier vertex arrays demands more space that can have a ripple effect on the rest of them, resulting in a TON of unnecessary allocations and copies of the entire global vertex buffer.

    Here is where the lack of persistent/accessible draw data outside of the callback is an issue.

    This is a way we could cut down on all of those allocations and copies (how my application deals with ImGui draw data):

    Do Once

    1. Get a sum of the number of vertices in all of the vertex arrays in the draw lists.
    2. Recreate the global vertex buffer only if necessary.

    For Each Draw List

    1. Upload array to the END of the Vertex Buffer (Executes Immediately)
    2. Setup Viewport/Scissors
    3. Set Shaders/Push Constants
    4. Issue Draw Command with END as an offset into the buffer
    5. Offset the END of the Vertex buffer by the number of verts to draw.

    This should perform a whole lot better when the amount of vertices in the scene is changing since we would only need to do it once up front (and since we don't even care about the contents before the resize we wouldn't need to perform a copy of the data to the new larger buffer.)

    Currently, however, we can't do this since we don't know the contents or even the size of any of the vertex data outside of a one-time callback. By the time the callback is over, that data would be gone and before the render function is called that data probably isn't there.

    So I hope this scenario demonstrates the user's possible need to go around the callback function. Thank you for the amazing work!

  • wrong behavior when scale the model

    wrong behavior when scale the model

    i am testing this great tool, but it behaves strange when scale the model . as the image display

    gizmoscaleerror

    (mouse pos in the image(there is an offset) is not the real position due to the recording software) using the lastest codebase

  • memory leak

    memory leak

    I modified the code in the sample program (main.cpp).

    #include "im3d_example.h" int main(int, char**) { #ifdef _DEBUG _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif . . . A memory leak message was found in the result window.

    Detected memory leaks! Dumping objects -> {4183} normal block at 0x00753568, 167 bytes long. Data: 68 35 75 00 00 00 00 00 00 00 00 00 00 00 00 00 {4182} normal block at 0x0BCB4FA8, 727 bytes long. Data: < O >; > > A8 4F CB 0B 3E 3B 17 3E 00 00 00 00 00 00 00 00 {4181} normal block at 0x0BC82148, 367 bytes long. Data: <H! &= &= > 48 21 C8 0B 94 26 5C 3D 94 26 5C 3D 00 00 00 00 {1376} normal block at 0x0BCCB108, 1807 bytes long. Data: < > 08 B1 CC 0B 00 00 20 C1 00 00 00 00 00 00 20 C1 Object dump complete.

  • Add Linux build of example for OpenGL 3.3 context

    Add Linux build of example for OpenGL 3.3 context

    Hello. This implements a Linux build of example.

    It uses glfw to do it. I think it might be beneficial to port Windows build to it so that the code is the same for all platforms and a lot of OS-spefic code will be removed (I might do it in the next PR)

    GLFW implementation of Dear ImGui was mostly taken from here. If we port all platform code to GLFW then I'll be able to use this implementation (I'd like to do the same with DirectX and OpenGL code specific to rendering ImGui - it'll be good to use "official" implementations of DirectX and OpenGL rendering so that we don't need to write our own ImGui renderer and use the standard solution)

    There was also an issue that I can't setup context after creating glfw window, so I put OpenGL context creation in InitWindow.


    P.S. Maybe we should switch to git submodules for both Dear ImGui and glfw later to not have to keep their code in this repo to keep it lighter...

  • Question: How to draw multiple shapes in different locations?

    Question: How to draw multiple shapes in different locations?

    I'm confused about how to draw primitives in different locations. If I run the following code:

    Im3d::PushMatrix(/* a valid non-identity matrix here*/);
    Im3d::DrawCylinder(Im3d::Vec3(0,0,0), Im3d::Vec3(0,height,0), radius);
    Im3d::PopMatrix();
    
    Im3d::PushMatrix(/* a different valid non-identity matrix here*/);
    Im3d::DrawSphere(Im3d::Vec3(0,0,0), radius);
    Im3d::PopMatrix();
    

    The resulting primitive's vertices in the draw callback are always exactly the same, regardless of the matrix I pass in. What is the correct way to transform a primitive so that its vertices are transformed? I can of course pass a different matrix directly into the shader, but this translates all of the shapes I draw, and not each one individually.

  • Add IM3D_API macro for dll export

    Add IM3D_API macro for dll export

    Hi John, Im3d cannot be exported inside its own dll for better modularization, it would be nice if you could add some IM3D_API macro in front of your API functions and in front of public classes such as Context or AppData. Thanks!

  • Errors compiling with VS2012

    Errors compiling with VS2012

    Hi,I am having errors during VS2012 compilation: for example : error C2864: 'Im3d::Vector::m_size' : only static const integral data members can be initialized within a class ::m_capacity' : only static const integral data members can be initialized within a class ): error C2864: 'Im3d::Vector::m_data' : only static const integral data members can be initialized within a class and many others How to fix?

  • Deal with transformed world coordinates.

    Deal with transformed world coordinates.

    Currently, my scene has already been translated, scaled, and rotated and I want to place a gizmo in such a scene. For example, if my scene is a bounding box of size 2000, I already translate (1000, 1000, 1000) and scale (0.001, 0.001, 0.001) to the scene (get the model matrix), and view the scene from (0, 0, 5) to (0, 0, 0) (get the view matrix) with a perspective camera (get the 'proj' matrix).

    How do I fill the AppData correctly? I can already calculate the camera position and direction and cursor origin and direction in world space. How can I place a gizmo at the location of the original (1100, 1100, 1100) coordinate? Thanks!

  • C Bindings

    C Bindings

    Hello @john-chapman, thank you for creating and sharing this awesome library. One important feature that the library is missing are C-bindings. These would not only allow C programmers to benefit from this library, but make it easier to port to languages such as Rust or zig.

  • Ids don't get reset if some control is still active and no subsequent Im3d::Gizmo call

    Ids don't get reset if some control is still active and no subsequent Im3d::Gizmo call

    This results in gizmo controls become unresponsive.

    Somewhat general steps to reproduce:

    1. Have gizmo drawn for some ID
    2. Select some gizmo control with mouse so it become highlighted
    3. Somehow stop drawing gizmo for this ID with control still being active
    4. Draw gizmo again (mouse state doesn't matter), probably with different ID
    5. All controls become unresponsive

    Related issue in our project https://github.com/citizenfx/fivem/issues/928 fixed with workaround by resetting ids manually if ID gets changed.

  • Intersect(Ray, Capsule)

    Intersect(Ray, Capsule)

    hello, thanks for your work.

    I embeded your library within a native/android application (Im3d on PC Version / my application runs correcly) but on the android version, the axis selection does not work

    due to an issue with Intersect (see t0 & t1 )

    bool Context::gizmoAxisTranslation_Behavior(Id _id, const Vec3& _origin, const Vec3& _axis, float _snap, float _worldHeight, float _worldSize, Vec3* _out_)
    {
    ......
    		float t0, t1;  <===== here
    		bool intersects = Intersect(ray, axisCapsule, t0, t1);  <===== here
    		makeHot(_id, t0, intersects);  <===== here
    ......
    }
    
    bool Im3d::Intersect(const Ray& _ray, const Capsule& _capsule, float& t0_, float& t1_ <==== here) 
    {
    	//IM3D_ASSERT(false); // \todo implement
    	return Intersects(_ray, _capsule);
    }
    

    t0 & t1 are not initialized & not filled by Intersect but used by makeHot workarround : makeHot => /_depth < m_hotDepth &&/ or Intersect => /* t0_ = 0.0f; t1_ = 1.0f;*/

    bool Context::makeHot(Id _id, float _depth, bool _intersects)
    {
    	if (m_activeId == Id_Invalid &&	/*_depth < m_hotDepth &&*/ <==== here _intersects && !isKeyDown(Action_Select)) {
    		m_hotId = _id;
    		m_appHotId = m_appId;
    		m_hotDepth = _depth;
    		return true;
    	}
    	return false;
    }
    

    or

    bool Im3d::Intersect(const Ray& _ray, const Capsule& _capsule, float& t0_, float& t1_)
    {
    	//IM3D_ASSERT(false); // \todo implement
    	t0_ = 0.0f;
    	t1_ = 1.0f;
    	return Intersects(_ray, _capsule);
    }
    

    regards

  • VertexData Cache

    VertexData Cache

    • Reduce draw cost for high order primitives by caching vertex data (+ primitive type).
    • Each cache maps to an ID.
    • Expose this system to the user e.g. BeginCache(_id);, EndCache(), DrawCache(_id) (but with better names).
    • Cached vertex positions are transformed by the current draw state as they are copied into the final buffer.
    • Transform other properties of the cached data (size/color).
  • AppData stack/per-layer

    AppData stack/per-layer

    Use-case: multiple views of the same data are trivially supported, however gizmos rely on some projection information for sizing and to handle interactions. Applications can hack around this by swapping out the AppData prior to making a Gizmo* call, but this isn't ideal since the context derives some state from AppData during NewFrame.

    • AppData stack: could use a stack however there's potentially a lot of data to push/pop, plus some processing overhead.
    • AppData per-layer: much cleaner from the application point of view: SetAppData takes a layer ID and the context stores 1 AppData per layer (by default a copy of the default layer's data). PushLayerId is therefore still cheap to call.
An immediate-mode, renderer agnostic, lightweight debug drawing API for C++
An immediate-mode, renderer agnostic, lightweight debug drawing API for C++

Debug Draw An immediate-mode, renderer agnostic, lightweight debug drawing API for C++. License This software is in the public domain. Where that dedi

Dec 24, 2022
Legion Low Level Rendering Interface provides a graphics API agnostic rendering interface with minimal CPU overhead and low level access to verbose GPU operations.
Legion Low Level Rendering Interface provides a graphics API agnostic rendering interface with minimal CPU overhead and low level access to verbose GPU operations.

Legion-LLRI Legion-LLRI, or “Legion Low Level Rendering Interface” is a rendering API that aims to provide a graphics API agnostic approach to graphic

Dec 6, 2022
ORE (OpenGL Rendering Engine) is a rendering engine developed for my college minor project assessment.
ORE (OpenGL Rendering Engine) is a rendering engine developed for my college minor project assessment.

ORE (OPENGL RENDERING ENGINE) What is ORE? ORE(OpenGL Rendering Engine) is a rendering engine with great and easy to use UI that allows the user to lo

Sep 23, 2022
Horde3D is a small 3D rendering and animation engine. It is written in an effort to create an engine being as lightweight and conceptually clean as possible.

Horde3D Horde3D is a 3D rendering engine written in C++ with an effort being as lightweight and conceptually clean as possible. Horde3D requires a ful

Dec 31, 2022
Cross-platform, graphics API agnostic, "Bring Your Own Engine/Framework" style rendering library.
Cross-platform, graphics API agnostic,

bgfx - Cross-platform rendering library GitHub Discussions Discord Chat What is it? Cross-platform, graphics API agnostic, "Bring Your Own Engine/Fram

Jan 8, 2023
A modern cross-platform low-level graphics library and rendering framework
A modern cross-platform low-level graphics library and rendering framework

Diligent Engine A Modern Cross-Platform Low-Level 3D Graphics Library Diligent Engine is a lightweight cross-platform graphics API abstraction library

Dec 30, 2022
Single header C library for rendering truetype text to the screen
Single header C library for rendering truetype text to the screen

kc_truetypeassembler.h Single header C library for assembling textured quads for text rendering using a graphics API. It generates a vertices and text

Nov 12, 2022
SoL (for Speed of Light, or sun in Spanish) is a Physically-based rendering library written in modern C++

SoL (for Speed of Light, or sun in Spanish) is a small rendering library written in C++20. Its goal is to strike a good balance between performance and usability, and allow easy experimentation for rendering researchers.

May 19, 2022
NVRHI (NVIDIA Rendering Hardware Interface) is a library that implements a common abstraction layer over multiple graphics APIs

NVRHI Introduction NVRHI (NVIDIA Rendering Hardware Interface) is a library that implements a common abstraction layer over multiple graphics APIs (GA

Jan 3, 2023
rlottie is a platform independent standalone c++ library for rendering vector based animations and art in realtime
rlottie is a platform independent standalone c++ library for rendering vector based animations and art in realtime

rlottie rlottie is a platform independent standalone c++ library for rendering vector based animations and art in realtime. Lottie loads and renders a

Dec 30, 2022
A minimalist library with basic facilities for developing interactive real-time 3D applications, with a strong emphasis on simplicity and ease of use.
A minimalist library with basic facilities for developing interactive real-time 3D applications, with a strong emphasis on simplicity and ease of use.

SlimEngine A minimalist and platform-agnostic base project for interactive graphical applications (2D/3D) with a strong emphasis on simplicity, ease o

Oct 29, 2022
Dec 31, 2022
A C++/DirectX 11 implementation of "A Scalable and Production Ready Sky and Atmosphere Rendering Technique"
A C++/DirectX 11 implementation of

Atmosphere Renderer A C++/DirectX 11 implementation of "A Scalable and Production Ready Sky and Atmosphere Rendering Technique" Features interactive e

Nov 20, 2022
This repository accompanies Ray Tracing Gems II: Next Generation Rendering with DXR, Vulkan, and OptiX
This repository accompanies Ray Tracing Gems II: Next Generation Rendering with DXR, Vulkan, and OptiX

Apress Source Code This repository accompanies Ray Tracing Gems II: Next Generation Rendering with DXR, Vulkan, and OptiX by Adam Marrs, Peter Shirley

Dec 29, 2022
Vulkan Minimal Hybrid Rendering
Vulkan Minimal Hybrid Rendering

Vulkan Minimal Hybrid Rendering A minimal hybrid rendering sample using ray query Features Rasterization Raytraced shadow Environment Vulkan SDK 1.2.1

Aug 31, 2022
⚡High-performance rendering for python
⚡High-performance rendering for python

ZenGL ZenGL is a minimalist Python module providing exactly one way to render scenes with OpenGL. pip install zengl Documentation zengl on Github zen

Dec 17, 2022
Source Code for "Ray Tracing Gems: High-Quality and Real-Time Rendering with DXR and Other APIs" by Eric Haines and Tomas Akenine-Möller

Apress Source Code This repository accompanies Ray Tracing Gems: High-Quality and Real-Time Rendering with DXR and Other APIs by Eric Haines and Tomas

Dec 29, 2022
Source code for pbrt, the renderer described in the third edition of "Physically Based Rendering: From Theory To Implementation", by Matt Pharr, Wenzel Jakob, and Greg Humphreys.

pbrt, Version 3 This repository holds the source code to the version of pbrt that is described in the third edition of Physically Based Rendering: Fro

Jan 7, 2023