水平有限,欢迎各位大神指正!
///
关于坐标相互转换的理论部分可以参阅 http://blog.csdn.net/popy007/article/details/1797121
解析vtk下世界坐标系world到显示坐标系下的转换:
首先呢在vtkInteractorObserver中存在函数ComputeWorldToDisplay()函数供其子类调用,源代码如下:
//----------------------------------------------------------------------------
// Description:
// Transform from world to display coordinates.
// displayPt has to be allocated as 3 vector
void vtkInteractorObserver::ComputeWorldToDisplay(double x, double y, double z, double displayPt[3])
{
if ( !this->CurrentRenderer )
{
return;
}
this->ComputeWorldToDisplay(this->CurrentRenderer, x, y, z, displayPt);
}
// Description:
// Transform from world to display coordinates.
// displayPt has to be allocated as 3 vector
void vtkInteractorObserver::ComputeWorldToDisplay(vtkRenderer *ren, double x, double y, double z, double displayPt[3])
{
ren->SetWorldPoint(x, y, z, 1.0);
ren->WorldToDisplay();
ren->GetDisplayPoint(displayPt);
}
///
借助于上边两个函数最终还是靠vtkRenderer来完成世界坐标系向显示坐标系下的转换,在函数中,首先在世界坐标系下定义了一个点(x,y,z,1),然后调用vtkViewport(vtkRenderer的父类)的WorldToDisplay()函数,下边我们解剖这个函数内部功能。
// Description:
// Convert world point coordinates to display (or screen) coordinates.
void WorldToDisplay() {this->WorldToView(); this->ViewToDisplay();};
这个函数先将world坐标系转换为view坐标系,然后再将view坐标系转换为显示坐标系
(1)下边解剖WorldToView()函数
// Convert world point coordinates to view coordinates.
void vtkRenderer::WorldToView()
{
double result[3];
result[0] = this->WorldPoint[0];
result[1] = this->WorldPoint[1];
result[2] = this->WorldPoint[2];
this->WorldToView(result[0], result[1], result[2]);
this->SetViewPoint(result[0], result[1], result[2]);
}
从函数流程可以看到,先获取我们之前设置的那个world世界坐标,然后把它转换为view坐标系下的坐标,并且存储起来,以便在转换为display坐标系的时候使用。我们看这个函数的主要部分:
// Convert world point coordinates to view coordinates.
void vtkRenderer::WorldToView(double &x, double &y, double &z)
{
double mat[16];
double view[4];
// get the perspective transformation from the active camera
if (!this->ActiveCamera)
{
vtkErrorMacro("WorldToView: no active camera, cannot compute world to view, returning 0,0,0");
x = y = z = 0.0;
return;
}
vtkMatrix4x4::DeepCopy(mat, this->ActiveCamera-> GetCompositeProjectionTransformMatrix( this->GetTiledAspectRatio(),0,1));
view[0] = x*mat[0] + y*mat[1] + z*mat[2] + mat[3];
view[1] = x*mat[4] + y*mat[5] + z*mat[6] + mat[7];
view[2] = x*mat[8] + y*mat[9] + z*mat[10] + mat[11];
view[3] = x*mat[12] + y*mat[13] + z*mat[14] + mat[15];
if (view[3] != 0.0)
{
x = view[0]/view[3];
y = view[1]/view[3];
z = view[2]/view[3];
}
}
我们可以看到此函数的重点在于从ActiveCamera中获得CompositeProjectionTransformMatrix,获得此矩阵与原来的世界坐标系相乘之后就可得到view坐标系下的值,下边我们看怎么获得这个矩阵呢?
//
首先呢从函数参数中可知首先得获得窗口的宽高比,通过vtkRenderer的GetTiledAspectRatio()函数
// Description:
// Compute the aspect ratio of this renderer for the current tile. When
// tiled displays are used the aspect ratio of the renderer for a given
// tile may be diferent that the aspect ratio of the renderer when rendered
// in it entirity
double vtkRenderer::GetTiledAspectRatio()
{
int usize, vsize;
this->GetTiledSize(&usize,&vsize);//Get the size of the viewport in display coordinates.
//some renderer subclasses may have more complicated computations for the
// aspect ratio. SO take that into account by computing the difference
// between our simple aspect ratio and what the actual renderer is
// reporting.
double aspect[2];
this->ComputeAspect();
this->GetAspect(aspect);
double aspect2[2];
this->vtkViewport::ComputeAspect();
this->vtkViewport::GetAspect(aspect2);
double aspectModification = aspect[0]*aspect2[1]/(aspect[1]*aspect2[0]);
double finalAspect = 1.0;
if(vsize && usize)
{
finalAspect = aspectModification*usize/vsize;
}
return finalAspect;
}
///
// Description:
// Return the concatenation of the ViewTransform and the ProjectionTransform. This transform will convert world coordinates to viewport coordinates.
//The 'aspect' is the width/height for the viewport, and the nearz and farz are the Z-buffer values that map to the near and far clipping planes.
// The viewport coordinates of a point located inside the frustum are in the range ([-1,+1],[-1,+1],[nearz,farz]).
vtkMatrix4x4 *vtkCamera::GetCompositeProjectionTransformMatrix(double aspect,double nearz,double farz)//其中nearz为0,farz为1
{
// turn off stereo, the CompositeProjectionTransformMatrix is used for
// picking, not for rendering.
int stereo = this->Stereo;
this->Stereo = 0;
this->Transform->Identity();
this->Transform->Concatenate(this->GetProjectionTransformMatrix(aspect,nearz, farz));
this->Transform->Concatenate(this->GetViewTransformMatrix());
this->Stereo = stereo;
// return the transform
return this->Transform->GetMatrix();
}
下边讲解其中的两个重要函数:
(1) // Description:
// Return the projection transform matrix, which converts from camera coordinates to viewport coordinates. The 'aspect' is the
// width/height for the viewport, and the nearz and farz are the Z-buffer values that map to the near and far clipping planes.
// The viewport coordinates of a point located inside the frustum are in the range ([-1,+1],[-1,+1],[nearz,farz]).
實際上 Z buffer 中能存放的數字當然會有一定的限度,所以通常會把 Z 值縮小到 0 ~ 1 的範圍。因此,在繪製 3D 場景時,就會需要把可能出現的 Z 值限制在某個範圍內。通常是用兩個和投影平面平行的平面,把所有超出這兩個平面範圍的三角面都切掉。這兩個平面通常分別稱為 Z near 和 Z far,分別表示較近的平面和較遠的平面。而在 Z near 平面的 Z 值為 0,在 Z far 的 Z 值為 1
vtkMatrix4x4 *vtkCamera::GetProjectionTransformMatrix(double aspect,double nearz,double farz)
//
其中nearz为0,farz为1
{
this->ComputeProjectionTransform(aspect, nearz, farz);
// return the transform
return this->ProjectionTransform->GetMatrix();
}
void vtkCamera::ComputeProjectionTransform(double aspect,
double nearz, double farz)
{
this->ProjectionTransform->Identity();
// apply user defined transform last if there is one
if ( this->UserTransform )//一般为NULL
{
this->ProjectionTransform->Concatenate( this->UserTransform->GetMatrix() );
}
// adjust Z-buffer range
this->ProjectionTransform->AdjustZBuffer( -1, +1, nearz, farz );//
if ( this->ParallelProjection )
{
// set up a rectangular parallelipiped
double width = this->ParallelScale * aspect;//ParallelScale越大最后得到的图像越小
double height = this->ParallelScale;//
,parallelScale代表 the height of the viewport in world-coordinate distances
double xmin = ( this->WindowCenter[0] - 1.0 ) * width;
double xmax = ( this->WindowCenter[0] + 1.0 ) * width;
double ymin = ( this->WindowCenter[1] - 1.0 ) * height;
double ymax = ( this->WindowCenter[1] + 1.0 ) * height;
this->ProjectionTransform->Ortho( xmin, xmax, ymin, ymax,
this->ClippingRange[0],
this->ClippingRange[1] );
}
else if(this->UseOffAxisProjection)
{
this->ComputeOffAxisProjectionFrustum();
}
else
{
// set up a perspective frustum
double tmp = tan( vtkMath::RadiansFromDegrees( this->ViewAngle ) / 2. );
double width;
double height;
if ( this->UseHorizontalViewAngle )
{
width = this->ClippingRange[0] * tmp;
height = this->ClippingRange[0] * tmp / aspect;
}
else
{
width = this->ClippingRange[0] * tmp * aspect;
height = this->ClippingRange[0] * tmp;
}
double xmin = ( this->WindowCenter[0] - 1.0 ) * width;
double xmax = ( this->WindowCenter[0] + 1.0 ) * width;
double ymin = ( this->WindowCenter[1] - 1.0 ) * height;
double ymax = ( this->WindowCenter[1] + 1.0 ) * height;
this->ProjectionTransform->Frustum( xmin, xmax, ymin, ymax,
this->ClippingRange[0],
this->ClippingRange[1] );
}
if ( this->Stereo && !this->UseOffAxisProjection)
{
// set up a shear for stereo views
if ( this->LeftEye )
{
this->ProjectionTransform->Stereo( -this->EyeAngle/2,
this->Distance );
}
else
{
this->ProjectionTransform->Stereo( +this->EyeAngle/2,
this->Distance );
}
}
if ( this->ViewShear[0] != 0.0 || this->ViewShear[1] != 0.0 )
{
this->ProjectionTransform->Shear( this->ViewShear[0],
this->ViewShear[1],
this->ViewShear[2] * this->Distance );
}
}
//----------------------------------------------------------------------------
// Utility for adjusting the min/max range of the Z buffer. Usually
// the oldZMin, oldZMax are [-1,+1] as per Ortho and Frustum, and
// you are mapping the Z buffer to a new range(0,1)
// Description:
// Perform an adjustment to the Z-Buffer range that the near and far
// clipping planes map to. By default Ortho, Frustum, and Perspective
// map the near clipping plane to -1 and the far clipping plane to +1.
// In PreMultiply mode, you call this method before calling Ortho, Frustum,
// or Perspective. In PostMultiply mode you can call it after.
void vtkPerspectiveTransform::AdjustZBuffer(double oldZMin, double oldZMax, double newZMin, double newZMax)//把z值范围(-1,1)映射到(0,1)
{
double matrix[4][4];
vtkMatrix4x4::Identity(*matrix);
matrix[2][2] = (newZMax - newZMin)/(oldZMax - oldZMin);
matrix[2][3] = (newZMin*oldZMax - newZMax*oldZMin)/(oldZMax - oldZMin);
this->Concatenate(*matrix);
}
/
// Create an orthogonal projection matrix and concatenate it by the current transformation.
// The orthographic perspective maps
[xmin,xmax], [ymin,ymax], [-znear,-zfar] to
[-1,+1], [-1,+1], [-1,+1].
void vtkPerspectiveTransform::Ortho(double xmin, double xmax,double ymin, double ymax, double znear, double zfar)//此时znear,zfar为相机裁剪平面的距离
{
double matrix[4][4];
vtkMatrix4x4::Identity(*matrix);
matrix[0][0] = 2/(xmax - xmin);
matrix[1][1] = 2/(ymax - ymin);
matrix[2][2] = -2/(zfar - znear);
matrix[0][3] = -(xmin + xmax)/(xmax - xmin);
matrix[1][3] = -(ymin + ymax)/(ymax - ymin);
matrix[2][3] = -(znear + zfar)/(zfar - znear);
this->Concatenate(*matrix);
}
(2)
// Description:
// For backward compatibility. Use GetModelViewTransformMatrix() now.
// Return the matrix of the view transform.
// The ViewTransform depends on only three ivars: the Position, the
// FocalPoint, and the ViewUp vector. All the other methods are there
// simply for the sake of the users' convenience.
vtkMatrix4x4 *vtkCamera::GetViewTransformMatrix()
{
return this->GetModelViewTransformMatrix();
}
(2)下边解剖ViewtoDisplay()函数
//----------------------------------------------------------------------------
// Convert view coordinates to display coordinates.
void vtkViewport::ViewToDisplay()
{
if ( this->VTKWindow )
{
double dx,dy;
int sizex,sizey;
int *size = NULL;
/* get physical window dimensions */
size = this->VTKWindow->GetSize();
if (!size)
{
return;
}
sizex = size[0];
sizey = size[1];
dx = (this->ViewPoint[0] + 1.0) *
(sizex*(this->Viewport[2]-this->Viewport[0])) / 2.0 +
sizex*this->Viewport[0];
dy = (this->ViewPoint[1] + 1.0) *
(sizey*(this->Viewport[3]-this->Viewport[1])) / 2.0 +
sizey*this->Viewport[1];
this->SetDisplayPoint(dx,dy,this->ViewPoint[2]);
}
}
加黑的部分其实就是一个线性插值的过程
假设display坐标系下x坐标为dis,view坐标系下的坐标为view,则有下列关系:
(view-(-1))/(1-(-1))=(dis-sizex*viewport[0])/sizex*(viewport[2]-viewport[0]);
经过变换即可得到上述 的dx值,同理dy也可以这样得到