Камера

63

Прежде чем трехмерная сцена будет отображена, понадобится расположить камеру в корректной позиции и ориентировать ее в правильном направлении. Это делается установкой в свойстве Viewport3D.Camera объекта Camera.

По сути, камера определяет способ проекции трехмерной сцены на двухмерную поверхность окна отображения. В WPF определены три класса камер: наиболее часто используемый PerspectiveCamera и более экзотичные OrthographicCamera и MatrixCamera.

Камера PerspectiveCamera отображает сцену так, что объекты, находящиеся дальше, всегда выглядят меньшими. Именно такого поведения большинство ожидает от трехмерной сцены.

Камера OrthographicCamera выравнивает трехмерные объекты, так что сохраняется точный начальный масштаб, независимо от местоположения каждой фигуры. Это выглядит немного странно, но удобно для некоторых инструментов визуализации. Например, приложения, предназначенные для технического черчения, часто используют именно этот тип представления. (На рисунке продемонстрирована разница между PerspectiveCamera и OrthographicCamera):

Отображение перспективы камерами разного типа

И, наконец, MatrixCamera позволяет определить матрицу, используемую для трансформации данной трехмерной сцены в двухмерное представление. Это сложный инструмент, предназначенный для высокоспециализированных эффектов и для переноса кода из других платформ (вроде Direct 3D), использующих камеру этого типа.

Выбор правильной камеры — относительно простая задача, но размещение и конфигурирование ее несколько сложнее. Первое, что должно быть указано — точка в трехмерном пространстве, где будет позиционирована камера, за счет установки ее свойства Position. Второй шаг — установка трехмерного вектора в свойстве LookDirection, указывающем ориентацию камеры. В типичной трехмерной сцене камера размещается чуть дальше одного из углов сцены с помощью свойства Position, а затем наклоняется для получения требуемого вида с помощью свойства LookDirection.

Позиция камеры определяет, насколько крупной будет сцена в окне просмотра. Чем ближе камера, тем больше масштаб. Вдобавок окно просмотра растягивается, чтобы вместить его контейнер и все его текущее содержимое. Например, если создать окно просмотра, заполняющее все окно, то за счет изменения размеров этого окна можно растягивать или сжимать всю сцену.

Свойства Position и LookDirection должны устанавливаться в сочетании. Если использована Position для смещения камеры, но с помощью LookDirection камера не сориентирована в правильном направлении, то можно вообще не увидеть содержимого трехмерной сцены. Чтобы правильно ориентировать камеру, укажите точку, которую нужно видеть в прямоугольнике камеры. Направление просмотра может быть вычислено по следующей формуле:

НаправлениеПросмотраКамеры = ЦентральнаяТочкаИнтереса - ПозицияКамеры

В примере с треугольником камера размещается в левом верхнем углу с координатами (-2,2,2). Если предположить, что необходимо навести фокус на центральную точку сцены (0,0,0), которая приходится на середину нижней грани треугольника, то направление просмотра вычисляется следующим образом:

НаправлениеПросмотраКамеры = (0, 0, 0) - (-2, 2, 2) = (2, -2, -2)

Это эквивалентно нормализованному вектору (1,-1,-1), поскольку он описывает то же направление. Как и в случае свойства Direction объекта DirectionalLight, здесь важно направление вектора, а не его длина.

После установки свойств Position и LookDirection может также понадобиться установить UpDirection. Свойство UpDirection определяет наклон камеры. Изначально UpDirection имеет значение (0,1,0), что означает направление прямо вверх, т.е. отсутствие наклона, как показано на рисунке:

Позиционирование и наклон камеры

Если немного сместить это направление, скажем, до (0.25,1,0), то камера будет слегка повернута вокруг оси X . В результате трехмерные объекты окажутся немного наклоненными в противоположном направлении. Это как если при обозрении сцены наклонить голову:

Изменение наклона камеры

Имея в виду все эти детали, можно определить PerspectiveCamera для отображения простой сцены с одним треугольником:

<Grid Margin="5">
      <Border BorderBrush="Yellow" BorderThickness="1">
      <Viewport3D>
        <Viewport3D.Camera>
          <PerspectiveCamera
          Position="-2,2,2"
          LookDirection="2,-2,-2"
          UpDirection="0,1,0"
           />
        </Viewport3D.Camera>
        <ModelVisual3D>
          <ModelVisual3D.Content>            
            <DirectionalLight
             Color="White"
             Direction="-1,-1,-1" />
          </ModelVisual3D.Content>          
        </ModelVisual3D>

        <ModelVisual3D>
          <ModelVisual3D.Content>
            <GeometryModel3D>
              <GeometryModel3D.Geometry>
                <MeshGeometry3D Positions="-1,0,0 0,1,0 1,0,0"
                                TriangleIndices="0,2,1" />
              </GeometryModel3D.Geometry>

              <GeometryModel3D.Material>
                <DiffuseMaterial Brush="LightBlue" />
              </GeometryModel3D.Material>

            </GeometryModel3D>
          </ModelVisual3D.Content>
        </ModelVisual3D>

        <tools:ScreenSpaceLines3D Points="0,-10,0 0,10,0"
          Thickness="1" Color="Blue">
        </tools:ScreenSpaceLines3D>
        <tools:ScreenSpaceLines3D Points="-10,0,0 10,0,0"
          Thickness="1" Color="Red">
        </tools:ScreenSpaceLines3D>
        <tools:ScreenSpaceLines3D Points="0,0,-10 0,0,10"
          Thickness="1" Color="Green">
        </tools:ScreenSpaceLines3D> 
      </Viewport3D>
      </Border>
    </Grid>

Финальная сцена показана на рисунке:

Трехмерная сцена с одним треугольником

С камерой связано еще несколько важных свойств. Одно из них — FieldOfView, управляющее размером сцены, которую можно видеть сразу. FieldOfView подобно трансфокатору в камере — по мере уменьшения FieldOfView вы видите все меньший участок сцены (который затем увеличивается, заполняя собой Viewport3D). С увеличением FieldOfView будет видна все большая часть сцены.

Однако важно помнить, что изменение поля зрения — это не то же самое, что перемещение камеры ближе или дальше от объектов сцены. Меньшие поля зрения сокращают расстояние между ближними и дальними объектами, в то время как большие усиливают перспективные отличия между ближними и дальними объектами. (Если вы имели дело с реальными камерами, то должны знать об этом эффекте.)

Свойство FieldOfView применяется только к камере PerspectiveCamera. Класс OrthographicCamera включает свойство Width, служащее его аналогом. Свойство Width определяет просматриваемую область, но не изменяет перспективу, поскольку эффект перспективы в OrthographicCamera не используется.

Классы камер также включают свойства NearPlaneDistance и FarPlaneDistance, устанавливающие мертвые зоны камеры. Объекты, находящиеся ближе, чем NearPlaneDistance, не появляются вообще, и объекты, расположенные дальше, чем FarPlaneDistance, также невидимы. Обычно NearPlaneDistance по умолчанию установлено в 0.125, a FarPlaneDistance — в Double.Positivelnfinity. Такие значения этих свойств делают этот эффект практически незаметным.

Однако в некоторых случаях возникает необходимость изменить эти значения, чтобы предотвратить визуализацию артефактов. Наиболее часто встречающийся пример — это когда сложная сетка находится слишком близко к камере, что может привести к z-тушению (z-fighting); также известно под названием совмещения (stitching)). В этой ситуации видеокарта не в состоянии корректно определить, какие треугольники находятся ближе к камере и должны быть отображены. В результате смешиваются разные артефакты поверхности сетки.

Z-тушение обычно случается из-за ошибок округления чисел с плавающей точкой в видеокарте. Во избежание этой проблемы можно увеличить NearPlaneDistance, чтобы отсечь объекты, находящиеся чересчур близко к камере.

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

Пройди тесты
Лучший чат для C# программистов