# How to make a drop-shadow for a Graphics3D objects?

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

 image = Graphics3D[Sphere[], Boxed -> False] 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: But combining them together is a bit harder... Any suggestions?

• Older versions of Mathematica had the routine Shadow[] in the package GraphicsGraphics3D. You might want to look into it. – J. M. will be back soon 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

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}]]; 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];

Graphics3D[{
Sphere[],
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] • Excellent - just what I needed! Thanks. – cormullion Jan 30 '12 at 10:11

Objects obscure their own shadows when viewed from the position of the light source. Consider the following 3D scene: If this scene is illuminated with a light source located at the camera position, the shadow of the cow will land on any part of the walls or floor which is obscured by the cow. Suppose we rasterize this scene with the lighting turned off and the walls glowing white - this provides a silhouette of the cow: To use this silhouette as a shadow texture, it is necessary to map from the 3D coordinates of the wall polygons to the 2D image position. This can be done using the ViewMatrix which @Heike shows how to compute in this answer.

Here is some code which allows a 3D scene to be interactively rotated with a button which creates a shadow from the current viewpoint, outputting a copy of the Graphics3D with the shadow applied and a single light source at the appropriate position.

First the code (thanks to Michael E2 for fixing the initialisation of ViewVector):

theta[{x_, y_, z_}] := ArcTan[z, Norm[{x, y}]];
phi[{x_, y_, z_}] := If[Norm[{x, y}] > .0001, ArcTan[x, y], 0];
alpha[vert_, v1_] := ArcTan[{-Sin[phi[v1]], Cos[phi[v1]], 0}.vert,
Cross[v1/Norm[v1], {-Sin[phi[v1]], Cos[phi[v1]], 0}].vert];
tt[v1_, vert_, center_, r_, scale_] := TransformationMatrix[
RotationTransform[-alpha[vert/scale, v1], {0, 0, 1}].
RotationTransform[-theta[v1], {0, 1, 0}].
RotationTransform[-phi[v1], {0, 0, 1}].
ScalingTransform[r {1, 1, 1}].
TranslationTransform[-center]];
pp[ang_] := {{1, 0, -Tan[ang], 1}, {0, 1, -Tan[ang], 1},
{0, 0, -Tan[ang], 0}, {0, 0, -2 Tan[ang], 2}};
spos[{t_, p_}, {x_, y_, z_}] := {#1, #2}/#4 & @@ (p.t.{x, y, z, 1});

DynamicModule[{pr, center, scale, v1, vv, theta, vert, va, vm, tex, vcf},
pr = Chartingget3DPlotRange@Graphics3D[gr];
scale = 1/Abs[#1 - #2] & @@@ pr;
center = Mean /@ pr; va = 25 Degree; vert = {0., 0., 1.};
v1 = 4.5 (Last@Transpose[pr] - center) + center; vv = {v1, center};
Panel[Column[{
ViewVector -> Dynamic[vv, (vv = #; center = vv[]; v1 = vv[] - center) &],
ViewVertical -> Dynamic[vert], Boxed -> False]],
vm = {tt[v1, vert, center, Cot[va/2]/Norm[v1], scale], pp[va/2]};
tex = Rasterize[Graphics3D[{gr, {Glow[White], EdgeForm[], shadowpolys}},
ViewMatrix -> vm, Boxed -> False, Lighting -> None],  ImageSize -> 800];
vcf = With[{m = {tt[v1, vert, center, Cot[va/2]/Norm[v1], scale],
pp[va/2]}}, spos[m, #] &];
Polygon[pts, rest, VertexTextureCoordinates -> vcf /@ pts]}},
Lighting -> {{"Directional", White, vv}, {"Ambient", GrayLevel[0.1]}},
Boxed -> False]], Method -> "Queued"]}]]]


It works like this. Create some 3D objects which will cast shadows:

cow = Translate[
Scale[ExampleData[{"Geometry3D", "Cow"}][], 3], {0.2, 0, 0.75}];


walls = {
Polygon[{{-1, -1, 0}, {-1, 1, 0}, {1, 1, 0}, {1, -1, 0}}],
Polygon[{{-1, -1, 0}, {-1, -1, 1.5}, {1, -1, 1.5}, {1, -1, 0}}],
Polygon[{{-1, -1, 0}, {-1, -1, 1.5}, {-1, 1, 1.5}, {-1, 1, 0}}]};


Then evaluate

shadowCaster[cow, walls] Rotate the Graphics3D to the desired viewpoint and click the button to get this: Note that there is only one shadow texture, which is used by all the shadow receiving polygons. It is therefore relatively quick even when a large number of polygons are used. e.g. here are ~3000 polygons approximating a curved surface:

surface = {EdgeForm[], Cases[Normal@
Plot3D[Exp[-2 (x^2 + y^2)^4] - 1, {x, -1, 1}, {y, -1, 1},
PlotPoints -> 40, PlotRange -> All,
MaxRecursion -> 0], _Polygon, -1]};  • This is awesome - ray-tracing next? I did have a problem, if all variables weren't initialized properly. Here are some suggestions: pr = Chartingget3DPlotRange@Graphics3D[gr]; scale = 1/Abs[#1 - #2] & @@@ pr; center = Mean /@ pr; va = 25 Degree; vert = {0., 0., 1.}; v1 = 4.5 (Last@Transpose[pr] - center) + center; vv = {v1, center}; – Michael E2 Jan 11 '14 at 19:59
• @MichaelE2, many thanks for the improved initialisation code - I just couldn't figure out how to get it right. – Simon Woods Jan 12 '14 at 11:14
• The cow-on-a-hill is very impressive. – bobthechemist Jan 12 '14 at 13:06
• Great, this is the way to go for smooth looking shadows on arbitrary polygons (I didn't see this until now). – Jens Feb 7 '15 at 4:21

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] 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},
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]]
] 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.

• Very good answer! – magma Jan 29 '12 at 15:48
• I'll just note that this is more or less what Shadow[] in older versions of Mathematica does. – J. M. will be back soon 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 '13 at 16:06
• @Dror, It's one of the routines in the old GraphicsGraphics  package in earlier versions of Mathematica... – J. M. will be back soon Mar 23 '13 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:

Options[project] = {"ObjectCenter" -> {0, 0, 0},
project[x_, direction_, normal_, OptionsPattern[]] := Module[
center = OptionValue["ObjectCenter"];
d = Normalize[direction];
n = Normalize[normal];
x /. Graphics3D[gr_, opts___] :> Graphics3D[
{
GeometricTransformation[
gr /. {
Glow[_] -> Glow[],

r_?(MemberQ[{RGBColor, Hue, CMYKColor, GrayLevel},
},
gr
],
Composition[
TranslationTransform[direction + center],
Quiet[Check[RotationTransform[{d, n}], Identity],
{RotationMatrix::degen, RotationTransform::spln}
],
ScalingTransform[10^-3, d],
Quiet@Check[
ScalingTransform[1./(n.d), n - (n.d) d],
Identity
],
TranslationTransform[-center]
]
]
},
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 (assuming the object was centered at the origin). The third argument, normal, is the normal vector of the surface onto which the shadow is projected.

Edit

The translation by vector direction is applied after the object has effectively been moved to a plane containing the origin. This happens in the "flattening step" (the multiplication by 10^-3 in ScalingTransform). If you want the translation to start from a different position, this can be specified via the option "ObjectCenter". For an example with a more specific description, see also this answer.

End edit

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]}]
] 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]}]
] • That's an impressive piece of code. I'm going to work through it later today. Thanks! – cormullion Jan 31 '12 at 11:19