четверг, 30 июня 2011 г.

Создание 3d модели в WPF



В данной статье мы с вами поговорим о принципах реализации трёхмерной графики в WPF. И создадим небольшое приложение , которое будет их реализовывать. Но сначала немного теории.

Графическое содержимое 3-D в приложении WPF инкапсулировано в элементе Viewport3D, который может участвовать в структуре двумерного элемента. Графическая система рассматривает Viewport3D как двухмерный визуальный элемент, подобный многим другим в WPF. Viewport3D функционирует как окно — окно просмотра — трехмерной сцены. Говоря точнее, это поверхность, на которую проецируется сцена 3-D.

В традиционном приложении 2-D элемент Viewport3D используется как любой другой контейнерный элемент, например «Grid» или «Canvas». Хотя элемент Viewport3D можно использовать с другими графическими объектами 2-D в том же графе сцены, нельзя сочетать объекты 2-D и 3-D внутри элемента Viewport3D. Этот раздел будет посвящен рисованию 3-D графики внутри объекта Viewport3D.

Координаты трехмерного пространства

Начало системы координат WPF для графики 2-D отсчитывается от левого верхнего угла области отрисовки (обычно экрана). В системе 2-D положительные значения оси x откладываются вправо, а положительные значения оси y — сверху вниз. Однако в системе координат 3-D начало располагается в центре отрисовываемой области, положительные значения оси x откладываются вправо, оси y — снизу вверх, а оси z — из центра к наблюдателю.



Пространство, определяемое этими осями, является стационарной системой отсчета координат для объектов 3-D в приложении WPF. При построении моделей в этом пространстве и создании источников света и камер для их отображения необходимо отличать стационарную систему отсчета координат (или «мировую систему координат») от локальной системы отсчета, которая создается для каждой модели при применении к ней преобразований. Помните, что в зависимости от настройки освещения и камеры, объекты в мировой системе координат могут выглядеть совсем по-другому или вообще быть невидимыми, но положение камеры не изменяет расположения объектов в мировой системе координат.

Камеры и проекции

Разработчики, работающие в координатах 2-D, привыкли к размещению графических примитивов на двухмерном экране. При создании сцены 3-D важно помнить, что фактически создается представление 2-D объектов 3-D. Поскольку сцена 3-D выглядит по-разному в зависимости от точки наблюдения, необходимо указать эту точку наблюдения. Указать эту точку наблюдения для сцены 3-D позволяет класс Camera.



Другой способ понимания того, как представляется сцена 3-D на поверхности 2-D, — это описание сцены как проекции на поверхность просмотра. Камера ProjectionCamera позволяет указать различные проекции и их свойства для изменения того, как наблюдатель видит модели 3-D. Камера PerspectiveCamera указывает проекцию сцены в перспективе. Другими словами, камера PerspectiveCamera предоставляет точку схода перспективы. Можно указать положение камеры в пространстве координат сцены, направление и поле зрения камеры и вектор, определяющий направление «вверх» в сцене. Следующая схема иллюстрирует проекции PerspectiveCamera.

Свойства NearPlaneDistance и FarPlaneDistance камеры ProjectionCamera ограничивают диапазон проекции камеры. Поскольку камеры могут быть расположены в любом месте сцены, фактически можно расположить камеру внутри модели или очень близко от нее, что усложняет правильное распознавание объекта. Свойство NearPlaneDistance позволяет определить минимальное расстояние от камеры, за которым не будут располагаться объекты. И наоборот, свойство FarPlaneDistance позволяет задать расстояние от камеры, дальше которого объекты не будут нарисованы; это гарантирует, что объекты, расположенные слишком далеко для распознавания, не будут включены в сцену.

Освещение

Источники света в графике 3-D выполняют ту же роль, что и реальные источники света: они делают поверхности видимыми. Более того, источники света определяют, какая часть сцены будет включена в проекцию. Объекты источников света в приложении WPF создают различные эффекты света и тени. Они смоделированы на основе поведения различных реальных источников света. Сцена должна включать, как минимум, один источник света, иначе модели будут невидимыми.

Указанные ниже источники света являются производными от базового класса Light:
·         AmbientLight: создает рассеянное освещение, при котором все объекты освещены одинаково, независимо от их расположения или ориентации.

·         DirectionalLight: создает освещение, аналогичное удаленному источнику света. Направленные источники света имеют свойство Direction, которое указывается как объект Vector3D, но без заданного местоположения.

·         PointLight создает освещение, как от ближнего источника света. Источники света «PointLights» занимают определенное положение и испускают свет из этого положения. Объекты на сцене освещаются в зависимости от их положения и расстояния относительно источника света. PointLightBase предоставляет свойство Range, которое определяет расстояние, за пределами которого модели не будут освещены светом. Класс «PointLight» также предоставляет свойства затухания, определяющие интенсивность ослабления источника света в зависимости от расстояния. Можно указать константу, линейную или квадратичную интерполяцию затухания источника света.

Исключение SpotLight наследуется от исключения PointLight. Источники света «Spotlights» освещают сцену подобно источникам света PointLight и также имеют расположение и направление. Они проектируют свет в конусообразную область, задаваемую свойствами InnerConeAngleOuterConeAngle, значения которых указываются в градусах.

Источники света являются объектами Model3D, поэтому можно преобразовывать и анимировать свойства источников света, включая положение, цвет, направление и диапазон.

Практика

Переходим к практике. В нашем примере мы создадим 3d модель кирпича, применим к нему текстуру и осветим.  Для создания этой простой трехмерной сцены используются Viewport3D вместе со следующими компонентами. Камера создается с помощью класса PerspectiveCamera. Камера определяет, какая часть трехмерной сцены видима. Сетка создается для указания формы трехмерного объекта (кирпич) с помощью свойства Geometry класса GeometryModel3D.
Материал, который будет отображаться на поверхности объекта (линейный градиент в этом примере и текстура кирпича), определяется с помощью свойства Material класса GeometryModel3D. Источник света создается для освещения объекта с помощью класса SpotLight.

Обратимся к коду.
<Window x:Class="_3DDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="3D Demo" Height="240" Width="500">
    <Window.Resources>


        <VisualBrush x:Key="mainCover">
            <VisualBrush.Visual>
                <Border Background="Yellow" BorderThickness="2">
                    <Image Source="/3DDemo;component/3201019298_b2099760e0_m.jpg" />
                </Border>
            </VisualBrush.Visual>
        </VisualBrush>

        <SolidColorBrush x:Key="top" Color="BurlyWood" />
        <SolidColorBrush x:Key="pages" Color="BurlyWood" />
        <SolidColorBrush x:Key="back" Color="BurlyWood" />
        <SolidColorBrush x:Key="side" Color="BurlyWood" />
    </Window.Resources>
    <Viewport3D>
        <Viewport3D.Camera>
            <PerspectiveCamera Position="0,0,25" LookDirection="15,6,-50" />
        </Viewport3D.Camera>

        <ModelVisual3D>
            <ModelVisual3D.Content>
                <!--<AmbientLight Color="White" />-->
                <SpotLight Color="White" InnerConeAngle="20" OuterConeAngle="60" Direction="15,6,-50" Position="0,0,25" />
             </ModelVisual3D.Content>
        </ModelVisual3D>

        <ModelVisual3D>
            <ModelVisual3D.Content>
                <Model3DGroup>

                    <!-- front -->
                    <GeometryModel3D>
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D
                                Positions="0 0 0, 10 0 0, 0 16 0, 10 16 0, 10 0 0"
                                TriangleIndices="0, 1, 2, 2, 4, 3"
                                TextureCoordinates="0 1, 1 1, 0 0, 1 0, 1 1"
                                />
                        </GeometryModel3D.Geometry>

                        <GeometryModel3D.Material>
                            <MaterialGroup>
                                <SpecularMaterial SpecularPower="99">
                                    <SpecularMaterial.Brush>
                                        <SolidColorBrush>Yellow</SolidColorBrush>
                                    </SpecularMaterial.Brush>
                                </SpecularMaterial>
                                <DiffuseMaterial Brush="{StaticResource mainCover}" />
                            </MaterialGroup>

                        </GeometryModel3D.Material>
                    </GeometryModel3D>



              

                    <!-- left -->
                    <GeometryModel3D>
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D
                            Positions="0 0 -4, 0 0 0, 0 14 -4, 0 14 0, 0 0 0"
                            TriangleIndices="0, 1, 2, 2, 4, 3" />
                        </GeometryModel3D.Geometry>

                        <GeometryModel3D.Material>
                            <MaterialGroup>
                                <DiffuseMaterial Brush="{StaticResource side}" />
                            </MaterialGroup>
                        </GeometryModel3D.Material>
                        <GeometryModel3D.BackMaterial>
                            <DiffuseMaterial Color="Blue" />
                        </GeometryModel3D.BackMaterial>
                    </GeometryModel3D>

                    <GeometryModel3D>
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D
                            Positions="0 14 -4, 0 14 0, 0 16 -4, 0 16 0, 0 14 0"
                            TriangleIndices="0, 1, 2, 2, 4, 3" />
                        </GeometryModel3D.Geometry>

                        <GeometryModel3D.Material>
                            <MaterialGroup>
                                <DiffuseMaterial Brush="{StaticResource top}" />
                            </MaterialGroup>
                        </GeometryModel3D.Material>
                        <GeometryModel3D.BackMaterial>
                            <DiffuseMaterial Color="Blue" />
                        </GeometryModel3D.BackMaterial>
                    </GeometryModel3D>

                    <!-- right  -->
                    <GeometryModel3D>
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D
                            Positions="10 0 0, 10 0 -4, 10 16 0, 10 16 -4, 10 0 -4"
                            TriangleIndices="0, 1, 2, 2, 4, 3" />
                        </GeometryModel3D.Geometry>

                        <GeometryModel3D.Material>
                            <MaterialGroup>
                                <DiffuseMaterial Brush="{StaticResource top}" />
                            </MaterialGroup>
                        </GeometryModel3D.Material>
                        <GeometryModel3D.BackMaterial>
                            <DiffuseMaterial Color="Blue" />
                        </GeometryModel3D.BackMaterial>
                    </GeometryModel3D>


                    <!-- top side -->
                    <GeometryModel3D>
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D
                            Positions="0 16 0, 0 16 -4, 10 16 0, 10 16 -4, 0 16 -4"
                            TriangleIndices="0, 2, 1, 2, 3, 4"
                                TextureCoordinates="0 1, 0 0, 1 1, 1 0, 0 0"
                                />
                        </GeometryModel3D.Geometry>

                        <GeometryModel3D.Material>
                            <MaterialGroup>
                                <DiffuseMaterial Brush="{StaticResource pages}" />
                            </MaterialGroup>

                        </GeometryModel3D.Material>
                        <GeometryModel3D.BackMaterial>
                            <DiffuseMaterial Color="Blue" />
                        </GeometryModel3D.BackMaterial>
                    </GeometryModel3D>

                    <!-- bottom -->
                    <GeometryModel3D>
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D
                            Positions="0 0 0, 0 0 -4, 10 0 0, 10 0 -4, 0 0 -4"
                            TriangleIndices="0, 1, 2, 2, 4, 3"
                                TextureCoordinates="0 1, 0 0, 1 1, 1 0, 0 0"
                                />
                        </GeometryModel3D.Geometry>

                        <GeometryModel3D.Material>
                            <DiffuseMaterial Brush="{StaticResource pages}" />
                        </GeometryModel3D.Material>
                        <GeometryModel3D.BackMaterial>
                            <DiffuseMaterial Color="Blue" />
                        </GeometryModel3D.BackMaterial>
                    </GeometryModel3D>

                    <!-- back -->
                    <GeometryModel3D>
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D
                            Positions="0 0 -4, 10 0 -4, 0 16 -4, 10 16 -4, 10 0 -4"
                            TriangleIndices="0, 2, 1, 2, 3, 4" />
                        </GeometryModel3D.Geometry>

                        <GeometryModel3D.Material>
                            <MaterialGroup>
                                <DiffuseMaterial Brush="{StaticResource back}" />
                            </MaterialGroup>
                        </GeometryModel3D.Material>
                        <GeometryModel3D.BackMaterial>
                            <DiffuseMaterial Color="Blue" />
                        </GeometryModel3D.BackMaterial>
                    </GeometryModel3D>


                    <Model3DGroup.Transform>
                        <RotateTransform3D CenterX="0" CenterY="0" CenterZ="0">
                            <RotateTransform3D.Rotation>
                                <AxisAngleRotation3D x:Name="angle" Axis="-1,-1,-1" Angle="70" />
                            </RotateTransform3D.Rotation>

                        </RotateTransform3D>
                    </Model3DGroup.Transform>

                </Model3DGroup>

            </ModelVisual3D.Content>
        </ModelVisual3D>
       
    </Viewport3D>
</Window>


Давайте посмотрим, что у нас в результате получилось. Помоему неплохо J.


В следующей статье мы научимся вращать нашу 3d модель.

6 комментариев:

  1. А 3d модель формата fbx, как отрисовать?
    Например сделанную в 3dMax.

    ОтветитьУдалить
    Ответы
    1. Можно попробовать конвертировать Вы можете 3D-объекты в XAML и затем использовать в WPF. Сушествуют плагины для преобразования 3D-модели в XAML. Также очень рекомендую посмотреть вот этот проект http://helixtoolkit.codeplex.com/

      Удалить
    2. Спасибо, я почитаю.
      а то приходиться использовать Unity, а
      там кнопки не красивые и событий для компонентов нет.
      Очень много гемороя.

      Удалить
  2. Как например отрисовать её и изменить масштаб?

    ОтветитьУдалить
    Ответы
    1. Ещё есть коммерческое решение http://www.erain.com/products/zam3d/DefaultPDC.asp

      Удалить
  3. Этот комментарий был удален автором.

    ОтветитьУдалить