Tell me more ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

What's the best way to make a drop shadow for a 3D object?

 image = Graphics3D[Sphere[], Boxed -> False]

sphere without drop shadow

I can get a blurry black outline of this:

imageShadow = 
 Blur[RegionBinarize[ColorNegate[image], (* bottom left corner --> *) {{1, 1}}, 
   0.1], 20]

which could act as a good shadow:

a shadow

But combining them together is a bit harder... Any suggestions?

share|improve this question
Older versions of Mathematica had the routine Shadow[] in the package Graphics`Graphics3D`. You might want to look into it. – 0x4A4D Jan 29 '12 at 12:01
Similar question: stackoverflow.com/q/6955692/615464 – Sjoerd C. de Vries Jan 29 '12 at 21:32
@SjoerdC.deVries good find. I didn't check the old site... – cormullion Jan 29 '12 at 21:35

3 Answers

up vote 11 down vote accepted

This produces a 2D shadow. If you meant a 3D shadow (on the x-y plane), see code below.

image = Rasterize[Graphics3D[Sphere[], Boxed -> False]];
shadow = Blur[RegionBinarize[ColorNegate[image], {{1, 1}}, 0.1], 20];

image = SetAlphaChannel[image, ColorNegate@Binarize[image, {1, 1}]];

Show[{shadow, image}]

Mathematica graphics

The position of the shadow has to be fine tuned manually.

I also managed to construct it in 3D (rotatable), though I cannot make the bottom polygon transparent.

shadow = Blur[
   RegionBinarize[Graphics[Circle[], ImagePadding -> 60], {{1, 1}}, 
    0.1], 40];
shadow = SetAlphaChannel[shadow, ColorNegate@shadow];

Graphics3D[{
  Sphere[],
  EdgeForm@None, Opacity@.7, Texture@shadow, 
  Polygon[{{-1, -1, -2}, {1, -1, -2}, {1, 1, -2}, {-1, 
     1, -2}, {-1, -1, -2}}, 
   VertexTextureCoordinates -> {{0, 0}, {1, 0}, {1, 1}, {0, 1}}]
  }, Boxed -> False]

Mathematica graphics

share|improve this answer
Excellent - just what I needed! Thanks. – cormullion Jan 30 '12 at 10:11

Suppose you have a flat surface with normal n going through point p0 and a directional light with direction dir, then the shadow of a point p onto this surface can be calculated according to

proj[p0_, n_, dir_][p_] := p - (p - p0).Normalize[n]/dir.Normalize[n] dir

Suppose you have a shape created with a ParametricPlot3D, for example

pt[r_, ph_, th_] := {r Cos[ph] Sin[th], r Sin[ph] Sin[th], r Cos[th]}
rf[ph_, th_] := 3/2 + 2 Cos[2 th] Sin[ph]^2

shape = ParametricPlot3D[pt[rf[ph, th], ph, th], {ph, 0, 2 Pi}, {th, 0, Pi}, 
  Mesh -> False]

Then the shadow of this shape could be calculated according to

shdw = With[{p0 = {0, 0, -4}, n = {0, 0, 1}, dir = {1, 0, -1}},
   ParametricPlot3D[proj[p0, n, dir][pt[rf[ph, th], ph, th]], 
    {ph, 0, 2 Pi}, {th, 0, Pi}, Mesh -> False, PlotStyle -> Black]];

Show[shape, shdw, PlotRange -> All]

Mathematica graphics


To get blurry edges on the shadow you could do something like this

With[{p0 = {0, 0, -4}, n = {0, 0, 1}, dir = {1/3, 1/2, -1},
    plotr = {{-8, 8}, {-8, 8}, {-5, 4}}},

  (* blurred image of shadow to be used as a texture *)
  tex = Blur[Rasterize[
    ParametricPlot[proj[p0, n, dir][pt[rf[ph, th], ph, th]][[;; 2]], 
      {ph, 0, 2 Pi}, {th, 0, Pi}, 
      Mesh -> False, 
      PlotStyle -> {Black, Opacity[1]}, 
      Axes -> False, Frame -> False,
      PlotRange -> plotr[[;; 2]],
      Background -> None], 
    Background -> None], 10];

  shdw = Graphics3D[{Texture[ImageData[tex]], EdgeForm[],
    Polygon[
      p0 + RotationTransform[{{0, 0, 1}, n}][Flatten[{#, 0}]] & /@ 
        Tuples[plotr[[;; 2]]][[{1, 2, 4, 3}]],
      VertexTextureCoordinates -> Tuples[{0, 1}, 2][[{1, 2, 4, 3}]]]}];

  Show[shdw, shape,
    Lighting -> {{"Directional", White, {0, -1, 1}}}, 
    PlotRange -> plotr,
    Axes -> False]]
  ]

Mathematica graphics

Similar to István's solutions, I'm using a blurred rasterized image of the projected shape as a texture for the surface on which the shadow is projected. To get a transparent texture I'm using ImageData[tex] as the texture rather than tex itself. To get the scaling right when applying the texture, I'm using the same PlotRange for tex as for the polygon.

share|improve this answer
Very good answer! – magma Jan 29 '12 at 15:48
thanks - excellent job. – cormullion Jan 30 '12 at 10:09
I'll just note that this is more or less what Shadow[] in older versions of Mathematica does. – 0x4A4D Jan 31 '12 at 1:29
Wish I could up-vote twice! @J.M. what is Shadow[]? Couldn't find it in the documentation. – Dror Feb 15 at 16:06
@Dror, It's one of the routines in the old Graphics`Graphics` package in earlier versions of Mathematica... – 0x4A4D Mar 23 at 12:29

Since a drop shadow is a projection, I thought it's useful to provide a more general solution that creates this projection for arbitrary Graphics3D objects with less manual tuning. I am skipping the blur effect because I want to focus on the projection issue (Mathematica isn't a ray tracer, so I feel it's a bit too painful to simulate shadow boundaries using blur).

Here is my code:

project[x_, direction_, normal_] := 
 Module[{d, n},
  d = Normalize[direction];
  n = Normalize[normal];
  x /. Graphics3D[gr_, opts___] :> Graphics3D[{Black,
       GeometricTransformation[
        gr /. {Glow[_] -> Glow[], 
          r_?(MemberQ[{RGBColor, Hue, CMYKColor, GrayLevel}, 
               Head[#]] &) -> Black},
        Composition[TranslationTransform[direction],
         RotationTransform[{d, n}],
         ScalingTransform[10^-3, d], 
         Quiet@Check[ScalingTransform[1./(n.d), n - (n.d) d], Identity]
         ]
        ]}, opts
      ]
  ]

The argument x is a 3D plot or graphics object. The second variable, direction, is parallel to the light rays and its length is equal to the offset between the object and its shadow. The third argument, normal, is the normal vector of the surface onto which the shadow is projected.

To illustrate this, I'll define a sample object (displayed below with its shadow):

gg = Graphics3D[{{Opacity[.5], Cuboid[]}, {Blue, 
    Translate[Scale[Cuboid[], .2], {1, 1, 1}/2]}, , {Glow[Red], Red, 
    Translate[Scale[Sphere[], .5], -{1, 1, 1}/4]}}, Boxed -> False]

Now display it with some coordinate axis for orientation, assuming light going in the direction {0,1,1} and falling on a surface tilted into the space diagonal {1,1,1}:

Show[gg, 
     project[gg, 2.1 {0, 1, 1}, {1, 1, 1}],
     Graphics3D[{Map[{Apply[RGBColor, #], Arrow[Tube[{{0, 0, 0}, #}]]} &, 
    2 IdentityMatrix[3]]}]
]

3D object and its shadow under illumination from diagonally below

The projection is stretched if the shadow surface isn't perpendicular to the rays. Of course there is the special case where the shadow surface is at a grazing angle to the light. I decided to handle this by not stretching the shadow.

Also observe that translucent regions create less dark shadows. And the whole thing is still a 3D object, not a bitmap.

Another example:

Show[gg, 
     project[gg, -1.5 {0, 0, 1}, {0, 1, 1}], 
     Graphics3D[{Map[{Apply[RGBColor, #], Arrow[Tube[{{0, 0, 0}, #}]]} &, 
    2 IdentityMatrix[3]]}]
]

3D object and its shadow under illumination from above

share|improve this answer
That's an impressive piece of code. I'm going to work through it later today. Thanks! – cormullion Jan 31 '12 at 11:19

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.