skia库的3D坐标变换其实也是无奈之举。参照SKCamera.cpp:

首先,定义了一个虚拟相机:

void SkCamera3D::reset() {
fLocation.set(0, 0, -SkIntToScalar(576));   // 8 inches backward
fAxis.set(0, 0, SK_Scalar1);                // forward
fZenith.set(0, -SK_Scalar1, 0);             // up
fObserver.set(0, 0, fLocation.fZ);
fNeedToUpdate = true;
}

求出相机的投影矩阵。这个代码里面叫做方位(orientation)

这个方位矩阵完成了从3D空间到2D空间的投影,

这个方法研究了很久,具体分析见注释,不多讲了。

相当于OpenGL的glFrustum函数。代码如下:

void SkCamera3D::doUpdate() const {
SkUnit3D    axis, zenith, cross;
fAxis.normalize(&axis);
{
SkScalar dot = SkUnit3D::Dot(*(const SkUnit3D*)(const void*)&fZenith, axis);
zenith.fX = fZenith.fX - SkUnitScalarMul(dot, axis.fX);
zenith.fY = fZenith.fY - SkUnitScalarMul(dot, axis.fY);
zenith.fZ = fZenith.fZ - SkUnitScalarMul(dot, axis.fZ);
(void)((SkPoint3D*)(void*)&zenith)->normalize(&zenith);
}
/*
[-z, 0, x]   [cross.fX,     cross.fY,       cross.fZ]
[0, -z, y]* [zenith.fx,     zenith.fY,  zenith.fZ]
[0,  0,  1]  [axis.fx,  axis.fY,        axis.fZ]
下面这个矩阵式是投影矩阵:
[-z, 0, x]
[0, -z, y]
[0,  0,  1]
投影到x,y z位置的投影矩阵,其中x, y恒为0。如果不为0,经推算,
这个矩阵是有错误的,用三角形相似,可以算出,应该是下面这个样子
[-z, 0, x-zx]
[0, -z, y-zy]
[0,  0,  1]
*/
SkUnit3D::Cross(axis, zenith, &cross);
{
SkMatrix* orien = &fOrientation;
SkScalar x = fObserver.fX;
SkScalar y = fObserver.fY;
SkScalar z = fObserver.fZ;
orien->set(SkMatrix::kMScaleX, SkUnitScalarMul(x, axis.fX) - SkUnitScalarMul(z, cross.fX));
orien->set(SkMatrix::kMSkewX,  SkUnitScalarMul(x, axis.fY) - SkUnitScalarMul(z, cross.fY));
orien->set(SkMatrix::kMTransX, SkUnitScalarMul(x, axis.fZ) - SkUnitScalarMul(z, cross.fZ));
orien->set(SkMatrix::kMSkewY,  SkUnitScalarMul(y, axis.fX) - SkUnitScalarMul(z, zenith.fX));
orien->set(SkMatrix::kMScaleY, SkUnitScalarMul(y, axis.fY) - SkUnitScalarMul(z, zenith.fY));
orien->set(SkMatrix::kMTransY, SkUnitScalarMul(y, axis.fZ) - SkUnitScalarMul(z, zenith.fZ));
orien->set(SkMatrix::kMPersp0, axis.fX);
orien->set(SkMatrix::kMPersp1, axis.fY);
orien->set(SkMatrix::kMPersp2, axis.fZ);
}
}

然后在patchToMatrix方法中,使用此投影矩阵对当前的变换UVO坐标系进行了变换,代码如下:

void SkCamera3D::patchToMatrix(const SkPatch3D& quilt, SkMatrix* matrix) const {
if (fNeedToUpdate) {
this->doUpdate();
fNeedToUpdate = false;
}
const SkScalar* mapPtr = (const SkScalar*)(const void*)&fOrientation;
const SkScalar* patchPtr;
SkPoint3D       diff;
SkScalar        dot;
diff.fX = quilt.fOrigin.fX - fLocation.fX;
diff.fY = quilt.fOrigin.fY - fLocation.fY;
diff.fZ = quilt.fOrigin.fZ - fLocation.fZ;
dot = SkUnit3D::Dot(*(const SkUnit3D*)(const void*)&diff,
*(const SkUnit3D*)(((const SkScalar*)(const void*)&fOrientation) + 6));
//  patchPtr的结构为{U,V,ORIGIN}其中U,V 代表列向量
// ORIGIN 是坐标原点
patchPtr = (const SkScalar*)&quilt;
/*
其中matrix表示一个3x3的矩阵
第一行代表U的系数,第二行是V的系数,第三行是diff 的系数
matrix 的每一列代表的是一个坐标轴。
*/
//第一列
matrix->set(SkMatrix::kMScaleX, SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot));
matrix->set(SkMatrix::kMSkewY,  SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot));
matrix->set(SkMatrix::kMPersp0, SkScalarDotDiv(3, patchPtr, 1, mapPtr+6, 1, dot));
//第二列
patchPtr += 3;
matrix->set(SkMatrix::kMSkewX,  SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot));
matrix->set(SkMatrix::kMScaleY, SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot));
matrix->set(SkMatrix::kMPersp1, SkScalarDotDiv(3, patchPtr, 1, mapPtr+6, 1, dot));
//第三列
patchPtr = (const SkScalar*)(const void*)&diff;
matrix->set(SkMatrix::kMTransX, SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot));
matrix->set(SkMatrix::kMTransY, SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot));
matrix->set(SkMatrix::kMPersp2, SK_UnitScalar1);
}

到此为止,也就把这个变换的思路给理解清楚了。跟OpenGL的思路类似。

第一。进行模型视图变换,通过一个patch的东西,UVO坐标系。

第二。进行投影变换。定位相机,具体参照如下函数:

 
 /*
由于x,y,z 与cross的点积,就是在cross方向上的投影。因此,对于x,y,z向量来说。
从世界坐标转换到相机坐标系的矩阵就是
[cross.fX,  cross.fY,       cross.fZ]
[zenith.fx,     zenith.fY,  zenith.fZ]
[axis.fx,     axis.fY,        axis.fZ]
由于是标准正交基,
他的转置或者说,逆矩阵,就是相机矩阵
[cross.fX,     zenith.fx,     axis.fx]
[cross.fY,   zenith.fY,  axis.fY]
[ cross.fZ ,zenith.fZ, axis.fZ]
[-z, 0, x]   [cross.fX,     cross.fY,       cross.fZ]
[0, -z, y]* [zenith.fx,     zenith.fY,  zenith.fZ]
[0,  0,  1]  [axis.fx,  axis.fY,        axis.fZ]
下面这个矩阵式是投影矩阵:
[-z, 0, x]
[0, -z, y]
[0,  0,  1]
*/

增加一些注释

下面增加一些系统介绍

SKCamera3D中关于模拟3D的算法研究

SkCamera是一个可以支持3D变换的组件,通过一些矩阵变换,最终获得了3D的效果。与OpenGL的思路基本相同,分为两个矩阵,一个是投影矩阵,一个是模型视图矩阵。

下面就这两个矩阵,并结合SkCamera的代码进行讲解。

1.       投影矩阵。

投影矩阵是通过SkCamera3D这个类进行生成和管理。SkCamera3D这个的作用还在于将一个SkPatch3D的一个坐标系转换为投影后的坐标系,通过patchToMatrix完成。

总而言之,这个类负责投影的相关工作。

class SkCamera3D {

public:

SkCamera3D();

void reset();

void update();

void patchToMatrix(const SkPatch3D&, SkMatrix* matrix) const;

SkPoint3D   fLocation;

SkPoint3D   fAxis;

SkPoint3D   fZenith;

SkPoint3D   fObserver;

private:

mutable SkMatrix    fOrientation;

mutable bool        fNeedToUpdate;

void doUpdate() const;

};

2.       基本投影算法

世界坐标系如图所示

X

Z

Y

O

相机方向

相机顶的方向

然后在这个世界坐标系中,根据想要的投影方式,初始化了一个相机(类似于OpenGL中的glFrustum函数)。相机顶朝下,相机的摄像头朝着屏幕里面。

对应SkCamera.cpp的代码如下:

void SkCamera3D::reset() {

fLocation.set(0, 0, -SkIntToScalar(576));   // 8 inches backward

fAxis.set(0, 0, SK_Scalar1);                // forward

fZenith.set(0, -SK_Scalar1, 0);             // up

fObserver.set(0, 0, fLocation.fZ);

fNeedToUpdate = true;

}

然后根据相机的位置获得一个投影矩阵。

公式如下:

【相机投影矩阵】*【相机世界矩阵】

根据2维齐次坐标的几何意义,也就是在Z=1平面上的投影。现在变为在Z=-z平面上的投影。具体参照如下示意图:这个图也展示了二维齐次坐标系的几何意义(投影+映射):

Z=0

P

Z=1

Z=-z

-z, 0, x

0, -z, y

0,    0, 1

具体可以验证这个,假设x’,y’,z’

那么,可得:

X’’ = -x’z + xz’

Y’’ = -y’z + yz’

Z’’ =  z’

进而得到了相机的坐标

X’’ = -x’z/z’ + x

Y’’ = -y’z/z’ + y

Z’’ =  1

另外,从三角形相似原理可以知道:

X’’ = -x’z/z’ 和Y’’ = -y’z/z’刚好把坐标刚好映射到了z平面上。然后进行平移,再加上相机的位置x,y 从而得到如下公式:

X’’ = -x’z/z’ + x

Y’’ = -y’z/z’ + y

这两个公式是相同的,从而验证了映射的正确性。

下面就是投影矩阵的具体实现方法:

void SkCamera3D::doUpdate() const {

SkUnit3D    axis, zenith, cross;

fAxis.normalize(&axis);

{

SkScalar dot = SkUnit3D::Dot(*(const SkUnit3D*)(const void*)&fZenith, axis);

zenith.fX = fZenith.fX - SkUnitScalarMul(dot, axis.fX);

zenith.fY = fZenith.fY - SkUnitScalarMul(dot, axis.fY);

zenith.fZ = fZenith.fZ - SkUnitScalarMul(dot, axis.fZ);

(void)((SkPoint3D*)(void*)&zenith)->normalize(&zenith);

}

SkUnit3D::Cross(axis, zenith, &cross);

{

SkMatrix* orien = &fOrientation;

SkScalar x = fObserver.fX;

SkScalar y = fObserver.fY;

SkScalar z = fObserver.fZ;

orien->set(SkMatrix::kMScaleX, SkUnitScalarMul(x, axis.fX) - SkUnitScalarMul(z, cross.fX));

orien->set(SkMatrix::kMSkewX,  SkUnitScalarMul(x, axis.fY) - SkUnitScalarMul(z, cross.fY));

orien->set(SkMatrix::kMTransX, SkUnitScalarMul(x, axis.fZ) - SkUnitScalarMul(z, cross.fZ));

orien->set(SkMatrix::kMSkewY,  SkUnitScalarMul(y, axis.fX) - SkUnitScalarMul(z, zenith.fX));

orien->set(SkMatrix::kMScaleY, SkUnitScalarMul(y, axis.fY) - SkUnitScalarMul(z, zenith.fY));

orien->set(SkMatrix::kMTransY, SkUnitScalarMul(y, axis.fZ) - SkUnitScalarMul(z, zenith.fZ));

orien->set(SkMatrix::kMPersp0, axis.fX);

orien->set(SkMatrix::kMPersp1, axis.fY);

orien->set(SkMatrix::kMPersp2, axis.fZ);

}

}

3.       模型变换矩阵的生成。

模型变换是通过如下结构体完成的。

struct SkMatrix3D {

SkScalar    fMat[3][4];

void reset();

void setRow(int row, SkScalar a, SkScalar b, SkScalar c, SkScalar d = 0)

{

SkASSERT((unsigned)row < 3);

fMat[row][0] = a;

fMat[row][1] = b;

fMat[row][2] = c;

fMat[row][3] = d;

}

void setRotateX(SkScalar deg);

void setRotateY(SkScalar deg);

void setRotateZ(SkScalar deg);

void setTranslate(SkScalar x, SkScalar y, SkScalar z);

void preRotateX(SkScalar deg);

void preRotateY(SkScalar deg);

void preRotateZ(SkScalar deg);

void preTranslate(SkScalar x, SkScalar y, SkScalar z);

void setConcat(const SkMatrix3D& a, const SkMatrix3D& b);

void mapPoint(const SkPoint3D& src, SkPoint3D* dst) const;

void mapVector(const SkVector3D& src, SkVector3D* dst) const;

void mapPoint(SkPoint3D* v) const

{

this->mapPoint(*v, v);

}

void mapVector(SkVector3D* v) const

{

this->mapVector(*v, v);

}

};

这样得到的矩阵式3D世界的3*4的矩阵。而投影矩阵只能对3*3的矩阵进行变换。而patch构成的坐标系却是3*3的。无法相乘。那么程序如何实现patch从3*4 到 3*3的一个转换呢。

下面是推导过程:

Ux    Vx     Nx    Dx

Uy    Vy     Ny    Dy

Uz    Vz     Nz    Dz

0       0       0       1

这个就是patch坐标系对应的3D世界的变换矩阵。其中U,V,N为基向量。D为坐标原点。

我们称之为D-UVN坐标系。

最重要的信息是,在D-UVN坐标系中,N轴上的值横为0.因为一开始,一个坐标x,y一定是平面的。因此,只需要求出对【x,y, 0, 1】的一个变换就行了。

可以得出:

x‘= Ux*x + Vx*y + Dx

y’ =Uy*x + Vy*y + Dy

z’=Uz*x + Vz*y + Dz

也就是如下矩阵:

Ux    Vx     Dx

Uy    Vy     Dy

Uz    Vz     Dz

代码如下:

void SkCamera3D::patchToMatrix(const SkPatch3D& quilt, SkMatrix* matrix) const {

    if (fNeedToUpdate) {

        this->doUpdate();

        fNeedToUpdate = false;

    }

 

    const SkScalar* mapPtr = (const SkScalar*)(const void*)&fOrientation;

    const SkScalar* patchPtr;

    SkPoint3D       diff;

    SkScalar        dot;

 

    diff.fX = quilt.fOrigin.fX - fLocation.fX;

    diff.fY = quilt.fOrigin.fY - fLocation.fY;

    diff.fZ = quilt.fOrigin.fZ - fLocation.fZ;

 

    dot = SkUnit3D::Dot(*(const SkUnit3D*)(const void*)&diff,

                        *(const SkUnit3D*)(((const SkScalar*)(const void*)&fOrientation) + 6));

 

    patchPtr = (const SkScalar*)&quilt;

    matrix->set(SkMatrix::kMScaleX, SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot));

    matrix->set(SkMatrix::kMSkewY,  SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot));

    matrix->set(SkMatrix::kMPersp0, SkScalarDotDiv(3, patchPtr, 1, mapPtr+6, 1, dot));

 

    patchPtr += 3;

    matrix->set(SkMatrix::kMSkewX,  SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot));

    matrix->set(SkMatrix::kMScaleY, SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot));

    matrix->set(SkMatrix::kMPersp1, SkScalarDotDiv(3, patchPtr, 1, mapPtr+6, 1, dot));

 

    patchPtr = (const SkScalar*)(const void*)&diff;

    matrix->set(SkMatrix::kMTransX, SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot));

    matrix->set(SkMatrix::kMTransY, SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot));

    matrix->set(SkMatrix::kMPersp2, SK_UnitScalar1);

}

 

 

 

 

 

总结:

   Sk3DCamera提供了一种方法,可以对图形进行仿3D变换,这对于想在2D空间进行3D处理的程序来说,是一个不错的选择。

skia库的3D变换研究相关推荐

  1. 前沿 | NVIDIA PyTorch库让3D深度学习研究更简单!

    点上方蓝字计算机视觉联盟获取更多干货 在右上方 ··· 设为星标 ★,与你不见不散 编辑:Sophia 计算机视觉联盟  报道  | 公众号 CVLianMeng 转载于 :英伟达NVIDIA [人工 ...

  2. Facebook 3D视觉研究最新进展

    点击上方"深度学习技术前沿",选择"星标"公众号 资源干货,第一时间送达 本文转载自机器之心 Facebook 的博客详细介绍了其在 3D 内容理解领域的研究进 ...

  3. [OpenGL ES 03]3D变换:模型,视图,投影与Viewport

    [OpenGL ES 03]3D变换:模型,视图,投影与Viewport 罗朝辉 (http://blog.csdn.net/kesalin) 本文遵循"署名-非商业用途-保持一致" ...

  4. Qt 3D的研究(三):显示3D模型

    原文地址::https://blog.csdn.net/gamesdev/article/details/43964499 相关文章 1.Qt之实现3D纹理渲染自由旋转空间立方体----https:/ ...

  5. BIM族库下载——3D乔木植物族库

    [资源介绍] 资源名称:BIM族库下载--3D乔木植物族库 资源分类: BIM族库.Revit族库 其他简介:3D乔木植物族库 [资源下载] 链接:https://pan.baidu.com/s/1N ...

  6. Windows UWP开发系列 – 3D变换

    在Win8.1中,引入了一个PlaneProjection可以实现3D变换,但它的变换方式比较简单,只能实现基本的旋转操作.在Windows 10 UWP中,引入了一个更加强大的3D变换Transfo ...

  7. html元素做3d变换,CSS 3D变换

    1.3D transform中有下面这三个方法: rotateX( angle ) rotateY( angle ) rotateZ( angle ) 意思是分别绕着X/Y/Z轴进行旋转. 学过一部分 ...

  8. python绘制3d图-python3利用Axes3D库画3D模型图

    Python3利用Axes3D库画3D模型图,供大家参考,具体内容如下 最近在学习机器学习相关的算法,用python实现.自己实现两个特征的线性回归,用Axes3D库进行建模. python代码 im ...

  9. 背水一战 Windows 10 (70) - 控件(控件基类): UIElement - Transform3D(3D变换), Projection(3D投影)...

    原文:背水一战 Windows 10 (70) - 控件(控件基类): UIElement - Transform3D(3D变换), Projection(3D投影) [源码下载] 背水一战 Wind ...

最新文章

  1. 区块链论文:Byzcoin,通过集体签名让比特币具有强一致性且强化安全
  2. idea 新建的java项目没发run_IDEA 如何创建一个普通的 Java 项目,及创建 Java 文件并运行...
  3. 网页检测不到java无法打印_如果PC连接到网络打印机,如何检查java?
  4. 数据预处理之归一化/标准化/正则化/零均值化
  5. 使用RemObjects Pascal Script
  6. c语言node类型_高阶宏的妙用技法,C语言宏你所不知道的聪明技巧
  7. WindowsXamlHost:在 WPF 中使用 UWP 的控件(Windows Community Toolkit)
  8. C语言常用库函数(含详细用法))
  9. 计算机4000字论文格式,科学论文格式要求4000字
  10. Python金融数据挖掘 第11章 复习思考题3 某年各省级行政区环境污染状况的统计数据(已经过标准化处理),现采用K均值聚类方法,编写Python程序将省级行政区分成4类。
  11. 非极大值抑制(Non-Maximum-Suppression)
  12. Vue中将十六进制颜色格式转换为RGB格式
  13. MYSQL 安装时出现的问题error: Failed dependencies
  14. 【unity 3d】--- 瞄准镜效果
  15. 简单介绍迪杰斯拉Dijkstra算法步骤
  16. 初识自定义View-View的弹性滑动
  17. HTML+CSS+JS 科学计算器apk+源码
  18. MySQL连接Navicat
  19. MRP专题五:例外消息(Exception message)
  20. idea导入项目问题:No implementation for org.apache.maven.model.path.PathTranslator was bound.

热门文章

  1. fibonacci数列前20项_等差数列、等比数列、调和数列等几种常见数列的总结
  2. php switch换界面,php switch的“高级”用法详解
  3. 2015计算机二级java真题_2015年计算机二级《JAVA》章节习题及答案(9)
  4. mysql 连接 优化_(一)MySQL 连接优化
  5. C++ string类型与数值型变量的相互转换
  6. Python进阶3——列表解析式和生成器表达式
  7. 错误LNK1107文件无效或损坏: 无法在 0x338 处读取
  8. python 日志 装饰器_【Python】装饰器实现日志记录
  9. php bootstraptable分页,Bootstrap table分页问题汇总【附答案代码】
  10. 为了探究不同光照处理_浅谈中考物理实验探究易错题