Instructions for how to do visualization with OpenGL (no GUI)
This tutorial was written for ldmx-sw v4.6.4 and contains features not available before that version
One of Geant4’s most reliable visualizers throughout the years, OpenGL (or OGL) is a fast, powerful, yet relatively low-resource rendering program. Despite these advantages, there are two major disadvantages. Firstly, out-of-the-box, it only supports command-line arguments for instructions. Perhaps in a future upgrade we can build Geant4 with Qt enabled so we can provide at least a basic GUI. Secondly, Geant4-specific documentation/tutorials are sparse and have mostly been taught in-person at Geant4 workshops etc. These make it a less attractive choice for visualization, but nevertheless, it’s what is currently working in ldmx-sw.
Basic visualization
g4-vis can be accessed with denv or with just. There is a required
argument (the detector.gdml file for the geometry you wish to
visualize) and an optional argument for a macro file, explained later.
As a concrete example (beginning from the base ldmx-sw directory):
denv g4-vis install/data/detectors/ldmx-det-v15-8gev/detector.gdml
or
just g4-vis install/data/detectors/ldmx-det-v15-8gev/detector.gdml
The detector.gdml file is our gateway to all of the detector’s
sub-components. Take a look in this file, and you’ll see that it loads
(most of) the other .gdml files in this directory one by one. This is
what they call a ‘module’ implementation in GDML. This works so long
as none of the module boundaries are intersecting i.e. the modules are
not physically touching each other. You can even place modules inside
other modules, so long as the parent module fully contains the child
module (again, no boundaries crossing). We’ll cover how to examine
individual components in a later section.
You may notice when you run g4-vis, you get dropped at a command
line interface (CLI). To actually see the geometry you just loaded,
you’ll have to open a viewer screen:
/vis/open OGLIX
Now we have a black screen! Hooray! Let’s populate it with our loaded geometry:
/vis/scene/create
/vis/scene/add/volume
/vis/sceneHandler/attach
The first command creates a new ‘scene’ for OGL. This is an internal instance of a geometry. Multiple scenes can be open at once and switched between, but this is frankly unnecessary functionality so we’ll ignore it from here on in.
The next line adds the ‘volume’(s) i.e. the components to be visualized. By providing no additional arguments, it loads everything into the current scene by default. Again, we’ll cover how to load individual components/modules later.
The final line simply connects your active scene to the viewer, using the ‘scene handler’ intermediary. If I were designing OpenGL, viewers and scenes would be the same thing, especially considering you can open multiple viewers with very little overhead, and we could eliminate two abstractions. Sadly that is not the case.
Navigating the geometry
Navigating the geometry using a CLI isn’t ideal, but it can be manageable, especially with a multi-screen setup. We only need a few commands for navigation; the craft is in knowing how to use them creatively to get the rendering you desire.
The visualization starts centered at the origin point (0,0,0), which in most geometries is the target, and with the viewing angle set to phi=0, theta=0. This is equivalent to standing at the downstream end of the beamline, near the beam dump, and looking backwards along the beam trajectory through the calorimeters. To view along the beamline from the upstream end, rotate your viewpoint 180 degrees:
/vis/viewer/set/viewpointThetaPhi 180 0
Normally, theta specifies the zenith angle and phi specifies the azimuth angle. However, since we use the Z axis as the beam direction in ldmx-sw, you can consider these definitions to be effectively swapped. You can also use a direction vector to specify your viewing angle. The equivalent to the previous command would be
/vis/viewer/set/viewpointVector 0 0 -1
A word of warning: OGL cannot render a viewpoint directly along the axis it considers to be ‘upward’. I never learned exactly why this is, but I guess it’s probably doing some math behind-the-scenes while rendering, which involves dividing by the deviation from the up direction, resulting in a division by zero. OGL by default considers the Y axis to be ‘up’, so these commands are undefined and will show nothing:
/vis/viewer/set/viewpointVector 0 1 0
/vis/viewer/set/viewpointVector 0 -1 0
/vis/viewer/set/viewpointThetaPhi 90 90
It’s also possible to change the ‘focal point’ of the rendering. This can be useful when inspecting components at different positions along the beam path. For example, in v15, to center your viewpoint at (roughly) the most upstream spot on the geometry, where the magnets begin, you can use
/vis/viewer/set/targetPoint 0 0 -1000 mm
OGL doesn’t do ‘perspective’. It doesn’t matter how close or far away a component is; its size relative to other components in the render will stay the same. However, it’s possible to zoom in closer on the focal point (or further away) by setting the zoom scaling factor. Try for example”
/vis/viewer/zoom 0.5
This will zoom out, making everything effectively twice as small. You can get the zoom back to normal with:
/vis/viewer/zoom 2
Occasionally, navigation will break, or not do quite what you want it to do, or you’ll end up lost and unsure which component of the geometry you’re even looking at. You can always return to the default view (focal point at (0,0,0), zoom 1, viewing angle (0,0)) with:
/vis/viewer/reset
When you’re done with your viewing, simply type ‘exit’ in the CLI to exit.
Loading components
There are two ways to specify which components are visualized. Either can be useful, depending on your needs.
The simpler but maybe less elegant way is to edit the .gdml files
directly, especially detector.gdml. If you delete or comment out one
of the components, it’s removed from the geometry. You can see how to
make GDML comment blocks in the files:
<!--This is an example GDML-style comment and will be ignored when
loading the geometry→
Another word of warning: removing components for visualization
purposes will break ldmx-sw in other ways. For example, if you remove
the hcal.gdml module from detector.gdml so that you can see the
other volumes more clearly, then try to run ldmx-sw, it won’t be able
to load an HCal into the geometry and it will crash.
You can also load individual volumes by specifying their names in the
OGL CLI (recommended). By default, a loaded volume will also display
all ‘nested’ volumes, i.e. volumes fully contained inside it (in
Geant4 geometry terms, we refer to the larger volume as the ‘parent’
and the nested volumes as the ‘daughters’). Each of the modules in
detector.gdml contains one big parent volume, which we often refer
to as the envelope (in case you needed more jargon in your life), so
by specifying this parent volume, you can load individual modules, or
combinations thereof.
With your OGL CLI open and detector.gdml loaded, try typing:
/vis/drawTree
This will display thousands of lines of code. Each line is the name of one of the volumes loaded into our geometry. As an example, the last several lines of the v15 geometry tree look like this:
"ldmx_magnet_PV":0 "coil_1_vol_PV":0 "coil_2_vol_PV":0
"shim_right_PV":0 "shim_left_PV":0
"corner_iron_block_top_left_PV":0
"corner_iron_block_top_right_PV":0
"corner_iron_block_bot_left_PV":0
"corner_iron_block_bot_right_PV":0 "center_iron_block_top_PV":0
"center_iron_block_bot_PV":0
Notice that the first line has less indentation than the next
lines. The indentations indicate mother-daughter relations. Thus, all
of the lines below ldmx_magnet_PV are daughters of ldmx_magnet_PV,
and by default will be loaded along with it. If you’re in the v15
geometry you can try this yourself:
/vis/open OGLIX
/vis/scene/create
/vis/sceneHandler/attach
/vis/scene/add/volume ldmx_magnet_PV
Notice that we attached the scene to the viewer before even loading
anything! This is fine; the viewer will refresh each time you add
something or make other significant changes. To save you lots of
scrolling, here are the names of the envelope for each module in
detector.gdml as of v15:
tagger_PV
TS_trgt_PV
trig_scint_tagger_PV
recoil_PV
em_calorimeters_PV
hadronic_calorimeter_PV
ldmx_magnet_PV
The ‘_PV’ is added to all of the names automatically by GDML, and
indicates that this is a physical volume. Don’t worry too much about
it - but you do have to include it. If you /vis/scene/add each of
these, you’ll load the entire geometry, just like we did by providing
no arguments to this command. You can also draw the geometry tree of
individual components, for example:
/vis/drawTree TS_trgt_PV
You may notice a : followed by a number for most volume names. This is the ‘copy number’. Often, when iteratively placing volumes, we’ll place the same volume with the same name multiple times. The copy numbers help them to remain logically distinct. If for some reason you’d like to examine a single copy of a volume placed multiple times, simply provide the copy number as a second argument when adding the volume:
/vis/scene/add/volume tspad3_lightpipe_volume_PV 40
This loads only one of the light pipes in TSPad3, the one with copy number 40.
Visualization macros
So far this tutorial has covered using OGL in the CLI. It’s also
possible to write a macro - a set of commands - which can be executed
sequentially. To execute all the commands in a macro file, simply use
(while at the g4-vis CLI):
/control/execute /path/to/macrofile.mac
It's also possible to execute the macro file immediately after loading
the geometry, by giving just the path to the macro as a second
argument:
just g4-vis /path/to/detector.gdml /path/to/macrofile.mac
A few example macro files have been included with this tutorial, to do things such as examining the TSPads in greater detail, rendering the ECal, viewing the entire geometry, and a special example which showcases some of the other tools available in the visualizer.
How to learn more
You can try your luck at googling for OpenGL tutorials. These are often quite old, but the functionality hasn’t evolved much in the past 10 years. There are also books and stuff but I’ve never actually seen one. I don’t know what to tell you for GDML. There’s a manual, but it’s missing a lot of details.
There are two essential commands which can help you learn more about
OpenGL: ls and help. First, let’s re-examine a typical OGL
command:
/vis/viewer/set/targetPoint
You can think of /vis/, /viewer/, and /set/ as directories. This
is just a way of organizing the hundreds of OGL commands
logically. Like a typical directory, you can use ls to see what’s
inside of it:
ls /vis/viewer/set
ls /vis
etc
However, the last part, targetPoint, is not a file. It is still a
command, and almost all commands accept arguments. To learn more about
the functionality and arguments of the command, use help.
help /vis/viewer/set/targetPoint
etc
Technical details that don’t really fit in the rest of the tutorial
but should be written somewhere
A recent PR has ‘fixed’ OGL in ldmx-sw by moving the XSD style sheets
- used by GDML for XML-style formatting - from online to offline
(i.e. local) locations. It seems that for whatever reason, internet
functionality is limited in the current version of ldmx-sw. I wouldn’t
even know where to begin fixing this, so we accrue a bit more tech
debt and work around it. For now, I’ve taken advantage of an existing
piece of the CMakeLists.txt for detectors, which gives absolute paths
to all of the GDML files when compiling ldmx-sw, and used it for the
XSD files as well. This has one major disadvantage in that it requires
the same set of XSD files to exist as copies in each geometry version
directory. So there will be a /path/to/v14/*.xsd and a
/path/to/v15/*.xsd and so on. This can be solved in a future small
PR, in which the .xsd files are in a separate directory and are only
copied once for all geometry versions.
Glossary
Copy - One instance of one volume which has been placed multiple times. For example, a TSPad has 48 identical scintillator bars, so there are 48 copies of the volume for a scintillator bar.
Daughter - A volume entirely surrounded by a larger volume, its ‘parent’.
Envelope - The parent volume of an entire module, containing all its components.
Focal point/focus - The central point of the OpenGL visualization. Defaults to the origin.
GDML - Geometry Description Markup Language, a variant of XML used to describe geometries. Like XML, it’s basically an enormous set of nested parameters, which are logically read and assigned to Geant4 objects during runtime.
Geometry - A general term for all of the volumes in a simulation. Sometimes interchangeable with the term ‘world’, when talking about one specific geometry.
Module - A self-contained collection of volumes. Has one ‘envelope’, a giant parent volume, populated with many daughters. Load several of these to make a complete geometry.
OGL - OpenGL (Open Graphics Library) is a cross-language, multi-platform application programming interface (API) for rendering 2D and 3D vector graphics. I copied this directly from a webpage.
Parent - A large volume which completely encompasses one or more smaller volumes, the daughters.
Scene - an internal instance of a geometry in OpenGL. A scene exists without being displayed until it’s attached to a viewer.
Scene Handler - the intermediary in OpenGL which communicates between the scene and the viewer.
Viewer - a graphical window in OpenGL, used to display the rendered geometry.
Volume - a single object in a geometry, such as one SiPM, one HCal quad bar etc. Interchangeable with ‘component’.
XML - eXtensible markup language. This is a generalized data storage language, with tools to categorize and search for/retrieve data quickly and unambiguously. It’s not very friendly for human readability, and is typically generated and read automatically.
XSD - XML Schema Definition. This is XML’s way of defining a set of rules for parsing and interpreting parameters. GDML is basically just XML with special XSD files designed for geometry reading/writing.
Documentation added by CJ Barton, Lund University, March 2026