Pergunta

I have the following image which I need to display in a Viewport3D:

enter image description here

The image texture is centered at (0,0) and its corner coordinates are (-1,-1,0), (1,-1,0),(-1,1,0),(1,1,0).

Since I am using PerspectiveCamera with a fixed field of view, I have to compute sufficient distance to put entire image into view:

enter image description here

Image is represented by a blue line, w is image width (w=2).

Camera position is (0,0,d) and hence a triangle is formed:

tan(fov/2) = (w/2) / d

d = (w/2) / tan(fov/2)

Now I put together XAML code for the 3D model and code-behing for computing camera distance:

XAML

<Window x:Class="Render3DTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="256" Width="256" Loaded="Window_Loaded">
    <Grid>

        <Viewport3D Name="viewport">

            <Viewport3D.Camera>
                <PerspectiveCamera Position="0,0,1" LookDirection="0,0,-1" FieldOfView="90" />
            </Viewport3D.Camera>

            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <AmbientLight/>
                </ModelVisual3D.Content>
            </ModelVisual3D>

            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <GeometryModel3D>
                        <GeometryModel3D.Material>
                            <DiffuseMaterial>
                                <DiffuseMaterial.Brush>
                                    <ImageBrush ImageSource="image.jpg"/>
                                </DiffuseMaterial.Brush>
                            </DiffuseMaterial>
                        </GeometryModel3D.Material>
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D Positions="-1,1,0 -1,-1,0 1,-1,0 1,1,0" TextureCoordinates="0,0 0,1 1,1 1,0" TriangleIndices="0 1 2, 0 2 3" />
                        </GeometryModel3D.Geometry>
                    </GeometryModel3D>
                </ModelVisual3D.Content>
            </ModelVisual3D>

        </Viewport3D>

    </Grid>
</Window>

code-behind

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    PerspectiveCamera camera = (PerspectiveCamera)this.viewport.Camera;

    double d = (1.0 / Math.Tan(camera.FieldOfView / 2.0));

    camera.Position = new Point3D(0.0, 0.0, d);
}

However, the Viewport3D does not display entire image:

enter image description here

Is there some other factor playing role? I don't want to use any tweaks or fudge factors in my computation.

Please note that resizing the window does not have effect on horizontal range of view as this is determined by camera FOV and its distance, so the problem does not have anything to do with control size - it is related to how WPF projects points from 3D to 2D.

Foi útil?

Solução 2

I figured out one possible solution making use of the linearity of the perspective camera.

  1. Set camera to a known distance (i.e. 1.0)
  2. Compute 2D bounding box of the model using Visual3D.TransformToAncestor
  3. Compute scale factor (bounding box size / viewport size)
  4. Multiply camera distance by the scale factor

In other words, if the camera is twice further, the image is twice smaller...

PerspectiveCamera camera = (PerspectiveCamera)this.viewport.Camera;

// set camera to a known distance
camera.Position = new Point3D(0.0, 0.0, 100.0);

Point3D[] points3D = new[]
{
    new Point3D(-1.0, -1.0, 0.0),
    new Point3D(1.0, -1.0, 0.0),
    new Point3D(-1.0, 1.0, 0.0),
    new Point3D(1.0, 1.0, 0.0)
};

double minX = Double.MaxValue;
double maxX = Double.MinValue;
double minY = Double.MaxValue;
double maxY = Double.MinValue;

GeneralTransform3DTo2D transform = this.viewport.Children[1].TransformToAncestor(this.viewport);

foreach (var point3D in points3D)
{
    Point point2D = transform.Transform(point3D);

    minX = Math.Min(minX, point2D.X);
    maxX = Math.Max(maxX, point2D.X);

    minY = Math.Min(minY, point2D.Y);
    maxY = Math.Max(maxY, point2D.Y);
}

Size currentSize = new Size(maxX - minX, maxY - minY);
Size desiredSize = new Size(this.viewport.ActualWidth, this.viewport.ActualHeight);

double scaleFactor = Math.Max(
    currentSize.Width / desiredSize.Width,
    currentSize.Height / desiredSize.Height);

camera.Position = new Point3D(0.0, 0.0, 100.0 * scaleFactor); // the known distance of 100.0 is multiplied by scaleFactor

Outras dicas

You are on the right track. PerspectiveCamera field of view (fov) is based on the Viewport3D width and tan(fov/2) accepts fov in radians. So camera z is:

    double fieldOfViewInRadians = perspectiveCamera.FieldOfView * (Math.PI / 180.0);
    var z = (0.5 * _viewport3D.Width) / Math.Tan(0.5 * fieldOfViewInRadians);
    perspectiveCamera.Position = new Point3D(0.0, 0.0, z);

Ensure that Control Panel > Display is set to Small 100%. There's a section which allows you to set Small (100%), Medium, and Large scaling. That also has an impact.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top