diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index ba838f3a..d26e86ca 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -31,6 +31,7 @@
- [Electrons on Target (EoT)](physics/stats/electrons-on-target.md)
- [ECal](physics/ecal/intro.md)
- [Layer Weights](physics/ecal/layer-weights.md)
+- [Visualization](physics/visualization.md)
# Developing
- [Getting Started](developing/getting-started.md)
diff --git a/src/physics/visualization.md b/src/physics/visualization.md
new file mode 100644
index 00000000..8a5b08f3
--- /dev/null
+++ b/src/physics/visualization.md
@@ -0,0 +1,416 @@
+## Instructions for how to do visualization with OpenGL (no GUI)
+
+__ __
+
+~~~admonish warning title="Compatibility"
+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:
+
+```
+