The ZX81 BASIC didn't have UDG features, but the characters were genererated in a similar same way as on the ZX Spectrum anyway, i.e. the characters were stored in an array of 8x8 pixels in ROM.
The difference is the Spectrum copied the characters' 8 bytes to its screen memory area (the part in the first 16 K of RAM that was read by the ULA to generate the picture), whereas the conversion from the ASCII codes stored in the ZX81 screen buffer to a sequence of bits to be sent to the ULA was performed by the Z80 (apparently by driving the ULA to the relevant address in ROM where the bits patterns for the current row to display were stored).
Hence the resulting "slow" mode, because the Z80 was mostly busy displaying the picture rather than running your software.
However, the display being partly done by software allowed a lot of freedom, though it was still limitated to selecting a memory area containing an adequate 8 bytes array of 8x8 pixels patterns. It also seems like they had to be located in ROM (I guess the ULA was made to address only this area in the memory map).
In particular, is was possible to generate high resolution graphics just by software, simply by redirecting the characters table's address to an area in ROM where the machine code bytes formed various relevant 8x8 bits patterns by chance.
Of course it wasn't always possible to find exactly the correct bit pattern for each 8x8 bits screen area, but the software was able to cleverly find the most appropriate location so you had least missing, unwanted or misplaced pixels on the screen.
It was even possible to draw line graphics and the software just had to convert the 8x8 pattern for each of the 32x24 character position in the screen buffer to a pseudo ASCII code that matched the desired pattern in the relocated "characters" table to display your graphics as accurately as possible (still with a few misplaced pixels here and there, depending on the existing patterns in ROM), and even some impressive video games were made that way.
Of course, depending on whether you were drawing line graphics and the translation into "character" numbers was possible on the fly (for instance if you were using premade shapes, UDG like, for instance for game objects), or you wanted to display a full screen bitmap picture, it was necessary to have enough RAM to store your desired pixels map rather than only ASCII codes, so a memory expansion module was necessary in most cases (I was using a Memopack 64k at the time).
Also, the Memotech HRG expansion module allowed full clean high resolution, probably by mapping RAM in place the ROM during display cycles. And it worked really great !