OpenShader月(nian)报 #2

咕咕,咕咕咕
(ge le, qi keng le)


时隔了很久之后...终于有了第三篇月报,在上一篇中我说到5月要肝毕设,那么,之后的时间? 之后的时间确实没有认真肝OpenShader,有很长一段时间在摸鱼,所以这段时间的进度怎么看都不像半年时间该做出来的...

  • Shadow Caster的裁剪
    在之前,阴影渲染也占据了不少性能开销,就像以前我提到在早期版本测试时使用3个阴影图的CSM时性能就急剧下降了,虽然池化VBO有效降低了渲染时的Drawcall开销,但在MC准备渲染数据时的开销依然不少,因此最好的办法就是在Shadow Caster中也进行一次裁剪,去除掉不能对玩家的视角形成阴影的物体和地形.

    关于裁剪体的计算,先将玩家镜头的视锥体朝向光源镜头的近裁面做一次投影,在投影得到的二维图形中找出最外层的边,每一个边沿着光源镜头的视线方向延伸得到面后,这些面就成了构成裁剪体的裁面.在这个裁剪体范围之内的物体大部分都能对玩家视角内行成阴影.如果说还有什么不完善的话,主要是刚才得到的裁剪体不包括近裁面和远裁面,对太阳光来说近裁面是没有必要的,而不计算远裁面就导致了一些本来不会贡献阴影的物体也被渲染了,会增加一些不必要的开销,一个未来的改进是选择玩家镜头的视锥体中,正面朝向光源的那些裁剪面,用这些裁面再做一次裁剪,就能去除掉那些false positive了.


    举例,比如如果这张图是一个渲染场景,中间高亮的橙色椎体是玩家镜头的视锥体,左上方为环境光源及其方向,圆锥、圆柱、棱角球和方块分别是4个可渲染的物体,那么在无Shadow Caster的裁剪时,这4个物体全部需要渲染.

    使用现在的视锥构建方法时,圆柱被剔除掉了,因为它即不在玩家视线内,也不会给玩家视线内的物体贡献阴影,然而方块仍然会被渲染,尽管它不会贡献阴影.

    最终目标是制作一个这样的裁剪体,保留下的物体只有圆锥 - 它会给玩家视线内贡献阴影,以及棱角球 - 它在玩家视线内.


  • 重新设计的多线程渲染流程
    显然,在OpenGL中不会有真正意义上的高性能多线程渲染(疯狂切context确实可以多线程,但它不够高性能),因此我们设计的多线程渲染流程也和其他的同类产品一样,努力压榨出更多的可在CPU端并行处理的东西,比如裁剪、收集渲染信息等,然后将它们分派到子线程上执行,主线程只保留最必要的工作:执行渲染指令.
    那么,我们先来看看上次月报(17年5月,应该叫半年报了?)时的渲染流程:


    在旧的渲染流程中,整个渲染被分为准备阶段和渲染阶段,在准备阶段程序会收集并配置渲染信息,主要工作分为镜头(计算每一个摄像机的位置和MVP矩阵)、地形(统计哪些RenderChunk需要被渲染)和实体(统计哪些实体和TileEntity需要被渲染)这三部分,此三部分有顺序依赖,不可调换顺序(地形需要镜头的信息来进行视锥裁剪,实体需要知道哪些区块要被渲染). 在旧设计中这三部分都会在准备阶段进行,尽管主线程在处理到每个部分时都会将工作分发给工作线程来进行,从而实现了并行化,但这里还有一个问题 - 主线程被完全浪费了,事实上渲染阶段其实无需等待准备工作全部完成时才进行,有些渲染操作并不涉及到地形和实体,比如天空背景和云朵;此外渲染阶段多线程的利用率非常低 - 只用到了两个线程. 因此我们重构的目标时尽可能缩短准备阶段,并尽量提高渲染阶段的多线程化程度,最终重新设计的渲染流程是这样的:

    在新的渲染流程中,只有镜头配置必须在准备阶段进行,其余的工作都是在渲染阶段进行,在启动渲染阶段时会有三个线程开始工作,主线程等待并处理渲染指令,一个工作线程生成渲染指令,另一个工作线程会串行处理地形的可见性判断,为什么这一步要单线程处理? 这涉及到它的工作方式,地形的可见性判断是用来找出哪些RenderChunk需要被渲染,主要的手段一是视锥裁剪,二是以玩家所在的RenderChunk为起点做一次广度优先搜索,为了防止搜索时重复搜索已处理过的节点,MC会在RenderChunk中标记一个时间戳,每次搜索到节点时都会尝试更新时间戳,如果更新成功(节点时戳比系统时戳旧)那么这个RenderChunk就是未被处理过的,如果更新失败(节点时戳与系统时戳一致)那么这个节点就是已经被处理过的节点,在多遍渲染时程序需要多次进行可见性判断,比如在渲染阴影时,需要有一个pass处理ShadowMap绘制的地形,一个pass处理玩家视角绘制的地形,如果想在此步骤并行处理的话,就需要把时戳换成一个原子整数,然后为每个pass分配一个bitflag,当此pass处理完一个节点时,就给那个原子整数标记上bitflag,显然,当所有的pass处理完毕之后还得再将那个原子整数清零,在旧的渲染流程中我就是这样做的,其结果呢? 嗯...确实是并行化了,但是在性能测试中发现更新原子变量的操作非常的慢,特别是最后重新清零的那个步骤,相比之下,之前串行处理时就不需要这个步骤,因为时戳始终是递增的,在处理完一个pass后,只要将系统时戳再喜+1就能继续处理下一个pass. 因此再三斟酌后,我将这一步换回了由一个线程串行处理所有pass的可见性判断. 但是之后的工作依然是并行处理,每当一个pass完成可见性判断后,程序就会再给线程池分配两个新任务 - 生成这个pass的地形渲染指令,以及准备实体的渲染信息.

  • 帧缓冲的Pingpong
    以前我在Shadersmod教程中提到过不要在一个着色器中对一个RenderTarget即读即写,因为在Texture Barrier出现之前对一个纹理在读出的同时进行写入属于未定义的行为,其结果是不可预料的,因此一般采用的策略是为一个需要即读即写的RenderTarget准备两个纹理,一个纹理作为读纹理,一个作为写纹理,每次运行完渲染后交换两个纹理的职责,原本读的下次变成写的,原本写的下次变成读的,这个方法被称为Pingpong. Shadersmod没有提供Pingpong功能,因此我们只能自行回避即读即写操作(其实回避的是异处读写,在片元着色器中原地读写的话在大部分显卡上实际上是允许的). 而OpenShader提供了Pingpong功能,在默认配置下程序会自动检测每个stage的帧缓冲和绑定的纹理,如果检测到一个纹理即被绑定到输入又被绑定到输出的话,就会自动启用Pingpong.

  • 图像统计 (WIP)
    图像统计之前我们已经介绍过了,主要是给像HDR之类的后处理特效用,不过...现在的OpenShader的图像统计功能非常菜鸡,仅仅只是达到了Shadersmod的水平,换句话说,就是只提供了获取屏幕中央深度的功能... 这个功能其实很鸡肋,想知道深度缓冲中央的值,直接给我texture(gdepth, vec(0.5, 0.5)).r不就得了嘛! 将这个值做成一个uniform除了卡流水线以外没任何用处,尽管中央深度可以通过将其替换为纹理采样来规避(怎么替换将是下一期月报的内容(豹笑)),但像统计全屏幕平均亮度这样的操作就只能靠实打实的图像统计来实现了,因此图像统计这方面还有不少工作要做...

  • RenderChunk渲染参数缓存 (WIP)
    这个...应该是最让我崩溃的部分,大部分功能在去年8月末就完成了,但由于两个折磨人的Bug导致它一直到现在都没彻底完工.

    简单地说,在之前准备渲染参数时需要程序逐一获取每一个RenderChunk的参数,这涉及到了大量的内存访问,以及随之而来的cache miss,即使是原本很简单的操作也会花费大量的时间,渲染参数缓存就是提前将这些参数缓存在一个整数数组中,将所有操作都简化成数组的复制和整数操作 -- 利用了OpenGL所有对象都以整数句柄的形式存在的特点. 比如渲染一个池化的RenderChunk时需要知道它在哪个VBO中、它的首地址偏移量和顶点数量,这些参数都可以缓存在整数数组中.

    这个优化被证明为是成功的,确实能提高不少性能,然而却带来了两个新bug.

    第一个bug是当玩家向任意一个方向移动一定距离时,就会发现反方向消失的区块会在面前出现...事先说明,这个bug已经被解决了,某天在我肝了几宿HOI4后决定再看看能不能解决这个让我崩溃了两个多月的bug,结果在灵光一现后(我一直笃信debug的时候灵感是最重要的 233)几个骚断点找出原因了,原因是我对MC自带的RenderChunk表(我对它的称呼,MCP把那个类称为ViewFrustum...咳)的理解不充分,我原本以为它会完整地利用所有空间,也就是表中每一项都会是一个可见的RenderChunk,然而实际上MC并没有充分利用每一个空间,在某些情况下,比如游戏载入之后玩家向某一个方向持续移动时,会出现某些项中的RenderChunk存在但却不该被渲染的情况. 原版MC中通过一个RenderChunk中的标记来判断是否该被渲染,而我在编写渲染参数缓存时没能正确缓存这个属性,结果导致了这个问题.

    远处那一排非常突兀的地形就是bug所导致的情况,实际上它们应该是在我背后过来的地方的某排区块.

    而第二个bug...则把我困惑到怀疑人生. 简单地说,在设计上,当内存池发生变化时,变化的内容(VBO的id、指针偏移量、数据长度)会被立刻刷新入缓存,然而现在存在的问题是在罕见的情况下缓存和实际内容会有一帧的不同步,结果导致因使用了错误的参数而渲染出错.

    出错的一帧的截屏

    这个bug一直到现在我都没能解决...最近倒是想出了一个workaround,暂时还没有实现,看看下一次更新前能不能做完吧 (鸽?)

  • 一些...杂七杂八的东西
    因为没有changelog,所以更新了什么东西都是靠翻git的变更记录来一点点查的(笑),主要是修bug和一些优化,其实现在OpenShader的状态是三十六拜后就差一哆嗦,然而这一哆嗦就是几个月都没哆嗦出来 ? 这段时间一直都在忙别的,看看这俩月能不能把最后一点肝出来吧,下一篇日志我们会介绍一些native方面的工作--
阅读全文 [...]

FGOW与FMM停止维护

准确说这并不意外,最近几个月如果你使用过FGOW和FMM镜像的话,肯定已经察觉到各种问题诸如无法下载之类的,事实上,FMM最后一次维护是在今年3月左右;而FGOW? 我都忘了上一次更新FGOW是什么时候了...

FGOW(ForgeGradle on Wall)与FMM(Forge Maven Mirror)开发于2014年5月,前者是用于解决一些ForgeGradle的缺陷,比如无法设置Maven源和其他必要文件的下载链接,无法像MCP那样将MC源码部署到开发目录;后者是一个非官方的Forge Maven源,搭建在SAE上. 这两个的组合曾经是很有效的,那么为什么突然就药丸了呢?
  • FGOW最初的设计并没有什么可扩展性,只是一个编码粗暴的针对FG1.1的小工具,而现在FG已经更新到了2.3...在许许多多的底层变动后,要更新FGOW可能跟重写一遍差不多了...
  • FMM搭建在SAE上,用过SAE的都知道它有一些坑爹限制,比如FetchUrl的8MB大小限制,最初这个问题不严重,毕竟鲜有哪个库能超过8MB,但后来FG打包附带了原本要独立下载的第三方库,体积直接猛增到了13MB,SAE的文档也诚不欺我,说8MB上限就是8MB上限,一字节也不带多下的,所以FMM的大文件(准确的说,只有FG的各个快照)一直都是在后台手动更新的...
  • 缺乏存在的意义,FGOW除了重设下载链接外,还有个重要功能是部署MC源码,然而后来有小伙伴指出其实可以直接在项目中建一个同包同名同内容的类,这样根据类加载顺序,实际载入游戏中的是项目中可供我们随意修改的那个类,由此一来,FGOW的那个功能就没什么用了.
  • 同上,既然FMM没了,那么FGOW也卵用了,思前想后,我觉得还是挂代理能一劳永逸地解决所有问题...而zzzz正好又提供了一个公用的SS账号用来构建Forge工作目录,所以我就可以弃坑了 (逃)
  • 缺乏足够的精力去继续维护--- 呸,直说吧,就是我太懒了 ?
  • SAE是要花钱的,当初FMM刚刚上线时SAE的价格还很实惠,我2012年注册时送的2000云豆能用十几年,然而自从SAE改了Mysql收费政策后,大概1000云豆只够用10天...当然这并不是主要原因,一个月30块钱还是氪的起的.
  • 并没有大量的关于紫sama蓝sama幽幽sama觉sama恋sama和玛艾露贝莉x莲子的福利!
那么现在FGOW和FMM没了,又该如何配置开发环境呢,之前提到了zzzz搭建了一个公共SS账号,账号在他的MCMod教程中(https://fmltutor.ustc-zzzz.net/1.1-配置你的工作环境.html). SS我觉得应该是现在码农们常备的"工具"了,假如你没有的话...就去下一个吧! 但是不要用他的链接里的SSR,前几天SSR的作者搞了个大新闻,被婊到删库退圈了 (滑稽) 阅读全文 [...]

OpenShader月报 #1

TL;DR: 弃坑啦!!!

好吧这是开玩笑的,虽然沉寂了很长时间,怎么看都像是"好难啊不做了"的样子,但项目确实还在继续开发中.本来说好1月或2月发一个预览版本,然而实际上...实际上从1月初到3月初这段时间我基本没怎么碰OpenShader的代码,一方面是家里有事,另一方面当时我在摸鱼两个即兴项目(还特么是两个??),等想起来"哟我还有一个大坑没填"时已经是3月中旬了,所以说现在写一篇月报其实挺符合现实情况.

那么,首先是关于之前开的那些坑填的状况:

  • 池化VBO&MultiDraw (90%)
    池化VBO和MultiDraw已经基本完成了,目前现存的问题是内存占用稍微有点大,似乎还存在内存颠簸的问题,不过这些都是可以优化的. 从表现来看,池化VBO是相当成功的,砖块渲染的开销一下子从之前的第一骤降到了第三第四,甚至低于天空渲染和云渲染的开销...(谁想得到天空渲染怎么会有那么大的开销??),其实这也不意外,一个池化的VBO能承载16个Chunk(是的,16x256x16的那玩意,不是16x16x16的RenderChunk)的顶点数据,并用1次绑定加1到4次DrawCall(取决于这些Chunk的位置,大多数情况下是1~2次,极端情况会是4次)将它们全部渲染出来,在过去这需要16次绑定和16次DrawCall,而且实际情况会比这还要多 --- RenderChunk只有16x16x16,在山地之类的立体地形中渲染1个Chunk需要渲染多个RenderChunk --- 不过也有可能比这少,毕竟有些RenderChunk可能不可见.但无论如何,总体开销都不可能比池化VBO+MultiDraw要低. 在实现VBO内存池时我遇到了个吔屎的问题,我忘了OpenGL中大部分DrawCall类指令的偏移量单位是顶点而不是字节...这意味着顶点大小不同的顶点数据不能存入同一个内存池,因为数据的偏移量必须是顶点大小的整倍数,否则在DrawCall中没法指定偏移量,而顶点大小不同的数据存在一起时对齐会变得非常麻烦,因此最后我设计成使用多个内存池来存储16个Chunk的数据,每个内存池只存储特定一种大小的数据,问题解决了,虽然浪费的内存变得更多了... 不过还有一个小问题是现在KHR_Debug_Callback返回的Debug信息中经常会狂刷"Pixel-path performance warning: Pixel transfer is synchronized with 3D rendering." 主要发生在内存池扩容删除旧缓存时,这是什么鬼...
  • 指令队列 (100%)
    指令队列顺利完工了,但是表现的却不是很好,性能感觉提升了不到10%...而且Debug变得异常艰难,因为报错后只知道是在哪种指令中出错了,甚至都不知道是哪个Stage提交的哪个指令...不过也不意外,Nvidia在AZDO(Approaching Zero Driver Overhead)那篇presentation中提到他们用软件实现的指令队列的性能提升也只有14%. 现在只能期待后继的优化中将尽可能多的工作塞到指令准备时并行执行了,毕竟目前渲染准备阶段占用的时间也挺多的,如果能砍掉这部分时间(无论是优化还是并行执行)那对性能也是有很大的提升.
  • Early-Clear (0% 蛤蛤蛤)
    这一个完全没弄,主要是暂时没有特别大的需求,但今后肯定会有的.

正在进行的工作/已完成的其他工作:

  • 清理临时代码 (90%)
    花了几天清理了很多临时代码,主要是ASM那方面,之前在开发时遇到需要修改MC代码的情况时,就把MC代码复制到项目中的同名类里,利用Java类加载顺序的特性来实现覆盖原始代码.现在用ASM彻底重写了这部分了,为即将到来的开源做好了准备. 至于其他方面的临时代码(大量的TODO) 慢慢解决吧...
  • 采样器 (100%)
    采样器决定了着色器如何从纹理中获取纹素,在过去(去年12月的版本)其实已经有采样器的原型了,但是只能用来控制着色器读取哪些纹理,而不能控制着色器怎么读取,现在开发者可以设置采样器的参数了,包括纹理过滤、各向异性过滤、Wrap、Mipmap以及是否开启比较模式.采样器有什么用呢? Shadersmod中启动shadowHardwareFiltering其实就是开启Shadow map的比较模式. 采样器还有两个版本的实现,一个是针对低版本的传统模式,通过glTexParameter修改纹理的采样参数来实现;另一个是针对拥有ARB_sampler_object扩展的高版本,通过采样器对象来实现.
  • 自定义Uniform (50%)
    虽然OpenShader内置了不少Uniform参数,但毕竟总有我想不到的时候,因此总得有一种方法能让光影包开发者自行设定Uniform,目前的解决方案是让开发者可以添加一个自定义Uniform,然后监听更新事件并手动更新Uniform内容,这样的设计有两个缺陷,一个是只有Mod式的光影包能写代码来处理事件监听,另一个是MinecraftForge的居然要求事件总线的注册要在Mod初始化中进行,否则就会抛一个警告信息...我在考虑要不要设计一种DSL让开发者通过简单的表达式来描述需要获取的内容;或者利用Java的ScriptEngine内置JavaScript的特性,让开发者写JS脚本去获取数据.不过这些都无法回避一个问题,就是运行时Minecraft的字段都是混淆的...如何访问这些被混淆的字段还是个问题.
  • 图像统计 (25%)
    简单地说,图像统计是用来获取渲染结果中的某些信息,比如平均亮度(用于HDR)或中央深度(用于DOF)什么的,这个操作主要面临的问题是如何将获取到的结果再递交回去,比如说将统计到的结果再注入到一个Uniform里,这是最简单的方式,不过会涉及到CPU-GPU同步的停顿,对于这个问题,国产的开源引擎KlayGE的解决方案是使用计算着色器来统计亮度,然后将结果写入一个纹理的固定位置(DX的左上角(0,0)),后续处理时就直接从此纹理的固定位置读数据就行了,整个操作可以一气呵成,无需CPU从GPU那里读回数据,顶多有必要的话加一个屏障保证HDR发生在亮度统计之后即可,写入纹理在OpenGL中对应的操作是Image Load Store,不过假如让我来实现这个的话,我可能会选择SSBO,因为Image Load Store到4.2才进入核心扩展,而4.3就有了SSBO了... 嗯,然后你问没有计算着色器的辣鸡该怎么办? 把纹理慢慢读回内存然后让CPU慢慢跑吧,同步的停顿想着就感人...
  • 兼容Shadersmod光影包 (60%)
    听上去像是一个很不可思议的事情,但考虑到OpenShader在设计时就是具有高度可配置性,那么Shadersmod的光影包不就是一种特殊格式的OpenShader光影包吗(笑),这个月我主要就是在弄这个,期间也找出并解决了OpenShader不少问题.目前的情况是已经能跑我在光影包教程中写的那个MyFirstShader了(不过还存在一些小问题),至于别的...我试了一下SEUS,看上去能跑,但是总感觉有些差别,特别是炫光效果经常会闪烁,估计是纹理采样参数有差别;体积云也会有断层,估计是噪音图的Wrap没有设置成REPEAT而导致;流体砖块的Attribute注入也跪了,滑稽 23333
  • 无尽的Bug... (1%)
    还有很多问题需要修复,有些是缺乏边界条件时的处理,正常使用没问题,但如果使用姿势稍有不对就 --- DUANG! 还有一些就是彻头彻尾的Bug,比如原版MC的RenderChunk载入是使用一个优先队列,距离玩家最近的未加载RenderChunk永远是会被最优先处理的,这保证了玩家在移动时自己身旁的RenderChunk总是被加载出来的,而现在这个设定貌似坏掉了,变成了一个先入先出队列,结果就是当玩家都已经跑进未加载区域了,那边还在吭哧吭哧地加载旧区块...
  • 开源! \o/ \o/ \o/
    说好了的开源项目,怎么迟迟没有开源呢? 最大的阻力:临时代码已经被消灭了,我打算等Shadersmod兼容差不多实现了后就传到Github上,啊当然是创一个新仓库了,我怎么会把现在的私有仓库中羞耻的旧代码亮出来! (笑)

差不多就是这些,我去肝毕设去了(又是弃坑一段时间的节奏?),学校突然把毕设报告从6月提到了5月,药丸药丸啊 ?

4.26更新

基本能跑SEUS 10.1了,除了没有DOF (因为还没实现图像统计) 但是有个奇怪的问题就是亮的吃屎...想不出这是什么原因啊...

4.28更新

能跑SEUS10.2了,SEUS11.0也差不多不过水面有些问题.10.2屏幕过亮的问题是eyeBrightness弄错了,此外还修掉了一些奇怪的问题,不过依然有一些光影会跪的比较惨,群众喜闻乐见的Chocapic13 V6天空渲染会莫名其妙地坏掉.

另外还有一个问题可能要在很久之后修复,甚至是不修复...就是矩阵精度问题,Minecraft一直是直接用glTranslate、glRotate之类的GL函数直接操作OpenGL矩阵,而OpenShader则提供了一个封装(通过ASM魔改1.8新增的GlStateManager来实现,具体原理篇幅有限先跳过了 233),对矩阵的操作会先缓存在应用层,到下一次DrawCall之前再通过glLoadMatrix更新到驱动,这个可以降低GL函数调用的开销,然而带来的问题是OpenShader的矩阵精度好像差了一点点...

这"一点点"对常见的矩阵(模型视图矩阵和投影矩阵)没有影响,但对法线矩阵(gl_NormalMatrix,还记得我在Shadersmod教程的附录里提到的计算方法吗?模型视图矩阵的左上3x3的逆的转置)有着微妙的影响,由GL函数得到的模型视图矩阵算出的法线矩阵在乘完法线后,似乎能近乎神奇地保证法线的单位长度依然保持在1左右,至少是小于等于1;而OpenShader的矩阵呢,乘完后长度稍微大了那么一丁点...非常非常小的一丁点,但在某些鲁棒性较差的算法里就会跪了,比如说我的那个Shadersmod教程里的MyFirstShader...

MyFirstShader里用到了一个将vec3格式的法线编码成vec2格式的算法:

vec2 normalEncode(vec3 n) {
vec2 enc = normalize(n.xy) * (sqrt(-n.z*0.5+0.5));
enc = enc*0.5+0.5;
return enc;
}

...

vec2 normal = normalEncode(gl_NormalMatrix * gl_Normal);

最要命的地方在于那个sqrt(-n.z*0.5+0.5),它假定法线的长度一定不超过1,因此z一定是介于[-1,1]的,由此它才可以放心大胆地计算sqrt(-n.z*0.5+0.5)而不用担心-n.z*0.5+0.5小于0时sqrt蹦个NaN,但是由于矩阵精度问题,这里的法线长度超过1了,于是n.z=1.00001, -n.z*0.5+0.5=-0.000005, sqrt(-0.000005)=NAN,完.IEEE754规定NaN参与的运算都会变成NaN,就像JS中的undefined那样毒性十足,于是整个算法就跪了,编码出的法线在特定的角度(一般是平面垂直玩家视线时)会毫无征兆地突然崩掉.解决的方法其实非常简单,给gl_NormalMatrix * gl_Normal套个normalize就行了,幸运的是市面上几乎所有光影包在获取法线时都是用normalize(gl_NormalMatrix * gl_Normal) (你问我的那个教程光影? 它可不算"流入市面"哦?),因此基本不用担心这个问题,所以我也不太急着修复它...(更何况我也不知道如何修复 hehe)

4.29更新

试了一下发现Chocapic13 V3也会跪掉,原因是Chocapic13 V3使用了一个Shadersmod非常罕见的特性:DRAWBUFFERS中允许空白占位符,比如DRAWBUFFERS:NNN1N2就是gl_FragData[3]向colortex1输出颜色,gl_FragData[5]向colortex2输出颜色,而gl_FragData[0]、[1]、[2]和[4]不向任何RenderTarget输出.我当时没料到还有这种操作 233 V6跪掉的原因是Optifine内置的新版Shadersmod似乎支持在一个pass中向一个缓冲同时即读即写了,奇怪的是它的文档中却明确提到"Writing to color attachments that the composite shader also reads from will generate artifacts" 难道他自己搞了大新闻结果忘了更新文档了吗 ? 其实针对即读即写的情况启用Ping-pong我之前已经打算把它作为OpenShader相对Shadersmod而言的新特性了,不过因为需求不大一直没实现,现在看来是被Optifine将了一军了 233

阅读全文 [...]

OpenShader月报 #0

虽然没有做paperwork的习惯,但我觉得在这时候还是写一篇总结这个月工作进展的文章比较好,毕竟卫星放出来如果没时不时地搞点大新闻的话总会让人觉得弃坑了是吧 233
  • RenderChunk更新优化
    众所周知MC的地图是以16x256x16的Chunk - 即所谓的区块 - 为存储单位,但在渲染时是以16x16x16的RenderChunk为单位进行更新和渲染,问题在于RenderChunk的更新并不是很快,在i5-4590的机器上大概需要6ms每单位,虽然MC已经引入了多线程更新RenderChunk的设计,但这只局限于由地图加载导致的更新,由砖块变化产生的更新依然要求单线程更新,这就导致了当发生大规模的砖块更新时游戏帧率会骤然下降,比如大规模流水或接入高频红石电路的红石灯等.
    RenderChunk的更新操作实际上是遍历区块中16x16x16的部分逐砖块地进行可见性判断,然后将砖块的模型面填充入顶点缓冲中,其中获取光照度和获取砖块的IBlockState这两步操作开销莫名的大,而在原版的更新流程中它们的调用又十分频繁,因此在此做一个简单的缓存就能戏剧性地加快原版更新速度.
    然而Forge还增加了它自己的更新流程,被称为ForgeLightPileline,顾名思义,它是解决在复杂模型下原版光照错误的问题,在默认情况下它是取代原版流程的,它的速度跟未经优化的原版差不多,然而它的可优化空间实在不大,一堆堆的三维数组像节日彩球一样在程序里传来传去还要不停地拆包打包可真没有什么优化手段,但是没有它又还真不行,因此这里采用的措施是增加一个判断器根据被渲染的砖块的模型类型来判断该采用哪个更新流程,毫无疑问这一步并不快,因此加入了一个LUT缓存,采用打表查表的方式来加速判断.
    此外还有一个被临时取消掉了的优化,是让上文提到的发生变化的砖块也进行多线程更新,这一项优化效果斐然,但是却存在两个巨大的缺陷,一个是在首次进入游戏时有大概5%的概率因为某个竞态条件而陷入卡死 - 提交入队列的更新任务莫名其妙地没有被执行,这个问题我调查了几天也没有结果;如果说它可以通过一个条件变量强制在首次更新时采用单线程来"解决"的话,下一个问题根本无法绕过,在更新区块时,某些BlockRenderLayer的变化要到下一帧才会反馈,首先要解释一下BlockRenderLayer(以下简称layer),从1.8开始MC中的砖块被区分为4种layer: SOLID(无Alpha测试,有Mipmap)、CUTOUT_MIPPED(有Alpha测试,有Mipmap)、CUTOUT(有Alpha测试,无Mipmap)和TRANSLUCENT(开启混合). 在渲染时MC会将按照这4个layer逐层渲染砖块,原因很显然,不同的layer需要不同的渲染状态.而在引入多线程更新后遇到的问题就是,当区块发生更新时,某些layer中的顶点变化总会慢一帧才能表现出来.
    比如,草地的layer是CUTOUT_MIPPED,石头和泥土地是SOLID,当你敲碎一个放置在草地旁的石头后,石头破碎的地方不会被填上草地,而且会露出一个空洞,这个空洞直到下一帧时才会补上,然而奇怪的是设断点会发现更新只发生了一次并且是发生在敲碎石头的那一帧,这难以解释为何一次更新的效果直到下一帧才会生效,在更新完毕后将数据从客户端(CPU/内存)上传到服务器(GPU/显存)的操作无论是单线程版本还是多线程版本都会被留在主线程进行,或许是由于内存可见性什么的问题,但不管怎么说,具体原因始终未能确定,因此只能暂时取消掉多线程更新这个优化.
    图:在击碎砖块的瞬间留下的空洞,这个空洞在下一帧即被填上了
  • 自定义砖块贴图
    Shadersmod增加了法线贴图"_N"和高光贴图"_S"两种额外的砖块贴图,现在OpenShader也完成了新增砖块贴图的功能了,开发者可以自定义要载入的贴图的后缀名以及当无贴图时的默认值,比如法线的默认值可以采用0xFF7F7FFF(BGRA格式的(1, 0.5, 0.5, 1)). 不过显然在切换光影包时重新载入一遍贴图会很慢,因此Mod提供了一个选项是提前缓存_N和_S两种后缀的贴图并且在切换光影包时不卸载它们.
  • 界面&选项
    现在已经有了一个类似Shadersmod那样的光影包菜单,并且实现了切换或重载,理论上讲Mod现在已经可以正常使用了.
  • 多线程渲染准备
    在进行正式渲染之前,MC需要先准备渲染数据,比如计算镜头数据、对场景做视锥裁剪等,这一部分在之前是单线程完成的,现在这一步是以Pass为单位进行多线程处理,当Pass数量小于等于CPU核数时,渲染准备所需的时间就从"所有Pass准备时间之和"变成了"最慢的Pass的准备时间",也算是省出了一些时间吧...
正在进行的工作:
  • 池化VBO&MultiDraw (60%)
    当我在测试用的光影包中尝试实现一个朴素三重CSM(不根据视锥做偏移,阴影镜头始终对准玩家)时发现性能急剧下降,从Profiler的结果来看一个问题是最大视距的那个阴影镜头所需要的准备时间太长了,另一个问题是砖块绘制过慢,在10视距时,玩家视角与3个ShadowMapping过程总共需要绘制1000+次RenderChunk,绘制一个RenderChunk时还要为每个layer绑定一次VAO再做一次Drawcall,这样算下来每帧会需要数千次Drawcall和等量的VAO绑定,性能瓶颈妥妥地是在驱动上,事实上确实当显卡占用没增加多少而CPU却有一个核心已经跑满了. 因此问题在于如何降低调用数量,N卡有个BindlessMultiDrawIndirect,可以在不绑定VAO/VBO的情况下进行单指令多渲染,但显然我们不能指望一个供应商独占扩展...因此我把注意力集中到了最简单的MultiDraw上,MultiDraw是个很早的特性,我记得好像是GL1.5时代就有了...它可以一次指令绘制一个几何体中多个不连续的部分,而缺点是中途不能切换纹理等,显然这对MC来说不是问题,关键在于如何将多个RenderChunk中的几个layer的顶点数据都整个在一个VBO中.
    我的解决方案是以4x16x4个RenderChunk为一组(也就是覆盖4x4个Chunk)使用一个手动维护的内存池来共同存储所有layer的顶点数据,这个内存池的实体是在显卡显存中的一个VBO,在客户端程序会负责空间分配以及数据传输等工作,当RenderChunk向内存池首次提交一次数据更新时,内存池会根据数据体积分配一段略大于数据体积的空间,然后返回给提交者一个指针(有意思的是,这个指针是可变值(Mutable),我知道一个可变值指针听上去即不可思议又恶心,但这确实能省下不少事),同时会在一个SortedSet中记录已分配的指针,有个例外是空数据提交产生的指针,这种指针不会被插入到那个SortedSet中.
    每一个内存池都要面临的问题是碎片整理和扩容,理论上这个内存池可以通过glCopySubBuffer来整理碎片,只要将末尾的内存复制到前面的空隙即可(glCopySubBuffer有个特性是复制源范围和目标范围不能重合,这导致了复制空隙紧后面的数据到前面来,以"冒泡"的形式整理碎片的方法不能用了),但我暂时还没实现,现在的做法是在扩容的同时顺便进行碎片整理,glBufferData在扩容的时候不会保留旧的数据,因此得先分配一个新的VBO,然后把数据复制过去,再销毁旧的.在复制时如果遇到空隙的话就会跳过去,同时纠正之后的指针的偏移量(这也是为什么它是可变值)
  • 指令队列 (30%)
    鉴于OpenGL要求必须在主线程渲染的特点,压榨性能的方式之一就是将渲染数据的整理工作通过多线程来处理,主线程只负责指令提交工作,指令队列响应了这一思想,所有渲染操作都被分类为一些指令,一个阻塞队列负责缓存这些指令,协线程负责收集数据和生成渲染指令,并将指令送入队列,主线程从队列取出指令并执行,这应该可以一定程度上提高性能,毕竟拥有更高的并行度,并且主线程执行的工作更单一,不会受CPU缓存污染的困扰. 现在,唯一的问题在于,从队列中读取指令并执行的开销会抵消这些提升吗...
  • Early-Clear (0% :P)
    上文提到了渲染和数据准备是分开的,但事实上有一种渲染操作"基本"无需数据 - 清除缓冲,说它"基本"是因为主视角的清除颜色缓冲还依赖于雾颜色,而雾颜色的计算是在相机计算中进行的,因此可以在完成相机计算后主线程提前开始为各个Pass进行缓冲清除,而协线程继续准备其余的数据.
  • 很...很多... (紫asdfghjkqwertyuiozxcvbnm%)
    显然今年没Demo了! 看看明年1月卫星能不能落地吧! 新年快乐 🙂
阅读全文 [...]

[新坑预告] Yet another shadersmod implementation

最近闭(zhuang)关(si)了几个月,我并没有被Hello Games干掉(顺便在这里祝贺他们没有跑路而坚持在Steam黑五打折期间出了个主要更新,以及敢于向玩家们弹小窗的勇气 - 他们是我见过的第二个向玩家弹窗发送更新通知的游戏厂商,第一个是制作MachineCraft的G2CREW) 这4个月我基本都在过着6点睡2点起(嗯,早晨6点,下午2点)每天见不到几个小时的太阳的生活,在肝腻了文明V(没有I)、舰R、崩3、WT后我终于决定该写点什么,正好手头这个秘密开发了5个月的坑终于有些眉目了,因此决定放个预(wei)告(xing),有人说把坑公开出来就能督促自己不弃坑,然而对我这样的脸皮略厚的人来说好像并没有什么用...2014年我公布了AsmEventBus(基于ASM的Java事件总线系统,比Guava那个基于反射的系统要快很多)和一个没公开名字的Java模块系统(如果称它是"类OSGI"显然有些装逼,但它做的事确实和OSGI差不多) 然后理所当然地坑了,其实对于前者我挺耿耿于怀的,明明只要再完善一点就是个很好的库...今年年初在日狗的软工作业要求下不得不放了个某文字H游戏引擎的Java开源复刻卫星,然后,嗯,然后没下文了.写到这时我自己都忍不住掩面笑了一下,看来羞耻柱这种东西对我这样倒错的人来说并没有什么监督作用.其实我挺怀念11~12年的那段时光,那段不知失败为何物敢于写任何自己想写的代码的日子,在11年的最后一个月我在这个房间的同一个角落半生不熟地用C#写一个文字猎奇游戏,用慢的吔翔的GDI+在WinForm上画文字(现在一想其实挺像ERA啊,当时我要知道ERA的存在的话是不是就给ERA写脚本去了?) 最吃精(?)的是当时我居然在试图写一个自己设计的脚本语言的解析器,幸好当时没做出来,不然这足以让现在的我感到自愧不如,有人说好的程序员应当在看到自己6个月以前写的代码时能发觉自己现在的进步,这么说我应该尽快删IDE退圈了. (笑) 不过我倒真的挺怀念那个项目,毕竟能亲手(即使是只有文字)肢解幻想乡的女孩子怎么想也是一件刺激又有趣的事情,有机会的话我一定要把它复刻出来. (啊呸)

怀古伤今的时间到此为止了,现在说说手头上这个坑,当时提出做MC光影Mod的复刻这个概念是在什么时候已经无从考证了,印象中是14年7月我和ici2cc给CustomSteve做光影Mod兼容时第一次提出了自己做光影的想法,不过当时由于很"容易"地完成了CS与光影的兼容工作,因此这个念头就被打消了.一年半后的16年2月我写完了光影包教程后和ici侃大山时聊到了光影Mod的种种不足,当时我开玩笑地提出自己也打算写一个光影Mod,ici沉默半响后问道你是认真的吗,这时我才开始真正考虑这件事,具体的讨论过程记不清了,结论大概是坑太大填不起,而且当时我刚完稿光影Mod教程很累,也并不想就这么立刻废掉自己的工作成果,更重要的是我想去填一个自己之前挖出来的大坑(这个坑不想提了...也不用猜,"基本上"从未公布过) 因此这件事就被放下了,时间到了6月,那时不知在哪我看到了一个消息,Continuum光影包的作者在用C++给MC写一个渲染器,不用说也知道它会支持第三方光影包,当时就把正在补伊里野的天空的我吓得把播放器关了,为什么呢,因为当初我被Continuum的作者肛过一次(大雾) 2月初我在撰写光影包教程的最后两章时ici弹小窗告诉我"被抢先了",当时吓得我差点提前去见幽幽子,ici赶在我失神之前发来条链接,我赶紧缓过来点开一看是个油土鳖视频列表,上面三四片Continuum的作者录制的光影包教程视频,简单地看了一下后我半自我安慰地得出了个结论:(局座脸)这教程,飞不起来! 本着公平竞争的原则,这里贴出视频列表地址,为什么那么说呢,我感觉他过度死扣光照、ToneMapping和PBR,而忽略了对光影Mod特性的介绍,最简单的例子,除了我的那篇附录以外还能从哪找到对光影Mod的技术规范文档呢,恐怕官方Wiki都没有这么详细,然而话是这么说,但毕竟人家已经抢先发出来了,"第一个光影Mod教程"这个头衔是抢不到了,只好奋笔疾书去抢"第一个成文的光影Mod教程" (笑) 这也是为什么我的教程中最后两章明显的很潦草的原因 (当然,我写烦了也是一个原因...) 后来我的教程发出来了,而他的教程弃坑了,我还顺手打了两发对他的黑枪 (诶嘿,我这人咋就这么爱打黑枪呢) 一个是他那个"号称世界最强却实际只是又一个C13衍生品的光影包",一个是他开的"MC的Vulkan渲染器"坑.故事就看似告一段落了,然而我万万没想到的是他那个Vulkan渲染器在知难而退后又蜕变成了"C++写的OpenGL4.5渲染器",然后又把我肛了一次!被一个人肛两次这事能忍吗!

然而当时是考试周,我只能忍下去了 (笑) 考试结束后我开始探究技术可行性(后来证明这几乎是无用的,所有遇到的问题这时都没发现) 并于7月的实习第一天在那间位于商住一体楼里的破房子中建了项目的Git仓库,版本记录显示前三天我写的代码除了Mod主类和Coremod的LoadingPlugin以外没有一行保留到了现在 (手动滑稽) 一方面说明了那个"百分之多少(记不清了)的代码是要在一年内被重构掉"的理论是正确的,另一方面说明了当时我是有多么低估了问题,虽然那次糟糕的实习让我失去了去CJ和Jeb见面(对此ici可以吹一辈子(笑))以及勾搭上养猪场的机会并且错过了魔都THO,但如果说那一个月实习有什么用的话,那就是让我塌下心能用当初徒手肝解析器的劲头从零制作"Yet another shadersmod implementation",不,它不叫"Yasi"或什么的(虽然现在看起来还挺酷!) 也不叫ShaderCraft云云,我将其命名为OpenShader,因为Open这个词对于开源程序员来说就如同猫薄荷对猫一样充满魅力,显然这是开源的,不过我将它暂时托管到了私有仓库中,因为当初我也实在不敢确定它可以完成,毕竟我之前失败过的太多了,事实上,直到11月初时它甚至还没法正常运行,而我最初的计划是10月初给出一个Demo...当初我写光影包教程时也曾"计划"在10月初完稿,不过现在看来这次我似乎不必拖到次年2月了,然而Mod维护是一件长期的工作,不是吗?主要的技术突破都是在11月中完成的,现在它已经实现了:

  • 高度自定义的渲染管线,可自定义每一帧(Frame)渲染时采用的Pass,以及每一Pass包含哪些绘制阶段(Stage)
  • 着色器加载系统,包含一个简单的Includer实现(无需那个ARB扩展).
  • 可自定义的帧缓冲和帧缓冲的挂件(颜色、深度、模板),包括挂件的尺寸和格式.
  • 顶点着色器的顶点属性(Attribute)注入.
  • 一致变量(Uniform)注入.
  • 优化的GlStateManager,尽可能地减少OpenGL调用.
  • 特性(Feature)系统,用于开启、关闭或改变一些MC的功能与属性,比如设置太阳偏斜(光影Mod的sunAngle)
  • 使用VAO渲染区块 (WOW!) 理论上讲90%+的显卡都支持ARB_vertex_array_object和APPLE_vertex_array_object中的一个,如果有哪个辣鸡卡两个都不支持,那它也不见得跑得动着色器.
  • 支持Mod式和外部文件式的光影包(其实只实现了前者)

当然,它还有一个不短的TODO List,由于没有写TODO List的习惯(??)这里先随手写上一些能想到的:

  • 纹理系统,比如光影Mod的载入法线和高光纹理,以及载入外部纹理文件,可以搞基于LUT的颜色校正啦.
  • 用户界面.
  • 外部文件式的光影包的解析.
  • 资源管理...
  • 条件允许时使用UBO更新一致变量.
  • 世界上有两群人需要人间会社程序员的人间关怀:乌干达的可怜儿童,和苹果机用户.
  • 还有一些疯狂的念头,不过都是要在前面这些完成的前提下才有...
  • 大量的,关于紫sama蓝sama幽幽sama觉sama恋sama秦心酱和玛艾露贝莉x莲子的福利

想说的暂时是这么多,如果成了的话喜大普奔,再一次弃坑了的话那就又是一次喜闻乐见的自挂城墙,我先睡觉(jue)去了...这里贴一个Mod式光影包的光影包初始化代码和渲染管线构建代码的一部分 (群众:有毛用啊!) 可以大概了解一下它的API风格,毕竟将来有了外部配置文件式的光影包后,基本上也会跟它差不多:

53057998_p0

不好意思,贴错了...



20161203071732 20161203065401

这个才对 √

阅读全文 [...]

实习结束,与对无人深空的黑枪

前几天实习结束了,开始的工作居然是做盗版书...(OCR把扫描版的字扣出来,然后手动纠错并录成Latex格式) 最后一周老板找我去谈♂话,说他去年培训的基本不会编程的本科生(??大叔你招的真的是本科生吗)出来可以月薪上万,劝我留下来继续跟他做学(jiao)♂习(yi),淦,你还想再让我做6个月盗版书?大叔见我一脸冷漠,还真让我先做一周PY交易再来考虑 - 他翻出了个以前的"实习生"做的产品,一个拿Python写的Wifi嗅探器,唔不过不是演示给我看,而是要我去调通了...它现在居然还运行不了,老板你的实习生这么忽悠你真的没问题吗...我看了下附带的文档,居然是写给评委求好评的,看来不是毕设就是什么竞赛的参赛作品,带着敬畏的心情我按照文档上的指示装好了依赖库,顺便纠正了源程序里几个硬编码的问题,看来他们是真没打算让这程序在除开发机以外的任何地方运行,然后启动了程序,等了半分钟没有任何响应后,正当我想问候Konqueror开发者们的家人时终于弹出了个VB风格的窗口,看上去还有模有样,然而我点了几个按钮却发现没有任何反应,就连主界面上的热点参数也是驴唇不对马嘴.我查了一下源码中的其他部分,结果感觉自己的智商收到了致命的打击...整个程序唯一有实际功能的部分只有启动系统时的热点嗅探,其他的部分只有个界面,包括主界面的那些参数都是事先填上去的...而且他们连子窗口的打开都没有做,只用PyQT画了个界面,看来是打算糊弄过文档中的截图部分就了事了,我把这个令人尴尬症发作的消息汇报给老板后老板仍不甘心,在联系了一通当事人后又找我说他们抗议说明明还制作了一个显示热点信息的功能 - 哦我知道,就是那个内容是事先填好的那东西?我又重新分析了一遍源码,发现有一段从未被调用的代码倒还真是跟将Wifi信息显示到主界面有关的代码,哎哎看来刚才冤枉人家了,不过为什么会从来没调用呢?在源码里搜了一夏发现调用被注释掉了,取消掉注释后程序再一运行,duang,挂掉了,原来那段代码是没写完的,就两个功能还有一个没写完,你们当时deadline可踩得够紧啊.当我把第二个噩耗汇报过去时他们已经把(前)老板拉黑了,可喜可贺的结局啊.老板也没再找我什么事,最后几天在班上干干私活肝肝舰(?)也就过去了,要说实习收获了什么,一个是学了Py交易--哦不,是学了Python和Latex,第二个是染上了舰瘾(!),第三个是干了些私活,就这三点微小的工作,谢谢.另外那大叔还想再留我干200天白工...谢谢您嘞,您还是专心去折磨下一波实习生去吧...

另外这几天无人深空终于发售了,这一个月无人深空的负面消息几乎接连不断,在我印象中似乎是在偷跑视频曝出之后才有的一波波的黑枪,排除故意抹黑和墙倒众人推的跟风黑以外,在发售后无人深空的表现确实是有些对不起当年的宣传,于是发售当天我就写了篇差评,顺便一提我看官方的Supoort提到游戏需要支持OpenGL4.5的显卡并且明确声明不支持I卡(什么东西需要OpenGL4.5并且永远与I卡无缘?对了,DSA) 于是我想看看一个用到了OpenGL包括DSA在内的全部先进特性的3A级游戏渲染是怎么写的,便挂上了调试器,然后...然后...我看到了这些,我就发个图,懂的秒转(开玩笑) 别忘了这不是OpenGL2.1时代的游戏,这是一个写明了要求OpenGL4.5(不是客服吹逼,着色器脚本中的#version可是写着要求450的(笑)) 把I卡全家直接打死在门外,写着AMD与狗不得入内的3A级游戏.

nmsr
Hello Games你他娘的找不出个会算线性代数的程序员就算了,用第三方的数学库也行啊,你TM glGetFloatv+glUniformMatrix4fv是个毛意思?是不是U3D出来后市场上连个会写引擎的程序员的招不到了? (U3D:妈的日常招黑)

另外附上我在Steam评测上的黑枪 🙂



开始我一直不明白为什么按E才能进入游戏.
后来当一架太空艇映入我的眼帘时,我才明白:
按E,方可赛艇!
-------------禁忌的分割线-------------
当初第一次看到无人深空的宣传片感觉内心受到了深深的震撼,这不正是我一直期盼的一款游戏吗?集沙盒游戏、过程生成、太空探险等大成者于一体的神作,于是我一直在关注这个游戏,只可惜官方放出的消息实在太少,很长一段时间官网唯一的内容只有封面上那个黑色八面体,但是每一部宣传片或宣传图都吊足了观众的胃口,从无人深空出现的那一天起,无限星辰一直在跳票,星际公民一直在揽钱,精英危险一直在放星球登陆的卫星,好像所有跟太空和星球沾边的游戏,都被无人深空的影子给盖上了.因此当它刚刚登陆Steam时我就将它加到了愿望单中,今年3月刚刚开放预订时我就砍了手.原定6月发售的游戏跳票到了8月,没有关系,我们都等了两年了,还在乎这两个月?
时间到了12号的晚上,小伙伴们盼星星盼月亮地等着0点游戏解锁,群里有人吐槽"这一刻我们都成了麦克雷",随着桌面上的时钟跳到00:00,我低吟一声"午时已到",重启了Steam,然而依然没有下载选项,跳到商店页一看,"将在大约less than one hour后解锁"?难道这游戏的13号解锁指的是UTC+7的13号?算了反正已经等了几年了,不在乎这一个小时.我看了一会Metacritic上主机玩家们对无人深空的评价,又刷了会知乎上对游戏的黑枪,1点很快就到了,我随手挂上4发大建,4个1:25:00,靠看来今天的运气现在就用光了,我重开了Steam,这一次终于有下载了.
我忘了游戏下载也需要时间这个问题,以现在3A级游戏的容量,几十个G的东西起码要下个一两天,这意味着这段时间我除了对群里那些百兆光纤的土豪羡慕嫉妒恨之外什么都做不了,在郁闷中我点开了下载,Steam告诉我需要下载2600MB的东西.
等等,2.6G?
我仔细数了一遍数字和单位,确定不是26G或2600G什么的东西.这游戏确实只有2.6G,你看隔壁塞欧弟和太太掉下来几十G的东西都只局限于地球或某个鸟不拉屎的外行星球上那么巴掌大的一小片空间,就连以沙盒著称的鸡踢诶5也不过是模拟了一个小岛就花了67G,之前听说塞欧弟:WIFI战争终于可以上太空了觉得因吹丝挺,现在看来无人深空用2.6G就能囊括整个宇宙,简直把它们完爆到妈都不认识了!我感动得泪流满面,我不知道什么不到1G的SpaceEngine,更不知道不到300MB的Pioneer,也绝不知道PS4版有6G,在这个靠塞无压缩的高清纹理、1080P CG影片和多国语音来恶意强撑游戏容量的时代,无人深空这个最后的良心为游戏圈带来一股清风,向世人宣告游戏大小不代表深度!8年前的这一天,在游民深空上有个孢子的无脑吹说"从此以后世界上不再需要别的游戏了,因为孢子就能模拟它们全部",正如那句老话"铁打的游民流水的喷子",那人就像千千万万游民喷子一样很快就消失在历史长河但中,但8年后的现在,我将继承他的衣钵,举起无脑吹的大旗,宣布"从此以后世界上不再需要别的游戏了,因为无人深空就吊打他们全部"!
家里水管虽小,但下2.6G的东西已足以,我上B站二周目了一遍秘封活动记录,然后用小号在下面评论区钓了个鱼"这破动画吃枣药丸",游戏已经下好了,上一次我感受到游戏在召唤我还是去年辐射4出的时候,这一次 - 我相信是最后一次,因为今后将不会有游戏再感动我了 - 我又感受到了无人深空的召唤!我来了!
进入游戏,启动果然很快,隔壁Minecraft点开游戏后先得点上支烟才能等到弹出启动界面,而无人深空几乎在我鼠标松开的那一刻就进入游戏了!制作商的Logo过后,是一片耀眼的群星,想必那一个个标签就是星系的名字吧,宇宙虽大,但与人类的野心相比仍太小,如果我不早点探几个星系,想必整个本星系群都要被中国人和挂机软件占满了吧,到时候看着满银河的"dajiba666"、"baoweinanhai"、"lajiyouxi"这样的国人风格星系名或"pan"、"1c0xr8Hy"、"h0yx"、"SSTM"这样Bot自动生成的星球名字,岂不恶心?群星很快就消失了,取而代之的是一个纯白的界面,上面只有一行字"初始化",嗯我明白,游戏总得预处理一些数据,我切出了游戏,群里管理说他卡在了初始化界面二十分钟了,我笑他那是什么破机器,然后我被口球了,我想切回游戏--
但却发现切不回去了.
我能理解这种情况,很多游戏在载入数据时都是没有响应的,因为就是有那么多半路出家的游戏程序员不知道要把资源加载放在另一个线程,或者在主线程中加一个单次资源加载的阈值超过这个阈值就要先去poll一下Windows事件再继续"少女祈祷中".然而此时我已经能听见游戏中BGM的声音了,载入时有BGM的游戏我还没见过几个,所以凭着多年游戏经验,我知道游戏不是卡在加载中,只是纯粹地切出去就不能切回来了,我打开了任务管理器,10分钟,这大概是我玩的Steam游戏中,最快的一个需要叫任务管理器来救场的游戏吧.(Update:我开始以为因为这是主机游戏所以开放商没考虑切出游戏的情况,后来小伙伴告诉我主机也能切出游戏的,啊露怯了← ← 姨夫:"这锅我不背!")
这一次我进入游戏后学乖了,我在"初始化"界面老老实实地等着,生怕它再一言不合死给我看,我打开手机收了刚才的建造,高雄、爱宕、中二病的摩耶和一脸想做的小婊子鸟海,靠真是一家人整整齐齐,我把她们全打包喂提尔比茨了.又收了一波远征后再看屏幕,依然是白的刺眼的屏幕和一行黑字"初始化",以及下面一个写着E的小圆圈,这游戏怎么载入这么久?我不敢切出游戏,只好在手机上Q去群里看看,我的口球还没下来,所以没法发问,只好慢慢翻他们聊天记录,我看到一个人提到进游戏要按E,我就笑了,劳资打了这么多年游戏日系美系欧系俄系甚至东南亚系游戏都玩过,见过按ESC按Z按回车按空格进游戏的,还真没听说过按E进游戏的,带着一脸的不屑,我随手按了一下E键,心里想着这要有用的话我直播口乞--
写着E的小圆圈多了一道黑边.
我哭了,这个"初始化"不是初始化游戏的,而是"初始化"我的智商的,我怎么就没想到那个E是要我按E键?姨夫的形象浮现在我眼前,手柄上的按键都是圆圈,你在屏幕上画个圆圈里面写个△那主机玩家的第一反应就是按下△,这是显而易见的.然而电脑上可不存在按键是圆的的键盘啊!幸亏这游戏不是按O进游戏,不然电脑玩家看着大圆中套一个小圆恐怕就是脑补出咪咪头也想不出是要按O进入游戏,我泪流满面地按住了E,进入了游戏,耳边传来电子音的女声,居然是中文,而且是标准的普通话,看来商店页那个"中文语音"不是制作商手滑写错了,这一点我要给一千个赞!我满怀激动的心情憧憬着我在异星球上的生活,脑中浮现出一幕幕宣传片中的世外桃源,然而映入我眼帘的是一个冰雪的世界,无垠的雪地与淡绿色的天空在远处混为一体,屈指可数的几株植物散落在脚下这片绿地上,HUD上-41°C、1.6Rad、15.4Tox提醒着我,这不是生机盎然的山中秘境,而是一个接近生命边缘的死亡世界,是的,探索、看画那是次要的,生存才是我们这些出生在恶劣地形的非洲玩家需要在意的事情.然而我就要在这个破星球上待一辈子吗?这时我看到了一架破损的太空船,于是便有了本文开头的那一幕.
遇险信标上一个红色的球球告诉我它叫阿特拉斯,它要指引我做完新手任务,我笑了,我见过的阿特拉斯是大西洋下的极乐城中勇敢地反抗莱恩暴政的自由战士,是穿越于寰宇之间充当着霸主帝国的经济主动脉的星际货轮,是站在内天体与氏族之间的第一道与最后一道防线的巨型机甲,而不是哪个面数低到可怜的几何球体,我点"对指引嗤之以鼻",然而什么也没发生,看来游戏的制作者没料到会有像我这样作死的,没制作这个对话分支,我决定虚心接受它的指导,点了"接受阿特拉斯的指引",然而却依然什么也没发生,WTF!?抱着试一试的态度,我长按鼠标,结果光标周围出现一个圆圈型的进度条,卧槽我第一次见到有哪个游戏是要长按鼠标来选择选项的!我TM这不是触屏啊!阿特拉斯在"接受了我的效忠"后就消失了,沃日!说好的新手任务呢!?
好在右下边有游戏提示,提醒着我要先修好飞船离开这个鬼地方,说起来我还没有给这个星球取名字呢,我调出了主菜单,选择为星系改名并上传,我做了一件我期盼了几个月的事,打开输入法,输入"huanxiangxiang"
然而什么都没有发生,这个游戏虽然支持中文,但并不支持输入法,于是我只好退而求其次,敲入了"Gensokyo",这也好,让老外也能看得懂,这个冰雪交加的死亡世界与大多数人眼中的幻想乡不太一样,不过倒也挺符合东方无限螺旋中3013年的那个永远处于核冬天的幻想乡.而至于星球的名字,我不知道幻想乡中那些地名的罗马音是什么,也不敢切出游戏去查;又觉得"Yuyuko my lover"有些太俗,于是我给它起了个很科幻的名字,"Gensokyo Prime",嗯,幻想乡主星,很好,肥肠科幻.
想修船没有资源怎么能行,我掏出了手中的多用途工具枪,抚摸着这个又硬、又粗、头还圆乎乎的东西,我找到了一块石头,HUD告诉我这个石头蕴含着丰富的铁,我假装自己是从2001年穿越来的,不懂它那自带打码的低清材质,将工具枪对准了那个石头,扣下了扳机,一束绿莹莹的光线喷涌而出,虽然这一幕我早已在偷跑视频中看了一遍遍了,但我还是为了配合这不可思议的景象,故意装出了吃惊的表情.从隔壁铁拳史蒂夫用手拍木头拍石头拍黑曜石,到屎大棒用物质操纵枪拆得多少土著家破人亡,修理地球变得原来越简单!老毛说过与天斗与地斗不如与人斗,那是因为他没见过这工具枪,如果他知道修理地球也是一件如此简单有趣的事情的话,估计他当年也就--
突然我的角色虎躯一震,拿着工具枪的那只手像触了电似的伸了回来,难道我被水表爆破了!?并不是,上一次我见到类似的一幕,还是我最后一次玩Halo:CE时在追杀对方的夺旗手,将他打到大概还剩最后一点血时突然像癫痫发作一样捧着冒着蓝焰的电浆步枪双手发颤;是我玩WH40K桌面战棋时一回合有6个勇敢的帝国卫队战士因为机械神教粗制滥造的电浆枪过热炸膛而殉国;是我玩机甲战士雇佣兵时打竞技场在最后一个对手只剩下最后一丝血时PPC一轮齐射没秒掉对方然后自己过热停机,硬生生被对面翻盘打死...没错,这就是科幻游戏中臭名昭著的过热!人类31世纪的智慧结晶,居然在零下41度的环境射一发就过热!?你TM属机甲战士的啊!?(这是BattleTech被黑的最惨的一次)
矿物采完了,该制作修复起落架的装甲了,然而游戏中却没有任何地方提示该如何合成物品,我在群里的口球已经被摘下来了,于是我赶快问了一下这个问题,一个小伙伴告诉我是把鼠标移到空格子上按E,卧槽原来E键如此重要!难怪游戏开头要玩家长按E键才能进入游戏啊.修好了起落架,然后需要去挖一个制作者脸滚键盘滚出来的名字的元素,扫描仪告诉我距离我最近的这种矿物走过去需要4分钟的路程,如果是在风和日丽的世界这一趟短途旅行并没有什么问题,然而我现在是在一个极地世界,防护服一边报告说维生系统只剩下一半的能量,一边报告说即将进入零下70度的极夜,群里管理员哭诉说他出生在了一个零上60多度的星球,我吐槽他可以当这是风之旅人来玩,然后又被口球了,靠,我这已经是冰雪奇缘了好不好.一路蹒跚到了矿区,路上还遇见了一个苹果垃圾桶,心想苹果真TM是土豪公司,到了未来已经可以满宇宙乱扔MacPro了,走过去一看居然是一个远古遗迹,遗迹教给我外星语中的一个词"給",不知道今后见到外星人后叫他们"给"会不会当我是基佬把我打死,等我挖完矿时我的防护服已经开始报警了,25%的能量肯定不够支撑我走回坠落点的,难道我的一周目就要这样结束了吗?我这才发现维生系统可以充能,而能量源可以是任何同位素,我瞅了眼旁边富含钚元素的植物,不禁感叹造物的神奇,也暗自吐槽难怪这个星球有那么高的本底辐射.
凌晨三点,我终于能离开这个终日被积雪覆盖的冰封星球了,飞船腾空而起,终于飞上天了!然而太空船在低空似乎没法随意调整高度,也没法将武器对准地面,看来我所属的文明的轨道轰炸政策是禁止,很好,这很圣母,我依依不舍地绕着坠落点绕了一个圈,望着地上已经几乎看不见的遇险信标,我相信总有一天我还是能再次遇见阿特拉斯,抬起机头,打开加力,脑补出的G力将我紧紧压在椅背上,我的耳边响起了无限螺旋中八云橙在离别时的赠言"愿你所在的世界的幻想乡,能够幸福常在",再见了!冰雪的幻想乡!在激动之余我随手按了一下E键.
飞船急停,极速下坠,在即将亲吻大地时又突然止住下降,缓缓地降落在茫茫的雪地上,舱门打开,将一脸懵逼的我再一次弹出到31世纪幻想乡永恒的极寒当中.
愿各位玩家的出生地,春色常在.
-------------禁忌的边境线-------------
总体来说,这游戏质量属于中等偏上,主要是当初画饼画得太大,卫星放得太高了,14年的采访当中制作人在聊到多人游戏时一脸灿烂的笑容,当时我们以为这是自信,现在我们明白这是骗煞笔时发自内心的欢乐.
那些舔无人深空的人,你们看看商店页上的宣传图,觉得脸被打的疼不?
啊当然,中文语音一定要支持!一定要点赞!

如果你也滋词这篇评测的话欢迎来点(jiao)♂赞(yi) http://steamcommunity.com/id/szszss/recommended/275850 当然如果拍砖点个差我也没法怎样的 ? 阅读全文 [...]

基本更新到1.9.4?

20160707023557
尼玛呀!本来心存侥幸觉得不会蹈覆那个推主的旧辙,结果扔柜子里没管过了三周一看卧槽居(guo)然弯了!而且弯的姿势跟推主的那只一模一样,支架根本就是摆设啊!下次再买波天宫的东西我直播食屌,popstick你妈飞了


咳,话归正题,现在教程的最主要的部分,基础篇的前三篇已经更新到了1.9.4了,如果你在我更新的时候正巧在读第三篇的话,恐怕难免会感觉像一夜间穿越了一样...这次更新也修正了一些之前的疏漏,我曾起过一个念头就是故意写一篇完全是按照错的讲的"教程"用来整蛊,现在发现在1.9时代(准确说已经到1.10时代了)留着一篇1.7时代的教程就已经够误导人了,再想到MCBBS上那篇ModLoader时代的教程...哎,为什么产生了一种自己站在时代发展的对立面的感觉  face5 阅读全文 [...]

挺尸预告?

真正的厨,即使知道是坑也要义无返顾地踩上去!
20160615225420
IMG_20160615_123941
以前早有耳闻这是东方圈中的邪神Saber(虽然我觉得最魔性的还是国内某东方杂志创刊号附赠的永远拼不上的黑白),实际到手后感觉造型不是那么糟糕,然而不得不说这款做工真糙啊,要不是知道这是冷门货不太可能有山寨,还真的会怀疑是祖国版...

上一篇文章是在4月份发的,熟悉我工作风格的都知道我喜欢闷声搞大♂新♂闻,事实上我也确实是在憋文章,然而这一次大新闻还没搞出来,电脑就(又一次)挂掉了(正所谓截稿前硬盘必亡之理,嗯哼?) 等到修好的时候已经到了期末各种deadline压上头的时候,因此一直拖到现在才有时间来填坑.

前几天在E3微软搞了个大新闻,宣布Minecraft会支持Mod...哦草,明明前不久我还在和别人讨论天国的官方API坟头草长得有多高,结果今天我就可以下岗了 ? 不过从目前已知的消息来看,应该是局限于移动版、主机版和用C++写的Win10商店版这几个版本,用Java的PC版不知道会不会有这个功能;Mod的形式大概是类似于脚本插件那样,从PC版越来越向数据驱动方向发展的趋势来看确实有可能(比如1.8到1.9最大的变化之一就是外置的掉落表).其实如果真的想要一个Moddable的高(C)性(P)能(P)的Minecraft的话...我个人认为MineTest远比Win10MC要靠谱.

然后就到了对PC版的黑枪,这一次Mojang出人意料地提早推出了1.10,按照流行的版本始终要比最新版要落后一版的定律(?),应该可以放心大胆地跟进到1.9.4了,前文已经提到1.9多了个外置的掉落表,除此之外还有双持武器、盾、滑翔翼等...这些除了双持武器以外没有什么功能性的更新,据说渲染系统有些新内容,不过对大多数开发者来说还用不上.虽然看着MCBBS Mod版上一堆1.7Mod真心疼,但我还是趁着这几天有时间更新一下教程吧("上学期"说要更新结果妥了一个学期也没弄 233),不然等过一段时间课设和实习堆上来更没时间弄了. 阅读全文 [...]

使用SIMD+CriticalNative在Java中加速矩阵运算

对于游戏开发来说,一个健壮高效的数学库是必不可少的,特别是对于3D游戏而言,动作系统在计算骨骼动画时会进行数量可观的矩阵乘法或求逆运算;渲染系统也需要频繁计算变换矩阵.虽然一次矩阵运算消耗的时间可能不多,但对于分秒必争的游戏渲染来说想要力争60fps、死守30fps底线就势必不能放过任何一个免费提升性能的机会. 阅读全文 [...]

关于新版ForgeGradle配置和FMM的一些事

半个月之前ForgeGradle更新了3月14号的2.1快照,FMM也自然抓取了更新,然而随之而来的是在Blog上一阵井喷式的Bug报告,而且出错原因都很奇怪,由于那段时间比较忙(lan)所以没怎么管,结果今天自己刷新工作环境的时候发现也中彩了,仔细调查下去没有发现出错根本原因但却发现了另一个问题:FMM上缓存了一个损坏的快照...FMM在抓取3月14号的FG2.1快照(2.1-20160314.023449-32)时下载不完整,显然用户从FMM上下载到的该版本是无法正常使用的,如果你遇到形似"ClassNotFound net.minecraftforge.gradle.common.BasePlugin"之类的,那么你就是遇到这个问题了,我已经手动更新了FMM上的缓存,但你可能仍需要手动删掉本地缓存,具体位置是在"C:\Users\[用户名]\.gradle\caches\modules-2\files-2.1\net.minecraftforge.gradle\ForgeGradle\2.1-SNAPSHOT"下的某一个文件中,损坏的文件是一个大小在11MB左右的.jar(正常的大小在13MB以上),如果懒得找的话可以把整个2.1-SNAPSHOT都删了,反正它下载起来也快...
而刚才本来想调查的错误,则是在配置时在getVersionJson阶段出现"xxx.json could not be parsed","FileNotFoundException: Inherited json file (null) not found"之类的,总而言之,就是在getVersionJson阶段出错的话,可能你需要使用一个较旧的FG版本,解决方法是把
classpath 'net.minecraftforge.gradle:ForgeGradle:2.1-SNAPSHOT'
改成
classpath 'net.minecraftforge.gradle:ForgeGradle:2.1-20160209.170057-21'
也就是手动指定一个老版本的FG2.1快照.另外,根据测试,这个问题只出现在Forge1.9之前(1.9没有发现这个问题).如果你遇到了这个问题的话,可以在留言中报告遇到的版本(Forge版本,使用的FG的版本,有无使用FGOW),我也会继续调查这个问题是Forge官方的锅还是FGOW的锅(毕竟这锅太大我扛不动啊 233)

最后还有一个问题,就是FMM准备要迁站的事,此前FMM一直架设在SAE上,其质量嘛...差强人意,不过这个月SAE增加了恶心的固定扣费内容,每天都会扣除58(10基础+48MySQL租费)云豆的基础费用,原本能用几年的免费额度如今只够几周了...再加上最近抓取失败的事,所以我准备把FMM迁到别处. 阅读全文 [...]