You have correctly understood your problem. Your clever trick for 2D collision detection is (usually) impractical in 3D precisely because a sufficiently precise voxel array is generally too much data to be keeping in memory or updating per-frame.
(One exception is explicitly voxel-based games, where you're paying that cost anyway, but I'll assume you're not doing that.)
Here's how 3D collision detection is typically implemented. I'll start with the simplest thing that can work and then refine it.
You have a collection — a data structure — holding all of the ‘physical’ objects in your game. Each frame, for every object in this collection, go through every other object in this collection, and call a routine which knows how to check these two objects for collisions. (For example, a slow bullet might be treated as a point — no volume of its own — and simply test whether that point is within the other object.)
This works, but since it iterates over all pairs of objects, it takes O(n²) time.
The first thing to do is to avoid wasting time on checking distant objects. To do this, you need some sort of spatial indexing in your collection of objects. One such data structure is an octree, which recursively partitions space into segments which eventually contain only a few objects. I won't include the details here, but in order to do any sort of spatial indexing, you will generally need to have routines which compute the axis-aligned bounding boxes of your objects — this is just a rough approximation of the space your object takes up. Once you have a spatial index structure, you can check only pairs of objects which are known to be in the same vicinity.
This algorithm is going to be (sloppily considering) something like O(nm² log n), where n is the total number of objects and m is the maximum number of objects all in one pile in one spot.
The second thing to do is avoid wasteful collision tests. (This is mainly applicable if you have lots of stationary objects that can never collide, but which are not treated as a special unique “this is the level geometry” object.) Instead of having a single collection of all physical objects, have (at least) two: one which stores all the moving objects, and one which stores all the stationary objects. Do the same pair-checking (using the spatially indexed collection) for the moving objects, and then check the moving objects against the stationary objects. In this way, you do not waste time checking adjacent stationary objects against each other.
Sound like a lot of work? Sure. The good news is, you don't need to write all that, unless you have a very special use case. Just use a physics library (or “engine”) in your code which will provide all of this for you. (Physics libraries also provide response to collision — bouncing off, falling down, etc. — but you can usually use the collision component without the dynamics component, or vice versa, if it is not suited for your game.)