다음을 통해 공유


WPF3Dで影

WFPの壁を破ろうシリーズ第6段?

WPF 3Dで平面投影シャドウを作ってみましょう。平面投影シャドウというのは3Dのジオメトリをペシャンコにして地面の上に配置する影です。影ですが、れっきとしたジオメトリ オブジェクトです。

実は、XZ平面(Z=0)に落とされた影を作ることはそれほど難しくありません。ジオメトリに次の行列(列優先)を適用するだけです。ここで l は光源の位置です。

 ly  -lx   0   0
 0    0   0   0
 0   -lz   ly   0
 0   -1   0   ly

任意の平面への影も、もう少し複雑な4x4の行列を適用するだけです。詳しくはReal-Time RenderingなどのCGの教科書を参照してください。

まず、次のように、シーン内にXAMLでティーポットを2つ配置します。もちろん一方は影なので色を黒色にします。

<ModelVisual3D x:Name="myScene">

 <ModelVisual3D x:Name="myShadow">

  <ModelVisual3D.Content>

   <GeometryModel3D Geometry="{StaticResource myTeapot}">

    <GeometryModel3D.Material>

     <DiffuseMaterial Brush="Black" />

    </GeometryModel3D.Material>

   <GeometryModel3D.BackMaterial>

    <DiffuseMaterial Brush="Black" />

   </GeometryModel3D.BackMaterial>

   <GeometryModel3D.Transform>

    <MatrixTransform3D>

     <MatrixTransform3D.Matrix>

      <Matrix3D M11 ="10" M33="10" M44="10" M24="-1"/>

     </MatrixTransform3D.Matrix>

    </MatrixTransform3D>

   </GeometryModel3D.Transform>

  </GeometryModel3D>

  </ModelVisual3D.Content>

 </ModelVisual3D>

             

 <ModelVisual3D x:Name="myTeapot">

  <ModelVisual3D.Transform>

   <MatrixTransform3D x:Name="myGeometryMatrix3D">

    <MatrixTransform3D.Matrix>

     <Matrix3D OffsetY="2" />

    </MatrixTransform3D.Matrix>

   </MatrixTransform3D>

  </ModelVisual3D.Transform>

  <ModelVisual3D.Content>

   <GeometryModel3D x:Name="myGeometry" 
Geometry="{StaticResource myTeapot}" >

    <GeometryModel3D.Material>

     <MaterialGroup x:Name="myMaterialGroup">

      <DiffuseMaterial Brush="White"  />

     </MaterialGroup>

    </GeometryModel3D.Material>

   </GeometryModel3D>

  </ModelVisual3D.Content>

 </ModelVisual3D>

コードでは、光源位置やティーポットの位置や向きが変更されたとき、先ほどの座用変換を適用するようにコールバックを作成します。

private void OnGeometrySliderValueChanged
(object sender, RoutedEventArgs e)

{

 if (this.IsLoaded)

 {

   myGeometryMatrix3D.Matrix = GeometryMatrix
(myAngleX.Value, myAngleY.Value, myAngleZ.Value,
myOffsetX.Value, myOffsetY.Value, myOffsetZ.Value);

   CreateShadow();

 }

}

private void PointValueChanged
(object sender, RoutedEventArgs e)

{

 if (this.IsLoaded)

 {

  Transform3DGroup tg = new Transform3DGroup();

  tg.Children.Add(new ScaleTransform3D(0.1, 0.1, 0.1));

  tg.Children.Add(new TranslateTransform3D
(PointLightPositionX.Value,
PointLightPositionY.Value,
PointLightPositionZ.Value));

  mySphere.Transform = tg;

  myPointLight.Position =
new Point3D(PointLightPositionX.Value,
PointLightPositionY.Value,
PointLightPositionZ.Value);

  CreateShadow();

 }

}

 

private void CreateShadow()

{

 Matrix3D mat = new Matrix3D();

 Point3D l = myPointLight.Position;

 Vector3D n = planeNormal;

 double d = planeDepth;

 mat.M11=l.Y; mat.M21=-l.X; mat.M31=0.0; mat.OffsetX=0.0;

 mat.M12=0.0; mat.M22=0.0; mat.M32=0.0; mat.OffsetY=0.0;

 mat.M13=0.0; mat.M23=-l.Z; mat.M33=l.Y; mat.OffsetZ=0.0;

 mat.M14=0.0; mat.M24=-1.0; mat.M34=0.0; mat.M44=l.Y;

 myShadow.Content.Transform =
new MatrixTransform3D
(Matrix3D.Multiply(myGeometryMatrix3D.Value, mat));

}

この影にはいくつかの制限があります。まず、平面にしか影を落とせません。でこぼこした地形や他のジオメトリには使えません(ペシャンコにしたジオメトリなので当然ですが...)。影が落ちる平面とおなじZ値を持つと、影と平面のどちらが上になるかが不定になり、いわゆるZファイティングが発生します。影か地形に少しオフセットを設定して必ず影が上にくるようにする必要があります。

RenderToBitmapを使って画像を生成すれば、WPFでも「シャドウ テクスチャ」くらいはできそうな気がします。シャドウテクスチャは地形に影のテクスチャを適用するテクニックなので、でこぼこした地形にも適用できます。さすがに、ピクセル単位で光源からの距離の比較を行う「シャドウマッピング」はWPFでは難しそうです。

Comments