精华内容
下载资源
问答
  • 第7章 二维几何变换 应用于对象几何描述并改变它的位置、方向或大小的操作称为几何变换(geometric transformation)。 几何变换有时也称为建模变换(modeling transformation),但有些图形系统将两者区分开来。...

    第7章 二维几何变换

    应用于对象几何描述并改变它的位置、方向或大小的操作称为几何变换(geometric transformation)

    几何变换有时也称为建模变换(modeling transformation),但有些图形系统将两者区分开来。建模变换一般用于构造场景或给出由多个部分组合而成的复杂对象的层次式描述等。

    基本的二维几何变换

    平移、旋转和缩放是所有图形软件包中都包含的几何变换函数。可能包括在图形软件包中的其他变换函数有反射和错切操作。

    二维平移

    通过将位移量加到一个点的坐标上来生成一个新的坐标位置,可以实现一次平移(translation)。实际上,我们将该点从原始位置沿一直线路径移动到新位置。

    将平移距离(translation distance) t x t_x tx t y t_y ty加到原始坐标 ( x , y ) (x,y) (x,y)上获得一个新的坐标位置 ( x ′ , y ′ ) (x^{'},y^{'}) (xy),可以实现一个二维位置的平移。
    x ′ = x + t x y ′ = y + t y x^{' } = x +t_x \quad y^{' } = y +t_y x=x+txy=y+ty
    一对平移距离 ( t x , t y ) (t_x,t_y) (tx,ty)称为平移向量(translation vector)位移向量(shift vector)

    在这里插入图片描述
    如果用矩阵表示
    P = [ x y ] P ′ = [ x ′ y ′ ] T = [ t x t y ] P = \begin{bmatrix}x \\ y \end{bmatrix} \quad P^{'} = \begin{bmatrix}x^{'} \\ y^{'} \end{bmatrix} \quad T = \begin{bmatrix}t_x \\t_ y \end{bmatrix} P=[xy]P=[xy]T=[txty]
    这样就可以使用矩阵形式来表示二维平移方程:
    P ′ = P + T P^{'} = P+T P=P+T
    下面的程序演示了平移操作。输入的平移向量用来将一个多边形的n个顶点从世界坐标系的一个位置移动到另一个位置,而OpenGL子程序用来重新生成平移后的多边形。

    class wcPt2D(
    	public: GLfloat x,y;
    }; 
    void translatePolygon (wcPt2D"verts, GLint nVerts, GLfloat tx, GLfloat ty){
    	GLint k; 
              for (k=0;k<nVerts:k++){
                  verts [k].x = verts [k].x + tx; 
                  verts [k].y =verts [k].y + ty; 
              }
               g1Begin (GL_POLYGON); 
                           for (k=0;k< nVerts;k++)
    			g1Vertex2f (verts [k].x, verts [k].y); 
               g1End();
    }
    

    二维旋转

    通过指定一个旋转轴(rotation axis)和一个旋转角度(rotation angle),可以进行一次旋转(rotation)变换。在将对象的所有顶点按指定角度绕指定旋转轴旋转后,该对象的所有点都旋转到新位置。

    对象的二维旋转通过在 x y xy xy平面上沿圆路径将对象重定位来实现。此时,我们将对象绕与 x y xy xy平面垂直的旋转轴(与 z z z轴平行)旋转。二维旋转的参数有旋转角 θ \theta θ和称为旋转点(rotation point 或pivot point)的位置 ( x r , y r ) (x_r,y_r) (xr,yr),对象绕该点旋转。基准点是旋转轴与 x y xy xy平面的交点。正角度 θ \theta θ定义绕基准点的逆时针旋转,而负角度将对象沿顺时针方向旋转。

    为了简化模型,我们先假设基准点为原点。
    在这里插入图片描述

    因此旋转后用角度 θ \theta θ ϕ \phi ϕ表示为
    x ′ = r cos ⁡ ( ϕ + θ ) = r cos ⁡ ϕ cos ⁡ θ − r sin ⁡ ϕ sin ⁡ θ y ′ = r sin ⁡ ( ϕ + θ ) = r cos ⁡ ϕ sin ⁡ θ + r sin ⁡ ϕ cos ⁡ θ x^{'} = r\cos(\phi + \theta) = r \cos \phi \cos\theta - r\sin \phi \sin \theta \\ y^{'} = r\sin(\phi + \theta) = r\cos\phi\sin\theta + r \sin\phi \cos \theta x=rcos(ϕ+θ)=rcosϕcosθrsinϕsinθy=rsin(ϕ+θ)=rcosϕsinθ+rsinϕcosθ
    在极坐标系中,点的原始坐标为
    x = r cos ⁡ ϕ y = r sin ⁡ ϕ x = r \cos \phi \quad y = r\sin \phi x=rcosϕy=rsinϕ
    所以可以得到:
    x ′ = x cos ⁡ θ − y sin ⁡ θ y ′ = x sin ⁡ θ + y cos ⁡ θ x^{'} = x\cos \theta - y\sin \theta \\ y^{'}= x \sin \theta +y \cos \theta x=xcosθysinθy=xsinθ+ycosθ
    如果用矩阵的形式表示:
    P ′ = R ⋅ P P^{'} = R\cdot P P=RP
    其中,旋转矩阵为:
    R = [ cos ⁡ θ − sin ⁡ θ sin ⁡ θ cos ⁡ θ ] R = \begin{bmatrix}\cos \theta & -\sin\theta \\ \sin\theta & \cos \theta\end{bmatrix} R=[cosθsinθsinθcosθ]
    但现在OpenGL、Java、PHIGS和GKS都按标准列向量方式表示。

    任意的旋转位置 ( x r , y r ) (x_r,y_r) (xr,yr)旋转点的变换方程:
    x ′ = x r + ( x − x r ) cos ⁡ θ − ( y − y r ) sin ⁡ θ y ′ = y r + ( x − x r ) sin ⁡ θ + ( y − y r ) cos ⁡ θ x^{'} = x_r + (x-x_r)\cos \theta - (y-y_r)\sin\theta \\ y^{'} = y_r + (x-x_r) \sin \theta + (y-y_r)\cos \theta x=xr+(xxr)cosθ(yyr)sinθy=yr+(xxr)sinθ+(yyr)cosθ
    线段的旋转可以通过用于每个线段端点,并重新绘制新端点间的线段而得到。多边形的旋转则是将每个顶点旋转指定的旋转角,并使用新的顶点来生成多边形而实现旋转。曲线的旋转通过重新定位定义的点并重新绘制曲线而完成。例如圆或椭圆,可以通过将中心位置沿指定旋转角对着的弧移动而绕非中心轴旋转。椭圆可通过旋转其长轴和短轴来实现绕其中心位置的旋转。

    class wcPt2D {}
    	public:
    		GLfloat x,y; 
    }; 
    void rotatePolygon (wcPt2D* verts, GLint nVerts, wePt2D pivPt, GLdouble theta)
    {
        wcPt2D* vertsRot; 
        GLint k; 
        for (k=0;k< nVerta:k++){
    	vertsRot [k].x=pivPt.x+(verts [k].x-pivPt.x) * cos (theta)-(verts [k].y-pivpt.y)* sin (theta); 
             vertsRot [k].y=pivPt.y+(verts [k],x-pivPt.x )* sin (theta)+(verts [k].y-pivPt.y)* cos (theta):
        ]
        g1Begin { GL_POLYGON}: 
            for(k=0;k<nVerts;k++)
      		g1Vertex2f (verteRot [k].x, verteRot [k].y): 
        g1End();
    

    二维缩放

    改变一个对象的大小,可使用缩放(scaling)变换。一个简单的二维缩放操作可通过将缩放系数(scaling factor) s x s_x sx s y s_y sy,与对象坐标位置 ( x , y ) (x,y) (x,y)相乘而得:
    x ′ = x ⋅ s x y ′ = y ⋅ s y x^{'} = x \cdot s_x \quad y^{'} = y\cdot s_y x=xsxy=ysy
    矩阵形式如下
    [ x ′ y ′ ] = [ s x 0 0 s y ] ⋅ [ x y ] \begin{bmatrix}x^{'} \\ y^{'}\end{bmatrix} = \begin{bmatrix}s_x & 0 \\ 0 & s_y\end{bmatrix}\cdot \begin{bmatrix}x \\ y\end{bmatrix} [xy]=[sx00sy][xy]
    或者是
    P ′ = S ⋅ P P^{'} = S\cdot P P=SP
    s x s_x sx s y s_y sy相同的值的时候,就会产生保持对象相对比例一致的一致缩放(uniform scaling)。当两个值不相等的时候就是差值缩放(differential scaling)。

    当缩放系数的绝对值小于1时,缩放后的对象向原点靠近;而缩放系数绝对值大于1时,缩放后的坐标位置远离原点。

    我们可以选择一个在缩放变换后不改变位置的点,称为固定点(fixed point),以控制缩放后对象的位置。固定点的坐标 ( x f , y f ) (x_f,y_f) (xf,yf)可以选择对象的中点等位置或任何其他空间位置。这样,多边形通过缩放每个顶点到固定点的距离而相对于固定点进行缩放。对于坐标为 ( x , y ) (x,y) (x,y)的顶点,缩放后的坐标 ( x ′ , y ′ ) (x^{'},y^{'}) (x,y)可计算为
    x ′ − x f = ( x − x f ) s x y ′ − y f = ( y − y f ) s y x^{'} - x_f = (x-x_f)s_x \quad y^{'} - y_f = (y-y_f)s_y xxf=(xxf)sxyyf=(yyf)sy
    可以得到
    x ′ = x ⋅ s x + x f ( 1 − s x ) y ′ = y ⋅ s y + y f ( 1 − s y ) x^{'} = x\cdot s_x + x_f(1-s_x)\\ y^{'} = y\cdot s_y + y_f(1-s_y) x=xsx+xf(1sx)y=ysy+yf(1sy)
    其中 x f ( 1 − s x ) x_f(1-s_x) xf(1sx) y f ( 1 − s y ) y_f(1-s_y) yf(1sy)都是常数。

    在这里插入图片描述

    class wePt2D {
        public: 
        GLfloat x,y; 
    };
    void scalePolygon (wcPt2D· verts, GLint nVerts, wcpt2D fixedPt, GLfloat sx, GLfloat sy)
    {
        wcPt2D vertsNew; 
        GLint k; 
        for (k-0;k<nVerts;k++){
            vertsNew[k].x=verts [k].x* sx+ fixedPt.x*(1-sx); 
            vertaNew[k].y=verts [k].y*sy+fixedPt.y*(1: sy): 
             
        } 
        g1Begin (GL POLYGON]; 
                 for (k-0;k<nVerte;k++) 
                 	g1Vertex2f (vertsNew [k].x, vertsNew [k].y); 
         g1End():
    

    矩阵表示和齐次坐标

    每个基本变换(平移、旋转和缩放)都可以表示为普通矩阵形式
    P ′ = M 1 P + M 2 P^{'} = M_1P+M_2 P=M1P+M2
    P P P P ′ P^{'} P都是坐标的列向量。矩阵 M 1 M_1 M1是一个包含乘法系数的 2 × 2 2\times 2 2×2的矩阵, M 2 M_2 M2是包含平移项的两元素列矩阵。

    对于平移, M 1 M_1 M1是单位矩阵。对于旋转或缩放, M 2 M_2 M2包含与基准点或缩放固定点相关的平移项。

    齐次坐标

    如果将 2 × 2 2\times 2 2×2矩阵表达式扩充为 3 × 3 3\times 3 3×3矩阵,就可以把二维儿何变换的乘法和平移组合成单一矩阵表示。这时将变换矩阵的第三列用于平移项,而所有的变换公式可表达为矩阵乘法。但为了这样操作,必须解释二维坐标位置到三元列向量的矩阵表示。标准的实现技术是将二维坐标位置表示 ( x , y ) (x,y) (x,y)扩充到三维表示 ( x h , y h , h ) (x_h,y_h,h) (xh,yh,h),称为齐次坐标(homogeneous coordinate),这里的齐次参数(homogeneous parameter)h是一个非零值,因此
    x = x h h y = y h h x = \frac{x_h}{h} \quad y = \frac{y_h}{h} x=hxhy=hyh
    普通的二维齐次坐标表示可写为 ( h × x , h × y , h ) (h\times x , h\times y,h) (h×x,h×y,h)。最简单的 h = 1 h=1 h=1。因此每个二维位置都可以用齐次坐标来表示 ( x , y , 1 ) (x,y,1) (x,y,1)来表示。

    二维平移矩阵

    使用齐次坐标方法,坐标位置的二维平移可表示为下面的矩阵乘法:
    [ x ′ y ′ 1 ] = [ 1 0 t x 0 1 t y 0 0 1 ] × [ x y 1 ] \begin{bmatrix}x^{'} \\ y^{'} \\ 1\end{bmatrix} = \begin{bmatrix}1 & 0 & t_x \\ 0 & 1 & t_y \\ 0 & 0 &1\end{bmatrix} \times \begin{bmatrix}x \\y \\ 1\end{bmatrix} xy1=100010txty1×xy1
    该平移操作可简写为
    P ′ = T ( t x , t y ) P P^{'} = T(t_x,t_y)P P=T(tx,ty)P

    二维旋转矩阵

    二维的旋转变换公式为
    [ x ′ y ′ 1 ] = [ cos ⁡ θ − sin ⁡ θ 0 sin ⁡ θ cos ⁡ θ 0 0 0 1 ] [ x y 1 ] \begin{bmatrix}x^{'} \\ y^{'} \\ 1 \end{bmatrix} = \begin{bmatrix}\cos \theta & -\sin \theta & 0 \\ \sin \theta & \cos \theta & 0\\0 & 0 &1\end{bmatrix} \begin{bmatrix}x \\ y \\ 1\end{bmatrix} xy1=cosθsinθ0sinθcosθ0001xy1
    其中旋转变换操作 R ( θ ) R(\theta) R(θ)的旋转参数 θ \theta θ

    二维缩矩阵

    相对于坐标原点的缩放变换可以表示为
    [ x ′ y ′ 1 ] = [ s x 0 0 0 s y 0 0 0 1 ] [ x y 1 ] \begin{bmatrix}x^{'} \\ y^{'} \\ 1\end{bmatrix} = \begin{bmatrix}s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0& 0 & 1\end{bmatrix} \begin{bmatrix}x \\ y \\ 1\end{bmatrix} xy1=sx000sy0001xy1

    逆变换

    对于平移变换,我们通过对平移距离取负值而得到逆矩阵。因此,如果二维平移距离是 t x t_x tx t y t_y ty,则其逆平移矩阵是

    T − 1 = [ 1 0 − t x 0 1 − t y 0 0 1 ] T^{-1} = \begin{bmatrix}1 & 0 & -t_x \\ 0 & 1 & -t_y \\ 0 & 0 & 1\end{bmatrix} T1=100010txty1

    逆旋转通过用旋转角度的负角取代该旋转角来实现。例如,绕坐标系原点的角度为0的二维旋转有如下的逆变换矩阵
    R − 1 = [ cos ⁡ θ sin ⁡ θ 0 − sin ⁡ θ cos ⁡ θ 0 0 0 1 ] R^{-1} = \begin{bmatrix} \cos \theta & \sin \theta & 0 \\ -\sin \theta & \cos\theta & 0 \\ 0 & 0 & 1\end{bmatrix} R1=cosθsinθ0sinθcosθ0001
    将缩放系数用其倒数取代就得到了缩放变换的逆矩阵。对以坐标系原点为中心、缩放参数为 s x s_x sx s y s_y sy的二维缩放,其逆变换矩阵为
    S − 1 = [ 1 s x 0 0 0 1 s y 0 0 0 1 ] S^{-1} = \begin{bmatrix}\frac{1}{s_x} & 0 & 0 \\ 0 & \frac{1}{s_y}& 0 \\ 0 & 0 &1\end{bmatrix} S1=sx1000sy10001

    二维复合变换

    利用矩阵表达式,可以通过计算单个变换的矩阵乘积,将任意的变换序列组成复合变换矩阵(composite transformation matrix)。形成变换矩阵的乘积经常称为矩阵的合并(concatenation) 或复合(composition)。由于一个坐标位置用齐次列矩阵表示,我们必须用表达任意变换顺序的矩阵来前乘该列矩阵。由于场景中许多位置用相同的顺序变换,先将所有变换矩阵相乘形成一个复合矩阵将是高效率的方法。因此,如果我们要对点位置P进行两次变换,变换后的位置将用下式计算:
    P ′ = M 2 M 1 P = M P P^{'} = M_2M_1P = MP P=M2M1P=MP

    复合二维平移

    假如将两个连续的平移向量 ( t 1 x , t 1 y ) (t_{1x},t_{1y}) (t1x,t1y) ( t 2 x , t 2 y ) (t_{2x},t_{2y}) (t2x,t2y)用于坐标位置P,那么最后的变换位置 P ′ P^{'} P可以计算为
    P ′ = T ( t 2 x , t 2 y ) { T ( t 1 x , T 1 y ) P } = { T ( t 1 x , t 1 y ) T ( t 2 x , t 2 y ) } P = T ( t 1 x + t 2 x , t 1 y + t 2 y ) P^{'} = T(t_{2x},t_{2y})\{T(t_{1x},T_{1y})P\} = \{T(t_{1x},t_{1y})T(t_{2x},t_{2y})\}P = T(t_{1x}+t_{2x},t_{1y}+t_{2y}) P=T(t2x,t2y){T(t1x,T1y)P}={T(t1x,t1y)T(t2x,t2y)}P=T(t1x+t2x,t1y+t2y)
    这表示两个连续平移是相加的。

    复合二维旋转

    P ′ = R ( θ 2 ) { R ( θ 1 ) P } = { R ( θ 1 ) R ( θ 2 ) } P P^{'} = R(\theta_2)\{R(\theta_1)P\} = \{R(\theta_1)R(\theta_2)\}P P=R(θ2){R(θ1)P}={R(θ1)R(θ2)}P

    并且
    R ( θ 1 ) R ( θ 2 ) = R ( θ 1 + θ 2 ) R(\theta_1) R(\theta_2) = R(\theta_1+\theta2) R(θ1)R(θ2)=R(θ1+θ2)

    复合二维缩放

    [ s 2 x 0 0 0 s 2 y 0 0 0 1 ] [ s 1 x 0 0 0 s 1 y 0 0 0 1 ] = [ s 1 x s 2 x 0 0 0 s 1 y s 2 y 0 0 0 1 ] \begin{bmatrix}s_{2x} & 0 & 0 \\ 0 & s_{2y} & 0 \\ 0 & 0 &1\end{bmatrix} \begin{bmatrix}s_{1x} & 0 & 0 \\ 0 & s_{1y}& 0 \\ 0 & 0 & 1\end{bmatrix} = \begin{bmatrix}s_{1x}s_{2x} & 0 &0 \\ 0 & s_{1y}s_{2y} & 0 \\ 0 & 0 &1\end{bmatrix} s2x000s2y0001s1x000s1y0001=s1xs2x000s1ys2y0001

    或者是
    S ( s 2 x , s 2 y ) ⋅ S ( s 1 x , s 1 y ) = S ( s 1 x ⋅ s 2 x , s 1 y ⋅ s 2 y ) S(s_{2x},s_{2y})\cdot S(s_{1x},s_{1y}) = S(s_{1x}\cdot s_{2x} , s_{1y}\cdot s_{2y}) S(s2x,s2y)S(s1x,s1y)=S(s1xs2x,s1ys2y)

    通用二维基准点旋转

    当图形软件包仅提供绕坐标系原点的旋转函数时,我们可通过完成下列平移-旋转-平移操作序列来实现绕任意选定的基准点 ( x r , y r ) (x_r,y_r) (xr,yr)的旋转。

    1. 平移对象使基准点位置移动到坐标原点;
    2. 绕坐标原点旋转;
    3. 平移对象使基准点回到其原始位置。

    [ 1 0 x r 0 1 y r 0 0 1 ] ⋅ [ cos ⁡ θ − sin ⁡ θ 0 sin ⁡ θ cos ⁡ θ 0 0 0 1 ] ⋅ [ 1 0 − x r 0 1 − y r 0 0 1 ] = [ cos ⁡ θ − sin ⁡ θ x r ( 1 − cos ⁡ θ ) + y r sin ⁡ θ sin ⁡ θ cos ⁡ θ y r ( 1 − cos ⁡ θ ) − x r sin ⁡ θ 0 0 1 ] \begin{bmatrix}1 & 0 & x_r \\0 & 1 & y_r \\ 0 & 0 & 1\end{bmatrix}\cdot \begin{bmatrix}\cos \theta & -\sin \theta & 0 \\\sin\theta & \cos \theta & 0\\ 0 & 0 & 1\end{bmatrix}\cdot \begin{bmatrix}1 & 0 & -x_r \\0 & 1 & -y_r \\ 0 & 0 & 1\end{bmatrix} = \begin{bmatrix}\cos\theta & -\sin\theta & x_r(1-\cos\theta) + y_r\sin \theta \\\sin\theta & \cos\theta & y_r(1-\cos\theta)-x_r\sin\theta \\ 0 & 0 & 1\end{bmatrix} 100010xryr1cosθsinθ0sinθcosθ0001100010xryr1=cosθsinθ0sinθcosθ0xr(1cosθ)+yrsinθyr(1cosθ)xrsinθ1

    该等式可以使用下列形式表示:
    T ( x r , y r ) R ( θ ) T ( − x r , − y r ) = R ( x r , y r , θ ) T(x_r,y_r)R(\theta)T(-x_r,-y_r) = R(x_r,y_r,\theta) T(xr,yr)R(θ)T(xr,yr)=R(xr,yr,θ)
    其中 T ( − x r , − y r ) = T − 1 ( x r , y r ) T(-x_r, -y_r) = T^{-1}(x_r,y_r) T(xr,yr)=T1(xr,yr)

    在这里插入图片描述

    通用二维基准点缩放

    在只有相对于坐标原点缩放的缩放函数时,缩放的变换序列:

    1. 平移对象使固定点与坐标原点重合;
    2. 对于坐标原点进行缩放;
    3. 使用步骤1的反向平移将对象返回到原始位置。

    [ 1 0 x f 0 1 y f 0 0 1 ] ⋅ [ s x 0 0 0 s y 0 0 0 1 ] ⋅ [ 1 0 − x f 0 1 − y f 0 0 1 ] = [ s x 0 x f ( 1 − s x ) 0 s y y f ( 1 − s y ) 0 0 1 ] ⋅ \begin{bmatrix}1& 0 & x_f \\ 0 & 1 & y_f \\ 0 & 0 &1\end{bmatrix}\cdot \begin{bmatrix}s_x& 0 &0 \\ 0 & s_y & 0 \\ 0 & 0 &1\end{bmatrix}\cdot \begin{bmatrix}1& 0 & -x_f \\ 0 & 1 & -y_f \\ 0 & 0 &1\end{bmatrix} = \begin{bmatrix}s_x& 0 & x_f(1-s_x) \\ 0 & s_y & y_f(1-s_y) \\ 0 & 0 &1\end{bmatrix}\cdot 100010xfyf1sx000sy0001100010xfyf1=sx000sy0xf(1sx)yf(1sy)1


    T ( x f , y f ) ⋅ S ( s x , s y ) ⋅ T ( − x f , − y f ) = S ( x f , y f , s x , s y ) T(x_f,y_f)\cdot S(s_x,s_y) \cdot T(-x_f,-y_f) = S(x_f,y_f,s_x,s_y) T(xf,yf)S(sx,sy)T(xf,yf)=S(xf,yf,sx,sy)

    在这里插入图片描述

    通用二维定向缩放

    参数 s x s_x sx s y s_y sy沿x和y方向缩放对象,可以通过在应用缩放变换之前,将对象所希望的缩放方向旋转到与坐标轴一致来实现在其他方向上缩放对象。

    首先完成旋转操作,使 s 1 s_1 s1 s 2 s_2 s2的方向分别与x和y轴重合。然后应用缩放变换 S ( s 1 , s 2 ) S(s_1,s_2) S(s1,s2),再进行反向旋转回到其原始位置。从这三个变换的乘积得到的复合矩阵为
    R − 1 ( θ ) ⋅ S ( s 1 , s 2 ) ⋅ R ( θ ) = [ s 1 cos ⁡ 2 θ + s 2 sin ⁡ 2 θ ( s 2 − s 1 ) cos ⁡ θ sin ⁡ θ 0 ( s 2 − s 1 ) cos ⁡ θ sin ⁡ θ s 1 sin ⁡ 2 θ + s 2 cos ⁡ 2 θ 0 0 0 1 ] R^{-1}(\theta) \cdot S(s_1,s_2)\cdot R(\theta) = \begin{bmatrix} s_1\cos^2\theta+s_2\sin^2\theta & (s_2-s_1)\cos\theta\sin\theta & 0 \\ (s_2-s_1)\cos\theta\sin\theta & s_1\sin^2\theta + s_2\cos^2\theta & 0 \\ 0 & 0 & 1 \end{bmatrix} R1(θ)S(s1,s2)R(θ)=s1cos2θ+s2sin2θ(s2s1)cosθsinθ0(s2s1)cosθsinθs1sin2θ+s2cos2θ0001

    矩阵合并特性

    矩阵相乘符合结合律,对于任何三个矩阵 M 1 M_1 M1 M 2 M_2 M2 M 3 M_3 M3。矩阵积 M 3 M 2 M 1 M_3M_2M_1 M3M2M1可以写成
    M 3 M 2 M 1 = ( M 3 M 2 ) M 1 = M 3 ( M 2 M 1 ) M_3M_2M_1 = (M_3M_2)M_1 = M_3(M_2M_1) M3M2M1=(M3M2)M1=M3(M2M1)
    因此,依靠变换的描述顺序,我们既可以使用从左到右(前乘),也可以使用从右到左(后乘)的结合分组来求矩阵乘积。有些图形软件包要求变换按应用的顺序描述。

    二维刚体变换

    如果一个变换矩阵仅包含平移和旋转参数,则它是一个刚体变换矩阵(rigid-body transformation matrix)。二维刚体变换矩阵的一般形式为
    [ r x x r x y t r x r y x r y y t r y 0 0 1 ] \begin{bmatrix} r_{xx} & r_{xy} & tr_{x} \\ r_{yx} & r_{yy} & tr_y \\ 0 & 0 & 1 \end{bmatrix} rxxryx0rxyryy0trxtry1
    其中,4个元素 r j k r_{jk} rjk是多重旋转项,元素 t r x tr_x trx t r y tr_y try是平移项。坐标位置的刚体变换有时也称为刚体运动(rigid motion)。变换后的坐标位置间的所有角度和距离都不变化。

    因此上述左上角的 2 × 2 2\times 2 2×2矩阵是一个正交矩阵(orthogonal matrix)的特性。说明,如果将子矩阵的每一行(或每一列)作为向量,那么两个行向量 ( r x x , r x y ) (r_{xx}, r_{xy}) (rxx,rxy) ( r y x , r y y ) (r_{yx},r_{yy}) (ryx,ryy)形成单位向量的正交组。这样的一组向量也称为正交向量组。每个向量具有单位长度。
    r x x 2 + r x y 2 = r y x 2 + r y y 2 = 1 r^2_{xx} + r^2_{xy} = r^2_{yx} + r^2_{yy} = 1 rxx2+rxy2=ryx2+ryy2=1
    并且向量相互垂直
    r x x r y x + r x y r y y = 0 r_{xx}r_{yx} + r_{xy}r_{yy} = 0 rxxryx+rxyryy=0

    二维复合矩阵编程

    其他二维变换

    反射

    产生对象镜像的变换称为反射(reflection)。对于二维反射而言,其反射镜像通过将对象绕反射轴旋转 18 0 ∘ 180^{\circ} 180而生成。我们选择的反射轴(axis of reflection)可以是在 x y xy xy平面内的一条直线或者是垂直 x y xy xy平面的一条直线。当反射轴是 x y xy xy平面内的一条直线时,绕这个轴的旋转路径在垂直于 x y xy xy平面的平面中;而对于垂直于 x y xy xy平面的反射轴,旋转路径在 x y xy xy平面内。下面举出一些普通的反射例子。

    关于直线 y = 0 y=0 y=0( x x x轴)的反射,可以由下列的变换矩阵完成。
    [ 1 0 0 0 − 1 0 0 0 1 ] \begin{bmatrix} 1 & 0 & 0 \\ 0 & -1 & 0 \\ 0 & 0 & 1 \end{bmatrix} 100010001
    对于 x = 0 x=0 x=0(y轴)的反射,翻动 x x x的坐标而保持 y y y坐标不变,这种变换的矩阵是
    [ − 1 0 0 0 1 0 0 0 1 ] \begin{bmatrix} -1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} 100010001

    在这里插入图片描述
    关于 x y xy xy的平面内任意直线 y = m x + b y=mx+b y=mx+b 的反射,可以使用平移-旋转-反射变换的组合来完成的。

    通常,我们先平移直线使其经过原点。然后将直线旋转到坐标轴之一,并进行相对于坐标轴的反射。最后利用逆旋转和逆平移变换将直线还原到原来位置。

    错切

    错切(shear) 是一种使对象形状发生变化的变换,经过错切的对象好像是由已经相互滑动的内部夹层组成。两种常用的错切变换是移动 x x x坐标值的错切和移动 y y y坐标值的错切。

    相对于 x x x轴的 x x x方向错切由下列变换矩阵产生:
    [ 1 s h x 0 0 1 0 0 0 1 ] \begin{bmatrix} 1 & sh_x & 0 \\ 0 & 1 & 0 \\ 0 & 0 &1 \end{bmatrix} 100shx10001
    该矩阵将坐标位置转换成
    x ′ = x + s h x ⋅ y y ′ = y x^{'} = x + sh_x \cdot y \quad y^{'} = y x=x+shxyy=y
    可以将任意实数赋给错切参数 s h sh sh.。然后将坐标位置 ( x , y ) (x,y) (x,y)水平地移动与其到 x x x轴的距离( y y y值)成正比的量。

    在这里插入图片描述
    错切操作可以表示为基本变换的序列。

    几何变换的光栅方法

    光栅系统的特殊功能为特定的二维变换提供了另一种方法。光栅系统将图像信息作为颜色图案存储在帧缓冲器中。控制矩形像素数组的光栅功能通常称为光栅操作(raster operation),将一块像素从一个位置移动到另一个位置的过程也称为像素值的块移动(block transfer,bitblt或pixblt)。图形软件中通常包含完成某些光栅操作的子程序。

    90°倍数的旋转可以很容易地利用重新安排像素矩阵的元素而实现。通过首先将阵列的每一行的像素值颠倒,然后交换其行和列来将对象逆时针旋转90°;通过颠倒阵列的每一行中元素的顺序,然后将行的顺序颠倒来得到180°的旋转。

    像素块的光栅缩放采用类似方法实现。我们用指定的 s x s_x sx s y s_y sy值对原始块中的像素区域进行缩放,并将缩放的矩形映射到一组目标像素上,然后按照其与缩放像素区域的重叠区域,设置每个目标像素的亮度。

    OpenGL光栅变换

    像素颜色值的矩形数组从一个缓存到另一个的平移可以作为如下的OpenGL复制操作来完成:

    g1CopyPixe1s (xmin, ymin, width, height. GL_COLOR):

    前面4个参数给出了像素块的位置和尺寸。而OpenGL符号常量GL_COLOR指定要复制的颜色值。该像素数组复制到刷新缓存中由当前光栅位置指定的左下角的一个矩形区域内。像素颜色值依赖于颜色模式的当前设定,按RGBA或颜色表进行复制。提供复制的区域(源)和复制目标区域均应位于屏幕坐标边界内。该平移可作用于任何刷新缓存或不同缓存之间。g1CopyPixels函数的源缓存用g1ReadBuffer 子程序选择,而目标缓存用g1DrawBuffer子程序选择。

    缓存中的一个RGB颜色块可以用下列函数存入一个数组:

    g1ReadPixels (xmin, ymin, width. height, GL_RGB.GL_UNSIGNED_BYTE, colorArray):

    如果颜色表索引存于像素位置,则将GL_COLOR_INDEX取代GL_RGB。为了旋转颜色值,必须如前一节所述重新安排颜色数组的行与列。然后使用下列语句将旋转后的数组放回缓存:

    g1DrawPixe1s (width,height,GL_RGB,GL_UNSIGNED_BYTE, colorArray);

    该数组的左下角放到当前光栅位置。我们用g1ReadBuffer选择包含原来的像素值块的源缓存,用g1DrawBuffer指定目标缓存。

    二维缩放变换通过指定缩放因子然后引用g1Copypixe1s或g1Drawpixe1s按OpenGL中的光栅操作来完成。对于光栅操作,使用下列函数来设定缩放因子:

    g1Pixe1zoom(sx,sy):

    这里,参数sx和sy可赋以任何非零浮点值。大于1.0的正值增太源数组元素的尺对,而小于1.0的正值减少元素尺寸。sx或sy中有负值或两个都为负值则生成该数组元素的反射及缩放。因此,如果sx=sy=-3.0,则源数组相对于当前光栅位置反射且数组的每一颜色元素映射到目标缓存中的3×3像素块。如果目标像素的中心位于一数组缩放颜色元素的矩形区域,则用该数组元素给它赋值。中心在缩放数组元素左边界或上边界的目标像素也赋以该元素的颜色。sx和sy的默认值均为1.0。

    二维坐标系间的变换

    非笛卡儿系统的例子有极坐标系统、球面坐标系统、椭圆坐标系统和抛物线坐标系统。

    给出了一个在笛卡儿坐标系 x y xy xy中用坐标原点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0)及方向角 θ \theta θ指定的笛卡儿坐标系 x ′ y ′ x^{'}y^{'} xy。为了将对象描述从 x y xy xy坐标变换到 x ′ y ′ x^{'}y^{'} xy坐标,必须建立把 x ′ y ′ x^{'}y^{'} xy轴叠加到 x y xy xy轴的变换,这需要分两步进行:

    1. x ′ y ′ x^{'}y^{'} xy系统的坐标原点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0)平移到 x y xy xy系统的原点 ( 0 , 0 ) (0,0) (0,0)
    2. x ′ x' x轴旋转到 x x x轴上

    坐标原点的平移可以使用下列矩阵操作表示:
    T ( − x 0 , − y 0 ) = [ 1 0 − x 0 0 1 − y 0 0 0 1 ] T(-x_0,-y_0) = \begin{bmatrix} 1 & 0 & -x_0 \\ 0 & 1 & -y_0 \\ 0 & 0 & 1 \end{bmatrix} T(x0,y0)=100010x0y01
    为了将两个系统的轴重合,可以顺时针旋转:
    R ( − θ ) = [ cos ⁡ θ sin ⁡ θ 0 − sin ⁡ θ cos ⁡ θ 0 0 0 1 ] R(-\theta) = \begin{bmatrix} \cos \theta & \sin \theta & 0 \\ -\sin \theta & \cos \theta & 0 \\ 0 & 0 & 1 \end{bmatrix} R(θ)=cosθsinθ0sinθcosθ0001
    把这两个变换矩阵合并起来,就给出了将对象描述从 x y xy xy系统转换到 x ′ y ′ x^{'}y^{'} xy系统的完整复合矩阵。
    M x y , x ′ y ′ = R ( − θ ) T ( − x 0 , − y 0 ) M_{xy,x^{'}y^{'}} = R(-\theta)T(-x_0,-y_0) Mxy,xy=R(θ)T(x0,y0)

    在这里插入图片描述

    OpenGL二维几何变换函数

    在OpenGL的核心库中,每一种基本的几何变换都有一个独立的函数。由于OpenGL是作为三维图形应用编程接口(APl)来设计的,所有变换都在三维空间中定义。在内部,所有坐标均使用4元素列向量表示,而所有变换均使用4×4矩阵表示。因此,二维变换可以通过在OpenGL中选择使第三维(z)不改变的z值来实现。

    基本的OpenGL几何变换

    4 × 4 4\times 4 4×4平移矩阵用下列子程序构造

    g1Translate*(tx,ty,tz)

    平移参数tx、ty和tz可赋予任意的实数值,附加于该函数的单个后缀码或者是f(浮点)或者是d。

    旋转矩阵 4 × 4 4\times 4 4×4下列函数生成:

    glRotate*(theta,vx,vy,vz)

    向量 v = ( v x , v y , v z ) v=(vx,vy,vz) v=vxvyvz的分量可以有任意的浮点数值。该向量用于定义通过坐标原点的旋转轴的方向。

    用下列函数可得到相对于坐标原点的4×4缩放矩阵:

    glScale*(sx, sy, sz)

    OpenGL矩阵操作

    将该矩阵看做建模观察矩阵(modelview matrix),它用于存储和组合几何变换,也用于将几何变换与向观察坐标系的变换进行组合。建模观察模式用下列语句指定:

    g1MatrixMode (GL_MODELVIEW)

    该语句指定一个4×4建模观察矩阵作为当前矩阵(current matrix)

    在这个调用后的OpenGL变换子程序用来修改建模观察矩阵,而后该矩阵用来变换场景中的坐标位置。用g1MatrixMode函数还可以设定另外两个模式:纹理模式(texture mode)颜色模式(color mode)。纹理模式用于映射表面的纹理图案,而颜色模式用于从一个颜色模型转换到另一个。后面几章将讨论观察、投影、纹理和颜色变换。

    建立建模观察模式(或任何其他模式)后,调用变换子程序所生成的矩阵要与该模式的当前矩阵相乘。另外,我们可以对当前矩阵的元素赋值,OpenGL库中有两个函数可用于此目的。使用下列函数可设定当前矩阵为单位矩阵:

    glLoadIdentity():

    也可以为当前矩阵的元素赋其他值:

    g1LoadMatrix*(elements16):

    参数elements16指定了一个单下标、16元素的浮点值数组,而后缀f或d用来指定数据类型。该数组的元素必须按列优先顺序指定。即先列出第一列的4个元数,接着列出第二列的4个元素,然后是第三列,而最后是第四列。

    也可以将指定的矩阵与当前矩阵合并:

    glMultMatrix*(otherElements16):

    实例

    from OpenGL.GL import *
    from OpenGL.GLU import *
    from OpenGL.GLUT import *
    import math
    
    winWidth, winHeight = 600, 600
    xwcMin, xwcMax = 0.0, 300.0
    ywcMin, ywcMax = 0.0, 300.0
    pi = 3.14159
    matComposite = [[0.0, 0.0, 0.0],
                    [0.0, 0.0, 0.0],
                    [0.0, 0.0, 0.0]]
    
    def init():
        glClearColor(1.0, 1.0, 1.0, 0.0)
    
    
    def get3x3Matfloat():
        temp = [[0.0, 0.0, 0.0],
                [0.0, 0.0, 0.0],
                [0.0, 0.0, 0.0]]
        return temp
    
    
    # 矩阵初始化
    def matrix3x3SetIndentity(matIdent3x3):
        for row in range(3):
            for col in range(3):
                matIdent3x3[row][col] = (row == col)
    
    # 显示三角形
    def triangle(verts):
        glBegin(GL_TRIANGLES)
        for k in range(3):
            glVertex2f(verts[k][0], verts[k][1])
        glEnd()
    
    
    def matrix3x3PreMultiply(m1, m2):
        mattemp = get3x3Matfloat()
        for row in range(3):
            for col in range(3):
                mattemp[row][col] = m1[row][0] * m2[0][col] + m1[row][1] * m2[1][col] + m1[row][2] * m2[2][col]
    
        for row in range(3):
            for col in range(3):
                m2[row][col] = mattemp[row][col]
    
    def rotate2D(pivotPt, theta):
        global matComposite
        matRot = get3x3Matfloat()
        matrix3x3SetIndentity(matRot)
        matRot[0][0] = math.cos(theta)
        matRot[0][1] = -math.sin(theta)
        matRot[0][2] = pivotPt[0] * (1-math.cos(theta)) + pivotPt[1] * math.sin(theta)
    
        matRot[1][0] = math.sin(theta)
        matRot[1][1] = math.cos(theta)
        matRot[1][2] = pivotPt[0] * (1-math.cos(theta)) - pivotPt[1]*math.sin(theta)
    
        matrix3x3PreMultiply(matRot, matComposite)
    
    def scale2D(sx, sy, fixedPt):
        global matComposite
        matScale = get3x3Matfloat()
        matrix3x3SetIndentity(matScale)
        matScale[0][0] = sx
        matScale[0][2] = (1-sx)*fixedPt[0]
        matScale[1][1] = sy
        matScale[1][2] = (1-sy) * fixedPt[1]
        matrix3x3PreMultiply(matScale, matComposite)
    
    
    def translate2D(tx, ty):
        global matComposite
        matTrans1 = get3x3Matfloat()
        matrix3x3SetIndentity(matTrans1)
        matTrans1[0][2] = tx
        matTrans1[1][2] = ty
        matrix3x3PreMultiply(matTrans1, matComposite)
    
    def transformVerts2D(nVerts, verts):
        global matComposite
        for k in range(nVerts):
            temp = matComposite[0][0] * verts[k][0] + matComposite[0][1] * verts[k][1] + matComposite[0][2]
            verts[k][1] = matComposite[1][0] * verts[k][0] + matComposite[1][1] * verts[k][1] + matComposite[1][2]
            verts[k][0] = temp
    
    def dispalyFcn():
        global pi,matComposite
        nVerts = 3
        verts = [[50.0, 25.0],[150.0, 25.0],[100.0, 100.0]]
        xsum , ysum = 0,0
        for k in range(nVerts):
            xsum += verts[k][0]
            ysum += verts[k][1]
        centroidPt = [0,0]
        centroidPt[0] = xsum/nVerts
        centroidPt[1] = ysum/nVerts
        pivPt = centroidPt[:]
        fixedPt = centroidPt[:]
        tx = 0.0
        ty = 100.0
        sx = 0.5
        sy = 0.5
        theta = pi/2.0
        glClear(GL_COLOR_BUFFER_BIT)
        glColor3f(0.0, 0.0, 1.0)
        triangle(verts)
        matrix3x3SetIndentity(matComposite)
    
        scale2D(sx, sy, fixedPt)
    
        rotate2D(pivPt, theta)
    
        translate2D(tx, ty)
    
        transformVerts2D(nVerts, verts)
    
        glColor3f(1.0, 0.0, 0.0)
        triangle(verts)
        glFlush()
    
    
    
    def winReshapeFcn(newWidth, newHeight):
        global xwcMax, xwcMin, ywcMax, ywcMin
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluOrtho2D(xwcMin, xwcMax, ywcMin, ywcMax)
        glClear(GL_COLOR_BUFFER_BIT)
    
    
    if __name__ == '__main__':
        glutInit()
        glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
        glutInitWindowPosition(50, 50)
        glutInitWindowSize(winWidth, winHeight)
        glutCreateWindow("几何变换".encode('gbk'))
        init()
        glutDisplayFunc(dispalyFcn)
        glutReshapeFunc(winReshapeFcn)
        glutMainLoop()
    
    

    在这里插入图片描述

    展开全文
  • 最近在做图像去噪方面涉及到二维空间变换,在此总结 二维空间变换1.综述2.平移仿射变换举例Step 1: 导入要变换的图像Step 2: 定义空间变换Step 3: 创建TFORM结构体Step 4: 执行变换Step 5: 观察输出图像3.定义空间...

    最近在做图像去噪方面涉及到二维空间变换,在此总结

    1.综述

    执行一般的二维空间变换包括如下三步:

    • 定义空间变换的参数;

    • 创建变换结构体TFORM,它定义了你所要执行变换的类型;

      TFORM结构体包含了执行变换需要的所有参数。你可以定义很多类型的空间变换,包括放射变换affine transformations(如平移translation,缩放scaling,旋转rotation,剪切shearing)、投影变换projective transformations和自定义的变换custom transformations。

      创建结构体的方法有两种:使用maketform或者使用cp2tform。

    • 执行变换。

    通过将要变换的图像和TFORM结构体传递给imtransform函数即可实现变换。

    用图来表示就是

    在这里插入图片描述
    下面以一个简单的平移仿射变换来展示空间变换的使用方法。

    2.平移仿射变换举例

    Step 1: 导入要变换的图像

    Matlab提供了一个棋盘图像,可以以此作为例子,它的调用函数为checkerboard,它将创建一个80X80像素的图像。

    % 导入图像
    cb = checkerboard;
    
    figure
    
    imshow(cb)
    

    在这里插入图片描述

    Step 2: 定义空间变换

    • 定义二维的空间变换需要一个3X3的变换矩阵,也可以通过在输入图像和输出图像上指定对应点的方式由maketform自动创建变换矩阵。

    这里使用如下的变换矩阵来定义空间平移变换

    % 定义变换矩阵
    
    xform = [ 1  0  0
    
        	  0  1  0
    
       		 20 40  1 ];
    

    在这个矩阵中,xform(3, 1)定义了图像在水平方向上平移的像素数,xform(3, 2)定义了图像在垂直方向上平移的像素数。

    Step 3: 创建TFORM结构体

    • 使用maketform函数可以创建TFORM结构体,参数为希望执行变换的类型和变换矩阵。
    % 创建TFORM结构体
    
    tform_translate = maketform('affine', xform);
    

    Step 4: 执行变换

    • 使用imtransform函数执行变换,参数为要变换的图像和TFORM结构体,函数将返回变换后的图像。
    % 执行变换
    
    [cb_trans xdata ydata]= imtransform(cb, tform_translate);
    

    返回值中包含的另两个额外输出参数xdata和ydata,表示输出图像在输出坐标空间的位置。xdata包含了输出图像拐角处像素的x坐标,ydata包含了这些像素的y坐标(这里说的x、y坐标是指像素的中点)。

    下图展示了平移变换的效果,可以看到,(1, 1)点的像素转移到了(41, 41)的位置,注意那一点的像素值没有变。
    在这里插入图片描述
    imtransform函数决定输出图像的像素值是把新位置映射回输入图像的相应位置。在平移变换中,由于图像的大小和旋转角度没有变,所以是一一映射;对于其他类型的变换,如放缩、旋转,此函数将在输入图像上插值计算输出图像的像素值。学过图像处理的这个应该很清楚。

    Step 5: 观察输出图像

    使用如下语句显示变换后的图像

            
    
    % 显示变换结果
    
    figure
    
    imshow(cb_trans)
    

    在这里插入图片描述
    从结果看来,貌似变换没有效果,变换后图像和原图像是一样的。但如果你检查xdata和ydata的数值,就会看到图像的空间坐标已经改变了。原始图像左上角的坐标由(1, 1)变为了(41, 41),右下角由(80, 80)变为了(120, 120),40已经被加到了每个像素的空间坐标上。

    >> xdata
    
    xdata =
    
        41   120
    
    >> ydata
    
    ydata =
    
    41   120
    

    而显示结果上没有变化的原因是函数imtransform得到的输出图像足够包含了变换后的图像,但没有包含全部的坐标空间。
    要看到变换的效果,需要使用imtransform函数的额外输入参数来指定输出图像的大小和能包含输出图像的输出空间。

    下面的代码使用了XData和YData两个额外参数来指定能包含输出图像的输出空间,而对于输出空间中不在输出图像上的像素默认为黑色,这可以通过改变imtransform函数的FillValues参数来指定。

    
    % 修正的变换结果
    
    cb_trans2 = imtransform(cb, tform_translate,...
    
        'XData', [1 (size(cb,2)+ xform(3,1))],...
    
        'YData', [1 (size(cb,1)+ xform(3,2))]);
    
    figure,
    
    imshow(cb_trans2)
    

    在这里插入图片描述

    3.定义空间变换的方式

    下面来讨论定义空间变换的两种方式:使用变换矩阵和使用对应点。

    使用变换矩阵

    Maketform函数可以接受一个N维的变换矩阵来产生TFORM结构体。而由于imtransform只能做二维变换,故只能指定3X3的转换矩阵。

    如使用3X3的矩阵来指定任何的仿射变换,而对于仿射变换,最后一列必须是0 0 1,所以你指定3X2的矩阵就足够了,在这种情况下,imtransform会自动添加第三列。

    下面的表格列举了imtransform可以执行的仿射变换以及相应矩阵的定义方法。
    在这里插入图片描述

    使用对应点

    • 除了指定变换矩阵,你还可以通过使用对应点来定义一个变换,让maketform自动生成变换矩阵。

    要对一个仿射变换使用此方法,需要在输入、输出图像上拾取3对非线性的点,而对于投影变换,则需要指定四对点。如

    in_points = [11 11;21 11; 21 21];
    
    out_points = [51 51;61 51;61 61];
    
    tform2 = maketform('affine', in_points, out_points);
    

    4.创建TFORM结构体

    • 有了上面的变换数据,就可以用maketform创建TFORM结构体了,也可以使用cp2tform函数来创建,具体详见Image Registration。

    在使用maketform创建TFORM结构时,需要指定必要的变换方式。如要做平移变换需要指定为仿射变换方式,除此maketform还支持投影变换,另外可以使用custom和composite选项来指定任意的空间变换。下表列出了maketform支持的变换类型
    在这里插入图片描述

    5.执行空间变换

    • 有了上面的TFORM结构体,就可以调用imtransform执行变换了。正如上面例子中所使用的那样
    [cb_trans xdata ydata]= imtransform(cb, tform_translate);
    

    另外,imtransform函数还支持几个可选输入参数来控制转换的不同效果,如输出图像的大小、填充值等。上面例子中的XData和YData就是控制输出图像大小的输入参数。对于填充值可以如下设置。

    指定填充值

    当执行一个变换时,输出图像上有很多像素不在位于原输入图像的位置,这些像素必须被赋予一些值,这就是填充值。默认情况下,imtransform函数设置这些像素为0,显示为黑色。而通过使用FillValues参数可以指定不同的颜色。

    对于灰度图像

    如果待转换的图像是灰度图像,就需要指定一个标量值来设置灰度的阴暗度。如下

    cb_fill = imtransform(cb, tform_translate,...
    
        'XData', [1 (size(cb,2)+xform(3,1))],...
    
        'YData', [1 (size(cb,1)+xform(3,2))],...
    
        'FillValues', .7 );
    
    Figure
    
    imshow(cb_fill)
    

    得到结果为
    在这里插入图片描述

    对于RGB图像

    对于RGB彩色图像,可以使用一个标量值或者一个1X3的向量。如果使用标量,imtransform会使用相应阴暗度的灰度值来填充;如果使用向量,则会将其按RGB彩色值来使用。

    % 彩色图像处理
    
    rgb = imread('onion.png');
    
    xform = [ 1 0 0
    
              0 1 0
    
             40 40 1 ];
    
    tform_translate = maketform('affine',xform);
    
    cb_rgb = imtransform(rgb, tform_translate,...
    
        'XData', [1 (size(rgb,2)+xform(3,1))],...
    
        'YData', [1 (size(rgb,1)+xform(3,2))],...
    
        'FillValues', [187;192;57]);
    
    figure
    
    imshow(cb_rgb)
    
    

    结果如下
    在这里插入图片描述

    原文章链接

    二维空间变换

    展开全文
  • 15.1 Ogre的“二维” 早在第一章,我们就曾经向读者介绍过,Ogre是一个3D图形渲染引擎,通过Ogre可以制作出各种各样的三维动画和游戏。但是,不要以为Ogre只能渲染跟3D相关的物体,Ogre同样可以渲染跟2D相关的...

    15.1 Ogre中的“二维”

            早在第一章,我们就曾经向读者介绍过,Ogre是一个3D图形渲染引擎,通过Ogre可以制作出各种各样的三维动画和游戏。但是,不要以为Ogre中只能渲染跟3D相关的物体,Ogre同样可以渲染跟2D相关的物体,比如:图形、图片、文字等等。另外,在开发一些小型2D游戏(而非大型3D游戏)的时候,可能只是需要Ogre进行“二维”的编程,而无处理与“三维”有关的操作。

    15.2 “表层(Overlay)”的概念

            在Ogre 中,“二维”的处理总是与“表层(Overlay)”的概念密切相关,这是因为Ogre中将所有的与“二维”相关的处理都被放置在称为“表层(Overlay)”中。顾名思义,表层是实质上是一个层,这个层覆盖在场景中所有其他的物体之上,而且,还有一点需要注意的是,表层在渲染队列的最后,也就是说,表层是场景中最后被渲染的。

            单纯从文字上讲,读者可能会对Ogre中“二维”、“表层”这些概念感到不好理解,其实,在我们之前章节的实例中,几乎每次都会看到Ogre中的“表层”。下面我们来看一个实例运行时的界面:

            上图是我们在第五章,运行一个实例的界面,可以看到,在界面中有一个三维的“食人魔”怪兽,另外在界面的左下角和右下角,分别有两个“层”(已用红框标出),它们是“二维”的,这就是Ogre中的“表层”。

     

    笔者注:

            Ogre中的表层(Overlay):一般是用来制作Gui界面的,但是这个Gui界面不具备用户交互的功能。这些表层位于整个“屏幕”的最上层,就像上图所示的那样。当然,读者可以自己设置“表层”的属性,如形状、大小、颜色和透明度等。

            当然,Ogre中的表层不一定非得应用于Gui界面,如果在三维游戏中需要使用二维的画面,也可以利用表层来实现。比如,在一款三维游戏中有一面墙,上面挂着一幅风景画,那么就可以利用表层来实现这个这个二维的“墙壁加风景画”的效果。

     

     

            还有一点要特别注意,Ogre中的二维常常通过表层来处理,但是,不是说表层只能处理与二维有关的物体,事实上,表层也可以处理与三维有关的物体。

     

    15.3 表层的框架结构

    15.3.1 基本概念

            所谓的表层框架,就是一个表层整体的布局和结构,因为不同的表层上的元素都有不同的布局样式和结构层次。下面的这段脚本代码,就可以生成一个完整的表层框架:

     

    MyOverlay

    {

             zorder 500

           

             container Panel(MyContainer1)

             {

                       metrics_mode pixels

                       left 0

                       top 0

                       width 50

                       height 50

     

                       container Panel(MyContainer2)

                       {

                                metrics_mode pixels

                                left 20

                                top 20

                                width 30

                                height 30

     

                                element TextArea(MyTextAreaOverlayElement)

                                {

                                         metrics_mode pixels

                                         left 15

                                         top 3

                                         width 15

                                         height 15

     

                                         font_name StarWars

                                         char_height 0.06

                                         colour 0.9 0.9 0.9

                                         caption MyTextAreaOverlayElement

                                }

                       }

               }

    }

     

            通过使用这段脚本,生成的表层框架如下图所示:

     

     

            从上图可以看出,表层框架中有两种不同的“物品”:Container(容器)和Element(元素),我们可以把他们统称为表层对象。顾名思义,表层对象就是存在于表层上的实实在在的对象。表层Container(容器)用来“盛放”表层Element(元素)。对于表层对象,有如下几点需要注意

            1.表层的外层必须是一个Container(容器)类型的表层对象,正如上图中的Container1。

            2.表层中的Container(容器)可以相互嵌套,也就是说,Container(容器)中可以再包含一个Container(容器)。正如上图中的Container1中又包含了Container2、3、4。

            那么,如何对表层中的对象进行定位呢? 我们可以在脚本的代码中看到其中的端倪。一般来说,对于每一个Element和Container,都是相对于其父容器进行定位,也就是说,看这个Element或Container盛放在哪一个容器中,比如,上图中的TextAreaOverlayElement2盛放于Container3中,那么Container3就是TextAreaOverlayElement2的父容器。

            在表层对象TextAreaOverlayElement2的定义脚本中有如下一行代码:

            metrics_mode relative

            可以看出,TextAreaOverlayElement2表层对象使用了relative即相对定位的方法,而其他表层对象的脚本代码中使用的代码是:metrics_modepixels,即采用的是像素定位的方法。

     

    笔者注:

            在我们选择表层对象的布局方式时,只能选择相对定位法或是元素定位法中的一种,不能同时选择两种。而且,容器本身与容器中的元素可以采用不同的定位方法。

    在实际应用中,多数是采用 “相对定位” 的方式对表层元素进行定位,这样做的好处是,在我们改变窗口的大小(或是表层的大小)时,表层对象的位置不会因此而发生相对的改变,另外,使用相对定位法时,我们也无需关心用户使用电脑屏幕的分辨率,因为相对定位不会因为屏幕分辨率的不同而使表层的布局结构发生改变。

     

     

            例如,上图中的底层容器Container1被放置到了屏幕绝对位置(0,0)处,Container2被放置到了相对于Container1(也就是Container2的父容器)的(2,5)处,而元素TextAreaOverlayElement1被放置在了相对于其父容器Container2的(15,3)位置处,但如果相对于Container1来说(即电脑屏幕(0,0)处),TextAreaOverlayElement1是被放置在了(17,8)位置处。(17,8)由它本身的相对位置(15,3)和它的父容器(2,5)相加得到。

     

    笔者注:

            1.在Ogre中,表层Element类下有非常多的派生类,也就是说,表层中的元素有非常多的种类,这些派生类都包括:PanelOverlayElement,BorderPanelOverlayElement以及TextAreaOverlayElement等。但是有一点需要注意,由于表层框架中不包括文本资源的类,因此如果我们需要在表层中渲染并显示文字,需要使用到表层之外的Font与FontManager类等来处理文本的渲染与显示。

            2.Ogre中的表层对象可以简单的分为两类:第一类是由Ogre进行管理的,上面的表层脚本就是其中的一个例子,这类的脚本由Ogre自身对其进行管理,多数用来进行一些简单的纹理输出与显示;第二类不由Ogre对其进行管理,比如我们在接下来会讲到的Gui界面等。这两类表层对象都可以被Ogre支持。

     

     

    15.3.2 表层对象的属性

            Ogre的表层元素类和表层容器类是可扩展的,也就是说,程序开发人员可以在基类已有功能的基础上,按照自己的需要对其功能进行扩展。

            不过,也有一部分表层的属性是Ogre中自带的,这些属性包括:Panel、BorderPanel、TextArea,我们可以在脚本中改变这些属性的值,以达到改变表层的目的。下面我们就对着几个属性一一介绍:

            (1)Panel(面板)

            我们可以把Panel(面板)看作是一个矩形区域,这个矩形区域可以包含其他的表层对象(表层元素和表层容器)。我们可以改变面板的透明度、面板的材质等不同的属性,下面我们就来一一介绍:

            · Transparent属性:该属性用来设置面板是否为透明。它有两个取值(true和false),当取值为true时表示,该面板为透明背景,且面板自己无须渲染。

            ·  tiling(layer,x_tile,y_tile)属性: 该属性用来设置材质的纹理在整个面板上X和Y方向贴图的次数。layer是指纹理层,取值为0一直到总纹理层数-1,比如,一共有5个纹理层,那么layer的取值就是0到4,我们可以利用layer设置多个纹理层相互叠加的背景效果。x_tile,y_tile分别是纹理在整个面板上X和Y方向贴图的次数。

            ·uv_coords (topleft_u,topleft_v,bottomright_u,bottomright_v)属性:该属性用来设置面板上的纹理的坐标位置。

     

     

            (2)BorderPanel(边缘面板)

            这种面板同第一种Panel面板相比显得更“高级”,它比Panel多了一个“边框”,这个边框是独立于面板的,也就是说,这个边框的属性可以独立于面板本身单独的设定。如果读者有过HTML编程的经验,相信对HTML中表格的操作不会陌生。BorderPanel的操作方法就跟HTML中的表格边框的操作方法类似,下面我们就详细的讲解一下它具有的属性:

            ·border_size(left,right,top,bottom),这个属性用来设置边框的尺寸,有left,right,top,bottom四个属性值,可以为表格的“左、右、上、下”等设置不同的尺寸。

             ·border_material(name),这个属性用来设置边框的材质,属性通过将材质的名称(name)作为一个参数传递进去,是边框具备一种材质。

     

    笔者注:

            边框的材质信息和面板中心区域的材质信息不同,因为中心区域里的材质通常只是平铺的,而边框上的材质往往就不能这样处理,因此,边框上面的材质通常不同于中心区域的材质。

     

            ·border_topleft_uv(u1,v1,u2,v2),这个属性用来设置边框顶部左侧的纹理坐标,与之类似的还有border_topright_uv,border_bottomleft_uv,border_bottomright_uv三个属性值,这里就不一一介绍了。

            ·border_left_uv(u1,v1,u2,v2) ,这个属性用来设置边框整体的纹理坐标,与之类似的还有border_right_uv,border_top_uv,border_bottom_uv三个属性,这里也就不一一介绍了。

     

            (3)TextArea(元素)

            通过它,可以渲染并显示文本。我们可以使用Ogre中的FontManager和Font类来定义字体,或者直接使用事先编写好的.fontdef文件。TextArea(元素)有如下的几个属性:

            ·font_name (name),这个属性用来设置要使用字体的名字。这个字体的名字通常被定义在一个.fontdef文件中,而且,在使用它之前要脚本调用这个“名字”时,它出在可用的状态。

            ·char_height(height),这个属性用来设置要显示的文字占屏幕高度的百分比。

            ·colour (red,green,blue),这个属性用来设置要显示的文字的颜色,有红、绿、蓝三个值。这三个值的取值范围都是0到1,通过这个属性,可以让我们要显示的文字有不同的颜色,使整个画面更加生动起来。不过,如果我们使用的字体已经实现定义好了颜色,那么就不必再使用这个属性了。

            ·colour_bottom(red,green,blue) 和 colour_top(red,green,blue),从这个属性的名称就可以看出来,它是用来定义文字上部和底部的颜色的,通过这两个属性,可以使文字的上部和下部显示不同的颜色,从而达到一种渐变的效果。

            (4)元素布局

            在上文中曾经介绍过,表层对象在其父容器中的定位方式有两种:相对定位法和元素定位法。每一个表层对象在其父容器中都有两个布局属性:垂直属性和水平属性。我们可以vert_align标记来设置垂直属性,标记的取值可以是top,center或者bottom中的一个;同样的,我们可以用horiz_align标记来设置水平属性,标记的取值可以是left,middle或者right中的一个。在默认的情况下,系统会自动将表层对象定位在其父容器的top和left处,也就是父容器的左上方。

     

    笔者注:

            下面,我们要向读者介绍一个深度(Z-Order)的概念,有过HTML编程的读者都会对Div中的层深度非常熟悉,每一个Div都有一个Z-Order的属性值,Z-Order是一个层深度的概念,它用来区分不同的Div层的叠放顺序,Z-Order值小的层放置在下面,Z-Order值大的层放置在上面。同理,在Ogre中,表层也有一个Z-Order的属性。这里的Z-Order的作用就是:意义在于: z-order值较的层会被绘制在底层,而 z-order值较大的层被绘制在上层,覆盖下面的层,因此这个Z-Order值,实质上是用来决定表层的绘制顺序的。

     

    15.4 有关表层的例子

    15.4.1 表层的简单使用

            在通过示例介绍模板的使用之前,我们先来通过一些简单的示例回顾一下表层元素的一些常用属性。需要说明一点:表层元素(OverlayElement)属性可以用在表层脚本中的container或element代码块中,它们必须各自占用一行。

            第一步,在media目录下新建一个文件夹名叫MyOverlays,然后在MyOverlays文件夹下新建一个文件名叫MyOverlay1.overlay,然后在这个文件内填入以下代码:

    MyOverlayTest1

    {

           zorder 500

           container Panel(MyOverlayTest1/ImagePanel)

           {    

                  metrics_mode pixels

                  horz_align right

                  vert_align bottom

                  top -640

                  left -650

                  width 640

                  height 640

                  material MyOverlayMaterial1

           }

    }

            这里MyOverlayTest1即为表层名,它的zorder值设置为了500,接着定义了一个container对象,类型名为Panel,它是一块能包含其它元素(或容器)的矩形区域,这里我们定义的实例名为“MyOverlayTest1/ImagePanel”。紧接着就是定义了一系列表层元素的属性:

            ·metrics_mode用来设置元素的大小和位置的单位,格式是metrics_mode<pixels|relative>。当采用像素模式的时候,元素的大小和位置以像素为单位进行衡量,在这种模式下,元素的位置和大小会随着电脑屏幕分辨率的变换而变换。例如:800*600分辨率下,某个元素的左上角坐标为20,20,大小为60,60。那么它将显示在屏幕左上方,但当分辨率为1024*768的时候,它的大小会发生变换,而且位置也会朝着左上方移动一下。当采用相对模式是,元素的位置和尺寸与显示分辨率无关,也就是说,元素的显示结果可以适应任何的屏幕分辨率。

            ·horz_align用来设置元素的水平对齐方式,其格式为:horz_align<left|center|right>。从属性的取值上可以猜想到,这个属性有可以设置3种元素的水平对齐方式:居左、居中和居右。

            ·left用来设置元素相对于它上一层的水平位置,其格式为:left <value>,在这里,元素的位置是相对于上一层的位置而言的。同理,top就是设置元素相对于它上一层的垂直位置。

            ·width/height用来设置元素的宽度和高度,它的大小是相对于屏幕大小而言的。

            ·material用来设置用于此元素的材质名。

            第二步,由于我们在第一步中使用到了MyOverlayMaterial1这个材质,所以我们还需要自己定义这个材质。打开media/materials/scripts这个文件夹,打开文件夹下的MyTest.material这个文件(如果没有这个文件,我们就手动新建一个),然后添加一个新材质,如下所示:

    material MyOverlayMaterial1

    {

        technique

        {

            pass

            {

                lighting off

                scene_blend alpha_blend

                depth_check off

                texture_unit

                {

                    texture leaf.png

                }

            }

        }

    }

            定义材质的内容我们这里就不多做介绍,如果读者哪里不明白可以参照材质介绍的相应章节温习一下。

            第三步,需要在程序中通过代码调用相应的Overlay,同样,我们使用我们在前面章节保存的模板代码,修改createScene函数中的内容如下:

    void createScene()

    {

    Ogre::Overlay*pOverlay =Ogre::OverlayManager::getSingleton().getByName("MyOverlayTest1");

    pOverlay->show();

    Ogre::Entity*ogreHead =mSceneMgr->createEntity("Head","ogrehead.mesh");

    Ogre::SceneNode*headNode =mSceneMgr->getRootSceneNode()->createChildSceneNode();

    headNode->attachObject(ogreHead);

    // Set ambient light

    mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5));

    // Create a light

    Ogre::Light*l =mSceneMgr->createLight("MainLight");

    l->setPosition(20,80,50);

    }

            在程序中调用Overlay的方式很简单,通过OverlayManager::getSingleton().getByName()函数获取到overlay的指针,再通过show函数打开显示开关即可。

            到此为止,大部分工作我们都已完成,但是如果现在编译并运行我们的程序还不行,因为我们还忘了一个重要的步骤,就是把我们的Overlay文件的路径告诉Ogre,要不然它是找不到我们的这个文件的,还记得resource.cfg这个文件吗,到我们的工作目录下打开resource_d.cfg(如果编译的是Release版本则打开resource.cfg文件),然后在[popular]部分        添加下面一行:

    FileSystem=media/MyOverlays

            保存,编译并运行我们的程序,我们会看到如下效果:

            通过上图,我们可以看到在“最上面”的一层,我们贴上了一张图,这片树叶无论我们怎样移动那个Ogre头,总是被这片树叶遮挡着,这也正是我们前面所要达到的效果,因为Overlay的渲染队列的渲染操作在所有其它渲染队列之后进行。

     

    15.4.2 模板的概念及其应用

            你可以使用模板创建各种有相同属性的元素,一个模板就是一个抽象元素,而且它并不增加到某一层中,它扮演的就是一个基类,元素可以继承并取得默认属性。要创建一个模板,关键字template必须是元素定义的第一个词(在container或element之前),模板元素被创建在最外层——它并不在某一层里指定。建议你在一个单独的层中定义模板,尽管这么做并不是必要的。在单独文件中定义模板,允许有不同的外观,方便替代。

            元素可以通过类似于C++继承的方式继承一个模板——通过在元素定义上使用:操作符。:操作符被放置在名字的右括号之后(以空格分隔)。要继承的模板名放在:操作符之后(也以空格分隔)。

            一个模板中可以包含子模板,子模板在模板被实例化时被创建,虽然使用template关键字对于子模板来说是可选的,但是为了清楚起见还是建议使用,因为子模板本身也是要做模板的。

           介绍完了模板,那么现在我们就再来通过一个示例向大家介绍一下模板的使用。

            第一步,打开MyOverlay1.overlay文件,继续添加内容,我们首先定义一个模板:

    template element TextArea(Overlay2/Basic2)

    {

           metrics_mode pixels

           left 150

           top 5

           width 90

           height 30

           font_name fangsong

           char_height 19

           caption IMedia

    }

            这里有几个我们没见过的属性:font_name、char_height、caption,其中font_name <name>表示要使用的字体名,这个字体必须被定义在一个*.fontdef文件中并确保在脚本使用时它是可用的。Ogre中默认自带了一个.fontdef的文件,我们可以打开media/fonts目录查看一下是否有个sample.fontdef文件,为了我们使用的方便,我们在这个目录下也自定义一个我们自己的文件MyFont.fontdef,然后按照sample.fontdef文件里的内容写一个新的字体名,如下所示:

    fangsong

    {

           type              truetype

           source           simfang.ttf

           size               32

           resolution      96

    }

            这里我们定义了一个叫做仿宋的字体,但是我们可以发现我们现在并没有simfang.ttf这个字体文件,因此我们需要到我们系统字体的安装目录把这个字体文件拷贝到我们当前这个文件夹下,以便Ogre在使用的时候能够找到,到我们系统的安装盘:\Windows\Fonts目录下,查找仿宋字体,然后拷贝到media/fonts目录下即可。这样一个新的字体名我们就创建好了。char_height <height>表示字幕的高度,虽然因为Ogre支持比例字体字母可能不尽相同,但是它会基于这个常量高度。caption是设置元素的文本标题。

            第二步,定义我们的Overlay,并让其中的元素继承刚才定义的模板:

    MyOverlayTest2

    {

           zorder 200

           container Panel(MyOverlayTest2/Panel2)

           {    

           element TextArea (Overlay2/NewText2) : Overlay2/Basic2  

            {

            }

           }

    }

            这里我们可以发现,在实例Overlay2/NewText2中我们没有定义任何东西,而是让它继承自模板Overlay2/Basic2,这样就可以继承模板的属性。

            第三步,修改应用程序代码中createScene函数中的内容如下:

    void createScene()

    {

    Ogre::Overlay*pOverlay2=Ogre::OverlayManager::getSingleton().getByName("MyOverlayTest2");

    pOverlay2->show();

    Ogre::Entity*ogreHead =mSceneMgr->createEntity("Head","ogrehead.mesh");

    Ogre::SceneNode*headNode =mSceneMgr->getRootSceneNode()->createChildSceneNode();

    headNode->attachObject(ogreHead);

    // Set ambient light

    mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5));

    // Create a light

    Ogre::Light*l =mSceneMgr->createLight("MainLight");

    l->setPosition(20,80,50);

    }

            这样,我们的工作基本算是完成了,编译并运行程序,我们应该是可以看到屏幕左上角有一行文字显示出来的,如果你能看到这些,说明你的程序已经没问题了,但是,或许读者朋友可能会和笔者当时遇到同一个问题,程序写得明明没有问题,但是就是看不到有一行文字出现,是的,工作做到现在,我们很可能还是看不到我们想看到的那一行文字,其实读者朋友不用担心,如果你是按照前面的要求一步一步坐下来的,那么的确你写的程序时没有什么问题的,经过我们在各大论坛上的查找,确实发现了这个问题:Ogre1.7.2 text rendering有个bug,如果不resize窗口大小,文字是不会显示的。不显示字体的原因是因为我们在使用的时候其实是没有加载font的关系,我们的程序运行时,如果我们的鼠标没有被捕获,也就是说如果我们的程序可以移出程序窗口的话,可以稍微拖拽缩放一下窗口试试,字体立刻就出现了。

            那么怎么解决这个问题呢?由于目前为止读者朋友可能还不太清楚怎样修改源代码,所以这里我们提供一种比较简单的解决方法,我们在创建Overlay元素之前如果能先读取一下字体,这样就可以显示文字了。在createScene函数体的最开始我们加入下面的代码:

    Ogre::FontManager *pFontManager =Ogre::FontManager::getSingletonPtr();

    Ogre::ResourceManager::ResourceCreateOrRetrieveResultres =pFontManager->createOrRetrieve("fangsong",Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

    res.first->load();

            这样,我们通过手动先读取一次字体的方式,就可以看到文字显示的效果了,编译并运行程序,我们应该就可以看到如下效果了:


            或许读者朋友会问直接把要显示的内容写在脚本文件里了,那么我们在程序中还能实时改变吗?是的,完全可以,我们在程序代码中可以完全获取整个Overlay上的所有元素,比如,我们要在程序中把刚才显示在屏幕上的“Imedia”修改为“Hello,IMedia”,那么我们只需在createScene函数体中再加下面两行代码即可:

    OverlayElement* gui=OverlayManager::getSingleton().getOverlayElement("Overlay2/NewText2");

    gui->setCaption(" Hello,IMedia");

            这里通过getOverlayElement函数即可获取到对应元素名的指针,然后即可对其修改相应内容。

     

    15.4.3 利用表层显示中文

            我们先来了解一下如何在Ogre中创建一个表层及如何在表层中添加表层对象。我们可以调用SceneManager::createOverlay函数来创建一个表层,当然我们也可以将表层定义在一个脚本中,然后通过Ogre调用这个脚本。实际应用中,多数是将表层定义在脚本中, 然后通过Ogre调用这个脚本。

     

    笔者注:

            我们可以一次定义很多个表层,这些表层默认都是不显示的。我们可以调用show()函数来设置其显示;当然,我们也可以一次显示多个表层,这就要用到Overlay::setZOrder()函数。

     

            在上一个小节中,我们曾向读者介绍过表层对象的概念,那么,我们要如何在Ogre中的表层上添加表层对象呢?

            在Ogre中添加一个表层对象需要调用OverlayManager::createOverlayElement函数。例如,如果我们要在表层上添加一个Panel面板,可以通过下面的代码实现:

            OverlayManager::getSingleton().createOverlayElement("MyPanel","myPanel");

     

    笔者注:

            有一点需要注意,只有OverlayContainers可以直接添加进Ogre的表层中去,而且内部容器的Z-Order值要大于外部容器的Z-Order值。如果我们想在表层上添加一个面板(Panel),可以直接调用Overlay::add2D函数。

            如果要在Container(容器)里面再添加表层元素或容器,可以调用OverlayContainer::addChild函数。

     

            好了,了解了上面这些基本的概念之后,我们就可以利用表层,在Ogre中实现文字的显示了。在Ogre中,文字是不能被单独渲染的,它必须作为纹理,投射到表层TextAreaOverlayElement中,用这样的方法渲染并显示。我们既可以使用微软为我们提供的TrueType类型字体文件(.ttf文件)来完成对文字的操作,也可以用“图片”的方式,即将带有文字的图片作为纹理,投射到表层中进行渲染。若我们使用微软为我们提供的.ttf字体文件来显示文字,那么我们就省下了制作纹理的时间(因为微软已经为我们创建好了纹理),不过,这种方法也有弊端,那就是Ogre在渲染这些纹理的时候要花费非常多的时间,从而会影响渲染效率。若我们使用了“图片”的方式,来显示文本,Ogre渲染纹理的时间就会大大缩短,不过,这些“图片”都需要用户自己手动制作,这就增加了前期处理的时间。在实际应用中,读者要充分权衡这两种方法的优点和缺点,以选择最适合自己程序的那种方法。

            说了这么多,到底怎样在Overlay上显示中文呢?或许读者朋友尝试过,但是总是显示不正确或是出错,这里笔者也把网友提供的一种显示中文的技巧和大家分享,希望对大家有用。例如,我们想在窗口上显示这么一句话:“你好,IMedia”。

            第一步,打开前面我们新建的Myfont.fontdef文件,修改为如下:

    fangsong

    {

           type              truetype

           source           simfang.ttf

           size               32

           resolution      96

           code_points  20320-20320 22909-22909 65292-65292 73-73 77-77 101-101 100-100 105-105 97-97

    }

            第二步,修改应用程序代码中的gui->setCaption(" Hello,IMedia");为:gui->setCaption(L"你好,IMedia"); 注意字符串前加上一个大写的L,表示将ANSI字符串转换成unicode的字符串,就是每个字符占用两个字节。

            编译并运行程序,你应该可以看到中文显示出来了:

            相信读者朋友最大的疑问在于MyFont.fontdef文件里新加的那几行内容了。我们新加的code_points就是要使用的Unicode字符编码,例如,‘你‘这个字的编码用16进制表示是4F60,转换成10进制就是20320,在Ogre字体定义中使用的是10进制,根据Ogre字体定义文档的描述,这个’你‘字需要在文件中描述成20320-20320这样的形式。你可以到http://www.chi2ko.com/tool/CJK.htm查看每个字符对应的编码。当然为了自动生成Ogre字体code_points,网上热心的网友自己动手写了一个自动转换的软件,大家可以到这里去下载:

            http://files.cnblogs.com/gogoplayer/Ogre/UnicodeViewer.rar

            http://files.cnblogs.com/gogoplayer/Ogre/UnicodeViewer.rar

     

    15.4.4 在表层中加入3D

            介绍了关于Overlay的这些内容,相信读者朋友应该对Overlay的基本概念有了一定的了解,更多的技巧还需读者朋友更多的实践经验。下面我们来分析本节的最后一个问题,也是读者朋友一直关心的,就是如何向Overlay中加入3D。

            虽然Overlay一般是用来处理2D界面的,如GUI界面。但有些时候,我们想做些特别的界面,比如左上角加入一个人物状态框,上面可以有人物动作,人物可以旋转观察等。那么我们该怎样向Overlay中添加3D实体呢?下面我们分几个步骤来讲:

            第一步,移出前面我们在程序中向createScene函数中增加的所有代码,增加如下内容:

    void createScene()

    {

    Ogre::Overlay*pOverlay1=Ogre::OverlayManager::getSingleton().getByName("MyOverlayTest1");

    pOverlay1->show();

    Ogre::Overlay*pOverlay =Ogre::OverlayManager::getSingleton().getByName("MyOverlayTest3");

    Ogre::Entity*ogreHead =mSceneMgr->createEntity("Head","ogrehead.mesh");

    headNode = new Ogre::SceneNode(mSceneMgr,"PointerNode");

    headNode->attachObject(ogreHead);

    headNode->setPosition(30,0,-160);

    ogreHead->setMaterialName("Examples/DepthCheck1");

    pOverlay->add3D(headNode);

    pOverlay->show();

    Ogre::Entity*ogreHead2 =mSceneMgr->createEntity("Head2","ogrehead.mesh");

    Ogre::SceneNode*headNode2 =mSceneMgr->getRootSceneNode()->createChildSceneNode();

    headNode2->attachObject(ogreHead2);

    headNode2->scale(3,3,3);

    // Set ambient light

    mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5));

    // Create a light

    Ogre::Light*l =mSceneMgr->createLight("MainLight");

    l->setPosition(20,80,50);

    }

            在这里得注意:headNode这个场景结点不能使用SceneNode::createChildSceneNode方法来创建结点(程序运行会报错),也不能使用SceneManager::createSceneNode方法来创建结点(这样做其实是可以显示出来的,但当对此结点进行旋转、缩放等操作时,实体会显示异常)。也就是说,我们在程序结束时要手动释放掉mPointerNode。

            第二步,添加第一步中用到的材质“Examples/DepthCheck1

    material Examples/DepthCheck1

    {

        technique

        {

            pass

            {

                depth_check off

                texture_unit

                {

                    texture RustySteel.jpg

                }

                texture_unit

                {

                    texture spheremap.png

                    colour_op_ex add src_texture src_current

                    colour_op_multipass_fallback one one

                    env_map spherical

                }

            }

        }

    }

            这里要注意,在材质中应该把深度检查关闭,不然可能被场景中的其它3D物体遮住。除了这种方式外,如果我们不想修改原来实体的材质,可以在程序中通过遍历实体各个子实体对应的材质通道关闭其深度检查的方式,也可以达到相同的效果。

            第三步,添加一个protected成员变量:

    Ogre::SceneNode*headNode;

            第四步,在构造函数中初始化成员变量:

    headNode = NULL;

            第五步,在析构函数中添加如下内容:

    delete headNode;

            编译并运行程序,我们将看到如下效果,我们的实体模型在Overlay上面了:

            读者朋友可以再添加一些正常情况的实体,通过移动鼠标和键盘的方式观察,可以发现无论怎么移动,我们在Overlay上定义的这个实体都是在最前面的。我们还可以通过旋转或移动headNode改变这个实体的状态。

     

    15.4.5 为场景添加背景

            前面部分我们一直在讨论的都是Overlay,但是有时我们的要求并不仅限于像使用Overlay那样一直把需要展示的东西放在窗口的最前面,也就是说,有的时候我们可能想把二维的一些显示效果放在我们要渲染的模型的背后,就上前面图中的那样,树叶在第一个Ogre头的后面,或许读者会问,这样的效果我们不是已经做到了,其实读者朋友不妨细细回味一下,之所以出现这样的效果是因为第一个Ogre头和树叶的渲染,我们都把它们放在了Overlay里面,而我们的不可能把所有的3D模型都放在Overlay里面,如果全部都采用这样的方式,无异于弄巧成拙,增加自己的工作量。或许读者朋友又会想到另一种方式:我们不是可以设置视口的背景吗,或者像添加天空盒那样都可以实现啊,但是读者朋友想象一下,如果我们想在背景和3D模型之间再放一些东西该怎么实现,如下图所示:

            如果我们想达到上图中所示的效果,我们可以Ogre中的另一个类Rectangle2D,它相当于一个Overlay一样的二维的平面,不过它可以灵活改变自己的渲染队列次序,我们可以把它放到模型后面渲染。

           接着前面章节的代码,我们继续添加内容,在createScene函数体中继续添加以下内容:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    mSceneMgr->setSkyBox(true,"Examples/SpaceSkyBox");//设置天空盒

    Ogre::Rectangle2D *rect2d =newOgre::Rectangle2D(true);

    rect2d->setCorners(-0.8f, 0.5f, 0.2f, -0.5f);

    rect2d->setMaterial("MyOverlayMaterial1");

    rect2d->setRenderQueueGroup(RENDER_QUEUE_SKIES_EARLY+1);

    rect2d->setBoundingBox(Ogre::AxisAlignedBox(-100000.0*Ogre::Vector3::UNIT_SCALE, 100000.0*Ogre::Vector3::UNIT_SCALE));

    Ogre::SceneNode*node =mSceneMgr->getRootSceneNode()->createChildSceneNode();

    node->attachObject(rect2d);

            代码分析:

            第1行:设置天空盒,也就是上图中我们看到的天空背景;

            第2行:定义一个Rectangle2D对象,构造函数我们传递一个参数true,表示包含纹理坐标;

            第3行:setCorners是设置二维矩形的相对大小,主要由四个参数,分别为左、上、右、下坐标,坐标范围如下图所示:

            第4行:设置二维矩形所使用的材质。注意,在我们定义的这个材质中应该把深度检查关闭,不然可能被场景中的其它3D物体遮住(MaterialPass中设置depth_check off来关闭深度检查),前面我们也提到过除了这种方式外,如果我们不想修改原来实体的材质,可以在程序中通过遍历实体各个子实体对应的材质通道关闭其深度检查的方式也可以达到相同的目的。比如说,我们这里使用的MyOverlayMaterial1材质中没有在pass通道中加上depth_check off这句,那么我们完全可以在程序代码中修改材质,如下所示:

    MaterialPtr material=MaterialManager::getSingleton().getByName("MyOverlayMaterial1");

    material->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false);

            通过这样的设置同样可以达到禁用深度检查的目的。

            第5行:这里就是我们前面提到过的Rectangle2D可以灵活改变自己要渲染的时机,我们可以查看一下Ogre中渲染队列中各个不同层次,setRenderQueueGroup(RENDER_QUEUE_SKIES_EARLY+1);的意思就是让我们的二维矩形放在下图中红色箭头处的次序进行渲染,正是因为这样,我们的二维矩形才会在其它3D模型之前渲染:

    enum RenderQueueGroupID

    {

    /// Use this queue for objects which must be rendered first e.g. backgrounds

        RENDER_QUEUE_BACKGROUND = 0,

        /// First queue (after backgrounds), used for skyboxes if rendered first

    RENDER_QUEUE_SKIES_EARLY = 5,



        RENDER_QUEUE_1 = 10,

        RENDER_QUEUE_2 = 20,

    RENDER_QUEUE_WORLD_GEOMETRY_1 = 25,

        RENDER_QUEUE_3 = 30,

        RENDER_QUEUE_4 = 40,

    /// The default render queue

        RENDER_QUEUE_MAIN = 50,

        RENDER_QUEUE_6 = 60,

        RENDER_QUEUE_7 = 70,

    RENDER_QUEUE_WORLD_GEOMETRY_2 = 75,

        RENDER_QUEUE_8 = 80,

        RENDER_QUEUE_9 = 90,

        /// Penultimate queue(before overlays), used for skyboxes if rendered last

        RENDER_QUEUE_SKIES_LATE = 95,

        /// Use this queue for objects which must be rendered last e.g. overlays

        RENDER_QUEUE_OVERLAY = 100,

    /// Final possible render queue, don't exceed this

    RENDER_QUEUE_MAX = 105

    };

            而我们再来看一下Overlay的渲染次序在后面,因此才会在3D模型渲染之后进行。

            第6行:设置边界盒,这里我们设置边界盒的范围很大的目的是希望二维矩形总是保持可见的。

            其它几行的代码都是我们前面介绍过的,这里就不再赘述。

    15.5 合成器与合成器链

    15.5.1 合成器

            合成器(Compositor framework)是Ogre新加入的一个特性,它是Ogre API的一部分,用户可以通过它轻松的实现后加工处理效果。简单的说,Ogre中的合成器是用来处理Ogre中的特效的,通过它,可以用来完成多种多样的特效。例如,我们可以通过合成器实现让一个表面暗淡的金属周围发光的特效;也可以通过合成器实现场景中的某个位置的朦胧效果等等。

            在Ogre中使用合成器,通常也是通过“脚本”来实现的,就像上一节中讲到的表层的使用方法一样,我们可以预先定义一种合成器脚本,然后在程序中调用它,以实现某种特效。Ogre中有一些自带的合成器脚本文件,我们可以在Samples/Common/Media/materials/scripts目录中找到这些脚本文件。进入上述目录,我们打开其中的脚本文件,看看其中的代码(注意:代码很长,这里只列出了一部分,读者可以在自己的电脑上打开这个文件并查阅其中的代码):

    //Dark Sylinc's Bloom

    compositor Bloom

    {

        technique

        {

            // Temporary textures

            texture rt_output target_width target_height PF_R8G8B8

            texture rt0 target_width_scaled 0.25 target_height_scaled 0.25 PF_R8G8B8

            texture rt1 target_width_scaled 0.25 target_height_scaled 0.25 PF_R8G8B8

     

            target rt_output

            {

                // Render output from previous compositor (or original scene)

                input previous

            }

     

            target rt0

            {

                // Start with clear texture

                input none

                // Horizontal blur pass

                pass render_quad

                {

                    // Renders a fullscreen quad with a material

                    material Ogre/Compositor/BrightPass2

                    input 0 rt_output

                }

            }

     

            target rt1

            {

                // Start with clear texture

                input none

                // Horizontal blur pass

                pass render_quad

                {

                    // Renders a fullscreen quad with a material

                    material Ogre/Compositor/BlurV

                    input 0 rt0

                }

            }

     

            target rt0

            {

                // Start with clear texture

                input none

                // Horizontal blur pass

                pass render_quad

                {

                    // Renders a fullscreen quad with a material

                    material Ogre/Compositor/BlurH

                    input 0 rt1

                }

            }

     

            target_output

            {

                // Start with clear output

                input none

                // Draw a fullscreen quad

                pass render_quad

                {

                    // Renders a fullscreen quad with a material

                    material Ogre/Compositor/BloomBlend2

                    input 0 rt_output

                    input 1 rt0

                }

            }

        }

    }

     

    compositor Glass

    {

        technique

        {

            texture rt0 target_width target_height PF_R8G8B8

     

            target rt0 { input previous }

     

            target_output

            {

                // Start with clear output

                input none

     

                pass render_quad

                {

                    material Ogre/Compositor/GlassPass

                    input 0 rt0

                }

            }

        }

    }

     

    compositor "Old TV"

    {

        technique

        {

            texture rt0 target_width target_height PF_R8G8B8

     

            // render scene to a texture

            target rt0 { input previous

    }

     

            target_output

            {

                // Start with clear output

                input none

     

                pass render_quad

                {

                    // convert the previous render target to a black and white image, add some noise, distort it,

                    // then render to scene aligned quad

                    material Ogre/Compositor/OldTV

                    input 0 rt0

                }

            }

        }

    }

            上面的代码中列出了三个合成器脚本,它们分别是compositor "Old TV"、compositor Glass和compositor Bloom。在代码中compositor "OldTV"的定义中,“inputnone”这句代码的作用是使使用合成器中的目标将存在于视口中的内容忽略;“pass render_quad”这句代码的作用是高速合成器将通路渲染到整个目标区域中来,在这里,与render_quad同级的取值有如下几种:

          ·render_quad:将通路渲染到整个目标区域中来。

     

          ·render_scene:只将屏幕显示的内容渲染到目标区域中来。

     

          ·tencil:将模板缓存中的内容进行渲染。

     

          ·clear:将这个值传入使用合成器的目标的缓存中。

     

    15.5.2 合成器应用实例1

            那么,了解了这么多关于合成器的基本概念,合成器在Ogre中是如何实现的呢?Ogre中和合成器的运作流程是什么样子的呢?下面我们通过一个实例来详细看一下。

            首先,我们准备一个场景,用来展示不同的合成特效。

            同样,我们使用我们在前面章节保存的模板代码,修改createScene函数中的内容如下:

    void createScene()

    {

         Ogre::Entity*ogreHead =mSceneMgr->createEntity("Head","ogrehead.mesh");

         Ogre::SceneNode*headNode =mSceneMgr->getRootSceneNode()->createChildSceneNode();

         headNode->attachObject(ogreHead);

    // Set ambient light

         mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5));

         // Create a light

         Ogre::Light*l =mSceneMgr->createLight("MainLight");

         l->setPosition(20,80,50);

    }

            为了更好的理解合成器中的各个概念,我们还是像以前一样,先通过一个简单的示例来逐步讲解,让大家熟悉整个的添加过程。

            第一步,在media/materials/scripts目录下在新键一个材质文件名叫MyTest3.material,并添加一个新材质,如下所示:

    material MyCompositorMaterial1

    {

                  technique

                  {

                                pass

                                {

                                              texture_unit

                                              {

                                              }

                                }

                  }

    }

            这里我们仅仅定义了一个空的材质,没有附加任何特别的东西。

    第二步,在media/materials/scripts目录下,新建一个文件名叫MyCompsitors.compositor,然后增加如下内容:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    compositor MyCompositor1

    {

                  technique

                  {

                                texture scene target_width  target_height  PF_R8G8B8

                                target scene

                                {

                                              input previous

                                }

                                target_output

                                {

                                              input none

                                              pass render_quad

                                              {

                                                            material MyCompositorMaterial1

                                                            input 0 scene

                                              }

                                }

                  }

    }

            第三步,在应用程序代码中的createScene函数中继续增加如下代码:

    Ogre::CompositorManager::getSingleton().addCompositor(mCamera->getViewport(),"MyCompositor1");

    Ogre::CompositorManager::getSingleton().setCompositorEnabled(mCamera->getViewport(),"Myompositor1",true);

            至此,所有的工作已经完成了,编译并运行程序,你会发现和原来没有添加这些时的效果是一样的,读者朋友不用担心,其实我们的合成器框架已经开始工作了,下面我们就结合上面的内容逐步给大家讲解各部分的概念及整个的运作流程。

     

    笔者注:

            合成器脚本是在资源组被初始化时装载的,Ogre会在所有与组相关的资源位置搜索“*.compositor”后缀的文件,并解析它。我们可以像上面第二步中那样定义合成器脚本,多个合成器也可能被定义在单独的一个脚本里。

    脚本中的每个合成器必须有一个名字,而且这个名字必须是唯一的,它可以包括路径字符以便逻辑上区分你的合成器,也为了避免重名,但是引擎并不将这个名字分级对待,而仅作为字符串处理。名字里也可以包含空格,但是如果这样的话,就必须用双引号括起来,如compositor "My Name"。  

     

     

    15.5.3 合成器应用实例2

            前面虽然我们把场景渲染成了一张纹理,但是并没有修改它,现在我们对它做一些工作,来看到不同的效果。

            这里我们使用shader着色程序来修改我们的纹理。

            第一步,修改MyCompositorMaterial1材质为如下内容:

    material MyCompositorMaterial1

    {

                  technique

                  {

                                pass

                                {

                                              fragment_program_ref MyCompFragmentShader1

                                              {

                                              }

                                              texture_unit

                                              {

                                              }

                                }

                  }

    }

            这里我们添加了对片断程序MyCompFragmentShader1的引用。

            第二步,定义我们的片断程序,注意,要放在上面材质定义的内容之前,因为我们在材质中引用它的时候要保证以及定义:

    fragment_program MyCompFragmentShader1 cg

    {

                  source Ogre3DCompShaders.cg

                  entry_point MyCompFragmentShader1_Main

                  profiles ps_2_0 arbfp1

    }

            第三步,我们可以看到第二步中我们使用的cg文件名叫Ogre3DCompShaders.cg,因此在media\materials\programs目录下,新建一个文件Ogre3DCompShaders.cg,然后填入以下内容:

    void MyCompFragmentShader1_Main(float2 uv : TEXCOORD0,

                                                            out float4 color : COLOR,

                                                            uniform sampler2D texture)

    {

                  float4 temp_color = tex2D(texture,uv);

                  float greyvalue = temp_color.r * 0.3 + temp_color.g * 0.59 + temp_color.b * 0.11;

                  color = float4(greyvalue,greyvalue,greyvalue,0);

    }

            编译并运行程序,你会看到如下效果:

            代码分析:

            这里我们可以发现由于我们增加了一个片断着色到我们的合成器中,最终渲染出来的效果变成了灰度图的效果,shader着色编程的知识我们这里就不多做赘述,如果读者哪里不明白可以参考GPU编程一章。回去查看前面合成器的工作流程我们可以发现,我们在修改纹理阶段通过在我们的材质中加入一个片段着色程序,的确达到了修改的目的,而片段着色程序中所完成的工作就是查询相应纹理坐标处对应的颜色,然后通过相应的公式把对应的RGB通道颜色转换成最终的灰度颜色,而我们计算时乘以的那些系数表示的是各种颜色通道的贡献值,研究发现程序这几个系数最终呈现的灰度图将会是一种比较好的效果。

            如果我们把片断着色程序中最后一行输出颜色的公式改为如下:

    color = float4( 1.0 - temp_color.r,1.0 - temp_color.g, 1.0 - temp_ color.b,0);

            我们然后运行程序会发现整个屏幕窗口中的颜色和最开始的相比都翻转过来了。

     

     

     

       

    15.5.4 合成器链

            合成器链,顾名思义,是一个由若干合成器组成的链,读者可以想成将这些合成器收尾相连形成一个链,前一个的输出是后一个的输入,这样一来,我们可以利用这个合成器链生成更复杂的特效,因为用合成器链处理的特效是所有链上的合成器的特效的叠加。例如合成器脚本1→2→3→4→5,当我们利用这个脚本实现某种3D特效的时候,分别经由1、2、3、4、5这五个合成器,然后将各自的效果叠加形成最终的效果,其中,各个合成器之间是通过“纹理传送”的方式进行通信的。另外,合成器可以通过调用脚本的方式实现。而且,合成器效果也可以从代码中直接创建。

            在15.2.1小节中,我们曾向读者介绍过,合成器的设计是脚本的形式,即独立于Ogre单独设计,最后通过Ogre调用就可以。在Ogre中,合成器是通过CompositorManager类来管理的,既然合成器链有很多合成器组成,那么合成器链本质上来说也是一个合成器。在合成器链中包含很多以脚本的形式载入的CompositorInstance类的实例,我们可以从这些实例中选择一个,并将其与某一个“视口”绑定。也就是说,绑定之后的实例就被挂接你可以把其中一个CompositorChain类的对象中,而且这个实例被CompositorManager类来管理。与有关合成器的操作息息相关的,还有以下几个类:CompositionTechnique(技术)、CompositionPass(通路)、CompositionTargetPass(目标通路)等,这些类都负责参与对合成器的管理和操作。

     

    15.5.5 合成器链使用的实例

            读者朋友或许会想这样的效果是不是太单调了,其实合成器的功能远不止这些,下面我们再深入一些,介绍一些怎么组合两个合成器效果。

            现在我们想让前面我们实现的灰度效果和翻转颜色效果进行结合,最终呈现一种新的效果,因此为了达到这个效果,我们先做一些重复性的工作:

            第一步,在MyTest3.material中复制MyCompFragmentShader1材质MyCompositorMaterial1的内容创建一个新的MyCompFragmentShader2和材质MyCompositorMaterial2(除了名称外,其它内容基本一样):

    fragment_program MyCompFragmentShader2 cg

    {

                  source Ogre3DCompShaders.cg

                  entry_point MyCompFragmentShader2_Main

                  profiles ps_2_0 arbfp1

    }

     

    material MyCompositorMaterial2

    {

                  technique

                  {

                                pass

                                {

                                              fragment_program_ref MyCompFragmentShader2

                                              {

                                              }

                                              texture_unit

                                              {

                                              }

                                }

                  }

    }

            第二步,再Ogre3DTestShaders.cg中恢复MyCompFragmentShader1_Main中的内容,把翻转颜色的内容恢复为原来的灰度图内容:

    color = float4(greyvalue,greyvalue,greyvalue,0);

            新建一个函数MyCompFragmentShader2_Main使其内容为计算翻转颜色的着色程序:

    void MyCompFragmentShader2_Main(float2 uv : TEXCOORD0,

                                                            out float4 color : COLOR,

                                                            uniform sampler2D texture)

    {

                  float4 temp_color = tex2D(texture,uv);

                  color = float4( 1.0 - temp_color.r,1.0 - temp_color.g, 1.0 - temp_color.b,0);

    }

            第三步,在MyCompositors.compositor文件中新定义一个合成器名叫MyCompositor2,内容如下:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    compositor MyCompositor2

    {

                  technique

                  {

                                texture scene target_width target_height PF_R8G8B8

                                texture temp  target_width target_height PF_R8G8B8

                                target scene

                                {

                                       input  previous

                                }

                                target  temp

                                {

                                       pass  render_quad

                                       {

                                                     material  MyCompositorMaterial1

                                                     input  0  scene

                                       }

                                }

                                target_output

                                {

                                              input  none

                                              pass  render_quad

                                              {

                                                            material MyCompositorMaterial2

                                                            input  0  temp

                                              }

                                }

                  }

    }

            第四步,把应用程序代码中使用MyCompositor1的两处地方修改为MyCompositor2,编译并运行程序,你将会看到如下黑白效果和翻转颜色结合的效果:

            代码分析

            在这个新的合成器中,我们需要两个纹理,一个用来存储场景,另一个用来存储一些临时结果(第5、6行)。对于场景纹理,我们和以前的内容一样,对于临时纹理,我们用来填充场景纹理和我们定义的黑白材质,接着使用临时材质和翻转颜色材质创建我们最后的输出纹理。从场景渲染的流程中可以看出,这一次我们首先创建了一个“中间纹理”,将其作为渲染目标作为接受程序中的“黑白纹理”,然后将这两次纹理的叠加结果输出,作为最终的显示效果。

            好了,了解了上面这些关于合成器的基础知识,现在让我们来看一些更复杂的操作。

            首先,为了思路的清晰,我们还是重新写脚本(虽然大部分内容还是一样),之所以不在原来的脚本上修改,是为了帮读者朋友理清思路,如果大家对这整个过程都已十分了解,不妨在原来的脚本基础上修改。

            第一步,我们需要在MyCompositors.compositor文件中定义新的合成器脚本名叫MyCompositor3,和原来的内容基本一样:

    compositor MyCompositor3

    {

                  technique

                  {

                         texture scene target_width target_height PF_R8G8B8

                         target scene

                         {

                                input previous

                         }

                         target_output

                         {

                                input none

                                pass render_quad

                                {

                                    material MyCompositorMaterial3

                                       input 0 scene

                                }

                         }

                  }

    }

            第二步,在我们需要在MyTest3.material文件中中定义一个片断着色程序MyCompFragmentShader3和一个新的材质名叫MyCompositorMaterial3,和原来的内容基本一样:

    fragment_program MyCompFragmentShader3 cg

    {

                  source Ogre3DCompShaders.cg

                  entry_point MyCompFragmentShader3_Main

                  profiles ps_2_0 arbfp1

    }

    material MyCompositorMaterial3

    {

                  technique

                  {

                                pass

                                {

                                              fragment_program_ref MyCompFragmentShader3

                                              {

                                              }

                                              texture_unit

                                              {

                                              }

                                }

                  }

    }

            第三步,在Ogre3DCompShaders.cg中定义一个新的函数名叫MyCompFragmentShader3_Main,填入以下内容:

    void MyCompFragmentShader3_Main(float2 uv : TEXCOORD0,

                                                         out float4 color : COLOR,

                                                            uniform sampler2D texture)

    {

                  float num = 50;

                  float stepsize = 1.0/num;

                  float2 fragment = float2(stepsize * floor(uv.x * num),stepsize * floor(uv.y * num));

                  color = tex2D(texture,fragment);

    }

            第四步,修改应用程序中的代码,把对合成器名的两处调用换成MyCompositor3即可,最后编译并运行程序,你将会看到如下效果:

            代码分析:

            我们可以看到,最终看到的画面基本无法识别了,其实原因很简单,因为我们在片段着色程序中对纹理做了修改,从而影响到了合成器的最终效果。由于其它部分的脚本内容我们都已经见过,所以这里我们主要分析一下MyCompFragmentShader3_Main函数中的内容,正是因为这几行代码才影响了我们程序最终的显示效果。

            函数体中第一行代码,我们定义了一个变量num,它代表了我们想要显示的像素个数,这里设置为50意味着每一个轴(x、y轴)上将由50个像素;第二行代码定义了第二个变量stepsize,我们用1除以像素的数量给它赋值,后面我们需要用这个值来计算纹理坐标。在这个函数中,我们要达到的功能时减少最终要显示的像素数量,原理很简单,我们是让一些相邻的像素具有同样的颜色。(注意:floor函数是对输入参数向下取整。例如floor(float(1.3))返回的值为1.0)。

            如果我们想在程序中手动改变最终想要显示的像素的数量,我们可以把对应的参数修改写到我们的程序中,修改MyCompFragmentShader3_Main函数的参数列表及其函数体中的内容如下:

    void MyCompFragmentShader3_Main(float2 uv : TEXCOORD0,

                                                                   out float4 color : COLOR,

                                                                   uniform sampler2D texture,

                                                                   uniform float numpixels //新添加

    )

    {

                  float num = numpixels; //修改部分

                  float stepsize = 1.0/num;

                  float2 fragment = float2(stepsize * floor(uv.x * num),stepsize * floor(uv.y * num));

                  color = tex2D(texture,fragment);

    }

            然后,到MyTest3.material中修改片段着色部分的代码,增加一个默认参数块:

    fragment_program MyCompFragmentShader3 cg

    {

                  source Ogre3DCompShaders.cg

                  entry_point MyCompFragmentShader3_Main

                  profiles ps_2_0 arbfp1

           //默认参数块开始

                  default_params

              {

                param_named numpixels float 50

              }

          //默认参数块结束

    }

            现在问题来了,我们该怎么在程序代码里修改“numpixels”这个参数呢?当然通过Entity我们也可以获取对应的材质从而达到修改着色器变量的目的,不过,这里我们介绍在合成器中使用的一种新的方法。因此,这里我们还需要在做一些工作才能达到我们的目的。

            第一步,让我们的程序继承一个新的类Ogre::CompositorInstance::Listener如下所示:

    class Example1 : public ExampleApplication,Ogre::CompositorInstance::Listener

            这是一个合成器监听器类,该类有两个主要的函数notifyMaterialSetup和 notifyMaterialRender,notifyMaterialSetup函数在编译渲染目标操作所包含的材质时激活;notifyMaterialRender 函数在编译渲染目标操作所包含的材质前激活,因此材质参数是可以变化的。

            第二步,这里我们重写notifyMaterialSetup函数,如下所示:

    void notifyMaterialSetup(uint32 pass_id, MaterialPtr &mat)

    {

    mat->setcu

    mat->getBestTechnique()->getPass(pass_id)->getFragmentProgramParameters()->setNamedConstant("numpixels",125.0f);

    }

            在这个函数体中我们通过传递过来的材质指针就可以取到材质中使用的着色器变量,从而对其进行修改。

            第三步,修改createScene函数体中关于合成器调用部分的代码,如下所示:

    Ogre::CompositorManager::getSingleton().addCompositor(mCamera->getViewport(),"MyCompositor3");

    Ogre::CompositorManager::getSingleton().setCompositorEnabled(mCamera->getViewport(),"MyCompositor3",true);

    Ogre::CompositorInstance*comp =Ogre::CompositorManager::getSingleton().getCompositorChain(mCamera->getViewport())->getCompositor("MyCompositor3");

    comp->addListener(this);//注册监听器

            由于我们继承了合成器实例的监听器类,因此这里注册之后,我们对着色器参数做出的修改(numpixels已经由原来的50修改修改为了125),就会影响到最终显示的效果,编译并运行程序,你会看到如下效果,由于数量增加了,所以比原来稍微清楚了一下,你可以修改这个数量,值越大最终显示的效果越清晰:

     

    笔者注:

            由于我们可以在程序中修改我们的着色器变量,因此如果读者感兴趣可以增加帧监听和键盘响应,这样就可以达到在程序运行的时候实时修改这个变量的目的,这样我们就能在程序运行时看到不同变量的显示效果了。

     

     



    PS:很久以前就打算把学习Ogre中遇到的知识分享给大家(虽然只是一些皮毛),但是说来惭愧,一直很懒,直到最近才抽出点时间写一些自己的理解(Ogre入门级的东西),所以难免会有很多不足之处,分享是一种快乐,同时也希望和大家多多交流!

    (由于在Word中写好的东西发布到CSDN页面需要重新排版(特别是有很多图片时),所以以后更新进度可能会比较慢,同时每章节发布的时间可能不一样(比如说我首选发布的是第二章,其实第一章就是介绍下Ogre的前世今生神马的,相信读者早就了解过~~~),但是我会尽量做到不影响大家阅读,还望大家谅解。)


     上述内容很多引用了网上现有的翻译或者内容,在此一并谢过(个人感觉自己有些地方写得或者翻译的不好),还望见谅,转载请注明此项!


    
    展开全文
  • 二维数组常见题型

    千次阅读 2020-02-22 22:38:53
    1.(求矩阵数字的和)编写一个方法,求整数矩阵特定的所有元素之和,使用下面的方法头: public static double sumCliumn(double[][] m, int columnIndex) 编写一个测试程序,读取一个3x4的矩阵,然后...

    1.(求矩阵中各列数字的和)编写一个方法,求整数矩阵中特定列的所有元素之和,使用下面的方法头:

    public static double sumCliumn(double[][] m, int columnIndex)

    编写一个测试程序,读取一个3x4的矩阵,然后显示每行每列元素的和。下面是一组运行示例:

    import java.util.*;
    class Demo05_09{
        public static void main(String[] args){
            /*
                (0,0) (0,1) (0,2) (0,3)
                (1,0) (1,1) (1,2) (1,3)
                (2,0) (2,1) (2,2) (2,3)
                累加行的时候 行不动 列动
                累加列的时候 列不动 行动
            */
            //1.输入一个3*4的矩阵
            Scanner scanner=new Scanner(System.in);
            System.out.println("Enter numbers:");
            double[][] matrix=new double[3][4];
            for(int i=0;i<matrix.length;i++){
                for(int j=0;j<matrix[i].length;j++){
                    matrix[i][j]=scanner.nextDouble();
                }
            }
            //2.打印每列的和,行动列不动
            for(int col=0;col<matrix[0].length;col++){
                System.out.println(sumColumn(matrix,col));
            }
        }
        public static double sumColumn(double[][] m,int col){
            double sum=0;
            for(int row=0;row<m.length;row++){        //从第一行开始遍历第一列,计算第一列的和
                sum+=m[row][col];
            }
            return sum;
        }
    }

    运行结果:

    2. 求矩阵主对角线元素的和,编写一个方法,求n x n的double类型矩阵中主对角线上所有数字的和,使用下面的方法头:
    public static double sumMajorDiagiagoal(double[][] m)

    编写一个测试程序,读取一个4×4的矩阵,然后显示它的主对角线上的所有元素的和下面,是一个运行实

    运行结果:

    Enter a 4-by-4 matrix row by row:
    1  2   3  4.0
    5  6.5 7  8
    9  10  11 12
    13 14  15 16
    Sum of the elements in the major diagonal is 34.5

    3.(代数方面:两个矩阵相乘)编写两个矩阵相乘的方法。方法头如下:

    public static double[ ] [ ]   mutiplyMatrix(double[ ] [ ] a,double[ ] [ ] b)

           为了使矩阵 a 能够和矩阵 b 相乘,矩阵 a 的列数必须与矩阵 b 的行数相同,并且两个矩阵的元素要具有相同或兼容的数据类型。例如对于两个3×3的矩阵,a,b,c就有:

    编写一个测试程序,提示用户输入两个3x3的矩阵,然后显示他们的乘积。下面是一个执行用例:

    class Demo05_11{
        public static void main(String[] args){
            //m*n n*p m*p 矩阵相乘 前者的列 必须等于 后者的行 对应行的列乘对应列的行再相加
            /*
            1 2 3        1 2    1*1+2*3+3*5   1*2+2*4+3*6
                    ×    3 4  = 
            4 5 6        5 6    4*1+5*3+6*5   4*2+5*4+6*6
            对于数学上的一些运算公式 如果直接通过看计算流程还是比较麻烦的
            此时推荐使用已知公式计算!
            */
            double[][] A={ //m*p
                {1,2,3},
                {4,5,6},
                {7,8,9}
            };
            double[][] B={ //p*n
                {0,2.0,4.0},
                {1,4.5,2.2},
                {1.1,4.3,5.2}
            };
            double[][] C=new double[A.length][B[0].length];//m*n
            for(int i=0;i<C.length;i++){
                for(int j=0;j<C[i].length;j++){
                    double sum=0;
                    for(int k=0;k<B.length;k++){
                        sum+=A[i][k]*B[k][j];
                    }
                    C[i][j]=sum;
                    System.out.print(C[i][j]+" ");
                }
                System.out.println();
            }
        }
    }

    4.编写程序提示用户输入一个方形的长度,随机的在矩阵中填入0和1打印,这个矩阵,然后找出整行整列或者对角线都是0或1的行、列和对角线,下面是这个程序的一个运行实例:

    import java.util.*;
    class Demo05_12{
        public static void main(String[] args){
            //1.输入方针的尺寸 创建方阵
            Scanner scanner=new Scanner(System.in);
            System.out.print("Enter size:");            //输入一个15x15的方阵
            int size=scanner.nextInt();
            int[][] m=new int[size][size];
            //2.随机的给方阵中填入0或1
            Random random=new Random();
            for(int i=0;i<size;i++){
                for(int j=0;j<size;j++){
                    m[i][j]=random.nextInt(2);          //随机的产生0和1
                    System.out.print(m[i][j]+" ");      //打印0和1
                }
                System.out.println();                   //一行打印完了要换行
            }
            //3.让行,列,对角线累加 sum==0 sum==size 表示全相等
            checkRow(m);                    //判断某一行是否全相等的函数
            checkCol(m);                    //判断某一列是否全相等的函数
            checkDiagonal(m);               //判断正对角线是否全相等的函数
            checkSubDiagonal(m);            //判断副对角线是否全相等的函数
        }
        public static void checkSubDiagonal(int[][] m){//判断副对角线是否全相等的函数
            int sum=0;
            for(int i=0;i<m.length;i++){        //从第一行最后一列开始比
                sum+=m[i][m.length-1-i];
            }
            if(sum==m.length||sum==0){          //某一行或某一列加起来是0还是该行或列的长度
                System.out.printf("副主对角线全相等且是%d\n",sum==0?0:1);
            }
        }
        public static void checkDiagonal(int[][] m){//判断正对角线是否全相等的函数
            int sum=0;
            for(int i=0;i<m.length;i++){
                sum+=m[i][i];
            }
            if(sum==m.length||sum==0){            //某一行或某一列加起来是0还是该行或列的长度
                System.out.printf("主对角线全相等且是%d\n",sum==0?0:1);
            }
        }
        public static void checkRow(int[][] m){    //判断某一行是否全相等的函数
            for(int i=0;i<m.length;i++){
                int sum=0;
                for(int j=0;j<m[i].length;j++){
                    sum+=m[i][j];
                }
                if(sum==m.length||sum==0){        //某一行或某一列加起来是0还是该行或列的长度
                    System.out.printf("第%d行全相等且是%d\n",i+1,sum==0?0:1);
                }
            }
        }
        public static void checkCol(int[][] m){     //判断某一列是否全相等的函数
            for(int j=0;j<m.length;j++){
                int sum=0;
                for(int i=0;i<m.length;i++){
                    sum+=m[i][j];
                }
                if(sum==m.length||sum==0){        //某一行或某一列加起来是0还是该行或列的长度
                    System.out.printf("第%d列全相等且是%d\n",j+1,sum==0?0:1);
                }
            }
        }
    }

    运行结果:

    5.(模式识别)连续的四个相等的数编写下面的方法测试一个二维数组是否有四个连续的数字,具有相同的值,这四个数可以是水平方向的,垂直方向的,或者对角线方向的。

    public static boolean isConsecutiveFour(int[ ] [ ] values)

             编写一个测试程序,提示用户输入一个二维数组的行数、列数,以及数组中的值,如果这个数组有四个连续的数字具有相同的值就显示true,否则显示false ,下面是结果为true的一些例子:

    import java.util.*;
    class Demo05_13{
        public static void main(String[] args){
            Scanner scanner=new Scanner(System.in);
            System.out.print("Enter row ,col:");
            int row=scanner.nextInt();        //输入行数
            int col=scanner.nextInt();        //输入列数
            int[][] m=new int[row][col];      //建立一个二维数组
            for(int i=0;i<row;i++){
                for(int j=0;j<col;j++){
                    m[i][j]=scanner.nextInt();    //输入数组数据
                }
            }
            System.out.println(isConsecutiveFour(m));    //调用isConsecutiveFour()函数
        }
        public static boolean isConsecutiveFour(int[][] m){//判断是否符合题目要求
            for(int i=0;i<m.length;i++){
                for(int j=0;j<m[i].length;j++){
                    //向右是否有连续四个数相等
                    if(j<=m[i].length-4){             //最多判断到该行长度-4,再往后没有四个数了
                        boolean flag=true;            //默认能找到连续四个数相等
                        for(int c=j+1;c<=j+3;c++){    //若有一个数不行等,循环结束,即该数向右连续四个数不相等
                            if(m[i][j]!=m[i][c]){
                                flag=false;
                                break;
                            }
                        }
                        if(flag){                     //如果循环正常结束,说明该数向右连续四个数相等
                            return true;
                        }
                    }
                    //向下是否有连续四个数相等
                    if(i<=m.length-4){                //最多判断到该列长度-4,再往下没有四个数了
                        boolean flag=true;
                        for(int r=i+1;r<=i+3;r++){
                            if(m[i][j]!=m[r][j]){     //若有一个数不行等,循环结束,即该数向右连续四个数不相等
                                flag=false;
                                break;
                            }
                        }
                        if(flag){
                            return true;
                        }
                    }
                    //向右下是否有连续四个数相等
                    if(i<=m.length-4&&j<=m[0].length-4){
                        boolean flag=true;
                        for(int r=i+1,c=j+1;r<=i+3;r++,c++){
                            if(m[i][j]!=m[r][c]){
                                flag=false;
                                break;
                            }
                        }
                        if(flag){
                            return true;
                        }
                    }
                    //向右上是否有连续四个数相等
                    if(i>=3&&j<=m[0].length-4){
                        boolean flag=true;
                        for(int r=i-1,c=j+1;c<=j+3;r--,c++){
                            if(m[i][j]!=m[r][c]){
                                flag=false;
                                break;
                            }
                        }
                        if(flag){
                            return true;
                        }
                    }
                }
            }
            return false;//四个方向都没有连续的
        }
    }


     

    展开全文
  • Matlab二维曲线绘图

    千次阅读 2015-04-10 17:29:47
    Matlab二维曲线绘图 【自己留着用】
  • 给定一个二维网格和一个单词,找出该单词是否存在于网格。 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。...
  • 清华山EPS次开发基础篇

    万次阅读 2017-06-22 21:22:43
     ...对于编码列表已经存在的地物,使用编码直接创建,则图层颜色等属性自动关联   CreateNewObjByCode( code) 参数 code 创建地物的编码 注1:对于含有破折号的地物编码,
  • 二维旋转  通过指定一个 旋转轴(rotation axis) 和一个 旋转角度(rotation angle) ,可以进行一次旋转(rotation)变换。在将对象的所有顶点按指定角度绕指定旋转轴旋转后,该对象的所有点都旋转到新位置。  一...
  • 游戏的三数学

    千次阅读 2015-08-31 09:45:29
    一、点和矢量 ...在三笛卡尔坐标系又分为左右手坐标系,用左右手来方便做记忆,大拇指指向X轴,食指指向Y轴,中指指向Z轴,3指垂直即可建立模型。左右坐标系的转换只需要把一个轴转换,保留另外两个轴的
  • 【物联网】矩阵式二维条码 介绍

    千次阅读 2017-08-11 15:33:07
    QR Code是由日本Denso公司于1994年9月研制的一种矩阵式二维条码,它除具有二维条码所具有的信息容量大、可靠性高、可表示汉字及图象多种信息、保密防伪性强等优点外,还具有以下特点: 超高速识读:QR Code码的超...
  • 2维FFT算法实现——基于GPU的基2快速二维傅里叶变换 上篇讲述了一维FFT的GPU实现(FFT算法实现——基于GPU的基2快速傅里叶变换),后来我又由于需要做了一下二维FFT,大概思路如下。 首先看的肯定是公式:...
  • 每一幅图像都包含某种程度的噪声,噪声可以理解为由一...常用的平滑处理算法包括基于二维离散卷积的高斯平滑、均值平滑,基于统计学方法的中值平滑,具备保持边缘作用的平滑算法的双边滤波。 在介绍基于二维离散卷...
  •  执行一般的二维空间变换包括如下三步: 1. 定义空间变换的参数; 2. 创建变换结构体TFORM,它定义了你所要执行变换的类型; TFORM结构体包含了执行变换需要的所有参数。你可以定义很多类型的空间变换,...
  • 计算机图形学:基本二维几何变换

    万次阅读 2012-02-27 16:28:08
    1.二维平移矩阵 2.二维旋转矩阵 首先确定基准点为坐标原点时点位置P进行旋转的变换方程: Φ表示点的原始角度位置与水平线的夹角,θ是旋转角。 在极坐标系,点的原始坐标...
  • 随着地理空间信息服务产业的快速发展,地理空间数据的要求越来越高。对地理空间数据的要求正朝着大信息量、高精度、可视...、地面三激光扫描仪测量原理;三、三激光扫描系统分类;四、三激光扫描系统特点;五、
  • MATLAB 的绘图:维和三

    千次阅读 2016-05-02 15:44:32
    plot 是绘制二维图形的最基本函数,它是针对向量或矩阵的来绘制曲线的。也就是 说,使用plot 函数之前,必须首先定义好曲线上每一点的x 及y 坐标,常用格式为: (1)plot(x) 当x 为一向量时,以x 元素的值为纵...
  • plot 是绘制二维图形的最基本函数,它是针对向量或矩阵的来绘制曲线的。也就是说,使用plot 函数之前,必须首先定义好曲线上每一点的x 及y 坐标,常用格式为: (1)plot(x) 当x 为一向量时,以x 元素的值为纵...
  • 庞大的数据和复杂的计算过程给用户带来了极大的不便,MATLAB自产生之日起就具有方便的数据可视化功能,新版本更是做出了很大的改进和完善,本文基于MATLABR2015b通过实例来展示绘制二维、三维和四维以及图形编辑与...
  • 第一讲1. 增强现实技术AR:--融合了三维动画、立体视觉和图像处理;--建模、渲染、位置标定、图像融合;...--在计算机屏幕上显示的二维图像,提供深度(或第三维)的错觉;2D+透视 = 3D透视使人产生深度的错觉。
  • 第六章 [键入文字 ] 数据库 1数据库 DB数据库系统 DBMS三者间的关系是 B A. DB 包括 DBS和 DBMS B. DBS包括 DB 和 DBMS C. DBS就是 DB,也就是 DBMS D.... 域 3 一个关系就是一张二维表垂直方向上的称为属性
  • 在本章中,我们考虑二维细胞自动机,特别是 John Conway 的生命游戏(GoL)。 像上一章的一些 CA 一样,GoL 遵循简单的规则并产生令人惊讶的复杂行为。 就像沃尔夫勒姆的规则 110 一样,事实证明 Go...
  • 数灾难问题及数据分析的降维方法一、数灾难(curse of dimentionality)降维的主要方法1. 投影(Projection)2. 流形学习、降维方法1. 什么是降维2. 为什么要降维3. PCA主成分分析(Principal components ...
  • 开源:VS.NET打印思想与2003/5DataGrid、DataGridView及二维数据如ListView等终极打印实现(全部源码)作者:长江支流关 键 字:VS.NET、打印、DataGrid、DataGridView、ListView、DataTable、二维数据打印日 期...
  • 码和位码主要原理

    万次阅读 2013-04-08 22:54:47
    在水平和垂直方向二维空间存储信息的条形码, 称为二维条形码(2-dimensional bar code)。 与一维条形码一样,二维条形码也有许多不同的编码方法,或称码制。就这些码制的编码原理而言,通常可分为以下三种...
  • 在三个细胞的邻域,有 8 种可能的配置,因此规则表中有 8 个条目。由于每个条目都占一个位,我们可以使用 8 位指定一个。使用 8 位,我们可以指定 256 个不同的规则。 Wolfram 的第一个 CA 实验就是测试所有 ...
  • 点击上方“3D视觉工坊”,选择“星标”干货第一时间送达今天分享的是:深度学习领域基于图像的三物体重建最新方法及未来趋势综述。原文:Image-based 3D Object Recon...
  • 重建

    千次阅读 2020-09-03 09:57:27
    重建技术通过深度数据获取、预处理、点云配准与融合、生成表面等过程,把真实场景刻画成符合计算机逻辑表达的数学模型。这种模型可以对如文物保护、游戏开发、建筑设计、临床医学等研究起到辅助的作用。   ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,932
精华内容 5,172
关键字:

二维表中垂直方向的列