Mapping an image on sphere and getting back the unwrapped equirectangular projection
I'm trying to achieve the following task:
Imagine having a 2:1 aspect ratio drawing canvas. I want to place rectangular images on this canvas (..x,y Cartesian coordinates) and as I move the image around further away from the canvas center (0,0) I want to get the distortion to the image as if it was sliding over the imaginary sphere's normals.
The logic I believe is to convert the (x,y) 2D Cartesian c开发者_Python百科oordinates of the image to the corresponding latitude, longitude spherical coordinates, and then apply the equirectangular projection to the sphere and return the image with the corresponding distortion (as if it's uvs where unwrapped in a 3D modeling package).
There is an application doing exactly that called "HDR Light Studio" which generates equirectangular (lat/long) HDR images for 3D scene lighting purposes. If you check out the videos on their site you will understand exactly what kind of image distortion I'm trying to achieve.
Please give me some help over the subject if you can. I have searched all around the net for info but no luck.
Thanks in advance
If I understood the question correctly you should be able to achieve the same effect by creating part of a sphere and using the image as a texture. Instead of moving the picture you rotate the sphere.
Code and XAML of the main window:
namespace WpfBalls
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Ball ball = new Ball();
ball.ImageSource = "YourPictureHere.jpg"; //path to the picture
visualModel.Children.Add(ball);
}
}
}
<Window x:Class="WpfBalls.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfBalls"
Title="3D WpfBalls" Height="400" Width="600">
<Grid>
<Grid.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Grid.Background>
<Viewport3D x:Name="viewPort" Grid.Column="0" Grid.Row="0" ClipToBounds="False">
<Viewport3D.Camera>
<PerspectiveCamera x:Name="camera" Position="0,0,-8"
UpDirection="0,1,0" LookDirection="0,0,1"
FieldOfView="25" NearPlaneDistance="0.125"/>
</Viewport3D.Camera>
<Viewport3D.Children>
<ModelVisual3D>
<ModelVisual3D.Content>
<AmbientLight Color="White" />
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D x:Name="visualModel">
<ModelVisual3D.Transform>
<Transform3DGroup>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D x:Name="rotationY"
Angle="{Binding ElementName=sliderY,Path=Value}" Axis="0,1,0" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D x:Name="rotationX"
Angle="{Binding ElementName=sliderX,Path=Value}" Axis="1,0,0" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
</Transform3DGroup>
</ModelVisual3D.Transform>
</ModelVisual3D>
</Viewport3D.Children>
</Viewport3D>
<Slider Height="23" HorizontalAlignment="Left" Margin="25,23,0,0" Name="sliderX" Minimum="-70" Maximum="70" Value="0" VerticalAlignment="Top" Width="100" />
<Slider Height="86" HorizontalAlignment="Left" Margin="25,52,0,0" Name="sliderY" Minimum="110" Maximum="250" Value="180" VerticalAlignment="Top" Width="27" Orientation="Vertical" />
</Grid>
</Window>
Code of the Ball class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Media3D;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace WpfBalls
{
public class Ball : ModelVisual3D
{
public Ball()
{
this.Content = new GeometryModel3D();
(this.Content as GeometryModel3D).Geometry = Tessellate();
}
static double DegToRad(double degrees)
{
return (degrees / 180.0) * Math.PI;
}
internal Point3D GetPosition(double t, double y)
{
double r = Math.Sqrt(1 - y * y);
double x = r * Math.Cos(t);
double z = r * Math.Sin(t);
return new Point3D(x, y, z);
}
private Vector3D GetNormal(double t, double y)
{
return (Vector3D)GetPosition(t, y);
}
private Point GetTextureCoordinate(double t, double y)
{
Matrix TYtoUV = new Matrix();
TYtoUV.Scale(1 / (2 * Math.PI), -0.5);
Point p = new Point(t, y);
p = p * TYtoUV;
return p;
}
public string ImageSource
{
set {
DiffuseMaterial dm = new DiffuseMaterial();
ImageSource imSrc = new
BitmapImage( new Uri( value, UriKind.RelativeOrAbsolute ) );
dm.Brush = new ImageBrush( imSrc );
(this.Content as GeometryModel3D).Material = dm;
}
}
public Point3D Offset
{
set {
this.Transform = new
TranslateTransform3D(value.X, value.Y, value.Z);
}
}
internal Geometry3D Tessellate()
{
int tDiv =750;
int yDiv = 750;
double maxTheta = DegToRad(360);
double minY = -1.0;
double maxY = 1.0;
double dt = maxTheta / tDiv;
double dy = (maxY - minY) / yDiv;
MeshGeometry3D mesh = new MeshGeometry3D();
for (int yi = 0; yi <= yDiv; yi++)
{
double y = minY + yi * dy;
for (int ti = 0; ti <= tDiv; ti++)
{
double t = ti * dt;
var p = GetPosition(t, y);
if (p.Z > 0 && p.X > -.5 && p.X < .5 && p.Y > -.5 && p.Y < .5)
{
mesh.Positions.Add(p);
mesh.Normals.Add(GetNormal(t, y));
mesh.TextureCoordinates.Add(GetTextureCoordinate(t, y));
}
}
}
for (int yi = 0; yi < yDiv; yi++)
{
for (int ti = 0; ti < tDiv; ti++)
{
int x0 = ti;
int x1 = (ti + 1);
int y0 = yi * (tDiv + 1);
int y1 = (yi + 1) * (tDiv + 1);
mesh.TriangleIndices.Add(x0 + y0);
mesh.TriangleIndices.Add(x0 + y1);
mesh.TriangleIndices.Add(x1 + y0);
mesh.TriangleIndices.Add(x1 + y0);
mesh.TriangleIndices.Add(x0 + y1);
mesh.TriangleIndices.Add(x1 + y1);
}
}
mesh.Freeze();
return mesh;
}
}
}
The code is based on this example: http://www.codegod.de/WebAppCodeGod/wpf-3d-animations-and-textures-AID439.aspx
精彩评论