Using Pixel Shaders is the most convenient & customizable way for Per-Pixel Lighting Fx like diffuse & Specular Normal Mapping.
The next thing that comes to my mind is with Register Combiners. We can use them to achieve proper real-time lighting by configuring the multi-texture combiners to perform the per-pixel math. Main disadvantages are that they are nVidia only & D3D does not support them directly. But, on nVidia cards, Register-Combiner paths are faster than Pixel-Shader paths. This is the reason why engines like Abducted use Reg-Combiners for their real-time lighting engine on nVidia cards. For ATI cards they use Shaders.
I think it is similar with Doom3 Engine as well.