在GLSL上发布了61.9万个俄罗斯方块,它们的渲染和一个简单的机器人

我有一个“想法”,可以使一个着色器(一个帧缓冲纹理)同时运行俄罗斯方块的最大数量。


以下是对所得代码如何工作的简短描述。


这是什么


每个俄罗斯方块都以三个像素工作,对于1920x1080分辨率,您一次可以运行619200份。 还制作了一个用于自动播放的简单机器人。
在帖子末尾,可以链接到运行和源。
视频已更新,显示剩余字段数,最多为零。



资料储存


尺寸为[10, 22] (10宽,22高度)的俄罗斯方块表。
每个单元格可以为空或不为空。
存储整个表总共需要22 * 10 = 220位。
一个“像素”是四个24位浮点数,每个像素96位。


在视觉上(调试框架的一部分),三个像素用红色突出显示,这是一个保存的字段:


图片


2 * 96 + 24 + 4
两个像素,第三像素的一个浮点数,第三像素的第二个浮点数的4位
第三个像素pixel3.zw中有两个未使用的浮点数,它们存储了逻辑状态 ,更精确地讲


  • z存储三个八位数字[a,b,c]
    -当前块的位置,作为数组中该位置的ID(大小为220位的数组,最大位置为220,小于0xff)
    -b时间,直到每帧-1自动下降(定时器)到该数字为止,当它变为0时它就落在块上
    -当前块的c ID
  • w也是[a,b,c] ,而且整个浮动的符号(正号或负号) 也是当前表中游戏结束的标志(以免浪费该字段,避免浪费资源)
    - 一个动作,无动作(0),向左(1),向右(2),依此类推,完整代码在Common中 ,动作具有两种状态, 检查左检查是否可以向左移动,然后将动作设置为left_ move
    -当前表的[b,c] 0xffff (16位)点,已刻录的行数

在第三像素的第二浮点数中还有20 未使用。


调试框架显示保存逻辑正常工作
左侧有一个大小为3个像素的白场,专门设置为显示间隙已正确处理(如果分辨率不是3的倍数,则条带将成一定角度)
75行的条件缓冲区A


图片


为什么需要动作ID:


  • 数据存储在三个像素中,不可能同时检查逻辑并在一帧中更改数据 (如果不执行所有逻辑并在每个像素中加载整个地图,则负载将增加数十倍)。
  • 因此, 数据存储逻辑在每个像素中工作,并执行接收到的命令left_mov ,验证命令left_check仅在一个像素(第三个像素) 执行。

慢的地方


  • 每三个像素(逻辑像素)解压缩整个地图(读取所有三个像素)。
  • 其余两个像素仅解压缩“自身”(一个像素)以执行保存的动作。
  • 在操作过程中, 线烧了,另一个像素被加载,因为桌子掉下来了,桌子的下部应该知道上面是什么。

存储算法性能


对于测试,也将#define debug设置为Common和AI 0
我得到了这样的结果 -渲染和处理所有619200字段时为10FPS
在12万场(25fps)


图片


机器人逻辑


逻辑非常糟糕 ,该僵尸程序在一分钟内就会耗尽,最高可升至60分。


考虑到基于所有可能的跌落的最佳位置,我无法通过许多周期来检查孔洞,壁架和可燃区域来开始一个良好的逻辑 ...
好的逻辑对我来说最多可以工作100份,并且在绕过所有循环时会产生很大的延迟。


我的机器人逻辑是这样的
所有逻辑都在缓冲区A的AI_pos_gen函数中,共有十行。


伪代码:
检查模块安装的高度是否等于当前列中字段的最大值(检查一行的高度)


 (4   ){ (  (10)){ (     ){  (    ,  )   best ID()  best POS } } }  (     )   (  )      0     1 

事实证明,三个循环很常见-他们将块放置在使得高度最小的位置。


当出现新块时调用AI_pos_gen函数,并返回从上方下落的位置,获取块ID并使其旋转,该函数在第三个像素(逻辑)中工作,即,它具有一个已满载的地图(地图数组)。
如果愿意,您可以轻松尝试编写您的机器人。


最慢的地方
仅增加一个循环即可测试漏洞 ,当机器人数量超过1万时,我的视频卡驱动程序崩溃了……我编写的机器人是我能做到的最“简约”的机器人版本,不幸的是它非常糟糕。


UI界面/渲染


所有呈现在Image中 ,UI逻辑在缓冲区B中。


渲染:
将屏幕拆分为小块,并在每个小块中绘制一张桌子,以最小的负荷。


加载地图的逻辑-未解压缩每个像素,未解压缩每个像素,仅解压缩了“必需位”(字面意思),功能代码为:


 int maptmp(int id, int midg) { int nBits = 8; ivec4 pixeldata = loadat(id, midg); int itt = (id / 24) / 4; //data pixel id 0-2 int jtt = (id - itt * 24 * 4) / 24; //component in data pizel id 0-3 int ott = (id - itt * 24 * 4 - jtt * 24) / 8; //component in unpacked value 0-2 int ttt = (id - itt * 24 * 4 - jtt * 24 - ott * 8); //bit after int2bit 0-7 ivec3 val = decodeval16(pixeldata[jtt]); int n = val[ott]; for (int i = 0; i < nBits; ++i, n /= 2) { if (i == ttt) { if ((n % 2) == 0)return 0; else return 1; //switch + return does not work on windows(Angle) /*switch (n % 2) { case 0:return 0;break; case 1:return 1;break; }*/ } } return 0; } 

为了避免滚动时的像素化 ,从43000开始,浮点数的小数部分会丢失,并且无法计算出将619千添加到UV进行滚动(会有像素而不是表格)。
所有滚动都分为一个大图块,并旋转一圈,最多向UV添加32。 ( 图片中的 207行)。


确定字段ID的方法相同。 ( 图片中的第215行)


用户界面


号码:
黄色是俄罗斯方块字段的数量。
左大-当前字段的编号。
在右下方-当前字段的点。


源和启动


Bufer A逻辑, Bufer B是UI控件, 图像渲染
来源https://www.shadertoy.com/view/3dlSzs (通过Angle编译的时间为16秒)
该机器人在此处被禁用(您可以启用它),并且所有字段都可以通过键盘进行播放


控制左/右/上/下箭头。


UI红色矩形重置,移动(单击LMB拖动鼠标),然后单击字段以滚动或选择要显示的字段。


从网络浏览器启动:


  • 使用chrome.exe运行chrome --use-angle = gl
  • 跟随链接到shadertoy
  • 在网站上的编辑器中,选择“通用”并删除#define no_AI
  • (也是常见的)将#define AI 199设置为0,即#define AI 0
  • 单击编译按钮(在着色器上的编辑器窗口下),然后单击全屏

第二个选项是在任何“着色器启动器”中运行着色器,这里是指向带有该着色器的* .exe文件的存档( 下载链接


OpenGL的编译时间约为10秒。


更新 :添加了带有孔检查的着色器https://www.shadertoy.com/view/wsXXzH
而不是在相同高度下获得更好位置的条件。 check_block_at_wh功能check_block_at_wh (第380 BufA行),同时检查位置的有效性,计算孔数,未添加新的循环以及条件行442至459 BufA。
它也可以在一分钟之内迅速燃烧30-60点(显然,您需要检查较大的区域是否有孔,但这会产生强大的制动作用)


还有两张图片解释了这项工作:
位置选择https://i.imgur.com/e0uENgV.png
该条件的块位置为https://i.imgur.com/ORECXUW.png

Source: https://habr.com/ru/post/zh-CN443042/


All Articles