I’m trying to implement a “tilt” effect (a slight 3D tilt of an element on press/hover) in pure WPF, similar to UWP/WinUI. I’m using the classic approach with Viewport2DVisual3D (Greg Schechter’s Planerator). However, when the visual has a DropShadowEffect applied, the content appears visually “deformed”/shifted relative to the expected center during rotation.
I’d like to understand how to do this correctly in WPF—either how to use DropShadow so the tilt works without deformation, or how to properly compute bounds/camera, or how to separate the shadow.
Environment
- WPF (.NET 6/7/8), Windows 10/11
- Effect: custom
TiltEffect(on press/hover, wraps the element in aPlaneratorand animates RotationX/RotationY/Depth) - 3D mapping:
Viewport2DVisual3D(Planerator) - Elements have transparent backgrounds; shadow is via
DropShadowEffect(or an equivalent blurred shadow)
What happens
- If I apply a
DropShadowEffectdirectly on the element that’s mapped into 3D (it’s theViewport2DVisual3D.Visual), WPF first renders the element into an off-screen texture with expanded bounds for the blur/shadow and only then projects it into 3D. - This changes the “optical” size and the offset relative to
RenderSize. As a result, during rotation/tilt, the center shifts and the whole thing looks like a deformation/shift of the content.
See screenshots:
- [before tilt] – content without rotation (as expected)
- [during tilt] – with
DropShadowEffect, you can see the shift and “odd” skew when rotated
(2 images: “before.png”, “tilted.png”)
Minimal Repro
A shortened example—a button in a container to which I apply shadow and tilt. The tilt code toggles an attached IsPressed; Planerator projects everything into 3D.
<local:DropShadowPanel
x:Name="Card"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
tiltEffectAnimation:TiltEffect.IsEnabled="True"
tiltEffectAnimation:TiltEffect.TiltFactor="7"
BlurRadius="20"
CornerRadius="10"
Direction="270"
RenderingBias="Performance"
ShadowDepth="0"
ShadowMode="Outer"
ShadowOpacity="0.8"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
UseLayoutRounding="{TemplateBinding UseLayoutRounding}"
Color="Black">
<Border
x:Name="PART_BorderContent"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4"
RenderTransformOrigin="0.5,0.5">
<ContentPresenter
Margin="{TemplateBinding Padding}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
</local:DropShadowPanel>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<Storyboard>
<tiltEffectAnimation:PointerUpThemeAnimation TargetName="Card" />
</Storyboard>
</VisualState>
<VisualState x:Name="MouseOver">
<Storyboard>
<tiltEffectAnimation:PointerUpThemeAnimation TargetName="Card" />
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<tiltEffectAnimation:PointerDownThemeAnimation TargetName="Card" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<!-- resources -->
<DropShadowEffect x:Key="Shadow" BlurRadius="18" ShadowDepth="6" Color="Black" Opacity="0.28" />
// The events only toggle IsPressed; TiltEffect then sets PlaceIn3D=true internally and animates the rotations
private void OnDown(object sender, MouseButtonEventArgs e) => TiltEffect.SetIsPressed((FrameworkElement)sender, true);
private void OnUp(object sender, RoutedEventArgs e) => TiltEffect.SetIsPressed((FrameworkElement)sender, false);
Planerator (from Greg Schechter) maps 2D into 3D and works great in most examples. In my case, I’ve already accounted for “neutral” bounds, because the shadow/clip can shift the origin into negative coordinates:
// key part: Update3D in the Planerator
private void Update3D()
{
if (_logicalChild == null) return;
Rect bounds = VisualTreeHelper.GetDescendantBounds(_logicalChild);
double w = _logicalChild.RenderSize.Width;
double h = _logicalChild.RenderSize.Height;
if (w <= 0 || h <= 0)
{
w = bounds.Width;
h = bounds.Height;
}
double fovInRadians = FieldOfView * (Math.PI / 180);
double zValue = w / Math.Tan(fovInRadians / 2) / 2;
_viewport3d.Camera = new PerspectiveCamera(
new Point3D(w / 2, h / 2, zValue),
new Vector3D(0, 0, -1),
new Vector3D(0, 1, 0),
FieldOfView);
// Compensate for bounds offset (shadow/clip can shift the origin)
_translateTransform.OffsetX = -bounds.X;
_translateTransform.OffsetY = -bounds.Y;
_scaleTransform.ScaleX = w;
_scaleTransform.ScaleY = h;
_rotationTransform.CenterX = w * OriginX;
_rotationTransform.CenterY = h * OriginY;
}
Even with this compensation, having a DropShadowEffect on the “front” visual still shows a visible shift/deformation during rotation.
What I’ve tried
Don’t apply
DropShadowEffectto the content; instead render the shadow as a separate layer below the content (e.g. aBorderwith the sameCornerRadius+BlurEffect/DropShadowEffect, and the content without an effect).
– Result: if the shadow is part of the same visual mapped into 3D, the issue appears (to varying degrees). If I keep the shadow outside the 3D child (i.e., the shadow is “2D behind the scene” and does not tilt), the tilt is clean, but the shadow doesn’t go through perspective.Caching (
RenderOptions.SetCachingHint,BitmapCache) – no significant effect.Compute everything from
GetDescendantBoundsand subtractbounds.X/Y(see above) – better, but still not 100% in all cases.Explicit
ClipToBounds, variousSnapsToDevicePixels,UseLayoutRounding– no complete fix.
Hypothesis
This is due to how WPF handles effects and Viewport2DVisual3D:
DropShadowEffect(like otherEffects) renders the 2D visual to an off-screen render target (texture) and expands its bounds for the blur/shadow.Viewport2DVisual3Dthen works with this “post-effect” texture, but the size/origin may not match what I expect fromRenderSizeor evenGetDescendantBoundsin various combinations (transparent background, corners, clips).- The result is that the rotation center and projection don’t correspond to the “visual” center of the content.
This matches the observation (paraphrased):
“When you apply DropShadowEffect directly to an element that is then mapped into 3D (Viewport2DVisual3D), WPF first renders the element into a texture with expanded bounds and only then projects it. This changes the visual dimensions and offset relative to RenderSize, which manifests as deformation during rotation.”
Questions
- Is there a “correct” way in pure WPF to use
DropShadowEffecton an element that is the visual forViewport2DVisual3Dso that tilt/rotation doesn’t lead to shifts/deformations? - Can we reliably get the “post-effect” bounds (i.e., what actually gets mapped into 3D) and use those for the camera/rotation centers? Is there an API for “render bounds” after applying
Effect(something likeGetRenderBounds)? - Is the recommended solution to fully separate the shadow:
- a) don’t map it into 3D (it remains 2D and doesn’t tilt), or
- b) create a custom “shadow layer” inside the same 3D surface but without using
Effect(e.g., manually drawn blurred shape), to avoid WPF’s off-screen render of effects?
- Is this a known limitation/bug of
Viewport2DVisual3D+Effect, or canDropShadowEffectbe configured so that it doesn’t affect the mapping (e.g., not expanding layout/render bounds)?
Notes on the MRE
- I’m using Planerator (variant from Greg Schechter’s blog); the front face is a
Viewport2DVisual3Dwith my visual, the back face is aVisualBrushwith the same logical child. - When
TiltEffect.IsPressed=trueis toggled, the element is wrapped in the Planerator andRotationX/RotationY/Depthare animated. - If I remove
DropShadowEffect(or keep the shadow outside the visual that’s mapped into 3D), the tilt looks correct.
If needed, I can attach a full MRE (Planerator + TiltEffect), but the snippets and description above should be enough to reproduce.
Goal
Find a stable and idiomatic solution in pure WPF:
- either how to use the effect/shadow so tilt works without shifts/deformations,
- or how to correctly compute/normalize bounds and rotation centers for
Viewport2DVisual3Din the presence of effects, - or confirm that the only robust approach is to fully separate the shadow from the 3D mapping (and if it needs to tilt, “simulate” it without
Effectdirectly in the drawing).
Source code is on GitHub: https://github.com/ORRNY/Wpf-DropShadowPanel-TiltEffect

