Rico's explained the basic principle well.
To add some details:
how does C interact with the driver
C application calls functions in libraries like DirectX, OpenGL, CUDA and OpenCL, in the exact same way it calls functions like printf().
These library functions then use OS specific mechanisms - such as ioctl (in linux), DeviceIOControl (in Windows), DMA (Direct Memory Addressing), etc - to send commands and data to GPU driver.
If you want your application to talk directly to GPU driver, *you* will have to call these mechanisms just like one of those library does.
Does Assembly come into this at all
Assembly language is a slightly more readable version of machine code. Machine code like "8B 47 04" (which is what an executable contains and the CPU understands) is
converted into a more readable assembler line like "mov eax,[edi+4]". Ultimately, assembly language too compiles down to the same CPU instruction set as C and other higher languages.
If you so wish, you can code your application in assembly instead of C; it's just another language.
Does driver need to be programmed in a certain language?
Practically speaking, drivers do a lot of memory and low level hardware manipulation. So they're usually written in languages that
make such manipulation safe without side effects, such as C/C++/assembler (and possibly even new languages like D and Rust, but I'm not too sure about them).
Higher level or managed languages result in a lot of side effects on CPU usage, CPU locking, memory usage, etc that may not be a good idea at the driver level.