`
daojin
  • 浏览: 693985 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

skia库的3D变换研究

 
阅读更多

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平面上。然后进行平移,再加上相机的位置xy 从而得到如下公式:

 

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的。无法相乘。那么程序如何实现patch3*4 3*3的一个转换呢。

 

下面是推导过程:

Ux    Vx     Nx    Dx

Uy    Vy     Ny    Dy

Uz    Vz     Nz    Dz

0       0       0       1

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

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

 

最重要的信息是,在D-UVN坐标系中,N轴上的值横为0.因为一开始,一个坐标xy一定是平面的。因此,只需要求出对【xy 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 编译好的库(windows)

    Skia是一个开源的2D图形库,由Google开发并维护,它被广泛应用于各种平台,包括Android、Chrome、Chromium以及其他许多软件项目中。Skia提供了丰富的2D图形API,支持矢量图形、文本渲染、图像处理等功能,使得开发者...

    Skia静态库-自己生成的

    自己生成的skia静态库,包括core的全部

    Skia图形库SDK,带头文件和静态lib(x86)解压共1.8GB

    Skia图形库SDK是一款强大的开源2D图形处理库,由Google开发并维护,广泛应用于Android、Chrome、Chrome OS以及各种嵌入式系统等项目中。它提供了丰富的API,支持多种平台,包括Windows、Linux、MacOS等。SDK中的...

    skia for windows 静态库

    Skia是一款开源的2D图形处理库,由Google开发并维护,广泛应用于多个平台,包括Windows。在Windows上,Skia库提供了丰富的图形绘制功能,适用于各种应用程序的开发,如浏览器、游戏、图像编辑工具等。它支持矢量图形...

    skia库-linux

    skia库,包含主要的类和功能。

    .net UI类..基于skia库形图的ppc类

    .NET UI 类库是用于构建用户界面的框架,而“基于Skia库形图的ppc类”则是在掌上电脑(PPC)平台上利用Skia图形库开发的UI组件。Skia是一个开源的2D图形处理库,由Google维护,被广泛应用于Android、Chrome、Firefox...

    skia描画库实例

    **Skia描画库实例** Skia是一款强大的2D图形处理库,由Google开发并开源。它被广泛应用于Android、Chrome、PDFium等项目,提供跨平台的高性能图形渲染能力。在本文中,我们将深入探讨如何在Eclipse环境中,结合NDK...

    skia.rar(2D图形库)

    Skia提供了丰富的API,支持多种硬件加速技术,以实现高效的图形绘制,包括直线、曲线、文本、图像处理以及复杂的图形变换。它的设计目标是跨平台兼容性,因此在不同的操作系统上都能够保持一致的性能和渲染效果。 ...

    Windows环境VS2017编译skia库-m84之已编译的库文件等

    Windows环境VS2017编译skia库-m84之已编译的库文件等 附件里面有已编译的x64和x86的库文件和头文件; 还有若干demo案例,有控制台的案例,也有和Qt结合的案例。 编译教程博客:...

    Google Skia图形库MingW, MSVC2017 64Bit编译库

    Google Skia图形库是一款强大的2D图形处理引擎,由Google开发并开源,广泛应用于Chrome、Android等项目。它提供高效、高性能的图形渲染能力,支持矢量图形、文本布局、图像滤镜等功能,且跨平台兼容性优秀。本文将...

    skia研究文档

    skia是个2D向量图形处理函数库,包含字型、坐标转换,以及点阵图都有高效能且简洁的表现。不仅用于Google Chrome浏览器,新兴的Android开放手机平台也采用skia作为绘图处理,搭配OpenGL/ES与特定的硬件特征,强化...

    Google Skia 2D渲染引擎 最新版本 编译库

    Google Skia是一个开源的2D图形处理库,由Google开发并维护,主要用于构建高性能的图形渲染系统。它被广泛应用于Android、Chrome和其他Google产品中,提供了丰富的2D图形API,支持矢量图形、像素操作、文本渲染以及...

    Skia绘图教程

    Skia是一款功能强大且高效的2D向量图形处理函数库,广泛应用于Google Chrome浏览器和Android开放手机平台等领域。Skia提供了字型、坐标转换、点阵图等多种图形处理功能,并且具有高效能且简洁的表现。 Skia绘图教程...

    Windows环境VS2017编译skia库-m84,亲测成功,使用官方编译的方法

    Windows环境VS2017编译skia库-m84,亲测成功,使用官方编译的方法。 附件是配套资源:gn和ninja工具 编译教程博客:https://libaineu2004.blog.csdn.net/article/details/106175625

    skia所需zlib静态库

    skia zlib 静态库

    android skia

    由于 Skia 的源代码开放,开发者可以深入研究并利用其强大的功能来实现自定义的图形绘制。 Skia 引擎的核心组件主要包括 SkCanvas、SkBitmap 和 SkPaint。首先,SkCanvas 是绘图操作的核心类,它持有一个设备引用,...

    cpp-Skia来自Google用于绘制文字图形和图像的完整的2D图形库

    2. **跨平台支持**:Skia提供对多种硬件和软件平台的支持,包括OpenGL、Vulkan、Metal、Direct3D以及软件渲染。 3. **高性能**:Skia优化了2D图形的绘制速度,利用现代GPU进行加速,同时在不支持GPU的平台上也能实现...

    Skia4Delphi-20220427.zip

    《Skia4Delphi:基于谷歌Skia图形库的跨平台2D图形API》 Skia4Delphi,如其名所示,是一个专为Delphi开发者设计的跨平台2D图形应用程序接口(API)。该库的核心是Google的Skia图形库,一个广泛应用于多个操作系统和...

    skia zlib 静态库 vs2015

    skia zlib 静态库 vs2015

    skia-编译好的库和代码

    里面有两个工程,一个是skia编译好的工程(包括skia工程和Android),Android里面包含有skia的环境。用eclipse就可以编译,同时里面有一个cario的工程,里面编译好一个例子,运行就可以,看到图片的输出。

Global site tag (gtag.js) - Google Analytics