so i managed to create several renderers using different render mechanisms, that works like a charm, fast and i have even more control over the different render mechanisms.
now i am struggling with rendering the orbits of the planets.
there is something strange i dont understand:
why is there some kind of artefact ?:

( as one can see, the artefact only appears at the points that are exactly on 45 degrees, -45 and so on…)
i am using this code:
class OrbitRenderEngine(BaseRenderEngine):
def init(self, ctx: moderngl.Context, width: int, height: int):
“”"
Render engine for drawing orbit circles using instanced rendering
:param ctx: ModernGL rendering context
:param width: Render window width
:param height: Render window height
"""
# Initialize with specialized shaders for orbit rendering
super().__init__(
ctx=ctx,
width=width,
height=height,
vertex_shader_path='shaders/instanced_vertex_shader_orbit.glsl',
fragment_shader_path='shaders/fragment_shader_orbit.glsl',
texture_type='orbit'
)
# Number of segments in each circle
self.segments = 64
# Create circle template buffer (normalized unit circle)
template_vertices = np.zeros((self.segments + 1, 2), dtype=np.float32)
angles = np.linspace(0, 2.0 * np.pi, self.segments + 1)
template_vertices[:, 0] = np.cos(angles) # x coordinates
template_vertices[:, 1] = np.sin(angles) # y coordinates
# Create template VBO
self.template_vbo = self.ctx.buffer(template_vertices)
# Create orbit instance data buffer (initially empty)
self.max_orbits = 10000
self.orbit_data_vbo = self.ctx.buffer(reserve=self.max_orbits * 12) # 3 floats (x, y, radius) per orbit
# Create orbit color buffer (initially empty)
self.orbit_color_vbo = self.ctx.buffer(reserve=self.max_orbits * 16) # 4 floats (r, g, b, a) per orbit
# Create VAO with all buffers
self.vao = self.ctx.vertex_array(
self.shader_program,
[
# Regular per-vertex attributes
(self.template_vbo, '2f', 'unit_circle'),
# Per-instance attributes (/i indicates instanced)
(self.orbit_data_vbo, '3f/i', 'orbit_data'),
(self.orbit_color_vbo, '4f/i', 'orbit_color')
]
)
# Track actual orbit count
self.orbit_count = 0
# orbit colors
self.normalised_orbit_colors = {}
self.set_normalized_orbit_colors()
# line width
self.ctx.line_width = qt_config.orbit_line_width
def set_normalized_orbit_colors(self):
self.normalised_orbit_colors["planet"] = normalize_color_arguments(0, 191, 255, 50)
self.normalised_orbit_colors["moon"] = normalize_color_arguments(0, 170, 200, 50)
def update_orbit_data(self, json_data):
"""
Updates orbit data from JSON dictionary
:param json_data: Dictionary containing object data with positions and orbit radii
:return: Boolean indicating if there are orbits to render
"""
# Create array for orbit data
orbit_data = np.zeros((self.max_orbits, 3), dtype=np.float32)
orbit_colors = np.zeros((self.max_orbits, 4), dtype=np.float32)
# Process each object in the JSON
orbit_index = 0
for obj_id, obj in json_data.items():
if not isinstance(obj, dict) or orbit_index >= self.max_orbits:
continue
# Extract orbit center (x, y) and radius
if 'x' in obj and 'y' in obj and 'orbit_radius' in obj:
orbit_data[orbit_index, 0] = obj['x'] # center_x
orbit_data[orbit_index, 1] = obj['y'] # center_y
orbit_data[orbit_index, 2] = obj['orbit_radius'] # radius
# Determine color based on object type
if 'type' in obj and obj['type'] == "moon":
orbit_colors[orbit_index] = self.normalised_orbit_colors["moon"]
else:
orbit_colors[orbit_index] = self.normalised_orbit_colors["planet"]
orbit_index += 1
# Update actual orbit count
self.orbit_count = orbit_index
# Write only the used portion of the buffers
if self.orbit_count > 0:
self.orbit_data_vbo.write(orbit_data[:self.orbit_count])
self.orbit_color_vbo.write(orbit_colors[:self.orbit_count])
return self.orbit_count > 0
def update_orbit_data_from_qt(self, qt_objects):
"""
Updates orbit data directly from Qt objects
:param qt_objects: List of Qt objects with position and orbit radius attributes
:return: Boolean indicating if there are orbits to render
"""
# Create array for orbit data
orbit_data = np.zeros((self.max_orbits, 3), dtype=np.float32)
orbit_colors = np.zeros((self.max_orbits, 4), dtype=np.float32)
# Process each Qt object
orbit_index = 0
for obj in qt_objects:
if orbit_index >= self.max_orbits:
break
try:
# Extract position and orbit radius from Qt object
# Adjust the attribute access according to your Qt object structure
center_x = obj.orbit_object.x
center_y = obj.orbit_object.y
orbit_radius = obj.orbit_radius
# Store the data
orbit_data[orbit_index, 0] = center_x
orbit_data[orbit_index, 1] = center_y
orbit_data[orbit_index, 2] = orbit_radius
# Determine color based on object type
if hasattr(obj, 'type') and obj.type == "moon":
orbit_colors[orbit_index] = self.normalised_orbit_colors["moon"]
else:
orbit_colors[orbit_index] = self.normalised_orbit_colors["planet"]
orbit_index += 1
except AttributeError:
# Skip objects that don't have the required attributes
continue
# Update actual orbit count
self.orbit_count = orbit_index
# Write only the used portion of the buffers
if self.orbit_count > 0:
self.orbit_data_vbo.write(orbit_data[:self.orbit_count])
self.orbit_color_vbo.write(orbit_colors[:self.orbit_count])
return self.orbit_count > 0
def render(self, cam_pos: list):
"""
Render all orbit circles with a single instanced draw call
:param cam_pos: Camera position and zoom as [x, y, zoom]
"""
if self.orbit_count == 0:
return
# Pass uniforms to the shader
self.shader_program['cam_pos'] = cam_pos
self.shader_program["iResolution"] = (self.width, self.height)
# Render all orbits in one instanced draw call
self.ctx.line_width = qt_config.orbit_line_width
render_mode = moderngl.LINE_STRIP if qt_config.strip else moderngl.LINE_LOOP
self.vao.render(render_mode, instances=self.orbit_count)
with these shaders:
vertex_shader:
#version 330 core
// Inputs
layout(location = 0) in vec2 unit_circle; // Unit circle template points
layout(location = 1) in vec3 orbit_data; // x, y, radius for each orbit instance
layout(location = 2) in vec4 orbit_color; // Color for each orbit instance
// Uniforms
uniform vec3 cam_pos; // (x, y, zoom)
uniform vec2 iResolution; // Screen resolution
uniform float line_width; // Line width in world units
// Output to fragment shader
out vec4 frag_color;
void main() {
// Determine if this is an inner or outer vertex based on vertex ID
// Even vertex IDs are inner vertices, odd vertex IDs are outer vertices
float radius_modifier = (mod(float(gl_VertexID), 2.0) == 0.0) ? -0.5 : 0.5;
// Calculate the radius offset based on line_width
float radius_offset = radius_modifier * line_width;
// Apply the radius offset to the unit circle point
vec2 offset_point = unit_circle * (orbit_data.z + radius_offset);
// Calculate the final world position
vec2 position = orbit_data.xy + offset_point;
// Convert world position to screen space
vec2 screenPos = vec2(
(position.x - cam_pos.x) * cam_pos.z,
(position.y - cam_pos.y) * cam_pos.z
);
// Convert screen coordinates to clip space
vec2 clipPos = vec2(
2.0 * screenPos.x / iResolution.x - 1.0,
-(2.0 * screenPos.y / iResolution.y - 1.0) // Flip Y for OpenGL
);
// Set the vertex position
gl_Position = vec4(clipPos, 0.0, 1.0);
// Pass the color to the fragment shader
frag_color = orbit_color;
}
and the fragment_shader:
#version 330 core
// Input from geometry shader
in vec4 frag_color;
// Output
out vec4 fragColor;
void main() {
// Simply use the passed color for the orbit line
fragColor = frag_color;
}
as soon as self.ctx.line_width is set to more than 1, the artefacts appear. Can someone see the the problem? my guess is there is something wrong with the vertecies… but on the other hand, if so, then a circle with line width = 1 would also look wrong, isnt it?
i hope someone can point out the problem.
ALSO: some AI told me that this is a common problem using moderngl.LINES. I am not sure to believe that, there must be a proper way to use LINES to draw a circle! We should use TRIANGLE rendering some people propose, that will work for shure ( i have also a triangle renderer for the orbits for testing) , but it is also much slower. Soi would prefer using LINES or similar rendering method without using TRIANGLE rendering. Thanks for your attention!