The difference is demonstrated by the following code snippet, where I've changed only the scoping construct. But let's first define a simple function:

 f[x_]:=x+a

Now we first look at `Block`:

 Block[{a=b}, Print[a]; a=3; f[x]+a]+foo[a,b]
 (*
 Prints: b
 --> 6 + x + foo[a, b]
 *)

As you see, the global value of a is temporarily assigned to b, then inside changed to 3. That is, not only the `a` inside the `Block`, but also the value of `a` in the function call from the `Block` is modified. Outside the block, the change is undone, including any change done inside the block (the `a=3`).

Let's now look at `Module`:

 Module[{a=b}, Print[a]; a=3; f[x]+a]+foo[a,b]
 (*
 Prints: b
 --> 3 + a + x + foo[a, b]
 *)

As you see, this time the a in the function does *not* evaluate to b. Indeed, the `a` inside the `Module` is replaced by a temporary variable, as can be seen by not assigning a value:

 Module[{a},a]
 (*
 --> a$84
 *)

Finally, `With`:

 With[{a=b}, Print[a]; a=3; f[x]+a]+foo[a,b]
 (*
 Prints: b
 --> 3 + a + x + foo[a, 3]
 *)

As you can see, the `a=3` now globally assigns 3 to `b`! That's because `With` actually replaces `a` with the "assigned" value, i.e. `b` in the whole body. That is, whereever the body contains `a` it's as if there was written `b` instead. But again, the value of `a` in the called function `f` is not affected.