D3D变换的理解

概念上的变换

1.简化变换的思想
使用 惯性坐标系 (惯性坐标系是世界坐标系平移到物体坐标系的位置)可以简化物体-世界,世界-物体,或者说任何两个坐标系间的转换做一个中间的转换,使问题变得简单。使用 嵌套坐标系(父坐标系-子坐标系-子坐标系)也可以分解问题,简化复杂的变换逻辑。 其实把问题简单化思考的方法,不外乎实例化,拆分它,转换它,再组合转换抽象回来,就可以很elegant的解决问题了。
很多物体间的方位和运动,需要在同一个坐标系中才能进行,以及渲染管道中本身要求多坐标系间转换来实现渲染

2.物体变换和坐标系变换的联系区别:
物体进行变换(例如把物体坐标系当做世界坐标系而不变,那么物体进行世界坐标系看到的要求进行旋转平移变换),等价于相反的顺序相反的量变换物体坐标系(例如把物体定住不变,那么需要坐标系进行相反的先相反方向平移相反方向旋转的变换)。其实变换了坐标系也会将坐标内所有的物体重新计算位置, 只是有时候那种更加合适那么使用那种变换,一般的是思考问题时是变换物体,但是实际中是变换物体坐标系(假定物体不变),因为变换坐标系更加方便处理多个坐标系间的变换,最终物体只需要重新计算位置一次

3.针对坐标系间的双向变换:
物体在物体坐标系到世界坐标系先变换,可以用坐标系先对坐标系进行旋转后进行平移到世界坐标系;世界坐标系中物体到物体坐标系变换,对世界坐标系先进行相反的平移后进行相反的旋转变换,其它坐标系间相互转换也符合该规律。相反的顺序主要是因为矩阵的组合变换求逆的要求

变换的详细理解

每个变换都可以解释为一个矩阵,也就是一个新的坐标系,如果新坐标系是用旧坐标系描述的那么是物体变换,如果对这个变换求逆那么是坐标变换。
连续变换就是连续的建立坐标系,且都是基于上一个坐标系的,所以变换的顺序不同得到的结果完全不同。至于相对任意轴不在原点的变换,先平移到原点再进行放缩旋转变换,在平移回去(平移矩阵求逆);这时只是得到相对于该任意轴的变换,对于具体的变换还需要先平移到物体所在的坐标系(物体每个点都是基于物体坐标系的情况下),然后再对物体进行变换。


1)同坐标系描述,直接变换物体(参考坐标系用旧坐标系表达):
一定要搞清楚前提条件(例如过原点的仿射变换和不过原点的差别巨大的); 也要搞清楚是一个变换,和多个变换的连续变换(例如非过原点的仿射平移是轴或者平面的,R = P_1 * R * P), 然后T是平移的是独立的,那么最终M = T * R); 也要弄清楚DX或者OPENGL变换函数的含义,有些情形下是不支持的或者通过恰当转化后改变了前提条件,那么才是可以使用的。

矩阵是代表物体的变换(虽然也可以代表坐标系变换但是最终还是要作用在物体上),该变换是用物体变换,假设变换到新的用旧坐标系表示的新的坐标系。那么物体进行该变换(缩放旋转平移)就相当于是到了物体变换到了这个参考坐标系,只是这个参考坐标系是用旧坐标系来表达的,但是从旧坐标系来看确实是变换了物体得到想要的效果。
矩阵的行向量是新坐标系的基向量,是用原坐标系轴假设变换(旋转或平移)到一个新的位置后在各个原轴上面分解的向量之和
矩阵的列向量是新坐标系各个基向量在某一个原轴上分解出来的向量的汇总,用于对物体向量的某分量在新坐标系下面重新组合计算位置
例子:
// 如果在同一个坐标系中直接变换就是了,但是每一个变换都是基于上一个解释的变换的(具有层级关系的不仅是欧拉角中),所以变换顺序很重要
// 将在原点位置描述的茶壶(茶壶网格顶点是相对于原点位置的)平移到这个地方,进行绘制
// 这里也可以看做是物体坐标系,到世界坐标系的变换
   D3DXMATRIX W;
    // 将茶壶平移到这个位置
    D3DXMatrixTranslation(&W,
        TeapotPosition.x,
        TeapotPosition.y,
        TeapotPosition.z);

    Device->SetTransform(D3DTS_WORLD, &W);
    Teapot->DrawSubset(0);


2)同坐标系描述,变换坐标系来变换物体(参考坐标系用旧坐标系表达)
不是用新坐标系描述,而是假设变换坐标系,那么物体就往相反的方向来变换,其实就是在变换物体,所以每一步只需要相反的量(相反量和 逆变换是完全不同的),不需要相反的顺序来变换。
其实这里的变换坐标系只是变换物体。

例如欧拉角变换转换为矩阵变换中,因为欧拉角描述的是坐标系变换,所以需要转换为物体变换(相反的量即可),因为每次都是坐标系的变换转换为每次物体的变换,得到的都是物体的变换,所以变换只用相反的量,不需要相反的顺序进行。得到的矩阵就是在旧坐标系中变换物体的矩阵。
M = M(-y) M(-x)M(-z), 相同的顺序,相反的量进行变换( 不是相反的顺序,因为变换中的坐标系都是相对于同一个坐标系的,每一步坐标系变换和物体变换相反,欧拉角本身就是如此定义的hpb都是局部坐标系的变换并非物体变换,至于进一步的证明还需要更多的积累和研究开源引擎? )
欧拉角中的,惯性到物体是M = M(-y) M(-x)M(-z),物体到惯性是:M = M(-z) ^-1M(-x)^-1M(-y)^-1,这里是不包括平移的,包括平移则和新坐标系描述变换一样的方式进行。因为欧拉角描述的是物体的变换,但是变换的内部小步骤又是通过变换坐标系方式来讲述的(整个感觉描述得比较模糊),所以根据head pitch row得到的是物体坐标系中描述的物体,如果需要渲染转换到世界坐标系,还是需要进行物惯转换和平移来做的。

3)新坐标系描述,变换物体来变换坐标系(参考坐标系用新坐标系表达)
用新的坐标系来描述物体,那么需要先将物体某个向量从旧坐标系变换到该新坐标系中,得到变换矩阵然后对变换求逆即得物体用新坐标系描述的变换。将需要变换到新坐标系中的物体乘以该矩阵就得到了物体在新坐标系中的位置,且不需要旋转观察视角就是正确的结果因为新坐标系默认也是x向右,y向上,z向内的。具体应用中所谓有时候变换物体,有时候变换坐标系来达到转换到同一个坐标系中以便计算碰撞检测相对运动等中的有时变换坐标系更方便就是这个意思
所以变换坐标系是变换物体严格相反的顺序,相反的量来进行变换。

例如惯物变换之间,世界坐标系变换到观察坐标系之间,因为是用新坐标系来描述,且是变换物体,所以变换物体不能用相反的量(因为坐标系改变了),而是用相同的量变换物体然后求逆(逆并不同于相反的量,因为相反的量是反向的变换,而逆是变换的撤销),要求相同的量相反的变换,得到坐标系的变换。
M =(PM)^-1 = M^-1P^-1,相反的顺序,相同的量求逆进行变换
坐标系间的转换:
物体到世界坐标系,通常是先缩放 -> 旋转 -> 平移; 世界到物体就要先平移->旋转->缩放,这是因为矩阵乘法不满足交换律导致的,深层次的原因是变换之间是层层嵌套的,每一个前提条件都代表了基于不同的基向量坐标系,所以变换次序是非常重要的。
例子:物体坐标系到世界坐标系:缩放旋转到惯性坐标系,进行相对于世界坐标系的平移,设置这样的变换坐标系,所有接下来渲染的物体都会进行这样的变换,其实物体本身是不清楚的,它把每一个变换作为一个新的坐标系,只不过这个新的相对的坐标系是世界坐标系而已。同样要得到相对于物体坐标系的变换,那么对物体到世界坐标系的变换矩阵求逆即可,求逆也就是相反的顺序相反的变换矩阵进行相乘,就会得到世界坐标系描述物体到物体坐标系(复杂模型或摄像机)描述物体的变换。

4)连续变换:每个都是基于上一个观察坐标系不管该坐标系是用旧坐标还是新坐标来表达的变换,而不是基于原坐标系的变换(除了第一个变换)。 如果物体的连续变换(一般物体到世界是先缩放旋转平移,世界到物体是平移旋转缩放变换,都是不能改变变换顺序的,因为连续变换符合结合律不符合交换律,都是基于前一个坐标系的) ,是矩阵直接连乘即可;如果是坐标系变换,先转换为物体的连续变换,然后求逆得到坐标系的连续变换结果。所以欧拉角中的连续旋转变换,是符合物体连续变换的直接变换即可。

一定要搞清楚前提条件(例如过原点的仿射变换和不过原点的差别巨大的); 也要搞清楚是一个变换,和多个变换的连续变换(例如非过原点的仿射平移是轴或者平面的,R = P_1 * R * P), 然后T是平移的是独立的,那么最终M = T * R); 也要弄清楚DX或者OPENGL变换函数的含义,有些情形下是不支持的或者通过恰当转化后改变了前提条件,那么才是可以使用的。

例子:
连续变换中其实坐标系本身是不知道自己是在纯粹变换了要描绘的物体,还是彻底的进行了坐标系变换的,每一个变换都是基于上一个坐标系进行了,所以变换顺序很重要。
// position reflection
    D3DXMATRIX W, T, R;
    //  平面为ax + by + cz + dw = 0. n法向量为(a,b,c), dw为平面到原点的距离
    D3DXPLANE plane(0.0f, 0.0f, 1.0f, 0.0f); // xy plane
    // D3DX的变换函数都是考虑了平移的,得到相对于任意平面(此为 xy plane)的镜像的矩阵,要得到任意平面的不一定过原点
    // 所以要先平移到原点,然后镜像,再逆向平移回去才会得到正确结果
    D3DXMatrixReflect(&R, &plane);

    // 平移到茶壶所在的坐标系,因为茶壶用本地坐标系描述
    D3DXMatrixTranslation(&T,
        TeapotPosition.x,
        TeapotPosition.y,
        TeapotPosition.z);

    // 物体是在本地坐标系中,但是要在世界坐标系中绘制物体,所以要先平移
    // 然后对平移后的世界坐标系中的物体进行一般矩阵变换,这个镜像变换是考虑了平移的,得到相对于世界坐标系的镜像结果

    W = T * R;
    Device->SetTransform(D3DTS_WORLD, &W);
    // 绘制出来
    Teapot->DrawSubset(0);


变换的实例

D3DXMatrixLookAtLH函数

体现了世界-观察坐标系的转换:
D3DXVECTOR3 Eye(0.0f, 0.0f, -5.0f);//camera在世界坐标系中的位置向量,观察坐标系的原点
 D3DXVECTOR3 At(0.0f, 0.0f, 0.0f);//at该点(可以是原点,也 可以是其它观察点) 的世界坐标系进行平移旋转到观察坐标系(物体位置会重新计算的)
 D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);//定义向上的方向,一般是[0,1,0]
    D3DXMATRIX V;
 // V是world-to-view空间的变换矩阵,内部会计算世界坐标系到观察坐标系的变换(物体假设不变),
 // 其实新的坐标系下所有物体的位置时重新计算了的。
 D3DXMatrixLookAtLH(&V, &Eye,  &At, &up);
  Device->SetTransform(D3DTS_VIEW, &V);//设置变换的状态,待最后综合各种变换得到最终实际变换时才变换(是真正的坐标系变换,将世界坐标系中的物体被变换到观察坐标系中)

返回的新坐标系是:

zaxis = normal(At - Eye)
xaxis = normal(cross(Up, zaxis))
yaxis = cross(zaxis, xaxis)

    
 xaxis.x           yaxis.x           zaxis.x          0
 xaxis.y           yaxis.y           zaxis.y          0
 xaxis.z           yaxis.z           zaxis.z          0
-dot(xaxis, eye)  -dot(yaxis, eye)  -dot(zaxis, eye)  1

注意到旋转部分是R变换物体的逆R^-1,且平移部分也是求逆后的结果。这就是坐标系间的变换。


D3DXMatrixPerspectiveFovLH函数

D3DXMATRIX proj;
    D3DXMatrixPerspectiveFovLH(
        // 假设物体不动,观察坐标系到投影坐标系的转换矩阵
        // 待最后综合各种变换得到最终实际变换时才变换(可暂时理解为观察坐标系中的物体被变换到投影坐标系内)
            &proj,
            D3DX_PI * 0.5f, // 90 - degree 观察坐标系中的上下夹角
            (float)Width / (float)Height, // 宽高比例
            1.0f, // 观察坐标系视锥体的近截面
            1000.0f);// 观察坐标系视锥体的远截面
    Device->SetTransform(D3DTS_PROJECTION, &proj);


D3DXMATRIX p = Rx * Ry;
// 世界坐标系的转换命令会被runtime commmand buffer调度,

// 因为前面的D3DTS_VIEW,D3DTS_PROJECTION会被真正渲染管道中的时候排序到后面去了
Device->SetTransform(D3DTS_WORLD, &p);

fovy视锥体的上下夹角(6面体,上下截面间的夹角,水平的夹角由h/w计算得到。),z轴平分该夹角。

 fovy变小时候,因为也要在屏幕90度中用,所以高度方向被放大了,反之fovy变大,高度方向变小。

aspectaspect = w / h,fovy = 90度,由投影矩阵的计算过程,投影yScale = cot(fovy/2); xScale = yScale /aspect.

关于上面的fovy夹角,aspect,和yScale, xScale之间的关系:

fovy只是指定上下夹角,其中左右夹角是需要根据aspect = w / h来计算的。具体因为这里的透视投影还是4D空间中缩放的,缩放时候:

yScale = Zoomy = cot ( fovy / 2);

xScale = Zoomx = yScale / (w / h) = ySacle * h / w;?为何要这样呢。

那是因为xScale 后,然后除以w转换到小立方体x属于[-1,1],会再次放大转换到屏幕坐标。

且长方形的屏幕,希望投影到的是长方体的摄像机空间,而不是立方体的摄像机投影到长方体的屏幕,要长方体的空间等比例的缩放到屏幕空间,那么需要进行等比变换。例如:考虑最左上角的顶点本来是(10000, 5000),4D投影中乘以投影矩阵,y乘以Zoomy,x乘以xScale = Zoomy / ( w/h), 因为fovy等于90度,w/h为100 /50 等于2,所以缩放后得到(5000, 5000)。z乘以下面 z * zf / (zf - zn) -zn*zf/(zf-zn)因为是对1/z线性插值得到的,此时还没有除以z,所以z乘法后大约还是等于z大小,且该z还是变换前的z,变换前的z大小因为fovy是垂直方向的,所以z=500。4D的投影矩阵变换后的空间,变换到屏幕空间时候除以w = 5000进行了巨大放缩,得到x',y'为(1,1);在这种情况下,进行屏幕坐标变换:

此处:x是透视投影变换后的x,y是透视投影后边的y也就是还不在[-1,1]的小立方体中:

win.x =  (x / z) * w / 2 + w / 2 = (x / z + 1)*w / 2;

win.y = -( y / z ) * h / 2 + h / 2 = ( - y/z + 1) * h / 2;

除以z是4D空间转换到3D空间中,且放置到小立方体中:

得到win.x = 1 * w / 2 + w / 2 = w = 100;

win.y = - 1 * h / 2 + h / 2 = 0; 这里x相比y放大了w/h倍,所以整个过程xScale先缩小屏幕比例的倍数也就是相对于zoomy的 1 / (w/h)倍数;

所以前面的:xScale = Zoomx = yScale / (w / h) = ySacle * h / w = cot(fovx / 2)得证;也得到视椎体中: fovx / 2 = acrcot( Zoomy / ( w / h) )。

变换矩阵为:

xScale     0          0               0
0        yScale       0               0
0          0       zf/(zf-zn)         1
0          0       -zn*zf/(zf-zn)     0
where:
yScale = cot(fovY/2)

xScale = yScale / aspect ratio
aspect ratio就是 (float)Width / (float)Height。

其实zf/ ( zf - zn),和zn* zf / (zn - zf)是对 1/z线性插值得到的,线性插值:

z' = a * (1/z) + b当z= zn时z'= 0,当z=zf时z'=1求得a和b的值。

至于为什么是1/z线性插值,那是因为这里的透视投影变换结果还是在4D裁剪空间中,且在4D空间中就要考虑好当转换到3D空间中(除以z)后z需要变换到[0,1]小长方体的深度区域内。所以透视投影4D空间中,需要对1/z进行插值,向量乘以透视投影矩阵得到Ztemp = a * z + b也还是在4D空间中且对z放大了恰当的倍数。当透视投影后进行屏幕空间变换,会除以w= z,这样一下子将物体从透视投影4D裁剪空间,转换到3D空间中也就是x属于[-1,1],y属于[-1,1],z属于[0,1]空间中,然后再进行屏幕变换到显示器中。

直接变换和连续变换问题和例子

//世界坐标转换SetTransform(D3DTS_WORLD, &M)是将物体坐标系转换到世界坐标系需要进行的转换,基于当前物体坐标系需要进行的变换
// 就可以了,连续变换世界坐标也不会改变或者重置世界坐标系原点,只是对当前需要绘制的物体产生影响。
// 因为建立的包围盒是变换以后的数据,所以绘制的时候又会根据世界矩阵变换一遍,所以设置世界矩阵变换为单位矩阵不变换绘制包围盒即可。



1.前提条件问题,如果是不包含平移的放射变换,那么缩放还是旋转的轴都是过原点的,否则需要先平移到原点然后进行仿射变换再平移回去才会得到正确的结果;两者该直接仿射变换时候就直接变换,该平移回来仿射变换再平移回去是不能混淆使用的。
平移的逆变换为:T(a,b,c,1)逆平移为T(-a,-b,-c,1),因为4D平移中其它都是1和0。

2.空间变换思维方式,矩阵行向量就是变换后的新坐标系的位置,用简单例子情景分析即可。
3. 非经过原点的,若用D3DX的进行变换,因为它变换默认是认为绕过原点的轴进行变换,且变换一刀切(缩放旋转部分和平移部分都直接用仿射公式进行变换),所以这种情况下是不能直接用DX仿射变换函数进行变换的。这个时候有两种方法:
1)通过改变变换轴或者平面,去掉平移部分,让其是过原点的轴和平面,令其为P2;然后将平移部分抽取为P,求逆回原点平移_P;用DX仿射变换基于P2条件求得变换矩阵R; 则最终变换为W = _P * R * P。
2)自定义过原点的仿射变换矩阵R,直接对不过原点的轴或者平面取得平移部分P,求逆得到_P,那么最终变换为:W = _P * R * P。
最后如果还有其他的连续变换,比如仿射变换前需要平移先,那么变换为:M = T * W;如果先仿射变换后再平移,那么为:M = W * T。
继续不断实践各种各样的变换,和研究开源引擎,看还有没有更好的方法和结论。
例子:
// 非过原点的镜面反射变换实现,position reflection
 D3DXMATRIX T;
 // 镜像平面定义,平面为ax + by + cz + dw = 0. n法向量为(a,b,c), dw为平面到原点的距离
 D3DXPLANE plane(0.0f, 0.0f, 1.0f, 1.0f); // xy plane
 // 平移到茶壶所在的坐标系,因为茶壶用本地坐标系描述
 D3DXVECTOR3 TeapotPosition(0.0f, 3.0f, -7.5f);
 
 D3DXMatrixTranslation(&T,
  TeapotPosition.x,
  TeapotPosition.y,
  TeapotPosition.z);
 // 方法1:抽取非过原点轴或者平面的平移部分,且用DX函数求取过原点的仿射变换矩阵
 D3DXMATRIX R,W,M;
 // 抽取非过原点的变换平面的平移部分
 D3DXMATRIX P(1, 0, 0, 0,
  0, 1, 0, 0,
  0, 0, 1, 0,
  0, 0, 1, 1);
 // 对平移部分求反
 D3DXMATRIX _P;
 D3DXMatrixInverse(&_P, NULL, &P);

 // 去掉平移部分的平面(有时候是轴),得到过原点的仿射变换
 D3DXPLANE plane2(0.0f, 0.0f, 1.0f, 0.0f); // xy plane
 D3DXMatrixReflect(&R, &plane2);
// 会先对平面进行单位化,但是也是没有用的

 // 得到非过原点的仿射变换矩阵
 W = _P * R * P;
 // 继续进行平移
 M = T * W;

 // 方法2:抽取非过原点轴或者平面的平移部分,自定义的过原点的仿射变换矩阵
 D3DXMATRIX RF(1-2*plane.a*plane.a, -2*plane.a *plane.b, -2 * plane.a * plane.c, 0,
  -2*plane.b*plane.a, 1-2*plane.b *plane.b, -2 * plane.b* plane.c, 0,
  -2*plane.c*plane.a, -2*plane.c *plane.b, 1 -2 * plane.c * plane.c, 0,
  0/*-2*plane.d*plane.a*/, 0/*-2*plane.d*plane.b*/, 0/*-2*plane.d*plane.c*/, 1);
 
 W = _P * RF * P;
 M = T * W;

 // 设置变换
 Device->SetTransform(D3DTS_WORLD, &M);
 // 绘制出来
 Teapot->DrawSubset(0);


正交投影和透视投影深入理解例子

// 设置屏幕坐标系大小定义,多视图,可以实现屏幕分割
    //D3DVIEWPORT9 viewport = {0, 0, width, height, 0.0f, 1.0f} ;
    //(*device)->SetViewport(&viewport);
    // 设备规范坐标系到屏幕坐标系D3D会自动进行变换,3D透视投影中通过:
    /*
     W/2, 0, 0, 0
     0, -H/2, 0,0
     0, 0, MaxZ-MinZ, 0
     X0+W/2, Y0+H/2, MinZ,1
     上述的参数含义就是D3DVIEWPORT9中定义的参数含义。
     3D透视投影矩阵的Z轴是对1/z进行插值,因为要在1/z后插值到[0,1]中,所以得到的常数是矩阵中的z位置,1/z是矩阵中的平移位置。
     2D正交投影矩阵只要对z插值在[0,1]中就可以了,常数矩阵中平移位置,z插值是矩阵中的z位置。
    
     3D透视投影的设备规范坐标系由透视投影矩阵和顶点除法变换得到,x属于[-1,1],y属于[-1,1],z属于[0,1](但是OGL中z属于[-1,-1]
     可见深度缓存中存储的深度也就是z在设备规范坐标系中的值, 但程序员也可以在世界坐标系和视图坐标系中对物体按z值排序渲染。
    
     2D正交投影的设备规范坐标系直接由正交投影变换得到,不会再进行除法,且前面的世界坐标系视图坐标系都是一样的,只不过正交
     投影去掉了进大远小的效果(z值不能影响了),直接经过正交投影变换如果x在[-1,1],y在[-1,1]规范坐标内的可以渲染,在外的不会
     渲染,z值也被正交投影放置到了[0,1]中放入深度缓存中。
     一般设置的正交投影设置下,也就是不缩放,那么物体在世界坐标系中的点和屏幕坐标系中的像素是对应的。
    
     // 2D和3D的区别仅仅是投影那一刻,他们在世界坐标系中都是一样的世界坐标空间的,摄像机空间也是一样的空间,
     设备规范化坐标系x属于[-1,1]y属于[-1,1]z属于[0,1]OGLz属于[-1,1],屏幕坐标系也是一样的。
     一般2D中位置用像素和格子来衡量就可以了,3D中位置需要虚拟空间尺寸和格子来衡量。

     // 其实D3D,OGL中的透视投影和正交投影都是没有丢掉z值的,知道z值情况下都是可逆的
     // 但是在不知道z值情况下,比如设置设备规范坐标系中的z值是1或者0.5或者0.0:

     // 那么在透视投影情况下计算得到的摄像机坐标空间下的点会不一样,但是单位射线向量都是一样的,
     // 因为透视投影下摄像机空间下相同视锥夹角的点,是会被投影到相同的设备规范坐标系的(因为除以w=z)。

    
     // 而在正交投影情况下得到的摄像机空间下的点会不一样,且单位化法向量也不会一样,
     // 只有知道真正的z值计算得到的法向量才是正确的(因为知道z值情况下D3DXMatrixOrthoLH正交投影也是可逆的)
     // 因为正交投影下摄像机空间下相同的视锥夹角的点,会被投影到不同的设备规范坐标系,因为没有除以w=z,正交投影只和x,y本身
     // 相关,和z不相关。在不同设备坐标系z下,所以得到摄像机坐标系中的变换射线向量是不成比例的,导致拾取算法的射线方向计算错误。

     // 射线方向计算错误导致z轴上是最远的相交,但此时计算x,y轴并没有超出包围盒区域;而透视投影钟z轴也是最远的相交,
     而此时x,y轴是超出了包围盒的。所以3D拾取算法不能用于2D中,2D需要一个忽略掉z值的鼠标点击点是否在包围盒矩形中就可以了。
    */

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值