在 LibGDX 中实现梯形精灵
Implementing trapezoidal sprites in LibGDX
我正在尝试为一个简单的 2D 游戏创建一个程序动画引擎,它可以让我用少量图像创建漂亮的动画(类似于这种方法,但对于 2D:http://www.gdcvault.com/play/1020583/Animation-Bootcamp-An-Indie-Approach)
目前我有保存不同动画对象数据的关键帧,关键帧是表示以下内容的浮点数组:
translateX、translateY、scaleX、scaleY、旋转(度)
我想将 skewX、skewY、taperTop 和 taperBottom 添加到此列表中,但无法正确呈现它们。
这是我尝试在 sprite 的顶部实现一个锥形,使其成为梯形:
float[] vert = sprite.getVertices();
vert[5] += 20; // top-left vertex x co-ordinate
vert[10] -= 20; // top-right vertex x co-ordinate
batch.draw(texture, vert, 0, vert.length);
不幸的是,这会产生一些奇怪的纹理变形。
我有点 Google 并查看了 Whosebug 并发现了这个,这似乎是我遇到的问题:
http://www.xyzw.us/~cass/qcoord/
但是我不明白它背后的数学原理(什么是 s、t、r 和 q?)。
有人能简单解释一下吗?
基本上,四边形越不像矩形,由于在整个形状上线性插值纹理坐标的影响,外观越差。组成四边形的两个三角形被拉伸成不同的大小,所以线性插值使得接缝非常明显。
为片段着色器处理的每个片段线性插值每个顶点的纹理坐标。纹理坐标通常与已经划分出的对象大小一起存储,因此坐标在 0-1 范围内,对应于纹理的边缘(并且该范围之外的值被夹紧或环绕)。这也是任何 3D 建模程序导出网格的典型方式。
对于梯形,我们可以通过将纹理坐标预乘以宽度,然后 post - 在线性插值后将宽度除以纹理坐标来限制失真。这就像弯曲两个三角形之间的对角线,使其斜率在梯形较宽边的角处更水平。这是一张有助于说明它的图片。
纹理坐标通常表示为具有分量 U 和 V 的 2D 向量,也称为 S 和 T。但是如果你想将尺寸从分量中划分出来,你还需要一个分量插值后除以,称为 Q 分量。 (如果您在 3D 纹理而不是 2D 纹理中查找某些东西,P 分量将用作纹理中的第三个位置)。
现在困难的部分来了...libgdx 的 SpriteBatch 不支持 Q 组件所需的额外顶点属性。因此,您可以克隆 SpriteBatch 并仔细检查并修改它以在 texCoord 属性中添加一个额外的组件,或者您可以尝试重新利用现有的颜色属性,尽管它存储为无符号字节。
无论如何,您将需要预先宽度划分的纹理坐标。简化这个的一种方法是,而不是使用四个顶点的四边形的实际大小,而是获取梯形的顶部和底部宽度的比率,因此我们可以将顶部部分视为宽度 1,因此保留它们一个人。
float bottomWidth = taperBottom / taperTop;
然后您需要修改TextureRegion 的现有纹理坐标以将它们预乘以宽度。由于上面的简化,我们可以保留梯形顶边的顶点,但是两个窄边顶点的U和V坐标需要乘以bottomWidth
。每次更改 TextureRegion 或其中一个锥度值时,您都需要重新计算它们并将它们放入顶点数组。
在顶点着色器中,您需要将额外的 Q 分量传递给片段着色器。在片段着色器中,我们通常使用按大小划分的纹理坐标查找纹理颜色,如下所示:
vec4 textureColor = texture2D(u_texture, v_texCoords);
但在我们的例子中,我们仍然需要除以 Q 分量:
vec4 textureColor = texture2D(u_texture, v_texCoords.st / v_texCoords.q);
但是,这会导致依赖纹理读取,因为我们在将矢量传递到纹理函数之前对其进行修改。 GLSL 提供了一个自动执行上述操作的函数(我假设不会导致依赖纹理读取):
vec4 textureColor = texture2DProj(u_texture, v_texCoords); //first two components automatically divided by last component
我正在尝试为一个简单的 2D 游戏创建一个程序动画引擎,它可以让我用少量图像创建漂亮的动画(类似于这种方法,但对于 2D:http://www.gdcvault.com/play/1020583/Animation-Bootcamp-An-Indie-Approach)
目前我有保存不同动画对象数据的关键帧,关键帧是表示以下内容的浮点数组:
translateX、translateY、scaleX、scaleY、旋转(度)
我想将 skewX、skewY、taperTop 和 taperBottom 添加到此列表中,但无法正确呈现它们。
这是我尝试在 sprite 的顶部实现一个锥形,使其成为梯形:
float[] vert = sprite.getVertices();
vert[5] += 20; // top-left vertex x co-ordinate
vert[10] -= 20; // top-right vertex x co-ordinate
batch.draw(texture, vert, 0, vert.length);
不幸的是,这会产生一些奇怪的纹理变形。
我有点 Google 并查看了 Whosebug 并发现了这个,这似乎是我遇到的问题:
http://www.xyzw.us/~cass/qcoord/
但是我不明白它背后的数学原理(什么是 s、t、r 和 q?)。
有人能简单解释一下吗?
基本上,四边形越不像矩形,由于在整个形状上线性插值纹理坐标的影响,外观越差。组成四边形的两个三角形被拉伸成不同的大小,所以线性插值使得接缝非常明显。
为片段着色器处理的每个片段线性插值每个顶点的纹理坐标。纹理坐标通常与已经划分出的对象大小一起存储,因此坐标在 0-1 范围内,对应于纹理的边缘(并且该范围之外的值被夹紧或环绕)。这也是任何 3D 建模程序导出网格的典型方式。
对于梯形,我们可以通过将纹理坐标预乘以宽度,然后 post - 在线性插值后将宽度除以纹理坐标来限制失真。这就像弯曲两个三角形之间的对角线,使其斜率在梯形较宽边的角处更水平。这是一张有助于说明它的图片。
纹理坐标通常表示为具有分量 U 和 V 的 2D 向量,也称为 S 和 T。但是如果你想将尺寸从分量中划分出来,你还需要一个分量插值后除以,称为 Q 分量。 (如果您在 3D 纹理而不是 2D 纹理中查找某些东西,P 分量将用作纹理中的第三个位置)。
现在困难的部分来了...libgdx 的 SpriteBatch 不支持 Q 组件所需的额外顶点属性。因此,您可以克隆 SpriteBatch 并仔细检查并修改它以在 texCoord 属性中添加一个额外的组件,或者您可以尝试重新利用现有的颜色属性,尽管它存储为无符号字节。
无论如何,您将需要预先宽度划分的纹理坐标。简化这个的一种方法是,而不是使用四个顶点的四边形的实际大小,而是获取梯形的顶部和底部宽度的比率,因此我们可以将顶部部分视为宽度 1,因此保留它们一个人。
float bottomWidth = taperBottom / taperTop;
然后您需要修改TextureRegion 的现有纹理坐标以将它们预乘以宽度。由于上面的简化,我们可以保留梯形顶边的顶点,但是两个窄边顶点的U和V坐标需要乘以bottomWidth
。每次更改 TextureRegion 或其中一个锥度值时,您都需要重新计算它们并将它们放入顶点数组。
在顶点着色器中,您需要将额外的 Q 分量传递给片段着色器。在片段着色器中,我们通常使用按大小划分的纹理坐标查找纹理颜色,如下所示:
vec4 textureColor = texture2D(u_texture, v_texCoords);
但在我们的例子中,我们仍然需要除以 Q 分量:
vec4 textureColor = texture2D(u_texture, v_texCoords.st / v_texCoords.q);
但是,这会导致依赖纹理读取,因为我们在将矢量传递到纹理函数之前对其进行修改。 GLSL 提供了一个自动执行上述操作的函数(我假设不会导致依赖纹理读取):
vec4 textureColor = texture2DProj(u_texture, v_texCoords); //first two components automatically divided by last component