Part 1: Process explanation
We prepare information for each object / instance to be rendered: position + extents i.e. how far from its point position, the object extends in X,Y,Z - also known as an axis-aligned bounding box.
We pass that information to the vertex shader. The vertex shader tests each position + extents against the view frustum, geometrically, to see if objects are outside, inside, or partly inside the frustum; in the last two cases they should be rendered (see next step). We have a buffer of boolean flags, one for each instance, in which we set the flag to true if instance is (partly) within the frustum. How does the vertex shader do this? By transforming each of the 8 corner points of the axis aligned bounding box, into view space, then using a trick with
w (the 4th component of your transformed 4D vector) to see whether or not it falls within the frustum. See
cull.vs in the archive at your link.
Now we pass this buffer of flags to the geometry shader. Based on whether the
flag has been set or not, we either emit geometry or do not so. Thats pretty simple; if an instance is not visible, we emit no geometry for it, meaning the GPU has less work to do. See
cull.gs in the archive at your link.
Part 2: Is it good?
It depends very much on circumstances. You will have to pass a lot of instance information down the system bus from CPU to GPU, which for bus-limited systems (older mobiles?) may be an issue. Whereas if you do the culling on the CPU side, you will never have to pass that information as you will only pass those instances which your CPU has determined are necessary, to the GPU. Having said that, it is commonly a good pattern at least on desktops and powerful consoles to shift as much work off to the GPU as possible since CPU time is best used for game / engine logic. The same may not hold true on mobile; you'd have to profile on your target systems to be sure; these are just hints.