libGDX
-

以前自己已经摸索过libGDX逐像素效果的用法了,但一直没有认真研究过顶点绘制的相关逻辑。现在借着AI问了一下,也算是入门了,写个文章记录一下。 画三角形 虽然底层是画三角形,但这里用画直线来举例子。 实际上画直线就是画一个矩形罢了,而一个矩形可以分割成两个三角形,所以实际上绘制一条直线就是绘制两个三角形。在直线的头尾增加以一半宽度向法线方向扩展,这样就能形成4个点。理论上很简单,不好理解的主要是Mesh的接口实现。 Mesh可以认为只是一个传给GPU的顶点数组,但这个数组要怎么读取要看具体参数怎么设置。 下面直接来看一个例子就好了: 这个例子很简单,看看注释就懂了,需要注意的是,VertexAttribute的构造参数最后一个是alias,会绑定到vert(点着色器)的入参,在这个例子里ShaderProgram.POSITION_ATTRIBUTE其实就是字符串”a_position”,所以数组里的坐标就成了vert里a_position参数了。这下我也总算是知道了这些莫名其妙约定俗成的参数是怎么来的。 填充颜色 虽然上面的例子解决了画形状的问题,但是实际绘制的时候,肯定还需要给形状填充颜色,比如说渐变色,我可以直接通过例如x坐标的变化生成一套渐变。 但是如果我想要让渐变沿线段的方向变化该怎么办?虽说我可以在frag(面着色器)上对屏幕坐标进行判断,但这样做就需要对每个像素点屏幕坐标进行一次计算,性能开销会增大。实际上,这个逻辑应该可以通过点着色器进行插值,从而避免计算下降到面着色器中。 可以在Mesh中提供顶点的uv数值空间,实际操作就是在Mesh中,给每个顶点增加一个新的Attribute: 这样子,就知道材质坐标的(0, 0)是“绘制材质”的左下角,而(1, 1)是右上角。如果我想沿绘制的线的方向着色,只需要保证起点是0,终点是1,那么对照着计算渐变颜色的插值就行了,在这个例子中取x或y作渐变插值都行。 扇形绘制 如果绘制的图形是圆形或者扇形,有个单独的绘制类型GL_TRIANGLE_FAN来绘制。在这种绘制类型下,可以省略传递顶点坐标,将固定第一个点为圆心,其余的点依次相连作为三角形的对边来绘制,如果总共要绘制N个三角形,那么实际上要传递N+1个点。 三角带绘制 与扇形绘制相似,三角带绘制方式也是一种用来省略顶点坐标的绘制方式,其解析逻辑是直接以数组上邻近的三个点作为坐标依次绘制,所以如果要绘制N个三角形,那么就需要传递N+2个顶点。下面用一个绘制圆环的代码做例子: 性能优化 虽然有三种绘制方法,但是其实更多的时候还是只用最通用的三角形绘制方法,因为这个方法最通用,可以把所有绘制都整合到一次Mesh的DrawCall中。而且需要注意,因为Mesh也是单独的一次DrawCall,所以不能跟Batch混用,必须首先结束当前事务。
-

一、Off-Screen Rendering 一些画面效果经常需要离屏渲染,离屏渲染需要创建一个独立的FBO来绘制,在绘制处理完成后再把绘制内容提取出来绘制到游戏显示的FBO里。 这个过程因为涉及到FBO的切换,所以需要跟OpenGL底层打交道。虽说用gdx-vfx库里的VfxFrameBuffer应该是可以自动处理OpenGL的嵌套FrameBuffer的问题的,但gdx-vfx的FrameBuffer限制了只能用TextureAttach的,如果想要使用MSAA之类的特性就不支持了,那只好自己写一个。 主要处理FBO绑定、Viewport和裁剪开关的备份和恢复,需要给裁减开关是因为通常在布局UI中会有一些布局裁剪会设置裁剪区间,如果不临时禁用裁剪的话在离屏FBO上也会被裁剪。 实际使用的时候大概是这样的: 二、GLSL 1.30 libGDX默认使用的GLSL语法是v1.20(Desktop和GWT),在安卓和iOS上是v1.00,我不打算做移动端,所以可以升级一下版本,能方便不少。 在shader文件头加上: 就可以使用GLSL v1.30(ES 3.00)的新接口和语法了,例如: 要在程序启动参数中启用GL30。 GWT项目则是简单一个: 三、MSAA 抗锯齿要启用GL30,上面已经有启用方式了。但GWT项目里不能用,虽然也可以开启GL30,但multi-sample不能设置大于1,所以在GWT release里要禁用MSAA。 要独立创建带multi-sample的FrameBuffer,只要是矢量图绘制到这个上就会自带抗锯齿了。 但是由于没有TextureAttachement,所以msaaBuffer不能直接获取Texture,实际使用通常需要resolve到其他的FBO去。比如resolve到VfxManager上:
-
随手写的,有空再整理。 一、Stage、ViewPort、Camera、FrameBuffer、Batch Stage就是实在的游戏世界,Stage与显示逻辑毫无关系,Stage只是在客观的描述游戏世界的内容。 ViewPort代表Stage到Screen的缩放方式(平铺、缩放、拉伸、延展等)。 Camera包含两部分,观察世界的位置与方向,以及如何投射到FrameBuffer。 FrameBuffer就是画布。 Batch是连接Camera投射和FrameBuffer工具,Batch在事务过程中不能修改Camera投影,也不能改变画布大小。 任何时候必须保证Camera大小与FrameBuffer大小相同。 二、布局坑minWidth, minHeight, prefWidth, prefHeight, maxWidth, maxHeight的处理逻辑非常恶心,和width还有height的关系很迷惑。 恶心的根本原因在于框架的布局逻辑里没有专门为测量做设计。通常布局的逻辑有两个阶段: 测量阶段:布局控件要先测量所有孩子控件的大小,来计算自己需要的总大小。此时控件没有真实大小。 布局阶段:然后根据自己的可用大小和孩子的预期大小权衡后进行布局。此时父级(包括当前)布局都已拥有真实大小。 这两个阶段本都是相对比较独立的递归过程,单独写都很好写,但框架实际只有一个layout函数要处理完整个逻辑时就很容易出错。测量阶段被用prefWidth和prefHeight简化了。 但框架里prefWidth和prefHeight都是接口,而且只有WidgetGroup会提供这个接口,这意味着用户不能在布局文件中预设控件的大小——因为prefWidth和prefHeight不是所有控件都有,甚至也不是个能写的数值。如果使用width和hegiht来填写这个期望值,那么当父布局因为某些原因无法按照预期大小对控件进行布局时,原来的width和height属性就会被覆盖,这个原始的“期望”大小就会消失。 总结下来注意的就两点: 测量阶段的函数不要使用任何需要控件实际大小的依赖,某些处理空余空间分配的逻辑不要尝试写进测量阶段。 对于用户的期望大小,尽量使用控件外的逻辑来赋予,而不要使用width和height,否则会丢失原始数据。 三、布局默认会把transform设置为true,导致孩子的draw方法会使用父布局的坐标 他妈逼的煞笔默认逻辑,查了我半天为什么会出现偏移错误,关键是layout出来的位置都是对的。 先这样吧,
