<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>易恺铭 / gameKnife</title><description>易恺铭 / gameKnife —— 图形与游戏引擎工程师。自研 C++20 + Vulkan 混合渲染引擎 gkNextEngine，写实时渲染、引擎架构与性能优化。</description><link>https://gameknife.github.io/</link><language>zh-CN</language><item><title>现代渲染能有多现代？</title><link>https://gameknife.github.io/tech/2025/10/28/how-modern-rendering-modern/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2025/10/28/how-modern-rendering-modern/</guid><description>去年5月，我开始了现代渲染学习之路。目前，作为一个一年半的现代渲染练习生，准备来讲讲这个议题： 作为一名古董级渲染工程师，我曾长期深耕于OpenGL ES领域。此次对现代渲染技术的学习，彻底刷新了我的认知。同时，…</description><pubDate>Tue, 28 Oct 2025 00:00:00 GMT</pubDate><content:encoded>![](../../assets/blog/ZRHAP_51UZF0rgpoRO_wH7204uMLiKGvJmpp8Qbz0Kw.webp &quot;gkNextEngine PathTracing渲染器&quot;)

去年5月，我开始了现代渲染学习之路。目前，作为一个一年半的现代渲染练习生，准备来讲讲这个议题：

&gt; 现代渲染能有多现代？

作为一名古董级渲染工程师，我曾长期深耕于OpenGL ES领域。此次对现代渲染技术的学习，彻底刷新了我的认知。同时，这些现代渲染方案已在Vulkan平台上通过真机测试，并能高效应用于现代移动设备。

Vulkan作为一项诞生于现代的最新API，其设计理念极致贴近硬件，同时也是目前唯一能实现全平台跨越的渲染API。Mac平台目前采用MoltenVK，未来将由一个更底层的kosmetkripse替代，以提供更接近原生的Vulkan支持。

为避免枯燥的平铺直叙，我将通过自问自答的形式，阐述我对现代渲染的当前理解。



## 1. 一个现代的shader是什么样的?

下面是shader codebase的bindless module的一段代码

```cpp
namespace Bindless
{
    public Sampler2D GetSampleTexture(int index )
    {
        return SampleTextureArray[NonUniformResourceIndex(index)].as&lt;Sampler2D&gt;();
    }

    public RWTexture2D&lt;T&gt; GetStorageTexture&lt;T : ITexelElement&gt;(int index )
    {
        return StorageTextureArray[NonUniformResourceIndex(index)].as&lt;RWTexture2D&lt;T&gt;&gt;();
    }

    public uint2 GetStorageTextureDimensions(int index)
    {
        uint2 dim;
        StorageTextureArray[NonUniformResourceIndex(index)].as&lt;RWTexture2D&lt;float4&gt;&gt;().GetDimensions(dim.x, dim.y);
        return dim;
    }
}
```

一年前的我，绝对不会相信，这是一段shader代码。而现在，每天写的就是这一类代码，非常现代



下面是硬件PT和软件ST渲染器的核心代码

```cpp
import Common;
import Bindless;
#include &quot;common/ShaderClock.slang&quot;

[shader(&quot;compute&quot;)]
[numthreads(8, 8, 1)]
void main(uint3 DTid : SV_DispatchThreadID)
{
    START_SHADERCLOCK()

    // compose raycaster, raytracer, etc
    FVisibilityBufferRayCasterV2 rayCaster;
    FHardwareRayTracerV2 tracer;
    FHardwareDirectIlluminatorV2 dIlluminator;

    // build renderer
    FPathTracingRendererV2 renderer;
    renderer.ExitProbability = 0.5f;
    renderer.ExitAfterFirst = Bindless.GetGpuscene().Camera-&gt;FastGather;
    renderer.HitNormalOffset = 0.001f;
    renderer.SampleDownscale = 1;
    renderer.Init(DTid.xy);

    if( !renderer.PrimaryHit(rayCaster) )
    {
        END_SHADERCLOCK(DTid.xy)
        return;
    }

    int sampleMultiplier = Bindless.GetStorageTexture&lt;uint&gt;(Bindless.RT_MOTIONMOMENT)[DTid.xy]
                           &gt; 0 ? 4 : 1;
    renderer.Render(tracer, dIlluminator, sampleMultiplier);
    END_SHADERCLOCK(DTid.xy)
}
```

```cpp
import Common;
import Bindless;
#include &quot;common/ShaderClock.slang&quot;

[shader(&quot;compute&quot;)]
[numthreads(8, 8, 1)]
void main(uint3 DTid : SV_DispatchThreadID)
{
    START_SHADERCLOCK()

    // compose raycaster, raytracer, etc
    FVisibilityBufferRayCasterV2 rayCaster;
    FSoftwareRayTracerV2 tracer;
    FSoftwareDirectIlluminatorV2 dIlluminator;

    // build renderer
    FPathTracingRendererV2 renderer;
    renderer.ExitProbability = 0.5f;
    renderer.ExitAfterFirst = true;
    renderer.HitNormalOffset = 0.1f;
    renderer.SampleDownscale = 2;
    renderer.Init(DTid.xy);

    if( !renderer.PrimaryHit(rayCaster) )
    {
        END_SHADERCLOCK(DTid.xy)
        return;
    }

    renderer.Render(tracer, dIlluminator, 1);
    END_SHADERCLOCK(DTid.xy)
}
```

与其说是着色器，其实更像一个渲染器。slang的语法，让封装，重构成为了一件非常简单的事情，一定程度上，对shader代码质量的提升也会很有帮助。



### Slangᴿ from nvidia

Slang，NVIDIA发起并集成于Vulkan SDK的项目，正是我曾构想的着色器语言，如今梦想成真。其高级语法、全平台编译（包括WebGPU）的特性，展现了极佳的普适性。附带的自动微分功能及工具链，也为渲染特征的预训练提供了新途径。

Slang支持泛型、成员函数、模块、命名空间等高级语言特性，让着色器代码库的编写体验，已然与C++开发无异。更令人称奇的是，我将一套GLSL着色器代码库转写为Slang后，性能竟有所提升。

拥有Slang，现代渲染开发无疑如虎添翼。



## 2. 现代的GPU资源管理是什么样的?

![](../../assets/blog/2oyM0ET6jyMW4x053vcgyn6pjTb2942fyhc36HloJYQ.webp &quot;通过bindless，实现可完全自由控制输出的visualizer，访问任意纹理资源&quot;)

个人理解，现代渲染，gpu资源管理是非常关键的一点。而管理gpu资源最重要的一点就是Bindless。最好做到零bind。

做到了零bind，让pipeline可以访问所有的东西。不管是写代码，还是做管线，都可以做到极高的自由度。

在我将管线完全换为零bind之后。有一天晚上，有一个解决残影问题的思路，我只花了几分钟的时间，就完成了修改，验证的几个循环，迅速的解决了问题，无需关心我需要将绑定的纹理换成哪些，需要加入和清理多少参数。而在没有实现零bind的时候，这种级别的改动应该至少是一晚上的开发时间。



做到零bind，主要需要以下几个vulkan feature: 

* Device Buffer Address
  * 这个feature，可以取得storagebuffer的gpu地址。将地址传入shader，便可以自由偏移，并以指针的方式，访问gpu资源
* Bindless
  * 对于storage texture和sampled texture，无法获取gpu地址。但可以将所有纹理&quot;bind&quot;进一个无穷大的，可以随时update的纹理数组。数组的索引，便可以作为纹理的gpu地址，实现零bind访问。
* PushConstant
  * pushConstant是vulkan的一个设施，在drawcall或者dispatch的时候，将小尺寸的数据通过command的方式传输给执行线程。我们把场景数据的gpu地址放在这个结构内，便可以实现完全的零bind。



这时，cpu和gpu的数据交互，被浓缩到了这样两个小结构中

```cpp
public struct ALIGN_16 GPUScene
{
    /* Scene Info */
    public UniformBufferObject* Camera;

    /* Scene Node Tree */
    public NodeProxy* Nodes;

    /* Global Vertice Buffer */
    public uint* Reorders;
    public half4* VerticesSimple;
    public GPUVertex* Vertices;
    public uint* Indices;

    /* Resources */
    public Material* Materials;
    public ModelData* Offsets;

    /* Others */
    public AmbientCube* Cubes;
    public VoxelData* Voxels;
    public PageIndex* Pages;
    public SphericalHarmonics* HDRSHs;
    public LightObject* Lights;

    /* IndirectDraw */
    public VkDrawIndexedIndirectCommand* IndirectDrawCommands;
    public GPUDrivenStat* GPUDrivenStats;
    
    /* TLAS */
    public uint64_t TLAS;

    public uint SwapChainIndex;
    public uint custom_data_0;
    public uint custom_data_1;
}

// bindless textures
[[vk::binding(0, 0)]] __DynamicResource SampleTextureArray[];
[[vk::binding(1, 0)]] __DynamicResource StorageTextureArray[];
```

以`gpuScene.IndirectCommand`为起点，我们就可以在gpu上渲染出整个场景。



## 3. 现代渲染是如何组织Draw的？

![](../../assets/blog/j13b7JlSGTjT4qLp8Qtnbia5WKoH7AykWdWb_uRA8OA.webp)

&gt; 一个疯狂的海洋球场景

* 8192个完全不同材质的海洋球
* 每个海洋球3000+tris，复杂遮挡
* 每个海洋球都经过物理模拟，随时运动
* SoftModern渲染器1000fps@1080p



现代渲染是通过gpu自驱动来完成drawcall的，而非传统的cpu驱动。零Bind的gpu资源管理，核心目的就是为了让gpu能够更完整的自驱运行，将cpu的干涉降到最低。



### &quot;传统&quot;gpu driven

比较可惜的是，在mesh shader/task shader出现之前，虽然有indirect draw，gpu的自驱运行尚做不到十分完美。

传统gpu driven的基本流程

1. 创建足量的indirectdraw
2. gpu cull，在ComputeShader中剔除的indirectDraw修改为空绘制
3. cpu发起足量的indirectdraw

这里会有一些问题，在桌面的gpu，空绘制的indirectdraw几乎没有开销，但在移动gpu上，这些空绘制也会造成较大的开销。



### &quot;现代&quot;gpu driven

vulkan新增了mesh shader / task shader，这解决了空绘制的问题。indirectdraw不再只能由发起。而可以通过task shader发起，这样，可以让gpu只发起需要绘制的indirectdraw，而不用填充大量空绘制。



### 模拟&quot;现代&quot;gpu driven

再次可惜的是，大量的移动gpu，不支持mesh shader。但有一篇文章提出了模拟方式，并指出amd的部分显卡的mesh shader就是软件模拟的。

实现上的思路其实比较简单，将所有drawcall的instance数据拼接进一个巨大的数组，通过单个drawcall调用



gkNextEngine目前还是使用的传统gpu driven的方式，由于meshshader的兼容性问题，可能后续会改进为模拟方式实现现代的gpu driven



## 4. 现代光照很复杂吗?

![](/media/blog/uxV5aL9hMgiwDEvgK4RFftYWdITdnSRVbuu1j0Ezcbw.gif &quot;gkNextEngine 4种渲染器的reference模式&quot;)



这个问题，我无法给出正确的回答。这一次现代渲染研究的初衷，就是想要正确的理解硬件光线追踪。而最本质的PathTracing算法其实是非常朴素的，我把它形容为，回归本源，忘掉trick。从相机出发，让光线“找到”光源。

当然，对比最初的实现，现在的codebase，已经复杂了很多。但，都不是类似于之前做渲染时的各种“巧妙”的trick。而是为了最大限度的加速上面说的这个最朴素的过程。

从某种程度上来说，**现代光照是很简单的**，他不再像之前那样要把直接光照，间接光照，一项一项的通过各种各样的手法，以不同的组合方式达到接近真实的渲染结果。而是一开始就有了最真实的渲染结果，只是很慢。通过各种各样的手法，在结果不怎么偏离的情况下，尽可能的加速这个过程。



### gkNextEngine中的加速方法

在gkNextEngine中，我设计了一个离散分布于场景中的AmbientCube探针系统。探针会通过多次射线追踪（GPU或CPU），获取其位置六个cube朝向的光照信息，并存储于一个类似体素的存储系统中。在PathTracing的过程中，光线按照概率提前退出，在退出的命中点采样附近的探针信息，插值出该位置的光照，作为其后续“光路”模拟，贡献于最终结果。通过控制提前退出的概率，我们便能控制PathTracing的平均追踪次数，从而平衡效率。这与NEE（Next Event Estimation）的思路有那么一点相似。

当然，仅凭此加速策略，在低于16个样本的情况下，我们依然无法得到一个可接受的结果。在离线渲染中，我们通常通过长时间、持续地累积样本并进行平均，来逐步消除噪声，最终获得一个平滑且高质量的图像输出。因此，在实时渲染的动态环境中，如果能尽可能地累积多帧样本，我们便有望逼近离线渲染的输出品质。举例来说，若一帧产生8个样本，成功累积16帧，我们便能获得相当于128个样本的渲染结果，这在视觉上通常已可接受。为此，我设计了一种特别的reprojection方案，它会根据objectid和normal，对diffuse、specular、albedo结果分别进行时间和空间上的样本累积，以实现高样本数的渲染。

最终，128个样本的渲染结果，即使在不接降噪器的情况下，也已是一个可接受的品质。



### hardware ray tracing

Vulkan通过扩展（extension）的方式，提供了硬件光线追踪的支持。这主要通过构建硬件加速结构（BVH），来实现高速的射线求交。

早期，Vulkan引入了一种“raytracing pipeline”的模式，通过编写多个阶段的着色器（shader），硬件会在射线的不同阶段进行调用，从而实现光线追踪。

后期，Vulkan又提供了“rayquery”的方式。这种方式允许在着色器的任何阶段发起射线查询，这也是gkNextEngine所选择的硬件设施，并且是目前移动设备上唯一支持的方案。基于Bindless的硬件资源管理，rayquery能够极其便捷地实现各种光线追踪算法，这无疑为开发带来了极大的灵活性。



### software ray tracing

在不支持光追的硬件上，比如基于moltenvk的mac笔记本，不能就此放弃现代渲染。回归到之前，因此我们就需要一种近似的软件实现的追踪算法。

当然，直接用computer shader实现一个bvh求交，也不是不可以，甚至tinybvh库已经给出了用于gpu使用的数据结构。也有很多人做了相关的实现。但感觉只是单纯炫技并没什么实际的用处，如果能快过硬件，那为什么显卡厂商不约而同的生产了那么多RT CORE？

因此，我基于实际工程出发，前面提到的用于提前退出的那个AmbientCube探针，是可以用来进行粗略的软件求交的。我的探针分布为0.25米一个，密度足以表达场景。

探针发出的射线命中mesh的背面，被视作距离为0，命中正面，则会记录距离。这样一个数据结构，便可以用来表示空间了。我们可以遍历一条射线上的所有探针，根据他的最大距离，来快速移动（比较类似SDF）。如果触碰到距离为0或1的探针，说明有了命中交点。



### hybrid context

gkNextEngine为移动端及桌面高帧率环境，实现了一种高效的混合渲染模式。该高性能渲染需求，正是AmbientCube探针系统的设计初衷。初期，其工作方式略似DDGI：AmbientCube探针通过硬件光追实时生成，所得结果经插值后作为场景光照。

后期，引入了tinybvh，将0.25米探针视为体素，支持软件射线追踪。数据被划分为voxel与AmbientCube两层。Voxel层可由CPU实时生成；AmbientCube计算则灵活地选择硬件或软件执行。

最终，AmbientCube探针数据作为混合上下文（hybrid context），在多种渲染模式中均扮演着关键角色：

1. **PathTracing：** 作为提前退出的后备缓存，提升渲染效率。
2. **SoftTracing：** 兼作提前退出的后备缓存与软件追踪的数据结构。
3. **SoftModern：** 提供漫反射光照来源及镜面反射遮挡数据结构。

通过此混合上下文，gkNextEngine的全局光照渲染展现出强大的伸缩性。



## 5. 时域渲染

这里想多讲几句，关于时域渲染。

从TAA出现的那一刻起，渲染的思路就完全改变了，通过重投影，将“样本”分摊到多帧，是一个绝佳的解决思路。并且配合现在的高刷显示器，样本的分摊，其实是可以做到“人眼补帧”的。

目前我的开发主要在120-240hz的显示器上进行，我尽量将渲染器的运行效率控制在120hz以上，我就会发现，截图下来的图像，和我肉眼看到的图像，质量已经有非常大的差距了。这就是“人眼补帧”。

在60hz的显示模式下，我就可以把样本数提升一倍，得到和120hz显示器下接近的肉眼图像质量，但

因此，gkNextEngine在后续的开发中，会继续坚持时域渲染这个特征，进一步发扬光大。包括渲染和逻辑的异步运行等，继续探索时域渲染的更多可行性。



## 瞄准未来

经过这半年的疯狂补课，我有一个清晰的感受：

&gt; 实时渲染的未来，游戏引擎的未来，一定有所颠覆

不光是光追这种精确而朴素的方法，gpu和cpu的解耦，通过LLM产生的AI革命，将会很快的影响游戏工业。

而尽管是目前使用最多，最为主流，最为先进的Unreal Engine和Unity Engine。也因为一路走来，有了太多太多的包袱。

说实话，我苦于各种引擎的材质爆炸已久。各种情形使得变体数量指数级的增长。面对硬件光追，也一定是传统开销叠加上硬件光追的开销。很难说，他们在面对现代渲染，面对未来，已经做好了准备。

因此，我决定把实验性质的gkNextRenderer变为gkNextEngine，整好也和我的gkEngine相呼应。

这次的gkNextEngine，我没有任何保留，我决定完全轻装上阵。当然它的目标：

&gt; Just for fun.

这比我的gkEngine的目标更为纯粹。



我给gkNextEngine定下了几条原则：

1. 永远使用最新技术，不考虑向前兼容
2. 拥抱强大的第三方库，绝不主动造轮子
3. 保持代码库的小体积，易读性



遵循这个规则，目前NextEngine除了图形层面的开发，还完成了诸多用于实际游戏的模块，并开发了一个类似MagicaVoxel的乐高搭建小游戏。拥抱强大的第三方库，也让我打开了新的世界。目前强依赖的第三方库：

* sdl: 完成操作系统抽象，窗口管理
* glm: 数学库
* tinybvh: CPU的加速结构
* quickjs: js脚本引擎
* lzav: 内存压缩
* miniaudio: 音频库
* tinygltf: 模型，场景数据交互库
* meshoptimizer: 模型处理，pvoking, simpify等
* joltphysics: 物理引擎
* ozzAnimation: 骨骼动画引擎
* spdlog: 日志系统
* stb: 纹理读写
* imgui: 界面

目前已有模块：

* NextEngine
* NextAnimation
* NextScene
* NextPhysics
* NextRenderer
* NextUtility



最近用SDL替换glfw，看到Sam Lantinga自1998年以来对SDL的持续投入，确实让人深感敬佩。他二十多年如一日地更新代码，甚至直到昨天还在为SDL3贡献力量，这种坚持不懈的精神，在快速迭代的软件行业中显得尤为珍贵。从最初的起点，到后来辗转几家公司，最终在Valve继续发光发热，并且许多Valve的软件和游戏都基于SDL，这本身就是一段传奇。

这样的历程，也让我对自己的项目——gkNextEngine——有了更深的期许。我真心希望它也能成为一个能够持续更新的开源项目，像SDL一样，不断演进，始终保持先进性。我希望这份热情和投入能够一直延续下去，直到我真正写不动代码的那一天。这不仅仅是一个技术项目的愿景，更是一种对创造和贡献的个人承诺。</content:encoded><category>tech</category></item><item><title>gkNextRenderer - YearOne</title><link>https://gameknife.github.io/tech/2025/05/16/gknextrenderer-yearone/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2025/05/16/gknextrenderer-yearone/</guid><description>去年五一节结束后，在RayTracingInOneWeekend项目的基础上，开始了gkNextRenderer的学习。一开始的目的很简单</description><pubDate>Fri, 16 May 2025 00:00:00 GMT</pubDate><content:encoded>去年五一节结束后，在RayTracingInOneWeekend项目的基础上，开始了gkNextRenderer的学习。一开始的目的很简单

* 学习现代渲染
* 学习硬件光追
* 大量的写一些自由代码

正好现在刚过了今年的五一节，是时候做一个正式的年度总结了。



这几天看到zeux大佬分享的一篇文章，他里面的一句话，感同深受：

&gt; my working level of rendering stopped around “late PS3-early PS4” level, notably excluding ray tracing, bindless, the exciting world of temporal jitter and “boiling soup of pixels” and other revolutionary advances since.

这简直是我的嘴替，我的渲染知识也是停留在了dx11时代，硬件光追，bindless，各种基于样本的渲染技术，通通的都错过了。这么多年主要是在搞移动平台的渲染和优化，以及各种系统的来回嫁接。在那之前，我还可以说我是一个图形程序员，现在我都不太听得懂其他人在说什么了...

内心的动力有了，就差一个契机，这个契机就是我买了一台支持硬件光追的M3Max笔记本，这台电脑算是我的现代渲染启蒙，我从苹果的官方例子开始，了解硬件光追的一些事情。后面，我回到了桌面平台，我了解到了这个基于vulkan的硬件光追项目，RayTracingInOneWeekend。并同时也又仔细的读了一遍原文。

作者的实现比较鲁邦，我又顺着NextWeekend, RestOfYourLife把后面的重要性采样，NEE等科目作了一遍。然后便开始思索，如何做实时的路径追踪。硬件来做，目标就是实时嘛。

当然，这方面其实也有很多人做得非常深入了，最新的UE5，也有一个完全基于硬件PathTracing的渲染器，并且有不俗的渲染质量，Nvidia的工程师很强。（不过这是后话了，当时刚涉足这个领域的我，并没有看到如此多的论文和信息，当然不知道那时如果看到了这些，会不会干脆就放弃了）

从实时路径追踪这个目标开始，慢慢的做了很多课题，就做到现在了。中间的过程，都是些比较繁琐的流水账，这里就按下不表了，写几个剖有感悟的点。作一个总结

## hardware-raytracing

硬件光追其实已经出现很多年了，从2060开始，可以说，现在steam上80%的机器都可以跑硬件光追了。一开始我是不太相信硬件光追能够成气候的，特别是rtx20时代。我的3080的显卡，感觉也就顺跑一个control，control效果是不错，不过他也是混合光追架构。2077的pathtracing渲染模式倒是非常惊艳，但是性能让我有点不敢相信。

直到开始自己实现硬件光追，我基本认定，未来应该是硬件光追的。甚至可以颠覆现在的渲染架构。

* 显卡厂商现在的着力点在这里，rtx20到50系，光追渲染性能的提升是光栅化性能的很多倍。
* 真正的照片集真实感，只能基于pathtracing。pixar和disney电影的这种渲染风格，也只能依靠pathtracing
* 真正的pathtracing已经被很多demo实现了（当然不是所有工况下）

因此，全面拥抱硬件光追，是一件必要的事情。RayTracing Gem上也有很多文章，并且有很多颠覆传统光栅化管线的做法。

## pathtracing

路径追踪其实才是最简单的渲染，RTIO的原始实现十分简单。根据材质的属性，向合适的方向，随机发射射线，遇到阻挡后，用diffuse来叠加，最终如果找到了光源（天，lightquad），用lightcolor叠加前面的diffuse结果，就得到了最终颜色。如果在n次内都没有找到光源，则此次追踪被抛弃。伪码写出来就这么几行

```glsl
vec3 org = getViewPos(camera)
vec3 dir = getViewRay(camera)
vec3 raycolor = vec3(1)

for i in n:
  rayContext ctx = traceScene(org, dir)
  if(ctx.m.type == TYPE_LIGHT)
    return rayColor * ctx.m.color;
  rayColor *= ctx.m.color;
  org = getHitPos(ctx);
  dir = getNextDir(ctx);  
```

最朴素的硬件路径追踪，shader代码量应该可以低于100行。

不过完整的pathtracing跑下来，实时性能还是比较难，因此目前出现了非常多的论文和talk，在讨论如何优化，如何做重要性采样，重要性重采样，xxxx采样。gkNextRenderer的PathTracing渲染器，使用了部分基础的优化策略，但更多的优化策略，还是需要从样本复用的角度来考虑。基于时空的样本复用还没有认真研究过蓄水池结构，而是自己在初期开发的reproject结构，基本够用。一定思路是类似蓄水池结构样本复用的，第二年的开发计划，应该要把蓄水池结构啃下来。

## visibilitybuffer

visibilitybuffer最早是由这篇论文提出的，解决的主要就是传统gbuffer在高overdraw的情况下，开销过大的问题。我是在做好了第一版pathtracing的情况下，开始看这个visibilitybuffer的。发现visibilitybuffer，刚好可以通过光栅化来完美替代primary ray。

visibility buffer有一些工程上的细节问题，一个问题就是现有api无法解决取得triangleId的问题（从硬件实现细节上看可能本就没有这个数据）。最暴力的做法把顶点全部裂开，每一个三角形都用独立的顶点，顶点上直接写上triangleId，这样在vs阶段，直接取顶点上的值输出即可。当然，公认的优化办法是使用provoking-vertex（激发顶点），在处理mesh的时候，对triangle进行精心排布，可以用较少的顶点开销达到同样的目的，zeux大神的mesh-optimizer库可以处理这个问题。

visibilitybuffer的传统用法，是用来解耦场景，把顶点信息和instance信息写在一个thin-gbuffer里，写入成本和读取成本都很低。然后后续使用一个个都compute shader，按照严格的顺序一次处理thin-gbuffer上的像素任务，最终完成shading。

## hybridtracing

路径追踪的最大问题，就是射线次数太多了，射线打得也太长了。消耗主要在求交。因此需要一种能够节省射线的渲染流程。visibilitybuffer可以节省掉第一次的primary-ray。second-ray可以使用硬件追踪，但可以减小距离，主要处理较近距离的遮蔽和反射效果。后续的ray，就可以考虑使用一些“cache”的数据了，最终基本能做到接近pathtracing的效果，但是每像素只有1-4次短距离tracing。

gkNextRenderer的hybridrenderer，在city场景可以做到2k@120fps

在slang统一了shader codebase之后，考虑对于不同的material type，可以选额在primary ray打到玻璃之后，走真正的pathtracing。

## tracing-gbuffer

这是一个反向的混合渲染，依赖一个primary-ray来替代传统的光栅化渲染，输出的结果是visibilitybuffer，后续的操作，即可走传统光栅化的后续流程了。

gkNextRenderer的reverse-hybird-renderer即是使用这个技术，整体渲染只有几个computeshader，传统光栅化的各种裁剪，drawcall优化的技术都不复存在了。

## vcpkg

vcpkg是c++的包管理器，由微软在github上开源维护。类似nodejs的npm。之前写c++，各种库的配置是最麻烦的一个过程，当然也有历史原因，那个时代，没有特别多的开源库，因此很多库只提供了各种编译器版本的lib和dll供使用。而现在这个时代，大量的开源库迸发，vcpkg的思路就是依赖开源库，cmake，在所有（尽量是）平台上自编译出静态库，使用作者写好的cmake为使用者提供编译支持，非常的方便。

gkNextRenderer使用了大量的vcpkg，当然，也有少部分的thirdparty代码（后续可以考虑帮这些库port一下），所以整个跨平台的编译开发都异常方便，基本所有功能都可以在我的多台设备上立即提供支持。

## cross-platform

gkEngine之前也支持跨平台，但是支持得很笨重。几乎可以算是在每个平台上实现了一个渲染器，部分核心代码做了跨平台编译。很多模块没有跨平台的支持。维护起来很复杂，渲染几乎就是完全不一样的，只有和场景接口的那一块保持了一致。

这次做gkNextRenderer，跨平台的需求放在了最前面，目前可以说是几乎100%跨平台。

* 选择vulkan，这是目前唯一一个可以实现完全跨平台的图形api。
* 第三方库都有意识的选择的能够跨平台的，尽量精简的实现。
* 利用github action，完成跨平台的ci，使得每一次PR，都有跨平台的编译检查，让每一次提交都是可以在所有平台上编译的。
* 周期性的全平台测试，我基本每周都会用我的mac笔记本，android手机，steamdeck跑一次阶段成品。以完成运行时的校验。

## bindless

bindless是现代渲染的一个重要组成部分，依赖现代gpu的address访问的机制。让渲染资源的访问，可以几乎自由的跑在gpu内部。bindless的出现，是我这次学习感受最为明显的。有了bindless，cpu的工作只是把gpu需要的资源，组织到一起，告诉gpu。光追时，gpu本就只能自由的根据hit的结果来访问需要的三角面，材质，纹理。而对于传统光栅化管线，也省去了之前逐个drawcall的多层级的寄存器绑定。只需要给一堆地址，gpu可以利用instanceid和triangleid，直接计算出顶点，材质，纹理的取用地址，然后在对应的资源上取值运算。

之前通过renderdoc，观察到诸如R星的荒野大镖客，Decima的地平线系列，他们的shader部分异常精简，基本都是使用了bindless，去掉了繁琐的传统渲染结构，实现高复杂度的场景。

## tinybvh

tinybvh是jacob的一个开源库，纯头文件实现的cpu/gpu加速的快速bvh查询。在cpu上可以做到惊人的射线检查速度，并且可以快速更新bvh结构。引入这个库的开端，是在做MagicaLego的时候，需要一个射线检测。因为有gpu的加速结构，所以之前的做法，都是从gpu用computershader做射线检测，再读回cpu完成的。而对于没有硬件光追的设备，就没办法了。恰好，以为好友正好聊到了这个库，碰巧他还实现了这个库的neon加速。我就拉下来用了，header only，非常轻量级。结构设计和gpu的加速结构也完全是吻合的，甚至后续版本可以直接构建gpu用的加速结构了。

再构建gpu加速结构的时候，同时也构建一份cpu的，这样两边都可以做射线检查这些工作了。射线检查也不需要等待硬件回读了。tinybvh的性能非常强，单帧10k级别检查次数是完全可以跑上120fps的。

因此，后续基于tinybvh，还完成了和gpu共享代码的probe generation算法。在场景内发射射线来生成ambient cube数据，用在后续渲染。完成了一版基于射线检查的shadowmap，可以异步的在cpu更新shadowmap，用于后续渲染。

## imgui

imgui之前一直被我不齿，可能是因为unity时代留下的后遗症。这次从RTIO库继承下来的imgui库，发现还是挺好用的，后来又深入研究了一些开源项目使用imgui开发编辑器的做法，用极少量的代码，实现了gkNextRenderer的编辑器架构，眨眼一看还十分像UE5。包含了outliner，content browser，propety editor等基础结构面板，还实现了一个node based的材质编辑面板。当然，editor的代码还很原始，投入的时间很不足，第二年的计划，可以在工具上对编辑器提一些要求，促进编辑器的发展。

## slang

slang是nvidia提出的一种大一统的shader语言，一开始，我只是想把glsl转换为hlsl，用dxc编译到spirv。后来发现某些语言特性依然不是很强，遂看到最新的vulkansdk已经自带slang的编译器了，原来，khronos已经把slang变为firstclass的shader语言了。于是又把转好的hlsl，用slangc编译了一遍，一次成功，基本一行不需要改。

slang不只是完全兼容hlsl那么简单，丰富的泛型支持，module支持，interface，非常多的语言特性。用了差不多一天时间，就把原来glsl上各种重复代码，难以维护的全局变量，传参逻辑等，优化成了和c++十分相似的codebase了。slang还有一个自动微分的基础设施，配合slangpy的训练，可以做到诸如神经网络纹理压缩之类的十分高档的功能。第二年的计划，可以考虑一个小case，把这个设施用起来。

## quickjs

quickjs是bellard的一个比较近的作品，作者是最为伟大的程序员之一。quickjs的实现十分优雅，对ecma标准的支持，几乎和v8是一致的。但是他的代码量极低，运行时的内存开销也极低。

gkNextRenderer最初引入脚本，是希望做一些灵活的交互动态的控制。当然，当时项目也在优化v8的js性能，对脚本语言也很感兴趣。就开始考察v8的接入，但发现v8太臃肿了，这可能也是各种chromium架构的app如此臃肿的原因，比如electron等。v8的尺寸，比目前整个gkNextRenderer还要大几倍，这让我感觉无法接受（幸好gkNextRenderer还处于初期阶段，如果成长为一个中型引擎，可能就接受了）。于是就看到了quickjs，quickjs的设计很优雅，小体积的优势其实非常大。而性能比v8弱的问题，在我看来其实也不算是问题，v8的性能在我们项目内，也发现很多问题。如果用quickjs，他天生“性能较弱”的特性，反而可以在写代码的时候，提醒我们要考虑性能，自然而然的只把一些事务性和流程性的工作写在脚本内，将复杂逻辑留在native语言上。

不过接入了demo后，gkNextRenderer的脚本引擎其实没怎么用起来。现在想来，应该是两个原因：

* 卡在了nodejs编译typescript这一步，因为我希望的脚本语言是typescript，如果选用javascript，其实是可以不需要nodejs的。而引用nodejs，在开发环境的自动部署上，当时遇到了一些问题。就导致后来没有自动部署脚本，也就没有接着往下发展写更多的脚本了。
* 脚本的框架，和gameplay框架相关。而我一直没有定下来一个想要发展的新demo的gameplay。当然，其实可以先搁置这些设计，把magicalego的逻辑转换为typescript，这个是第二年计划可以做的事情

第二年的计划，做一个更丰富gameplay的demo应该是必须的。gameplay的类型目前还在考察，但是目标应该是一个地形巨大，并且可以拥有很多实体的世界，以极限测试引擎的承载能力。

## moltenvk

这是一个在macos上跑vulkan的项目，目前除了硬件光追的支持缺失，其他都非常正常。一开始，我其实发愁选择vulkan可能在macos/ios平台上就不能用了，但moltenvk的实现还是很不错的，我还打通的抓帧，可以用xcode的gputrace来观察实际的gpu运行情况。目前就是硬件光追的缺失，但metal的硬件光追，实现上是完全和rayquery对齐的。并且spirv转metal的转义都已经被完成了。只是api层面还需要moltenvk再努把力。相信假以时日，moltenvk是可以平顺的支持vulkan的所有特性的。

## android

安卓用vulkan做现代渲染，兼容性超出我的想象。一开始，也是看到我的骁龙8gen2手机可以支持硬件光追，并且是vulkan的ray query。所以下的大力气做了安卓平台的兼容编译。这一块还是做了一些“跨平台”的支持，主要是支持android更为特殊的显示框架，而不能用glfw了（glfw其实也有人做android的port，但android版本更新太快了，还是用原生方式容易一些）。看下来，8gen2的光追性能，基本上和steamdeck的那2cu是持平的，也不是不能用... 然后，超出我想象的是，我的骁龙865，居然可以完整支持目前除了硬件光追之外的所有特性。bindless，transfer queue都是支持的。并且效率也很强。bindless的兼容性也超出我的想象了。不过目前还是可以继续以8gen2为平台，同步渲染器的开发。

第二年的计划，android平台倒是没什么特别的期待，能够同步目前的渲染器开发，并且解决掉一些特殊的性能问题即可。

## github copilot

今年AI的进步速度让我非常惊讶，目前，gkNextRenderer里已经有大量代码是和AI协作一起写的了，今年sonnet claude 3.7模型，写c++, glsl, hlsl这些，水平都有很大提升。并且一些我似是而非，没有细究过的知识点，提给他，他甚至能直接写出可用代码，就好似我囫囵吞枣学过一遍的程度。这个时候再辅以调试和进一步的资料阅读，能够极大的提升在未知领域上的进步水平。Ambient Cube Probe这个系统，就有很多这种未知领域的知识细节，是在有基础shader代码的基础上，和copilot协作书写，调试，翻阅资料这个过程中，熟悉起来的。

我越来越相信，通用人工智能，是可能在目前的技术基础上出现的了。今年deepseek的梁文峰的一句话，让我醍醐灌顶：

&gt; 我们理解人类智能的本质就是语言，人的思维就是一个语言的过程。你以为你在思考，其实可能是你在脑子里编织语言。这意味着，在语言大模型上可能诞生出类人的人工智能（AGI）。

因此，接下来的开发工作，我会毫无保留的加入AI辅助，越来越多的借助AI的力量，已经学会更好的使用AI。

## gkNextEngine

目前github上的tag还是A Realtime PathTracer。不过后续的开发目标，可能会把他变成gkNextEngine，我已经有一个gkEngine了，那是我成长的一个见证。当然，前几天回看之前博客园的文章，还是有不少批评，认为和CryEngine太像了，抄袭CryEngine。应该说当时CryEngine就是我的学习目标，而当时略显稚嫩的我，当然是以模仿目标为第一要义，但是所有技术点，都是一个一个实现调试出来的。每一行代码，也是自己一行一行敲出来的。

多年之后，重新开始NextEngine，也算是对初心的一个回应。这一次的我，已是一个征战15年的老兵了，对各大商业引擎也有了更加深刻的认识，这一次我选择从头开始，不刻意的模仿和树立一个目标。尽可能不重复造轮子，以尽量优雅的方式，实现gkNextEngine。

gkNextEngine的目标依然是从自我成长和学习出发，当然，也更多的关注和其他人共同的成长，我选择整个开发过程的开源，并且全平台支持。作为一个长期项目，希望能像Spartan Engine那些前辈一样，持续的以年为单位来贡献与分享。

目前gkNextEngine已经集成了不少游戏引擎相关的模块，底层渲染，操作系统基础设施，gltf的完整支持，物理系统，脚本系统，声音系统，视频编码等等。未来的一年，希望能提出一个更加全面的gameplay demo，来驱动基础设施的进一步开发，并且把现有的机能整合起来。

回想起2013年那一段，3个人一起写一个新引擎的过程。大师哥一己之力搞定了除了渲染和多平台适配之外的所有，并且在一年后还开了一场发布会。的确是鬼斧神工，石破天惊。可惜目前已经物是人非，当时的开发流程，只能隐藏在历史之中了。

希望在2035年，回想起2025年的开始的gkNextEngine，在感慨之余，没有遗憾。</content:encoded><category>tech</category></item><item><title>UE4&apos;s iOS Cross Compile</title><link>https://gameknife.github.io/tech/2019/09/29/ue4-ios-cross-compile/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2019/09/29/ue4-ios-cross-compile/</guid><description>安卓的真机版本搞定了，比预计快了很多，因此，准备把iOS的真机版本也提上日程。 由于已经身经百战，之前好歹也出过2-3个iOS真机包了，这次准备直接在构建机的虚拟macOS系统上开搞</description><pubDate>Sun, 29 Sep 2019 00:00:00 GMT</pubDate><content:encoded>&lt;br&gt;
&lt;br&gt;

### 编译地狱

安卓的真机版本搞定了，比预计快了很多，因此，准备把iOS的真机版本也提上日程。

由于已经身经百战，之前好歹也出过2-3个iOS真机包了，这次准备直接在构建机的虚拟macOS系统上开搞

git pull 源码，编译源码，编译编辑器，编译game，cook，package，archive，至少第一个可以上手机的ipa包，肯定是一气呵成~

没想到，第三步就翻车了...

编辑器，编译报一大堆错，嗯，也正常，我们的代码各种msvc风格，改就是了~ 可是，什么骚操作，居然有 XXXProj 和 XXXProjEditor的循环依赖，

而且，还不太好改，windows因为dll的关系，editor下这种循环依赖是可以运行的。

本来，是准备静下心来，认真梳理关系，整理一下代码，让mac编译通过。然而，一来是时间有限，二来，一直看ue4有iOS的远程编译，是不是可以试试呢？

&lt;br&gt;

### 远程编译

找了篇文章，介绍iOS的Remote Build，原理其实挺简单的，就是通过同一个网络下的ssh，windows ssh到mac系统，
将构建用的依赖文件发送过去，并在远程mac系统编译，再把编译结果发送回来。

这样依赖，mac机完全作为一种“资源”，只需要安装有xcode，具备build能力即可，
而不需要ue的超大源码，也不需要编译出ue，也不需要编译出项目的editor，不需要cook，
只具备构建app和打包app的能力即可。

这对于已经在Windows系统上统一了PC和Android构建的我们来说，应该是一个很大的福音，我们可以统一整个构建环境在windows上，外挂一个</content:encoded><category>tech</category></item><item><title>UE4 &amp; Gradle &amp; NDK-Build &amp; Clang &amp; std14 &amp; LLVM</title><link>https://gameknife.github.io/tech/2019/09/20/ue4-with-gradle/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2019/09/20/ue4-with-gradle/</guid><description>来新项目1个月整了 动态寻路和一些功能开发暂告一个段落了，最近一周开始搞ue4与上android真机的事情。 不再是之前玩具demo的玩法，涉及到很多第三方组件的整合与一个庞大的native库的迁移</description><pubDate>Fri, 20 Sep 2019 00:00:00 GMT</pubDate><content:encoded>&lt;br&gt;
&lt;br&gt;
### 缘分

来新项目1个月整了

动态寻路和一些功能开发暂告一个段落了，最近一周开始搞ue4与上android真机的事情。

不再是之前玩具demo的玩法，涉及到很多第三方组件的整合与一个庞大的native库的迁移

不管是主动还是被迫的，把上面一堆的，陌生的技术名字，都摸了一遍。总算，基本搞明白了Android最新的这套工具链的用法，以及与UE4的整合

&lt;br&gt;
### UE4 APL &amp; UPL

Epic真的喜欢造Language，Unreal Plugin Language，怎么说呢，在接入第三方库的时候，还是挺好用的，详细如下

&lt;br&gt;
### Gradle Toolchain

什么是Gradle，他一般来说，和ANT对应，是一种构建系统。在UE4里面，作为Archive和Deploy阶段才出手的东西，详细如下

&lt;br&gt;
### Clang &amp; NDK14+

上一次实际使用ndk，记得他的版本好像还刚到10，r10e，这次接触，已经是16和20版本了，工具链也变成了纯clang了，折腾了挺久，详细如下

&lt;br&gt;
### std14

由于前几年都没怎么好好写native代码，最近也是就着ue4提供方式在使用新的c++标准，这次因为使用clang来编译现在用msvc开发的代码，算是把c++标准重新理了一遍，详细：

&lt;br&gt;
### LLVM

各种工具链，都没绕开LLVM，现在windows平台编译也支持Clang了，就顺手看了几篇LLVM的历史与发展，总算真正对其开始了了解。也明白了，为什么LLVM还跟shader有关系，嗯，关系不小。</content:encoded><category>tech</category></item><item><title>寻路算法-重新启发</title><link>https://gameknife.github.io/tech/2019/08/25/path-finding-review/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2019/08/25/path-finding-review/</guid><description>在新地方, 第一个需要处理的任务就是寻路. 说起来, 跟我做的独立项目有一些类似, 场景都是动态的. 需要动态的碰撞检测, 动态的寻路.</description><pubDate>Sun, 25 Aug 2019 00:00:00 GMT</pubDate><content:encoded>&lt;br&gt;
&lt;br&gt;
### 引子

在新地方, 第一个需要处理的任务就是寻路. 说起来, 跟我做的独立项目有一些类似, 场景都是动态的. 需要动态的碰撞检测, 动态的寻路.
而新项目的频率更高, 会有高频的场景变化, 并且路线会有不断的变化可能, 例如:

* 移动的可行走的平台
* 断桥
* 地洞

等等, 那么传统的navmesh, 或者说那种dynamic navmesh, 也基本上效率跟不上了.
因此, 就可能需要重新审视寻路系统, 开发适合项目需求的一套性能和效果得以平衡的寻路系统了.

&lt;br&gt;
&lt;br&gt;
### 寻路算法-审视

#### Dijkstra算法

这是一种一定正确的算法, 他会遍历整个区域的行走节点, 从而找到一条真正的最短路径. 然而弱点也显而易见, 效率过低, 基本不可用.

#### Greedy Best-First Search算法

俗称贪心算法, 从起点到终点, 每次都从临近的可行走节点, 选择一个当前的&quot;最佳&quot;解, 一般是欧式空间里, 距离终点最近的哪那一个节点. 逐步往下, 不走回头路.

贪心算法一定能找到一条可行路线, 但一般不是最优的. 优点在于复杂度很低, 效率高. 弱点, 当然就是很难找到最优解, 路径看起来非常&quot;短视&quot;.

#### A*算法

A*算法是对Dijkstra算法的一种优化, 他引入了一种启发式搜索, 核心是一个cost函数

f(n)=g(n)+h(n)

g是路径的实际消耗, 而h是剩余距离的预估消耗, 预估消耗一般使用欧式距离, 以保证搜索是收敛的.

因此,A*搜索在Dijkstra的基础上, 加入的一个h(n), 使得搜索可以往&quot;更为可行&quot;的方向进行,而放弃那些不太有可能性的方向,此为&quot;启发&quot;的含义

可以看到

* 如果将cost函数变为f(n) = g(n), 那A*算法便退化为Dijskstra算法, 为一个全量搜索
* 如果将cost函数变为f(n) = h(n), 那A*算法便退化为贪心算法, 准确性降低

A*可以说是一种较为准确,又较为快速的寻路算法

&lt;br&gt;
&lt;br&gt;
### 寻路算法-应用回顾

#### 道路生成时的应用

##### cost函数

在之前开发过程生成地形时, 其实就从头写过一个类似A*算法的启发式路径搜索算法. 核心的cost函数依然为:

f(n) = g(n) + h(n)

h这个启发函数依然是使用的欧式距离.
而改造了g函数, 一般的g函数就是实际的&quot;行走&quot;距离.

g的cost来自于几个方面:

1. 坡度消耗, 坡度越陡峭, 消耗越高, 因此最后的路径会倾向于选择缓坡, 形成盘山公路样式的路径
2. 水域消耗, 越接近水域, 消耗越高, 最终的路径会尽量离水域较远
3. 曲率消耗, 行走时会持续计算最近三个路径点的曲率, 曲率越高, 消耗越高, 最终路径会尽量选择较为平直的路径
4. 阻挡消耗, 水,峭壁是阻挡体, 消耗正无穷, 此路不通

##### 寻路网格

在3d空间的寻路和2d空间不同, 因此, 是将地形按照一定的grid, 采样出一个寻路的2d node空间. 
然后, 在实际需要生成的时候, 从node空间中, 采样出以起始,终点的中心为原点,
起始,终点长度为半径的一个圆形的子空间. 所有的路径最终在这个空间中进行.

##### 效率

效率直接和g(n)函数的设计有关, 当g(n)消耗很平均的时候, 搜索次数会十分高, 因为路径会展开得非常快. 同时, 采样和g(n) h(n)函数的运算速度也息息相关. 
这方面都有较大提升空间, 而因为当时的道路生成的时效性要求不高, 所以我是将实现进行分帧, 相当于在背景运行, 可以慢慢的完成最终的道路生成

&lt;br&gt;
&lt;br&gt;
### 现在的思路

navMesh本身是一种在静态空间内十分高效的一种寻路解决方案, 基于体素的导航面生成, 也能非常好的处理在3D空间内的寻路. 这是2d寻路算法无法比拟的.
然而在动态空间中, 他本身的生成消耗, 可能大于了其对寻路优化的贡献.

因此, 可能会考虑从基础的A*算法上进行优化与扩展, 以在动态环境中提供既可以高效更新导航数据, 又能快速寻路的算法.

#### 导航数据

##### 思路

在环境初始化, 改变时, 对修改区域的环境进行重采样, 分布到一个2d node空间
对node进行连通性梳理, 传统的2d grid, 联通性就是4或8方向. 而为了能够达到各方向行走, 我们可以在2层-3层空间内进行联通

##### 问题

对于多层的可行走区域, 暂时没有很好的想法, 目前可以考虑将空间分为多层2d空间, 在部分地方, 进行跨空间的联通

#### 寻路

##### 基础

h(n), 考虑为欧式距离

基础的g(n), 可以就考虑为实际的行走欧式距离

这样的算法足够简单,也足够收敛.
并且,在构建联通性时,可以直接将每一个step的g(n)缓存在节点上,更加高效

##### 进化

g(n), 可类似之前道路生成算法, 加入一些游戏性相关的cost计算, 让路径避开一些&quot;不好&quot;的区域

##### 优化

在进行搜索时,可以考虑加速的的容器结构
ARA* , D* , B* 等 A* 的变种算法考虑</content:encoded><category>tech</category></item><item><title>过程生成地形-概要</title><link>https://gameknife.github.io/tech/2019/07/30/a-preface-of-spg/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2019/07/30/a-preface-of-spg/</guid><description>按照之前的flag，近日得空，对今年做过程生成的技术点作了一个归纳和梳理，拟一个提纲。 下面一个大体结构规划 提纲拟出来，发现这是一个庞大的课题。虽然自己生写出了一套过程生成地形的算法，但要深究细节，还是有诸多不清楚。因此每一个点，…</description><pubDate>Tue, 30 Jul 2019 00:00:00 GMT</pubDate><content:encoded>### 过程生成地形 - 规划
---

按照之前的flag，近日得空，对今年做过程生成的技术点作了一个归纳和梳理，拟一个提纲。

下面一个大体结构规划

![placeholder](http://assets.processon.com/chart_image/5d3e4e6ee4b043dcf848c387.png)

提纲拟出来，发现这是一个庞大的课题。虽然自己生写出了一套过程生成地形的算法，但要深究细节，还是有诸多不清楚。因此每一个点，要向外人讲出来，
都需要再做更多都功课
&lt;br&gt;
纸上得来终觉浅，因此，在展开每一个课题都同时，可能，也需要匹配上一定都代码和工程。
所以，干脆，我着手重头来写一遍过程生成，把重新做功课和实现结合起来，同时也方便读者直接上手。

&lt;br&gt;

因此，顺便就构思出了这个课题的全貌：

* 过程生成地形的技术分享会是一个较大的项目，会拆分为多个章节。
* 每个章节，可能会有若干篇文章，这些文章背后，有最新鲜的实现代码。
* 通过章节顺序的调整，基本可以保证实现和知识点可以循序渐进，娓娓道来。

&lt;br&gt;

其他细节，在过程中调整。整个课题，我预计耗时半年到一年。先在我的个人博客公开。

&lt;br&gt;

下一步，就是规划中的第一步 - 总体流程了。会利用现有的实现，大致介绍整个生成流程，以及途中会遇到的一些技术点。也作为整个课题的第一炮，开一个漂亮的头。</content:encoded><category>tech</category></item><item><title>对三年来没有blog post的一个总结</title><link>https://gameknife.github.io/life/2019/07/21/refresh/</link><guid isPermaLink="true">https://gameknife.github.io/life/2019/07/21/refresh/</guid><description>最近到gameknife.github.io更新一些东西，发现上一篇blog，距离现在已经3年了。 最近有一些想法，想要书写下来，才想起来blog这个“古老”对载体。</description><pubDate>Sun, 21 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;br&gt;

最近到gameknife.github.io更新一些东西，发现上一篇blog，距离现在已经3年了。

&lt;br&gt;

最近有一些想法，想要书写下来，才想起来blog这个“古老”对载体。
这几年来，“知乎”迅猛发展，逐渐替代来之前中文社区中对csdn，博客园等站点，原来经常关注的一些个人技术博客也停止更新。倒是一件好事。
因此，我后续计划中的一些技术类blog更新，也会同步到“知乎”上去，不过先还是会在这里把思路理清。

&lt;br&gt;

可能一些旧友会问，这三年，gameKnife干什么去了？gkEngine停止更新了，然后断断续续在SoftRenderer和ShaderMonki项目有少量维护。
几乎在网上销声匿迹。
&lt;br&gt;
嗯，这三年，是我在腾讯天美工作室工作的三年。基本上，9-10-6，撑过了这三年。
&lt;br&gt;
当然，也经手了好几个上线的游戏，且还有一些名气，只是游戏最后，不会有staff
&lt;br&gt;
要说成长，成长了许多，不管从技术，生活，还是说话，职业度上面。

&lt;br&gt;

三年前，自己写下那篇满腔热血的文章，那是来天美工作室前的那件事。因为“办公室政治”，放弃了自己的GreatGame，来追求家庭的平衡和纯粹的技术追求。

&lt;br&gt;

三年之后，可以说，家庭的平衡，我慢慢争取到了，不管是薪酬回报，还是回到老婆身边，也有了可爱的儿子。
&lt;br&gt;
而纯粹的技术追求，只能说，我尝试了，有一些小的成就，更多的，是无奈。

&lt;br&gt;

三年间，职级升到了“毕业等”。技术委员会评委，项目leader，对我对技术也算是“十分认可”。
&lt;br&gt;
但，我却越来越感到慌张。
&lt;br&gt;
在项目中，不知不觉的，开始越来越被要求“追求”一些“虚”的东西。
&lt;br&gt;
XXX系统，YYY解决方案，ZZZ工具流
&lt;br&gt;
这是我最近一年多做得最多的东西，然而，他们并没有什么卵用。一个系统还没做完，下一个系统已经排上日程，后天就等着组合成一个解决方案。我从前不敢相信，
一个系统的开发时间，竟然只有一天。
&lt;br&gt;
很多的开发任务，在你对其完全没有概念的时候，需要你评估出开发时间，然后将这些开发任务在时间线上“排满”。
&lt;br&gt;
自然而然，你的开发任务，不能delay，也不能提前。你评估出的时间内，要么，“顽强的”做完，要么，“等待它”做完。
&lt;br&gt;
近两年来，在同一个项目组的同事间，完全没有技术交流与分享。或者说，仅限于晨会与周会，工作汇报类型的交流与分享。
&lt;br&gt;
所有人，包括我自己，9-10-6，一天不留。

&lt;br&gt;

2019年春节后，我迎来了我可爱的儿子，也迎来了公司破天荒的work-life-balance作息时间。似乎，之前的矛盾要得以解决了。儿子到来之后，诸事顺利。

* 春节前后，我和几个同事从老项目抽调出来，开始新项目的预研，从头开始写过程生成算法，用低开销来制作开发大世界。
* 在996.ICU闹得最火热的那段时间，我享受着865.WLB的生活，每天到家之后，可以在家人旁边，继续钻研一些有价值的技术点。
* 终于从相处了2年多的Unity引擎抽出身来，接触UnrealEngine，一改之前对UE的态度，拥抱开源的Epic，造出了一个伟大的Tools
* 开始使用“激励式”开发，来自几个合作者相互的激励，工作任务变得动态，做完一个就赶紧搞下一个，并且可以来回的优化和提升

然而也就过了一个月，一切又回到了一开始。

* 回到了更加变本加厉的8-10-6
* 开始毫无章法，毫无计划，粗鲁的开发快速demo
* 停止过程生成算法的研究
* 重新被安排“评估式”开发

&lt;br&gt;

并且，变本加厉的是。在一个只有8人的小团队内，竟然有4个“把握方向”，“紧盯计划”的领导者。
&lt;br&gt;
如果一直逆来顺受，这时倒还可以继续顺水推舟，然而我却无法接受在经历了那一段欢乐的开发时光之后，重新回来接受一个又一个让人沮丧的“命令”
&lt;br&gt;
果然，一起开发的几个同事，率先提出了转岗。
&lt;br&gt;
我想，是时候往外看一看了。

&lt;br&gt;

有时候，机会一直在那里，只需要你推开窗户。
&lt;br&gt;
一推开窗，机会就如同新鲜空气一般扑面而来

&lt;br&gt;

终于有可能，在成都这片土地，就可以体面的开发自己的GreatGame了。
&lt;br&gt;
也许，这次我真的可以有一份DreamDriving的工作了。

&lt;br&gt;

同时，接下来也会是work-life-balance的开发节奏，因此，我决定把这半年来在过程生成方面的研究，重新整理，在github上开一个开源项目，一篇文章一篇文章的把过程生成这件事讲清楚。同时，也是让自己，对这一块知识体系，有一个更加系统的认知。

&lt;br&gt;

flag setup，诸君共勉！</content:encoded><category>life</category></item><item><title>Unity3D中的MaterialPropertyBlock</title><link>https://gameknife.github.io/tech/2016/04/28/MaterialPropertyBlock/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2016/04/28/MaterialPropertyBlock/</guid><description>最近两天，在针对Unity3D做传统gpuskin的尝试。 所谓传统gpuskin，即使指利用vertexshader，通过传入的bone matrix进行蒙皮计算的方式。</description><pubDate>Thu, 28 Apr 2016 00:00:00 GMT</pubDate><content:encoded>&lt;br&gt;

背景故事
---

最近两天，在针对Unity3D做传统gpuskin的尝试。

所谓传统gpuskin，即使指利用vertexshader，通过传入的bone matrix进行蒙皮计算的方式。

而Unity3D提供的gpu skin，是一种更加鲁棒的方法：
利用&quot;StreamOut&quot;，在正常的vs-ps流程前，进行一次“通用”的蒙皮计算，而后stream out。因此，Unity3D的gpu skin对于用户来说是完全透明的！

然而不幸的是，这个特性，DX9享受不到，GLES2也享受不到。因此，Unity3D声明，gpu skin只能在dx11以及gles3平台才可使用。

更深入的实现细节之后再写文章详解，而今天要说的，是初次实验后的性能优化阶段... 发生的故事。

这一次的性能优化，才让我意识到了之前的一个很大的误区。

&lt;br&gt;

瓶颈所在
---

在初步demo实现之后，通过UntiyProfiler，逐步将实验期间的(12ms/9chr)的cpu消耗，降低到(1ms/9chr)。然而结果并不理想，因为主线程的cpu skin prepare + skin wait也仅仅使用了(0.8ms/9chr)。

不过还是准备将用例直接port到真机上看看瓶颈所在。

不看不知道，一看吓一跳。

在iPhone5S上，消耗为3ms/9chr，而cpu skin的消耗为2.5-3ms/9chr。看起来，和PC上比例差不太多。

而当我在Instrument Time Profiler中仔细查看调用才发现，GSKINRenderer.Update的消耗，超过50%都消耗在了Material.SetVector这个函数之上！而我本身认为非常繁重值得优化的一个Matrix4x4 Decompose操作，仅仅消耗了大约25%的时间。
![placeholder](../../assets/blog/blog-add/materialsetvector.jpeg)
而Material.SetVector，最终其实只会促成glUniform4fv的调用，而这个调用在GLEngine库中的查看，全局消耗非常小。
![placeholder](../../assets/blog/blog-add/gluniform4fv.jpeg)
看来，最大的问题在于对Material.SetVector的优化！

&lt;br&gt;

瓶颈形成分析
---

那么</content:encoded><category>tech</category></item><item><title>界面纹理压缩（CbCr + YAlpha）</title><link>https://gameknife.github.io/tech/2016/04/25/UITexCompress/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2016/04/25/UITexCompress/</guid><description>最近一个月都在做Unity3D的相关优化，在一个庞大的实际项目中做优化。 内存占用，帧消耗，加载速度等。。。 上周在做内存占用优化时，突发奇想了一个纹理压缩的思路：YCbCr，接下来做了实验和集成，发现效果很不错。</description><pubDate>Mon, 25 Apr 2016 00:00:00 GMT</pubDate><content:encoded>### 。

最近一个月都在做Unity3D的相关优化，在一个庞大的实际项目中做优化。
内存占用，帧消耗，加载速度等。。。

上周在做内存占用优化时，突发奇想了一个纹理压缩的思路：YCbCr，接下来做了实验和集成，发现效果很不错。

### 背景

我们的项目的UI纹理比较多，Atlas也都较大。然而etc压缩格式不支持Alpha通道，而pvrtc格式多alpha通道压缩质量也堪忧。
于是我们采用了拆分alpha，加一次采样的方式，对Android和iOS的格式和质量进行统一。

将原本的rgba纹理，拆分为rgb + alpha两张etc／pvrtc4rgb纹理。大小也就是4bpp + 4bpp = 8bpp，和dxt5的大小一直，只是需要多一次采样。
效果上完全达到要求。

### 修改

在分析内存占用时，发现在gpu对象中，ui纹理的占比非常大。遂思考直接降分辨率，结果效果惨不忍睹

晚上突发奇想，想起之前crysis3的gbuffer带宽优化方法，有一个基于视频算法的trick，大概就是利用了YCbCr这种方式。
将原始的r,g,b三个通道，转换为亮度，色度r，色度b三个通道。一般的讲，亮度是最为高频的信号，而两个色度相对低频。
因此就可以将亮度以原尺寸保留，而两个色度通道以半精度，甚至四分之一精度保存。来降低带宽。

我们目前的ui纹理，已经拆分为两张纹理，正好可以利用yuv，将信号按高低频区分，分别存储，再在shader中组合，岂不妙哉！

### 实现

说干就干，实现分为了以下步骤：

1. 纹理预处理

2. 材质复写

3. shader修改

### 效果和对比

RGBA32原图

RGB + A全精度图

RG 1/4 ＋ YA全精度图

### 总结和展望</content:encoded><category>tech</category></item><item><title>一桌再好的菜，吃的人多了，也就变了味</title><link>https://gameknife.github.io/life/2016/03/14/Change/</link><guid isPermaLink="true">https://gameknife.github.io/life/2016/03/14/Change/</guid><description>上一次的博文，还是踌躇满志的五周年总结。当时正在加班加点的突击一个DEMO。 而今，我终于获得了大概两周的休息时间，准备交接转岗。</description><pubDate>Mon, 14 Mar 2016 00:00:00 GMT</pubDate><content:encoded>### ·
---
上一次的博文，还是踌躇满志的五周年总结。当时正在加班加点的突击一个DEMO。

而今，我终于获得了大概两周的休息时间，准备交接转岗。

### ·
---
没错，我的Great Game没有完成，我背弃了我的梦想。

### ··
---
这曾是一个多么梦幻的团队，面试时吸引了我。
我才从成都直接奔赴深圳，与老婆两地分居，为的就是一腔热血，做好这个Great Game。

当国庆回来看到同事们梦幻般的进度之后，更加确信我们会做出Great Game。因此自己也为此赌上了全部，拼尽全力。

### ···
---
然而，却在第一个DEMO完成之后。这么多天才的人，却输给了政治。整整陪玩了2个月没做什么像样的东西，却背了一身黑锅。

### ····
---
一桌上好的饭菜，本来几个志同道合的兄弟吃得happy，结果来了一帮大爷，告诉你们饭不是这样吃的，
你这些菜，怎么还能吃这么香，tm老子吃了那么多年的饭局，从没见过有你们这样还能吃这么香。
至于怎么才能吃的好，还得我带着你们，然后你们自己悟。懂么？

### ·····
---
于是我们只能利用有限的假期前后的时间，违背上级命令，暗中的完成我们心目中那个真正的MOCK-UP VERSION。

可笑的是，这个不得到承认，未得到评价的MOCK-UP VERSION，却成为了反对这个DEMO的人的某种筹码，得到了青睐。

WHAT THE FUCKING SITUATION.

### ······
---
所以我背弃了自己的梦想，但我绝不会丢掉它。也许，这种梦想的东西，真的需要以后，再找到这些天才的人，用最纯粹，独立的方式来完成吧。
暂时，还是重新找回家庭的责任与纯粹技术的追求。

### ·······
---
我从来都是一个务实的人。</content:encoded><category>life</category></item><item><title>工作五周年</title><link>https://gameknife.github.io/life/2016/02/22/five-years/</link><guid isPermaLink="true">https://gameknife.github.io/life/2016/02/22/five-years/</guid><description>今天是2016年2月22日，从2011年2月22日正式参加实习开始。我工作满五周年了。 五周年，我在4个不同的公司工作过。算下来，跳槽比较频繁。并且，还涉及了两个行业：游戏和虚拟现实。</description><pubDate>Mon, 22 Feb 2016 00:00:00 GMT</pubDate><content:encoded>### ·

今天是2016年2月22日，从2011年2月22日正式参加实习开始。我工作满五周年了。

### ·

五周年，我在4个不同的公司工作过。算下来，跳槽比较频繁。并且，还涉及了两个行业：游戏和虚拟现实。
想起5年前，和同期的毕业生一起进入畅游公司，开始新手任务，玩《鹿鼎记》，每天去万千百货吃午饭，
每天做2个多小时的地铁往来八宝山和传媒大学，仿佛就在昨天。

如今，一起入职的伙伴们，都陆陆续续已经或者准备结婚了，5年过得真快。

从2011年毕业，到2016年，我和老婆辗转从北京，到成都，再到深圳。

在北京读完了书，

在成都安家，结婚，

结完婚，我又只身一人来到深圳继续打拼。

5年经历了很多，折腾了很多，感谢老婆，感谢你一直以来对我的支持，忍受，溺爱。

### ·

虽然打上了Life的Tag，但github.io还是一个技术博客，恩，言归正传。捋一捋一路过来的技术历程。

五年间，虽有王婆卖瓜之嫌，但我还是大言不惭的觉得，我算是个成长很迅速的同学。这必须要感谢，每个公司我的直接领导。

##### 张哥

畅游的张哥，从实习开始一直关照我，让我能够按照自己从兴趣和长处，
在喜欢的事务上不断发展和探索。同时项目也非常hardcore，可惜后来因为公司的方向问题，没能继续在畅游完成蛮荒，是为遗憾。
在畅游看懂，理解，吃透了cryengine，也对现代图形渲染的几个方面有了一些心得，同时自己的gkEngine终于找到了钻研的方向。

##### p叔

在永航的p叔，虽然总是对我很严厉，甚至有几次，劈头盖脸的骂了我几顿。但我后来一直觉得，他是我人生中重要的导师。
人生中的几个重要导师，除了p叔，总是给我鼓励，赞扬我的每一次突破。但p叔，在我没做好的时候，直接了当的说你做的是垃圾，必须重做。
在我做好的时候，总会觉得还可以有更好的性能和效果。
在永航虽然只呆了半年多，但我在永航写出了内存磁盘，写出了软件渲染器，看论文，做实现，研究出了CPU/GPU的巨量头发模拟和渲染。
还有机会和X51的古董及玩家博弈，在各种MX440和GTX680中的显卡中做兼容性适配。
要说技术的飞升，也就是在永航的这半年，p叔的严厉，我学会了开发之前先做设计，UML MODEL, UML SEQ，多人review。
感谢p叔！

##### 李大师哥

后来，在恩师的引荐下，来到了工作时间最久的中视典。李大师哥，我的直接领导，带着我，三个人，一年时间，硬生生重新code出了公司的下一代引擎。
他是我见过最“不折手段”的程序员，不管用什么方法，他就是能在第二天天亮之前，让客户看到他想要的DEMO。
我也更加明白了效率和质量是能够达到完美结合的。
李大师哥在人生规划上也对我有不少的启发，后来因为我自己和老婆的计划，想要离开北京，回到成都，李大师哥也各种为我安排，真心感谢！

##### 现任老大

最后，还是选择了来到鹅厂，是时正式在成都公司遇到业务和技术无法共同发展的问题，来到深圳，看到了一个各种散发着热爱游戏氛围团队，
于是15年下半年，结完婚的我，便来到这里想实现毕业以来就一直想实现的梦想：做自己喜欢的游戏！
老大对我的帮助很多，也体会到了老大在管理方面的理念，从而带出了一个十分开放和高效的团队，非常高级。目前在职，业务方面就不说太多了。

有些谄媚的说了这么多，都是真情流露！

### ·

五年间，我最骄傲的事，是我持续的开发我的毕业设计：gkENGINE，并且还一拖再拖终于将其开源。
作为一直持续更新的个人项目，可以说，我绝大多数的技术积累和沉淀，都来自于此。并且在之后的各种工作中，gkENGINE就像一个字典，不光自己查，还可以给同时查阅。

不过说来惭愧，自从来了鹅厂，来自开发游戏的激情，占据了全部的时间，gkENGINE也停滞更新了一段时间。
希望在稍后得闲的时候重新更新起来，最近又沉淀了不少技术，并且发布了vulkan api。是时候再搞一波提升了！

### ·

最后，有些话不吐不快。

The only way to do great work is to love what you do.

官僚，制度，山头，GET THE FUCK OFF MY WAY!</content:encoded><category>life</category></item><item><title>完全便携系统-WinToGo U盘系统的制作</title><link>https://gameknife.github.io/tech/2015/10/24/full-portable-system/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2015/10/24/full-portable-system/</guid><description>这周项目开始轻松一些了，周末收到同学的安利，购置了一个超高速u盘，读写200MB/s+，貌似芯片用的是ssd芯片。 同学还用这个u盘制作了一个win to go系统。据说特别牛逼，带上一个u盘，装满驱动，就可以走到哪里都用自己的电脑了</description><pubDate>Sat, 24 Oct 2015 00:00:00 GMT</pubDate><content:encoded>### ·

这周项目开始轻松一些了，周末收到同学的安利，购置了一个超高速u盘，读写200MB/s+，貌似芯片用的是ssd芯片。

就是这货:



同学还用这个u盘制作了一个win to go系统。据说特别牛逼，带上一个u盘，装满驱动，就可以走到哪里都用自己的电脑了~

经他推荐，用一个软件制作系统，感觉异常简单。于是我下载了最新版软件，如法炮制，

结果遇到了很多问题，根本无法进入系统，甚至根据网上的教程，开始判定我买到了不能做系统的盘...

最终，由于型号一致，我直接蛮力的克隆了他的u盘，果不其然... 成功了。于是我判定，一定是那个坑爹的软件有问题，身为geek的自己，绝对不能忍受这个耻辱。
便开始找寻这个软件的原理，然后决定手动制作一枚容量极致精简的u盘系统。终于，今天算是完全搞定，下面这里记录一下，有需求的朋友可以一试。

### ·

1.准备工作
---

* 准备原版win10镜像
* 准备imagex工具和bcdboot工具
* 制作vhd文件

2.部署windows
---

* 使用imagex工具部署win10到vhd
* 使用bcdboot写入引导信息

3.设置启动
---

* 给分区写入引导信息

4.通过u盘启动
---

* 通过u盘启动，进入系统，准备设备
* 设置用户名密码，退出，回到现有系统

5.精简系统
---

* 利用ntfs压缩精简
* 利用dism工具精简
* 人肉精简
* 精简结果和对比数据

6.差分vhd
---

* 创建差分vhd
* 使用差分vhd

7.使用
---

* 软件安装与备份</content:encoded><category>tech</category></item><item><title>梦想驱动的工作</title><link>https://gameknife.github.io/life/2015/10/16/dream-driven-works/</link><guid isPermaLink="true">https://gameknife.github.io/life/2015/10/16/dream-driven-works/</guid><description>今天是10月16日，从10月8日回到公司上班开始，我连续上了8天班，每天早9点到晚11点，并且完全自驱。 这也许是我职业生涯以来最长的一次“连杀了”。</description><pubDate>Fri, 16 Oct 2015 00:00:00 GMT</pubDate><content:encoded>#### Longest Streak
---
今天是10月16日，从10月8日回到公司上班开始，我连续上了8天班，每天早9点到晚11点，并且完全自驱。
这也许是我职业生涯以来最长的一次“连杀了”。
然而到今天回家打开电脑，想要写下这篇博文来记录时，才发觉好像之前都没有觉得过累，很神奇。

#### Fantastic Team
---
其实一切，是源于这个梦幻的团队。9月29日，我提交完项目的初始代码后，匆匆跟同事交代累几句，便匆匆上飞机回成都陪老婆过假期。
同事们在国庆加了几天班，到10月8日我回到公司时，居然发现我们到游戏可以玩了，而且还有不错到体验。
看到节前我完成到一些效果粗放到展示在场景中，立马勾起了想要优化和持续开发到激情。
可能这真的是一个梦幻的团队，每个人的努力工作都会相互激励。

#### Powerful Boost
---
连续8天的加班，主要是将之前的一些问题解决，并和同事一起把demo版本的效果和体验向前推进。这几天每天满打满算10多个小时的工作，
推进了风格化渲染进一步改进，昼夜系统，气候系统，NativeRenderHack，跨平台版本编译测试，快速海水反射，后处理链进化等多个功能。
可以说这一周恐怕完成了之前半个月的工作量。

#### DreamDriven
---
然而反观之前，工作内容很低的“版本坚守”加班，即使一个晚上，就感觉耗尽了我所有的体力，第二天只想躺在床上。
我想，这两者之间，应该是缺乏了一个驱动力。
而这个驱动力，不是金钱。之前的“版本坚守”加班只需要待在公司随时待命即可，接到工作之前只需搞自己想搞的事情即可，
并且还有按小时计算的丰厚奖金。
而这次的加班，除了周末每天有200元的补助，上下班的打车报销。金钱上没有更多的驱动力了。

所以，这个驱动力，应该是梦想。
我想这是我第一次在工作中体会到之前在学校开发gkENGINE，开发毕设游戏那样的感觉。

攻破一个又一个想法和难点，实现一个又一个的可能与不可能。开发自己喜欢的游戏，为自己创造游戏！

#### Break
---
当然休息还是必要的，虽然心不觉得累，但是身体应该疲倦累。
这周给自己安排一个周日的休息，在家洗衣服，看书，看看电视。同时，睡两个更长的觉！

#### End
---
梦想驱动的工作，真的能让效率爆发式的增长！期待我们进一步的demo版本。</content:encoded><category>life</category></item><item><title>WE ARE MAKING A GREAT GAME!</title><link>https://gameknife.github.io/life/2015/09/25/making-great-game/</link><guid isPermaLink="true">https://gameknife.github.io/life/2015/09/25/making-great-game/</guid><description>不知不觉，到公司已经2个月了。 这两个月，和团队融合，一起经历了一次ce，慢慢的和这一波有梦想和激情的人们熟络起来。 经过几个版本的拉扯，我们的游戏项目终于决定了最终的方向：</description><pubDate>Fri, 25 Sep 2015 00:00:00 GMT</pubDate><content:encoded>![placeholder](../../assets/blog/blog-add/sketch.jpg)

不知不觉，到公司已经2个月了。
这两个月，和团队融合，一起经历了一次ce，慢慢的和这一波有梦想和激情的人们熟络起来。

经过几个版本的拉扯，我们的游戏项目终于决定了最终的方向：

#### A TOTAL SANDBOX GAME。

这真的是我梦寐以求的项目，

* 过程生成的各种环境
* 过程生成的生物群
* 过程生成的道具
* 完全实时的环境创造
* 风格化渲染风格，全平台发布

每一个点都充满了技术挑战和实现的快感。每一个点都可以创造无限的gameplay。

We are making a great game!

现在的工作状态让我感受到了之前在各种3a大作的making of中那种羡慕的感觉（虽然还没有完全达到）。我们是在完成一个我们每个人都憧憬和热爱的游戏。两周的时间，我一有空都在思考如何让项目变得更好，周末在公司做技术预研，很快的时间推demo。每一个人都饱满的向前推进。应该是从毕业以来，能把我跑得最满大项目，（基本8核全开了吧！）。目前gkENGINE都暂时停止了更新。

在这个人生的节点，能够有机会和这样一波人开发这样一款游戏，要感谢机遇，同时更要抓住机遇。将这个项目变成一个真正伟大的游戏！</content:encoded><category>life</category></item><item><title>构建MacOSX工具链</title><link>https://gameknife.github.io/tech/2015/08/19/create-macosx-toolchain/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2015/08/19/create-macosx-toolchain/</guid><description>最近两天，利用晚上的时间，为gkENGINE构建了原生的MacOSX工具链。此时可以完全脱离windows及windows虚拟机，</description><pubDate>Wed, 19 Aug 2015 00:00:00 GMT</pubDate><content:encoded>### ·

最近两天，利用晚上的时间，为gkENGINE构建了原生的MacOSX工具链。此时可以完全脱离windows及windows虚拟机，
直接在一台纯净的Mac机上clone, build, package, deploy，直接发布iOS App以及直接在MacOSX中运行gkENGINE了。

### ·

其实这是一个比较复杂的过程，大概的步骤有以下几点

* 改写bat到sh

&gt; 其实这是一个看起来较为简单，实际操作起来还挺麻烦的工作。在windows下我设置了几个系统变量来保存引擎根目录，其中使用了当前目录和日期变量。而在macosx上，在一个shell中sh一个shell，在子shell中设置的环境变量不会保留到父ahell。因此，只能使用source命令来include，到source的语句目录又不会切换，这是个cmdbatch很不相同的地方。最后包括文件的遍历，各种字符串的拼接，都需要做挺多流程上的改变。

* 编译gkResourceCompiler

&gt; shell翻译完之后，就要开始测试了，接下来得主要问题就是shell中调用的各种控制台程序了。7zr，texconv，pvrtextool，gkresourcecompiler这几个。其中大概就texconv不太可能有osx的版本，其它都可以重新寻找和编译。7zr, pvrtextool很快找到了osx版本，接下来就是编译gkresourcecompiler的osx版本，gkcommon的库全部是跨平台的，所以编译很顺利。平台相关的atc_compiler没有加入编译，gkresourcecompiler目前只能处理obj -&gt; gmf和utf8了。

* 寻找或替换MacOSX上的纹理转换工具

* 重新打包</content:encoded><category>tech</category></item><item><title>Unity3D粗犷优化</title><link>https://gameknife.github.io/tech/2015/08/14/unity3d-draft-optimize/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2015/08/14/unity3d-draft-optimize/</guid><description>刚进组，准备从一些独立工作切入项目，于是选择进行项目效果和性能的提升与优化。于是先开始优化性能，压榨出可供提升质量的空间。</description><pubDate>Fri, 14 Aug 2015 00:00:00 GMT</pubDate><content:encoded>### ·

刚进组，准备从一些独立工作切入项目，于是选择进行项目效果和性能的提升与优化。于是先开始优化性能，压榨出可供提升质量的空间。

### ·

取名粗犷优化的原因是这个优化和提升的过程只有一周时间，因此只能粗犷的优化，放弃精雕细琢。那么首先要做的就是profile。

* RenderQueue优化
 * 渲染队列初探
之前其实对unity3d如此底层的profile真的还没做过，于是直接将程序打包到真机上，用xcode instrument来抓gpu帧，观察最终的commandlist。
 * 瓶颈确认
第一次的瓶颈说来很神奇，在gui上，而且还在看不到的gui上。
 * 原因分析
 * 问题解决
* Memory优化
 * 起因
 * 工具使用
 * 优化策略
 * 问题解决
 * 进一步解决
* Resource优化
 * 静态依赖检查</content:encoded><category>tech</category></item><item><title>初到鹅厂</title><link>https://gameknife.github.io/life/2015/08/03/arrive-tencent/</link><guid isPermaLink="true">https://gameknife.github.io/life/2015/08/03/arrive-tencent/</guid><description>这一篇blog隔了好久... 终于，7月18号，7月25号连续两周在成都、太原的两场婚礼办完之后。我和我新婚的太太，与2015年7月26日，正式踏上了深圳的土地。飞机下午准点到了宝安机场，走过比首都机场T3还要漫长的通道，取到了行李。…</description><pubDate>Mon, 03 Aug 2015 00:00:00 GMT</pubDate><content:encoded>### ·

这一篇blog隔了好久...

### ·

终于，7月18号，7月25号连续两周在成都、太原的两场婚礼办完之后。我和我新婚的太太，与2015年7月26日，正式踏上了深圳的土地。飞机下午准点到了宝安机场，走过比首都机场T3还要漫长的通道，取到了行李。本准备依照上次我们的行程，在出租车排队的地方打一个的士，结果老婆灵机一动，让我试试uber。

Uber最近不知道在作甚，在成都我们的几个uber账号都因为电子邮件验证页面不能登录的问题，而不能打车了。因此在深圳只能抱着试一试的心态，结果打开app很顺畅的打到了车，司机很健谈，他正好顺路要去蛇口接他老婆，因此最快速度把我们送到了前海路我们订的酒店。

话说这uber，最近感觉资金链是不是断了。首先是司机的补助，成都一组目前都已经降为高峰时50元/小时保底，平峰无奖励了。前两天在深圳打uber和司机聊天，深圳也变成了和成都一样的政策，司机对50元/保底没有概念，还给他解释了一番，下车时还连连道谢，说要是没有你，我估计还亏着钱开这破车...

uber在成都已经出现司机荒了，大量司机不开了，那天早上6点起床赶飞机，在一环路上打开uber竟然“无可用车辆”，遂打开1号专车直接订了一辆，贵是贵，但是别人专业司机的确敬业。

话说回来，司机很健谈，给我们介绍了下深圳的基本情况，便穿过宝安区来到了南山区前海路，我们在酒店放下行李，便准备出门寻觅出租房。在网上看了下58同城，感觉深圳前海路也就3000+的房租租一个一居。结果一到中介，全傻眼了。说什么4000以下没有了，3800出来个单间，要么？不要被抢了...我擦，这什么行情？

我们从一居看到单间，看到合租...全部都不太满意。时间都到了晚上8点了，准备先作罢明天再找，先回酒店休息了。结果在路边又找到一家中介，居然有一间2800的单间。

真是功夫不负有心人，这个房间虽然是小了点，但是厨房卫生间阳台都有还挺干净，家电也是新的。一个人住还不错，两个人住能凑活。不敢奢求更多了，当晚旋即付了定金，签合同。房子得等两天才能入住，完后打了车回到酒店。感叹效率真高，到深圳半天便找好了房子。

### ·

深圳这房价也真是醉了，我们租的这破房，均价60000+。要啥没啥，配套也差，就是挨着所谓前海自贸区。在3月30号之前貌似还不到30000，这速度真是太猛了。看来深圳也只能成为年轻拼一拼的城市了。好吧，我再次回到了第二个&quot;北京&quot;。

不过这里还是比有北京没有的优势，空气好，没雾霾，还几乎都是大蓝天。城市规模适中，即使住偏远一点也能1个小时上班。活力，深圳平均年龄应该比北京小不少，适合年轻人在这里打拼。

### ·

第二天一早就前往公司报道了，第一天出门挺早，就选择了坐公交试试，这给了我一个下马威。在车站就疯了半小时的车，上车又坐了半小时，没多长的路生生的花了整整一小时。。。不过也好，到科兴刚好到时间，省得发愁在公司楼下无聊。

入职，培训，接受大公司的进门洗脑。公司培训是全公司级别的，发现公司什么类型的岗位都有，还有两个同事以前是安全系统的。。。

入职的三天培训时间，顺带把自己的工位安排妥当了。机器配置还是很不错的，终于又回到了有办公机的年代了，回到了那不用把工作带回家的年代，真好！

### ·

到组第二天，主动请缨为当前的一个版本搞一些锦上添花的工作，以此来熟悉项目和开发流程。于是工作就这么上路了。</content:encoded><category>life</category></item><item><title>婚礼邀请系统WIS的开发与总结</title><link>https://gameknife.github.io/tech/2015/06/12/wis/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2015/06/12/wis/</guid><description>这周，终于把毕业论文，公司部分交接工作搞完了。终于可以开始搞给老婆拍胸脯承诺的电子请柬了。 由于前几周的出差，大量的工作内容，老婆已经开始着急，婚期还有一个多月了，我们不论实体还是电子请柬还一封没发呢。</description><pubDate>Fri, 12 Jun 2015 00:00:00 GMT</pubDate><content:encoded>#### ·

这周，终于把毕业论文，公司部分交接工作搞完了。终于可以开始搞给老婆拍胸脯承诺的电子请柬了。
由于前几周的出差，大量的工作内容，老婆已经开始着急，婚期还有一个多月了，我们不论实体还是电子请柬还一封没发呢。

于是，上周日。把工作都搞定之后，便开始着手开发这个WIS系统（Wedding Invitation System）。

终于在几天的小功能添加和bug修改后，与今天正式上线，朋友们反响也挺好。于是开始着手这这样一篇总结，免得一些及时的经验之后又忘掉了。

这里附上系统的通用登录地址

[http://112.74.127.44](http://112.74.127.44)

#### 需求分析

快速推进完成一个电子请柬，

必须功能

* 适配各种尺寸移动设备屏幕，网页
* 简明快捷的介绍婚礼时间与婚礼地点
* 提供互动功能，可以让来宾留言祝福
* 可以在微信上方便的分享

附加功能

* 在婚礼地点的介绍提供电子地图与导航
* 提供视频短片的上传和分享服务
* 后台统计，搜集数据

#### 技术选型

这块没什么好选的，在一周内要搞出一个完备的系统，肯定需要选择熟悉常用的技术，因此，之前搞过的apache php mysql成了不二选择。

##### 服务器软件-LAMP

apache + php + mysql，这一套系统写起来很方便，并且功能强大。对于wis这个小系统来说，性能上也足够应付了。

##### 服务器-阿里云

阿里云，很早之前，在大二的时候，因为要给比赛的作品搭一个网站，当时选择了成都一家公司提供的虚拟主机+域名的套餐。价格但是不贵，但是性能较低，主要问题还不在主机，当时都是静态网页只是添加了一些炫酷的本地效果。主要问题在于带宽。所以这一次是不倾向于使用虚拟主机的。

云服务器是一个很好的概念。
首先，免维护，云服务器的实体计算单元都由财力雄厚技术扎实的大公司主导。
其次，配置动态，云服务器的配置，带宽，都可以随时进行动态的更改，并且是精确到秒的。带宽的添加甚至都不用重启服务器。例如我可能做一个网站，一开始没什么人气，我可以选择便宜的低带宽方案，突然发现用户量猛增，我可以立即将带宽提升。
最后，租期灵活，相比之前虚拟主机动辄一年的租期，阿里云提供多种租期，例如我买了两个月包月，等婚礼结束半个月后，这个网站系统就可以考虑下线了。
还有一点，服务器提供各种虚拟机镜像，例如我们需要的LAMP系统，就直接有对应镜像，买下之后直接配置上传系统即可，centos不太熟，但我却在短短1小时内就将整个系统整合上去。

##### 开发工具

一开始的几个系统，都是用eclipse在写php，顺带写写html，然后拷贝到远端服务器。语法高亮倒是有，但是整个开发很不顺畅。这次尝试了一下新工具，发现了phpStorm和webStorm，真是神器！

PhpStorm是jetBrain公司推出的专门针对Php的IDE。自带git，自带本地版本管理，自带SFTP,FTP连接。可以直接与远端服务器通信，更新，比对，下载代码。</content:encoded><category>tech</category></item><item><title>这一次的目标，是星辰大海！</title><link>https://gameknife.github.io/life/2015/05/04/newjob/</link><guid isPermaLink="true">https://gameknife.github.io/life/2015/05/04/newjob/</guid><description>这一次，我打脸了。 之前我说我很享受在成都的生活，很看好自己在成都的未来。 但是一个月前，我却开始谋生了再一次离开家乡的想法。</description><pubDate>Mon, 04 May 2015 00:00:00 GMT</pubDate><content:encoded>### ·

这一次，我打脸了。

之前我说我很享受在成都的生活，很看好自己在成都的未来。

但是一个月前，我却开始谋生了再一次离开家乡的想法。

而今天，我收到了深圳腾讯的OFFER，即将再一次踏上征途。

### ·

一切，源于gkENGINE的开源发布。

在春节，我花了大概一半的时间在完善gkENGINE上。终于在过完春节假期的一周内，将gkENGINE开源了。
一瞬间，我再一次通过网络，接触到了我已经离开多时的技术圈子。我硬着头皮的登上了github，结识了很多朋友。
同时，更重要的，我发现了我掩藏在内心的不开心。

### ·

&gt;
&gt; 我已经接近一年没有专注于尖端的游戏技术了。
&gt;
&gt; 我已经慢慢开始从一个技术人员，转变为市场人员了。
&gt;
&gt; 我的日常着装，从裤衩T恤板鞋，变成了西裤衬衫皮鞋。
&gt; 
&gt; 我开始一周花费平均1-2天的时间，用于出差，见客户，拼方案。
&gt;
&gt; 开始各种的救火，给小平台写脚本，给一个山寨硬件加软件接口。在验收前给软件加一些领导要看的标语。
&gt;
&gt; 我开始频繁出差，频繁填写各种借款，报销单，在酒店睡觉，各地的饭馆吃“好吃”的东西。
&gt;
&gt; 我下班回家只是希望放松，看看视频，混混恶恶就到了睡觉的时间。再也没有之前熬夜写代码到2点的激情了。
&gt; 

### ·

但是开源gkENGINE后，我又找回了在畅游，在永航工作的感觉。我开始更多的思考技术，重新开始更多的思考设计，开始意识到如果一直保持这一年的状态，自己可能会慢慢的落伍了。
但是我在现在的工作中感觉不到技术的氛围，一点没有。
不管公司的大老板，还是身边的同事，尽管大家在业务上兢兢业业，公司的项目做得也还算靠谱认真。
但对技术的要求或者说眼界，仅限于够用即可，公司可没有更多的时间和精力来思考和发展技术了。
特别的，当老板跟我谈到之后的规划之后，我失望了。

于是我选择了开始寻找机会。

首先我选择了成都腾讯，毕竟刚回家安家一年，今年正好要办婚事，根本没有想过要离开家乡。
面试还挺开心，但是聊的过程中慢慢感觉到这里对技术的需求，也仅限于够用即可，并且，他们还暗示我说在这里恐怕你也找不到技术的氛围。
终于，可能由于确实不太契合，最终错过了这次机会。

可能出于一时的热血上头，我顺手给opengpu论坛里的一个大牛投了一份简历，深圳腾讯。
深圳，这个之前在北京漂泊时都几乎没有考虑过的城市。

### ·

结果来得太快，第二天电话面试，第五天直接飞去了深圳。

一天的面试下来，聊了好几轮。看了他们之前的作品，以及现在正在做的事的demo。我燃了。竟然在大公司还能有这样的团队，还能开发这样的游戏。还没离开科兴园的时候，自己已经决定想来试一试了！

可是回到成都，面对我和老婆一年来慢慢建立的小家，我们的新房，我们的新车。实在是割舍不下。

### ·

因此我自己其实是彷徨的，曾经一度想要放弃这个机会。

这里要感谢我的老婆，她很支持我，她对我将这是一个我梦想中的机会，这是我从上学时就一直希望做的事情。所以再困难，牺牲再大，也值得去完成他，至少在未来，我不会因为年轻时错过这次机会而后悔。

### ·

这一次的目标，是星辰大海！</content:encoded><category>life</category></item><item><title>用bootcamp安装win10</title><link>https://gameknife.github.io/life/2015/04/20/bootcamp-install-win10/</link><guid isPermaLink="true">https://gameknife.github.io/life/2015/04/20/bootcamp-install-win10/</guid><description>今天上午去见了个客户，他们要开发一个油气项目的工程可视化软件。项目看起来和我们之前做的工作相似性不高， 但是个人觉得完全值得一做，属于一个有优质项目。两个油气工程师，将需求整理得十分清楚，看起来好像软件工程师一般。</description><pubDate>Mon, 20 Apr 2015 00:00:00 GMT</pubDate><content:encoded>今天上午去见了个客户，他们要开发一个油气项目的工程可视化软件。项目看起来和我们之前做的工作相似性不高，
但是个人觉得完全值得一做，属于一个有优质项目。两个油气工程师，将需求整理得十分清楚，看起来好像软件工程师一般。
同时，他们对时间的把控和开发计划的规划也比一般的项目经理更加精准合理。

中午完成沟通，下午得闲，由于今天车不能走三环，便干脆回家开工。

周末笔记本上的windows系统像中了魔咒一般运行一段时间就变得极慢。同时win系统上已经安装了太多的垃圾软件，存留了过多的垃圾。
回来突然突发奇想干脆把bootcamp的win7系统重做一下，干脆做成win10。

于是便开始往大硬盘上备份工程和数据，然后下载win10的技术预览版。

用bootcamp制作系统盘时，竟然下载了好久的驱动... 主力电脑不好用了，干脆去PS3玩会GTAV。回来一看，竟然下载了2.66GB的驱动！

恩，终于制作完了系统盘，现在正在重新分区...

额，分完了，安好win10继续commit!

安装还是比较快的，进win10了，首先体验了一下高分屏配高dpi的效果，确实比win7改善了不少。然后呢，整个系统感觉比win8用起来习惯一些。开始装vs，装unity，装各种工具，先试试看了！</content:encoded><category>life</category></item><item><title>SoftRenderer迁移到github</title><link>https://gameknife.github.io/tech/2015/04/08/soft-renderer/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2015/04/08/soft-renderer/</guid><description>昨天花了些时间，将codeplex上的softrenderer v0.3 clone下来，然后做了些改动，迁移到github上。现已上传：</description><pubDate>Wed, 08 Apr 2015 00:00:00 GMT</pubDate><content:encoded>昨天花了些时间，将codeplex上的softrenderer v0.3 clone下来，然后做了些改动，迁移到github上。现已上传：

[https://github.com/gameKnife/Softrenderer](https://github.com/gameKnife/Softrenderer)

目前的效率蛮高

head, 640 x 360 px, 2186tri, 135fps

![head](../../assets/blog/blog-add/sr_show_2.jpg &quot;head&quot;)

sponza, 640 x 360 px, 78118tri, 27fps

![head](../../assets/blog/blog-add/sr_show_1.jpg &quot;sponza&quot;)

这次修改的地方：

* 让softrenderer重新成为一个学习项目
* 将之前的hw renderer暂时去除，以使得其更加纯粹
* 添加除了sponza之外的一些小模型：head, teapot, knox，使后续开发测试有更多资源可选
* 修改了rasterizer中的一处错误，之前的SrRendPrimitive队列结构的建立，没有用到operator new，也就没有进行16字节对齐的内存申请。所以在场景简单时（这个情形和特殊，复杂的场景居然没有问题了），有时在SIMD运算时会有问题。
* 修改了shader constants的大小，之前的192有点太大了，比较耗。

目前的版本还有几处bug，到时候提到issue上

同时，对目前版本还有几个规划：

* 将架构整理的更加简单清晰
* 进一步探索可以优化加速的地方
* 使用最新的intel compiler再测试下速度，特别是avx指令集</content:encoded><category>tech</category></item><item><title>在线ocr服务试用及改进想法</title><link>https://gameknife.github.io/life/2015/04/06/online-ocr/</link><guid isPermaLink="true">https://gameknife.github.io/life/2015/04/06/online-ocr/</guid><description>今天帮老婆从图片转一些文字，肯定是要用OCR了。有一些相关的软件，但是都太庞大。就转几张图，遂开始寻找在线OCR服务。 尝试了几个国内网站，识别率太残忍了。之后，这个网站进入视野。先说说优点：</description><pubDate>Mon, 06 Apr 2015 00:00:00 GMT</pubDate><content:encoded>### ·

今天帮老婆从图片转一些文字，肯定是要用OCR了。有一些相关的软件，但是都太庞大。就转几张图，遂开始寻找在线OCR服务。

&gt; [http://www.onlineocr.net/](http://www.onlineocr.net/) 

尝试了几个国内网站，识别率太残忍了。之后，这个网站进入视野。先说说优点：

* 全球语言都能转，需要在页面上选择一下目标语言。
* 识别率尚可，基本能达到80-90%
* 操作快捷，上传文件速度较快

缺点呢：

* 上传png文件比jpg识别率低了很多
* 识别率应可以进一步提高

### ·

最后，自己对这次试用，有一些个人的瞎想，暂且记录在这里。

* 是否可以根据词组或常用搭配的数据库，进一步提高识别率。相当于给OCR配上输入法的数据库。
* 是否可以通过连接搜索引擎，对OCR的内容进行搜索识别，如果有类似的语句，可以直接汇入进一步提高识别率。
* 是否需要对图片进行进一步处理，以减小png和jpg对识别率的影响</content:encoded><category>life</category></item><item><title>Vertex Array Object的纠缠</title><link>https://gameknife.github.io/tech/2015/03/31/vao-issues/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2015/03/31/vao-issues/</guid><description>前些天，把一件一直想做又没做的工作完成了！将gkRendererGL330调试通过，在OSX, WIN32上跑起来了。 顺便，还完成了另一项壮举，将GL330和GLES2的代码统一了起来。…</description><pubDate>Tue, 31 Mar 2015 00:00:00 GMT</pubDate><content:encoded>### ·

前些天，把一件一直想做又没做的工作完成了！将gkRendererGL330调试通过，在OSX, WIN32上跑起来了。

顺便，还完成了另一项壮举，将GL330和GLES2的代码统一了起来。在少量的地方通过宏或者VirtualAPI的方式封装，来区隔开来。

整个过程蛮顺利的，在上安卓真机之前...

### ·

统一的过程，主要就是将近期实现的GLES2上的deferred lighting和到GL330。处理了几个纹理生成，纹理格式的错误之后。
再修改了一下shader的preprocess逻辑，GL330没有经过太多的波折就成功的跑起来了，
（除了昨天发现的一个导致GL330比GLES2画面发暗的bug）。

统一中，主要就是就Vertex Array Object的使用进行了封装。VAO主要有一下几个函数：

* glGenVertexArrays
* glBindVertexArray
* glDeleteVertexArrays

而在iOS中，VAO需要extension支持，就变为了

* glGenVertexArraysOES
* glBindVertexArrayOES
* glDeleteVertexArraysOES

好在只需要宏替换一下函数名，参数以及效果没有太大问题。

而在android的adreno驱动中，也提供VAO的extension支持，但是使用了之后却发现顶点全部花掉。尝试搜索了很久解决方法，也只能放弃。最终在android中只能放弃使用VAO。

因此，最终将VAO的过程抽象为**bind_or_gen_vao**, **use_layout**, **unbind_vao**三个过程。

在三个阶段中根据平台，分别完成

* genVAO/bindVAO 或 bindBuffer
* 非VAO，应用vertex layout
* unbindVAO 或 unbindBuffer

最终，在三个平台上用同一套驱动代码驱动起来。</content:encoded><category>tech</category></item><item><title>安装vc100独立工具链</title><link>https://gameknife.github.io/tech/2015/03/23/install-vc100-toolchain/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2015/03/23/install-vc100-toolchain/</guid><description>这周末花时间尝试研究制作vc100独立工具链，功夫不负有心人，事实证明是完全可行的。下面我给出独立工具链的下载地址和安装注意事项。</description><pubDate>Mon, 23 Mar 2015 00:00:00 GMT</pubDate><content:encoded>#### 前言

这周末花时间尝试研究[制作vc100独立工具链](http://gameknife.github.io/tech/2015/03/21/make-custom-vc100-toolchain/)，功夫不负有心人，事实证明是完全可行的。下面我给出独立工具链的下载地址和安装注意事项。

#### 下载地址

[http://pan.baidu.com/s/1sjOBUkX](http://pan.baidu.com/s/1sjOBUkX)

#### 安装注意事项

系统要求：win7+ &amp; 64bit

环境要求：vs2011+ &amp; 没有安装vs2010

安装方法：

1. 下载package.zip, 放置于c:\, 
2. 解压到c:\package, 使得文件install\_vc100\_toolchain.bat存在于c:\package\install\_vc100\_toolchain.bat
3. 使用**管理员**身份运行c:\package\install\_vc100\_toolchain.bat
4. 等待pause, 按下回车cmd关闭后，安装完成</content:encoded><category>tech</category></item><item><title>html5界面开发</title><link>https://gameknife.github.io/tech/2015/03/22/html5-editor/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2015/03/22/html5-editor/</guid><description>前些天由于做私活做了做web前端开发，这两天又多关注了一下html5，发现了很多有趣，强大的项目。 首先是早有耳闻的erget，</description><pubDate>Sun, 22 Mar 2015 00:00:00 GMT</pubDate><content:encoded># ·

前些天由于做私活做了做web前端开发，这两天又多关注了一下html5，发现了很多有趣，强大的项目。

首先是早有耳闻的erget，

虽然erget很强大，感觉工具链都铺完了。但是看起来erget对我用处不大，我毕竟不需要开发html5的程序，我只是：

```
开发需求和变动尤其频繁，
代码量巨大
不需要一些高端特效
用MFC太蛋疼
的
editor

用web前端技术实现的可能性感兴趣
```

# ·

最终，我发现了它：

[fireball-x](http://fireball-x.com)

![fireball](http://fireball-x.com/images/Fireball-UI.png)

这个工具的全部都是由html5开发。我下载了一个测试版本，貌似是使用类似chormium的技术在本机解析和运行前端代码。

目前编辑器逻辑和样子都很像unity3d。惊叹于其交互体验和界面风格。

不知道其与c++程序通信和交互使用什么方式，对于我之前设想的engine挂接web editor的设想是否支持。

目前看起来这个项目相当强悍，开发者johnny wu表示在公开测试的时候会开放源代码。

# ·

对于用web写native app界面，其实已经有过一些成功案例，有道云笔记，有道翻译貌似就是这个架构。

叫 [hex](http://hex.youdao.com/zh-cn/index.html)

在github上有开源托管:

&gt; [https://github.com/netease-youdao/hex](https://github.com/netease-youdao/hex)

有时间可以调研，写点通信的小demo出来。</content:encoded><category>tech</category></item><item><title>重设r6300v2路由器</title><link>https://gameknife.github.io/tech/2015/03/22/play-ddwrt/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2015/03/22/play-ddwrt/</guid><description>前几周玩路由器时，听信谣言，尝试超频，结果路由启动就进入无线断线-重连的循环中... 幸好机器是在京东买的，保修，取货，换新...</description><pubDate>Sun, 22 Mar 2015 00:00:00 GMT</pubDate><content:encoded># ·

前几周玩路由器时，听信谣言，尝试超频，结果路由启动就进入无线断线-重连的循环中...
幸好机器是在京东买的，保修，取货，换新...

今天把机器拿回来了，ddwrt系统没了，jffs2中的各种conf肯定也没了，xunlei没了，之前配好的lightTPD服务器系统也没了...

恩，反正做过一次，这次直接换kong ac的最新版本呗~

# ·

一路很快下来，开机 -&gt; 刷r6300v2.chk -&gt; ddwrt -&gt; 查看版本号到了24850M -&gt; 收手 -&gt; 开始装必要服务: xunlei, svn, minidlna

我了个大擦， ipkg, opkg都没有？

什么情况... 哦，查到一篇文章说，执行以下kong ac提供的bootstrap，就帮你把opkg装好，真贴心

那么，

`
root@DD-WRT:·# bootstrap...
`

我了个大擦，kong ac的网站被403了... 路由自己翻墙不便啊...

# ·

开始，找方案，结果，发现了这么一个源:

&gt; [http://qnapware.zyxmon.org/binaries-armv7/](http://qnapware.zyxmon.org/binaries-armv7/)

真是大牛逼！还提供了安装脚本

&gt; [http://qnapware.zyxmon.org/binaries-armv7/installer/entware_install_arm.sh](http://qnapware.zyxmon.org/binaries-armv7/installer/entware_install_arm.sh)

技术真是进步太快啊，跟不上脚步了...  相当年，搞着破路由真是踏破铁鞋啊...

果断安装上opkg，然后，各种软件都有！

`
opkg install subversion-server
`

`
opkg install mysql/php5/...
`

这次大概花了一小时，配置成功，svn, minidlna, smb服务器 都 重新通过DDNS连到gameknife.cc上，又重新为我工作了！

wordpress那个博客可以废弃了，考虑要不要再本机搭建一个jekyll的博客系统呢~</content:encoded><category>tech</category></item><item><title>制作自定义vc100工具链</title><link>https://gameknife.github.io/tech/2015/03/21/make-custom-vc100-toolchain/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2015/03/21/make-custom-vc100-toolchain/</guid><description>gkEngine发布后，由于第三方库版本以及我自己是vs2010的使用者等原因，gkEngine对vs2010之后的版本基本是没有支持。由于使用的havok, toolkitpro界面库等原因，…</description><pubDate>Sat, 21 Mar 2015 00:00:00 GMT</pubDate><content:encoded># ·

gkEngine发布后，由于第三方库版本以及我自己是vs2010的使用者等原因，gkEngine对vs2010之后的版本基本是没有支持。由于使用的havok, toolkitpro界面库等原因，导致gkHavok, gkHavokAnimation, gkstudio无法编译。

而相当多的朋友，使用的是vs2010之后的版本，所以导致编译无法进行下去。

其实解决方案相对简单，找一个vs2010安装，然后在vs2013等高版本选择vc100的工具链编译即可。不过这不免需要多下载安装一次工具链。

vs编译是经由msbuild来做的，那么理论上我们只用把msbuild系统及其依赖的sdk拷贝到相应位置即可。

如果能直接剥离出来一个独立的工具链安装包，相信所有朋友都愿意一试的。

所以，周末开始利用一台只安装vs2013的计算机来进行实验。

# ·

最终选择了在虚拟机中安装一个纯净的win7系统，然后单独安装一个vs2013

&gt; 这里不得不提一下虚拟机的快照功能，确实太好用了。这种系统级的文件操作，有了快照完全就和玩游戏一样嘛！

然后，开始拷贝msbuild, 依赖的vc, crt, atlmfc, win sdk等... 最终精简下来，需要部署的内容大致有：

```
windows/microsoft.net/assembly/microsoft.build.cpptask.common
windows/microsoft.net/assembly/microsoft.build.cpptask.win32
windows/microsoft.net/assembly/microsoft.build.cpptask.x64

program files(x86)/microsoft visual studio 10.0/vc
program files(x86)/microsoft visual studio 10.0/ide/下的几个工具

program files(x86)/msbuild/microsoft.cpp/v4.0下的工具链

program files(x86)/microsoft sdks/windows/v7.0a
```

最后，再在注册表的HKLM/software/syswow64node/microsoft/msbuild | visualstudio | windows sdks几个项目下面添加一些注册表项，整个工具链就可以安装成功了

这时候，再打开vs2013，选择do not upgrade，就可以用vc100工具链编译工程了。

# ·

最终，我再删除了一些没有用的win sdk, vc sdk的库后，写了bat安装脚本，打包。最终的vc100工具链安装包打包大小为50+mb。相当精悍了。回头写篇wiki放在gkENGINE的repo里，给大家方便。

# ·

最后有一点ps，由于我是win7 64bit操作系统，所以没有对xp和win7 32作测试。
可能需要修改一下注册表和路径位置，使用32位的朋友可以自行尝试修改，ok之后可以提到wiki上，谢谢！</content:encoded><category>tech</category></item><item><title>一个不相关领域的私活</title><link>https://gameknife.github.io/life/2015/03/20/private-work/</link><guid isPermaLink="true">https://gameknife.github.io/life/2015/03/20/private-work/</guid><description>前些天受朋友之托，让帮找人开发一个系统。需求是: 找了一圈朋友没有做这个事情的，自己分析了下。后台如果用php+mysql来做，操作很简单。主要工作在于前端，要进行不同平台，分辨率的适配。</description><pubDate>Fri, 20 Mar 2015 00:00:00 GMT</pubDate><content:encoded>#### 1

前些天受朋友之托，让帮找人开发一个系统。需求是: 
&gt; 在场地查勘，将查勘结果在手机或平板电脑上，按照一定规则填表(填表包括文字和拍摄的照片)，将填好的表格上传备份。其他人员在后台可以检索提交的表格。

#### 2

找了一圈朋友没有做这个事情的，自己分析了下。后台如果用php+mysql来做，操作很简单。主要工作在于前端，要进行不同平台，分辨率的适配。

正好前些天使用github.io搭建了这个blog系统，jekyll其实是蛮类似php的一个生成系统，只不过是静态的。遂自己接下来，争取一天搞完。

说干就干，把本站的git后台直接扒下来（主要是用css和html结构），html直接生搬jekyll生成后的结果，各平台的分辨率适配就搞定了。
接下来，开始搭后台，用php做了下数据库的操作，然后开始往html中嵌入php，统共“提交-页面-列表”三个页面，相互链接。

开发+debug大概就花了2个多小时，真是超乎寻常的效率！倒是最后写服务器环境部署文档又花了不少时间。

#### 3

今天想来，web开发还真是省时省心，debug又快，还稳定安全。要是用传统的c/c++在windows平台用底层开始写，开发量至少得海10倍以上。

术业有专攻，类似这种系统，其实可以两者结合。把app和页面结合起来，native develop和web develop分别发挥专长。
最简单的当然是直接在app中直接包裹一个浏览器来打开页面。也可以在某些地方嵌入页面。

&gt; 例如如果用gkENGINE开发一个类似shaderToy的工具，shader编辑这块，完全就可以交给嵌入的页面，语法高亮，
&gt; 数据分析之类的完全可以交给页面，比起生写文法解析，richText的显示会方便太多。

进而想来，如果完全用web develop来做Editor，是不是更加的脑洞打开了？其实想想也不是不可能，之前在知乎上看见过一个用web实现的和unity3d长的很像的editor，改天可以倒腾来玩玩！</content:encoded><category>life</category></item><item><title>gkENGINE windows平台快速上手指南</title><link>https://gameknife.github.io/tech/2015/03/12/windows-startup/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2015/03/12/windows-startup/</guid><description>windows平台快速部署分为，部署，编译，运行，打包四个步骤。 部署开发平台，安装和准备平台所需的第三方依赖库以及测试资源</description><pubDate>Thu, 12 Mar 2015 00:00:00 GMT</pubDate><content:encoded>windows平台快速部署分为，部署，编译，运行，打包四个步骤。

### 部署

*部署开发平台，安装和准备平台所需的第三方依赖库以及测试资源*

1. 从github / codeplex pull最新版本
&gt; **github GIT** | [https://github.com/gameknife/gkEngine.git](https://github.com/gameknife/gkEngine.git)

1. 运行 init\_engine\_res.bat ，建立引擎目录环境

1. 从资源服务器获取depends.7z第三方依赖库和media.7z资源库
&gt; **depends.7z** | [http://pan.baidu.com/s/1i38C5ud](http://pan.baidu.com/s/1i38C5ud)
&gt; 
&gt; **media.7z** | [http://pan.baidu.com/s/1qWK90Jy](http://pan.baidu.com/s/1qWK90Jy)
&gt; 
&gt; 分别放置于code/thirdparty下和exec/media下

1. 运行hand\_make\_env.bat，部署第三方依赖环境

1. 运行hand\_make\_resource.bat，部署和编译资源

### 编译

*编译gkENGINE*

1. 通过code/engine/solution/gkENGINE_vc10.sln打开工程

1. 选择Develop | Win32编译配置

1. Build Solution

1. 等待编译结束，如果全部工程编译成功，则编译完成，否则请到codeplex上提交issue

### 运行

*测试运行*

1. 使用exec/bin32/gkLauncher.exe打开，自动进入默认的测试用例模块。

2. 模块的配置可以在exec/media/config/startup.cfg中详细配置

3. 在测试用例模块中，方向键左右切换主项目类型，上下键切换当前项目，回车键执行测试用例

4. 使用exec/bin32/gkStudio.exe打开编辑器，可以进行场景编辑。编辑器代码位于code/editor/gkstudio中

### 打包

*打包为二次运行包，隐藏开发资源，剔除不必要文件，以供发布运行*

1. 测试完成后，使用exec/tools/version\_task/build\_version_pc.bat来打包pc版本的运行包

2. 运行包生成完成后，会保存在exec/builds目录下</content:encoded><category>tech</category></item><item><title>Blogging Like a Hacker</title><link>https://gameknife.github.io/2015/03/11/blog-like-hack/</link><guid isPermaLink="true">https://gameknife.github.io/2015/03/11/blog-like-hack/</guid><description>终于可以抛开wordpress那笨重的后台了！然后所有页面都可以由.md来自由掌控。post/page就像写readme一样方便。</description><pubDate>Wed, 11 Mar 2015 00:00:00 GMT</pubDate><content:encoded>## 像一个黑客一样写博客！

终于可以抛开wordpress那笨重的后台了！然后所有页面都可以由.md来自由掌控。post/page就像写readme一样方便。

&gt; What a fucking coders&apos; heaven!

接下来，gallery，然后是cnblogs上的一些文章，统统搬过来吧！</content:encoded><category>tech</category></item><item><title>近期计划</title><link>https://gameknife.github.io/2015/03/11/plan/</link><guid isPermaLink="true">https://gameknife.github.io/2015/03/11/plan/</guid><description>以后的计划和博客就发在这里了，现在对近期的业余时间做一个规划。</description><pubDate>Wed, 11 Mar 2015 00:00:00 GMT</pubDate><content:encoded>以后的计划和博客就发在这里了，现在对近期的业余时间做一个规划。

* gameknfie.github.io 后续开发, 对博客分类，归档
* gkENGINE的后续开发计划，加入到issues中
* 将SoftRenderer整理一下，放一个更易上手的版本到github上
* 将博客园上的部分文章搬到github.io
* 将gallery丰富起来
* 整理gkENGINE的快速上手指南，加入到gkENGINE的wiki
* 整理gkENGINE的资源制作文档，加入到gkENGINE的wiki

---

&gt; ps
&gt; 
&gt; 发现如果将wiki页先post到github.io, 就能插入图片等素材，而后再将md直接同步回wiki就好，这样应该比较方便。不知道是否有人做过类似的同步发布脚本。</content:encoded><category>tech</category></item><item><title>gkENGINE技术特性</title><link>https://gameknife.github.io/tech/2015/03/11/tech-feature/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2015/03/11/tech-feature/</guid><description>a cross-platform game engine gkENGINE的技术特性 gkENGINE截图 gkStudio  ↓</description><pubDate>Wed, 11 Mar 2015 00:00:00 GMT</pubDate><content:encoded>![placeholder](../../assets/blog/gkengine_logo.png &quot;logo&quot;)
========

a cross-platform game engine

gkENGINE的技术特性
---

##### 渲染

* 延迟光照 &amp; 延迟着色管线
* 准·基于物理的着色技术
* shader条件编译系统
* 现代后处理技术：HDR, SSAO, DOF, GODRAY, COLORGRADING
* 多线程渲染：渲染提交在单独线程，与其他事务并行
* 多渲染API：支持DX9，GL3，GLES2渲染api并灵活切换
* 多LOD层级地形系统
* TIME OF DAY：全天候环境参数插值

##### 系统

* 跨平台开发：底层基础库通过部分操作系统特例化，实现完全的平台无关开发
* 多种操作系统支持: windows, macosx, ios, android
* TASK和TASK分发系统：可将任何独立事务包装为TASK供分发器进行多线程执行
* PAK文件系统：将文件进行lzma压缩打包，pak系统提供接管文件系统，做到无缝切换
* gameobject系统：将对象抽象为gameobject，组合gameobjectlayer实现灵活的功能组装和扩展

##### 物理&amp;动画

* 物理模块通过接口抽象，插件式集成，同时支持havok及physx。暂未自主开发物理引擎
* 骨骼动画模块通过接口抽象，可接入havok动画引擎驱动，提供强大的多线程高效动画驱动能力，自主开发的骨骼动画模块正在筹划
* 内建trackbus动画模块，用于驱动各类gameobject，实现cutscene等功能

##### 工具链

* gmf模型格式：对obj进行二进制优化，拥有绝佳的加载速度及与obj文件互转的能力
* gkMaxPort：插件与脚本结合的3dsmax工具包，可快速整理模型，处理纹理及材质，导出到引擎，直接测试
* 资源编译器：针对多种运行平台，对纹理，材质，模型文件进行特定生成和处理
* 各类辅助的事务处理脚本：方便进行打包，垃圾清理，设备部署，跨平台调试等工作

##### 编辑器

* 基于MFC的编辑器框架，提供关卡场景开发，模型预览，材质编辑，动画编辑，角色编辑等功能

##### 其他

* 支持oculus rift dk1/dk2,
* 支持3d显示，支持左右分割/上下分割3d模式

gkENGINE截图
---

**gkStudio  ↓**

![placeholder](../../assets/blog/gkstudio.jpg &quot;gkStudio截图&quot;)

**gkEngine室外效果  ↓**

![placeholder](../../assets/blog/out1.jpg &quot;gkEngine室外截图1&quot;)
![placeholder](../../assets/blog/out2.jpg &quot;gkEngine室外截图2&quot;)

**gkEngine室内效果  ↓**

![placeholder](../../assets/blog/indoor1.jpg &quot;gkEngine室内截图1&quot;)
![placeholder](../../assets/blog/indoor2.jpg &quot;gkEngine室内截图2&quot;)
![placeholder](../../assets/blog/indoor3.jpg &quot;gkEngine室内截图3&quot;)</content:encoded><category>tech</category></item><item><title>Hello World</title><link>https://gameknife.github.io/2015/03/10/hello-world/</link><guid isPermaLink="true">https://gameknife.github.io/2015/03/10/hello-world/</guid><description>开源 gkENGINE 之后才发现 GitHub 上有这么多好项目和好用法——本站的第一篇。</description><pubDate>Tue, 10 Mar 2015 00:00:00 GMT</pubDate><content:encoded>与github真是相见恨晚，多亏下决心开源了gkENGINE。才发现github居然有这么多牛叉的项目和牛叉的用法。</content:encoded><category>tech</category></item><item><title>Tile-Based架构下的性能调校</title><link>https://gameknife.github.io/tech/2014/02/28/opengl-insight/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2014/02/28/opengl-insight/</guid><description>by Bruce Merry GameKnife译 在大概1个月之前，花了两个小时的时间阅读了OpenGL Insights上的两篇关于移动平台GPU的优化文章。当时正巧在公司作移动渲染器的优化和整理，顿觉醍醐灌顶。…</description><pubDate>Fri, 28 Feb 2014 00:00:00 GMT</pubDate><content:encoded>-  转自我的cnblogs

## Performance Tunning for Tile-Based Architecture
## Tile-Based架构下的性能调校

by Bruce Merry

GameKnife译

&lt;br&gt;
&lt;br&gt;

### 译序                       

在大概1个月之前，花了两个小时的时间阅读了OpenGL Insights上的两篇关于移动平台GPU的优化文章。当时正巧在公司作移动渲染器的优化和整理，顿觉醍醐灌顶。同时搜索国内关于这方面的经验文正或者翻译文章，感觉少之又少。所以，萌发了翻译这两篇文章的想法。其实也是第一次做翻译工作，本觉读两篇文章就用两个小时，翻译大概一天就够了把。 结果第一天写了一下午，也就翻译出了一个开头，发现这不是一件容易事，因此干脆就慢慢来翻译。

终于，一个月之后，总算翻译到了最后一章。全文翻译下来，才发现第一次阅读其实读漏，读错了很多东西，同时，在开发实践的过程中，又对文中的很多章节有了更深刻的理解。因此在某些地方加上了自己的译注。

最后，把这篇译文分享出来，希望能给在移动GPU平台开发图形程序的朋友一些帮助。

 

### 引言                      

OPENGL 和 OPENGL ES标准描述了一种虚拟渲染管线，这个管线将三角形的处理规定为如下流程：

* 变换三角形的顶点
* 对变换后的顶点进行光栅化，生成像素
* 对像素进行着色，写回帧缓存(framebuffer)
* 然后按照这个流程进行下一个三角形的处理，如此往复。然而，这对GPU的工作来说，并不是一个高效的流程，GPU会经常的重排和并行化这些过程以达到更高的效率。

在这篇文章中，我们会详细阐释tiled-based渲染方法，tiled-based架构在主流的移动平台GPU上是一个通常的流行图形管线组织方式。我们会关注：什么是tiled-based渲染，为什么要用它，以及我们需要做些什么（注: 相对于传统的立即渲染方式）来达到效率的优化。

我们假设阅读者是这样的人：

* 已经拥有优化OPEN GL程序的经验，熟悉标准优化技术，例如减少状态切换，减少DRAWCALL，降低shader复杂度以及纹理压缩。
* 想要得到关于tiled-based GPU上的优化建议。
* 有一点，需要牢记于心：每一个GPU, 每一个图形驱动， 每一个程序都是不同的，具有不同的性能特征。基本上，性能调校是一个不断分析和实验的过程。因此，这个本文几乎不会提供“立竿见影”的金科玉律，但是会尝试阐释出如何通过不同的手段来估算性能开销，最终帮助读者选择优化方法。

本文是主要是介绍如何最大化程序性能的，但由于tiled-based GPU是移动设备的主流，我们也会提到一些电量消耗的控制。大多数桌面程序的目标是简单的想尽一切办法在一秒内渲染更多的的帧，始终100%的消耗可以使用的电能（注：俗称跑满）。而在移动平台上，谨慎的将帧率限制在一个合适的水平，可以节省电能会有效的延长电池寿命，同时会相对的提高用户体验。当然，这并不意味着我们可以在达到了目标帧率后就可以放弃优化：越多的优化会给系统更多的休眠时间，这会更加节省电能。

最后，这篇文章的内容主要聚焦在OPENGL-ES上，因为这是tiled-based GPU的主要市场，我们会偶尔提到桌面OPENGL特性以及他们是如何运转的。

 

### 背景                    

桌面GPU的主要目标是最高性能。而移动GPU则不同，它必须要平衡性能和电能的消耗。设备中电量开销最大的消耗者之一就是内存带宽，相对来说，计算比缓存更加经济高效，但是计算也会产生一些临时数据，越多的数据被抛弃掉，就会消耗越多的电能。

OPENGL的虚拟管线需要大量的显存带宽来支持。我们来举个例子，一个通常的用例：每个像素需要从 深度/模板缓存中读出深度值进行比较，然后将新值写回深度/模板缓存，同时将颜色值写上颜色缓存，按照常规的情况，这里算作传输了12byte(color = 4byte, d/s = 4byte, d/s读写，color只写，一共4+4+4=12byte)。不过，这是在假设没有重复绘制，没有颜色混合，没有多通道算法，没有多采样抗锯齿的情况下。如果加上这些花哨的操作，一个像素传输100bytes是一件轻轻松松的事情。因为最多有4bytes的数据需要用来显示一个像素，这是一个对带宽和电能的过度消耗。实际上，桌面GPU通常都会采用压缩技术来减少带宽消耗，但他仍旧是一个显著的消耗。

 

为了减少这个凶残的带宽需求，大多数移动GPU都使用了tiled-based渲染。在最基础的层面，这些GPU将帧缓存（framebuffer），包括深度缓存，多采样缓存等等，从主内存移到了一块超高速的on-chip存储器上（注：类似cache，十分昂贵高速的存储器，为不导致歧义，on-chip就不再翻译了）。这样，由于存储器on-chip了，他就和计算发生的芯片无限接近，这样，计算芯片就能以远低于常规消耗的电能来读写存储器了。如果我们可以放置一块超大的高速on-chip芯片，那么这篇文章就可以到此为止了... 但是不幸的是，那样会需要太多的硅片了。因此，最终on-chip存储器，或称tile缓存，在有些GPU中小到只能容纳16x16个像素。

这样就带来了一些新的挑战：如何在如此小的一块tile缓存中渲染出高分辨率的图像？解决方案就是将OPENGL的帧缓存切割成16x16的小块（这就是tile-based渲染的命名由来），然后一次就渲染一块。对于每一块tile: 将有用的几何体提交进去，当渲染完成时，将tile的数据拷贝回主内存，如同图表23-1所示。这样，带宽的消耗就只来自于写回主内存了，那是一个较小的消耗：没有d/s，没有重绘的像素，没有多采样缓存。同时，消耗极高的深度/模板测试和颜色混合完全的在计算芯片上就完成了。

现在，我们回到OPENGL API。这个没有根据tile-based架构来设计的渲染API。OPENGL API是典型的立即模式：他描述在当前状态下需要绘制的三角形，而不是提供一个装载了所有三角形和其状态的场景结构。因此，在tile-based架构上实现opengl，我们需要在一帧内收集所有提交过的三角形，并在之后再一并使用它们。相对的，在早期的固定管线gpu上，这一切工作都是通过软件方式完成的。现在大量的可编程移动平台gpu设计了专门的硬件单元来处理这件事(注：比如powervr的tiler等)。对于每个三角形，我们使用gl_position的输出语义来决定哪个tile可能会被这个三角形影响，从而将这个三角形保存进这个tile的区域数据结构。同时，每一个三角形还需要将当时的渲染状态打包，例如：ps的shader，常量寄存器，深度判断方式，等等。当一个tile渲染结束后，区域数据结构就会被用于查找和这个tile相关的三角形以及像素渲染状态。

这样看来，我们貌似将一个带宽的问题挪到了另一个地方：不同于顶点数据立即的被光栅化然后被像素着色，在tile-based架构中，三角形会被保存下来以备之后使用。这样的话，就需要有足够的内存来保存原始顶点数据，顶点着色器的输出结果，三角形索引，像素状态，以及其他的一些在区域数据结构中的内容。我们可以把这些收集的数据称为frame data（ARM文档把这些数据叫做多边形列表polygon lists，而Imagination Technologies文档把他们称为参数缓存parameter buffer）。tiled-based GPU是成功的，因为这些额外的数据读写对带宽需求一般会比我们通过on-chip操作节省下来的带宽开销要小得多。只要提交的三角形保持在一个合适的水平，这个说法就一直是成立的。而过度的细分模型的几何表面，会使得frame data过度膨胀，从而导致tile-based GPU的优势不再，反而由于frame data的高带宽消耗，反而比立即模式更慢。

![placeholder](../../assets/blog/blog-add/mgpu-arch.png)

上面的图表表示了tile-based GPU中数据的流向。最大的带宽消耗在像素处理器和tile buffer之间，他们都位于on-chip存储器上。相对的，下面的图表是立即模式GPU的数据流向，多采样，颜色，深度，模板缓存都位于主存储器上。数据的流向需要经过存储总线。

 

### 清空和抛弃FRAMEBUFFER   

当我们在做性能调校时，关于tile-based GPU最需要铭记的一件事就是：我们正在渲染的这一帧并不是framebuffer而是frame data。他是生成framebuffer的一系列数据：转换过的顶点，多边形，状态切换。不同于framebuffer，这些数据是随着DRAW CALL的增长而增长的。所以保证每一帧正确的终止是非常重要的，否则frame data会变得无穷的大。

当交换双缓冲窗口时，窗口系统会负责交换BACK BUFFER。在EGL和GLX这两种窗口系统中都有允许使BACK BUFFER失效的实现。因此，驱动程序可以在每次交换的时候，将frame data丢弃掉。然后从一张空白的画布开始。（EGL实现中，程序可以设置让后备缓冲保留，在下一节中会详细介绍）。

在使用framebuffer objects的时候，情况就变得更加复杂了，framebuffer objects不存在一个“交换”的操作。特别的，可以考虑使用glClear操作。典型的桌面GPU是立即模式架构的（immediate-mode），意味着他们在三角形准备好的时候就开始绘制像素。在一个立即模式的GPU上，一个glClear的调用就直接将clear值写入framebuffer了，因此这个代价是较高的。程序员们会使用各种技巧来避免这个操作，比如：如果他们知道接下来将会完全覆盖的绘制，他们就不clear颜色buffer。交替的利用半深度空间来避免clear深度缓存。然而，这些技巧只是在以前有用，他们已经被硬件级别的优化给超过，甚至，可能由于和硬件优化冲突而降低效率！(注：这儿的说法实在不能完全苟同...很多避免clear的操作还是很有用的)

在tile-based架构中，防止clear可能对于性能来说是个灾难（注：没错，绝对是一个大灾难，导致你的帧率下降为1/4都有可能），因为每一帧都是构建在frame data中的，清空buffer的时候会简单的释放掉frame data中的已有的数据。换句话说，glClear的性能代价不仅非常的低，而且他还能通过抛弃冗余的frame data而提高效率。

为了得到这个特性带来的全部好处，清空所有应该清空的framebuffer是很有必要的。使用scissor，color write mask或者只clear一部分颜色/深度/模板缓存会阻碍frame data的清空（这里要特别注意，译者吃过这亏，在tilebased gpu上，scissor, stencil这些常见的性能优化手法在这里都是性能灾难…）。最安全和最易移植的方法如代码列表所示。

1. glDisable(GL_SCISSOR_TEST);
1. glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1. glDepthMask(GL_TRUE);
1. glStencilMask(0xFFFFFFFF);
1. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

这些操作需要在每一帧开始前完成，除非窗口系统已经代为操作。当然，mask和scissor如果已经设置正确后，就不必每一次都显示的设置了。（一般不会广泛的使用这两个功能）（注：在PVR硬件上，如果不将COLORMASK归位，CLEAR直接就不会成功...）

上面的讨论都只局限于这个API上：glClear。glClear是一个底层命令，而不是一个高层的hint。如果你的程序需要同时运行在tile-based架构和immediate-mode架构上的话，就会比较尴尬。所以，OpenGL ES开发者需要可移植的性能的话，可以考虑EXT_discard_framebuffer这个扩展，他提供了这个hint。这个扩展的调用是glDiscardFramebufferEXT，他告诉驱动程序：当前bind的这个framebuffer已经没用了，你可以随时把他用来做任何事。tiled-based架构可以通过这个hint来更加显式的释放frame data，同时，immediate-mode架构可以忽略掉这个hint。代码列表23.2展示了应该如何使用这个Hint。

```
const GLenum attachments[3] = { COLOR_EXT , DEPTH_EXT , STENCIL_EXT };
glDiscardFramebufferEXT(GL_FRAMEBUFFER , 3, attachments);
```

Discards操作在framebuffer object，或者说render-to-texture上有另外的作用。当渲染一个3D几何体到一张纹理时，例如生成环境贴图，我们需要使用深度缓存。但是，一旦渲染完后，我们就不需要使用了。这时，我们就可以通过调用glDiscardFramebufferEXT来告诉驱动程序可以释放frame data了。但是这个时候，我们还不用unbind这个framebuffer object，同时tile-based GPU还可能根据这个hint，来决定不把depth从tile buffer拷贝回主内存。尽管还在桌面OPEN GL上还不可用，EXT_discard_framebuffer还对多采样抗锯齿的framebuffer起作用，多采样buffer可以在他解算回单采样buffer时就被抛弃掉，节约带宽。在写作这篇文章的这时，EXT_discard_framebuffer相对来说还比较新，还需要一些实验来确定这些Hints在各种特别的实现下到底有多高效。

 

### 增量式的帧更新                  

对于一个移动相机的3D场景，比如第一人称射击游戏，我们有理由相信每一个像素在每一帧都会改变，所以，每一帧清空framebuffer不会摧毁掉任何有用的信息。但对于更多GUI类的程序来说，有很多类似控件或者消息窗口等不会改变的东西，他们没有必要每帧都重新生成。开发者在tile-based GPU上使用EGL时会经常惊奇的发现，backbuffer不会被保留到下一帧。EGL 1.4允许显式的通过EGL_SWAP_BEHAVIORL来申请保留，但是这在tile-basedGPU上不是默认设置，因为他会降低效率。

要理解为什么back-buffer保留机制会降低效率，让我们重新考虑一个tile-based GPU如何在一个tile内渲染像素。如果framebuffer在一帧开始时被clear掉，tile buffer只需要初始化所有像素为clear color即可。但是如果framebuffer需要保留，tile buffer就需要从原来的buffer中取出颜色，并安放到tile上正确的位置，这是需要大量带宽的。带宽的消耗可以看做是将上一帧图像作为纹理绘制到这一帧。是否使用帧保留，要根据场景的复杂度来决定，如果重新绘制一次这个帧都会比保留帧来得快，那么就选择每一帧都重绘，否则，选择帧保留。

高通提供了一个设备扩展（QCOM_tiled_rendering）来应对这种用例。程序可以显示的指明哪个区域是准备更新的，然后所有不在这个区域的渲染都会被屏蔽掉。GPU只需要处理与这个区域有交集的tile，剩下的区域不会被触碰到，因此framebuffer会被保留下来。这个扩展同样包含了类似EXT_discard_framebuffer的特性，来允许用户显示的指出当前的内容是否需要保留。举个例子，我们考虑一个程序，一个3D渲染的区域，在x,y处，长宽为w x h，将要被完全的替换，剩下的区域都不需要改变。那么我们就可以使用如下的代码，来加速这个过程：

```
glStartTilingQCOM(x, y, width , height, GL_NONE);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glViewport(x, y, width , height);
// Draw the scene
glEndTilingQCOM(GL_COLOR_BUFFER_BIT0_QCOM);
eglSwapBuffers(dpy, surface);
```

注意，我们必须要为EGL的EGL_SWAP_BEHAVIOR设置为EGL_BUFFER_PRESERVED。

### FLUSHING                    

Tiled-based GPU有时又被称作“延迟”的，这是因为显卡驱动程序会尽可能的屏蔽掉不必要的像素渲染。下面的这些操作，会导致framebuffer的内容被强制更新

* eglSwapBuffers 以及和它类似的窗口系统操作
* glFlush和glFinish
* glReadPixels, glCopyTexImage, 和glBlitFramebuffer
* 在当前帧使用遮挡查询
* 使用render to texture的结果进行渲染
* 切换framebuffer的绑定，例如glFramebufferRenderbuffer或者glRenderbufferStorage,又或者切换的RTT，都会导致framebuffer的flush。因为framedata只对原来的attachment有效。

在tiled-based gpu上，下面这个渲染方式的效率会十分低下：

* 绘制一些三角形
* 使用framebuffer的结果
* 绘制另外一个三角形
* 使用framebuffer的结果
* 再绘制一个三角形...
* 当每次需要使用framebuffer结果的时候，都会导致另一次全新的像素着色流程：最坏的情况是每一个framebuffer中的像素都执行一次读取、写入的操作，即使你只需要绘制仅仅一个三角形。因为每一次像素着色流程的消耗都非常高，因此我们的目标是尽量每帧只存在一个像素着色流程。

即使是从GPU中取得framebuffer的内容，这个消耗也是存在的，比如说读取render to texture的纹理颜色或者调用glReadPixels去读取pixel pack buffer，因为每一次的“绘制-读取”操作都会需要像素着色流程再次运行。和桌面平台的立即模式GPU对比，glReadPixels在执行时的消耗基本上可以不用关心。

在某些driver下，glBindFramebuffer也会导致为绑定前的那个framebuffer开启一次像素着色流程。因此，一帧内，每个framebuffer最好只绑定一次。举个例子，考虑一个场景，里面有一些光滑的物体，使用了实时生成的环境图。常规的做法是：在每个物体渲染前，将这个物体需要的场景图渲染出来，然后使用。但是，这里更好的做法是，在绑定最终绘制的framebuffer前，就将所有物体需要的环境图全部生成好，这样能够减少framebuffer的重复绑定，从而提高效率。

除了上述的这些情景，这里还有一个情况会导致framebuffer的强制刷新。由于内存是有限的，所以用于framedata的内存大小会随着这一帧提交的几何体大小而变化，当你一直渲染越来越多的几何体而不进行framebuffer交换或者清空的话，程序最终会导致内存溢出。为了防止这个情况发生，driver最终会进行强制更新，来确保内存不会溢出。这个操作代价是十分大的，不同于交换操作，所有的缓存，包括MSAA缓存，会被写到其他地方，之后再重新加载回来以继续渲染，这会导致一次16倍于常规强制刷新的贷款消耗。

这个情况，就意味着，场景的性能表现和渲染的三角面数量并不是线性相关的。因此，我们不能简单的用小场景来作性能测试，在估计当前程序能够承载的目标三角面数量时，有必要针对这些应用情景作一些检查。

 

### 渲染迟滞                        

由于一帧内的顶点和像素的处理是发生在相对独立的阶段的，应用程序会将CPU处理, 顶点处理，像素处理安排在相邻的三帧中。如下图所示。当一个渲染命令提交后，要在当帧之后的第三帧，渲染结果才会显示出来。

![placeholder](../../assets/blog/blog-add/frame-late.png)

延迟除了会影响用户的操作感受外，还会影响从GPU中往CPU回读信息的操作。同步的查询操作，例如glReadPixels，将会导致CPU挂起强制等待后两帧的返回。即使是异步查询，例如遮挡查询，查询结果最终会被读回，过于频繁的查询调用也会导致CPU挂起等待。

如果可以等待结果返回时再使用，那么 GL_QUERY_RESULT_AVAILABLE这个check就有用了。为立即模式编写的代码一般会断定，查询的结果在一个固定的时间段内一定会得到返回，或者可以等待带更长时间，甚至poll到它返回为止。同样的，如果必须使用glReadPixels的话，从那些已经完成的framebuffer上取得像素颜色而不是当前绘制的framebuffer会极大的提高效率，降低渲染迟滞，同时得到一个相对可接受的查询反馈。

迟滞还体现在另一个重要的地方：当资源在使用时修改资源。一个普遍的例子就是动画mesh，这是一个每帧都需要更新顶点数据的用力。之前的顶点数据可能还在被上一帧的顶点计算单元使用，而这是如果应用程序要更新顶点缓存，那么这块内存必须等待上一帧的使用操作完成后才能被写入。在大多数情况下，drivers通过创建一块额外的内存拷贝来防止等待，（将新值写入拷贝内存，待之前的使用完成后再更新）。但是，在一个内存，贷款都十分吃紧的移动设备上，这个copy-on-write的发生是值得我们关注的。特别的，当频繁更新的资源在一帧多次使用时，这个问题会更加严重，会导致多次的copy-on-write。所以，如果可能，所有的资源更新尽量在资源使用前完成，我们显式的进行copy-on-write的管理，而不是将它扔给driver。

在使用诸如EGL_KHR-image_pixmap或GLX_EXT_texture_from_pixmap这类扩展时需要非常小心，他们会修改操作系统的pixmaps。驱动程序通常不会有更多的自由在内存中移动这些资源。有可能会使得整个系统挂起，或是强制提交所有结果到framebuffer，然后重新加载。

tiled-based GPU通常比立即模式GPU有更高的迟滞，因此，为立即模式GPU优化的代码可能在tiled-based GPU上需要重新优化。

### 隐藏表面剔除                 

立即模式GPU处理重叠物体是用新像素覆盖已绘制像素，这里会有两个多余的消耗：一个是被覆盖像素的着色消耗，一个是被覆盖像素的冗余带宽消耗。在tiled-based gpu上，后一个消耗是不存在的，因为屏幕像素将在完全处理结束之后再写会主内存，但是着色的消耗依然存在。所以，进行高层裁剪，从前往后的组织不透明物体的渲染仍然十分必要，这可以通过硬件的“预深度检测”(early depth test)来提高效率。因为上述这这些问题，在tiled-based gpu上，在CPU排序和GPU着色消耗之间的平衡方式的选择上可能和立即模式GPU有所不同。

PowerVR的GPU家族，拥有像素着色阶段的逐像素表面剔除特性[Ima 11]。在运行任何像素shader之前，多边形会被预处理来决定哪些像素可能对最终的结果有贡献，之后，只执行这些有贡献的像素，剔除掉其他像素。这个剔除方式需要对几何体进行排序，要完成这个优化，像素shader的结果必须要确保能够完全的覆盖他们遮挡的像素。而如下注入带有discard指令的shader，或者使用了Mask，alpha-test, alpha-to-coverage, 颜色混合特性将会屏蔽掉优化，因为他们“遮挡”的像素有可能对最终的图像产生贡献。因此，逐像素表面剔除特性只会对需要他们的物体开启。

当PowerVR系硬件的“隐藏面剔除”功能失效时，还有另外一个选择，使用一个（深度流程）depth-only pass：关闭颜色写入，赋予空的像素shader来生成深度缓存，接下来再使用正确的像素shader来正式的绘制场景。depth-only pass会决定每个可见像素的深度，接下来在真正绘制的时候，只有这些可见像素会被处理（通过预深度检测）。

depth-only-pass技术在立即模式GPU和tiled-basedGPU上对于复杂的着色计算场景来说都是十分有效的优化手段，但trade-off却是不同的。在两种平台上，depth-only-pass都会增加顶点处理和光栅化的消耗。在立即模式GPU上，depth-only-pass会增加额外的带宽消耗，因为深度缓存的访问次数会翻倍。在tiled-based GPU上，depth-buffer的访问很快，不会增加主内存的带宽消耗，但是由于所有提交到frame data的几何体信息都复制了一次，因此这里会有一个较小一些的带宽消耗。因此，对于带宽瓶颈的程序来说，depth-only-pass在立即模式GPU上没有优化效果时，换到tiled-based GPU上也许会有优化效果。

### 颜色混合                       

在立即模式GPU上，颜色混合通常是一个代价很高的操作，因为完成混合需要一个在framebuffer上的读取-写入循环，这个操作发生在相对较慢的主内存中。而在tiled-based GPU上，读取-写入循环完全发生在芯片的快速存储器中，所以这个操作的代价非常小。一些GPU还专门实现了处理颜色混合的硬件，来使颜色混合变得几乎免费，其他的GPU一般都使用shader指令来实现颜色混合。因此，颜色混合会降低着色运算的最大指令数。

需要注意的是，我们只是指出了颜色混合的直接消耗，让一个物体部分透明还带有间接消耗，因为这个物体不能被当做遮挡体了。被物体遮住的像素需要被处理，而如果不用透明，他们本该可以被隐藏面剔除或者预深度检测时被剔除。

### 多采样                         

多采样是一个相对高效的提升画面质量的技术，而又不用牺牲像超采样那样多的代价。每一个在framebuffer上的像素都存储多个采样结果，这些采样结果将在最后生成抗锯齿图像。然而，被光栅化后的图源，每个像素只用被着色一次。但这已经让像素着色消耗变得很大了，在immediate立即模式GPU上，多采样会带来很大的带宽消耗：4次多采样（通常的采样数选择），使得所有对framebuffer的读写操作的带宽消耗提高四倍。多种硬件在选择多采样位置的方法上进行优化，但多采样仍然是代价很高的操作。

相反的，在tile-based GPU上，多采样的代价是很小的，因为多采样的采样点只需要保留在on-chip缓存中，在所有处理完成后才写回framebuffer的主内存。因此，多采样不会带来多余的带宽消耗。

当然，这里依然会有两个消耗代价：

首先，4次多采样需要4倍的tile缓存。由于tile缓存容量相当的宝贵，一些GPU会在开启多采样时，缩小tile的尺寸，以容纳采样点需要的缓存。缩小的tile会带来一些额外的性能开销（每个tile都需要存储更多的图元），但是减半tile的尺寸并不一定会减半性能，所以当程序瓶颈在像素着色时，只会看到一个很小的性能下降。

其次，多采样的另一个开销（在immediate立即模式GPU上也会存在）则是在物体边缘会生成更多的像素。如下图所示，每个多边形会检测到更多的像素。这还不止，这些多采样的区域，前景和背景几何体会同时向同一个像素贡献颜色以供混合，这些图元都需要被着色，因此硬件隐藏面剔除机制不能剔除掉这些图元。这些额外的图元消耗会根据场景的边缘多少而不同，但10%是一个比较好的初始猜测值。

![placeholder](../../assets/blog/blog-add/m-msaa.png)

### 性能分析方法                 

在立即模式GPU上，ARB_timer_query扩展可以用来获取某一段场景渲染的消耗。以个范围内需要分析的渲染命令被glBeginQuery和glEndQuery包裹起来，然后这些渲染命令消耗的时间会被测量，并返回。

尽管这个扩展有可能被实现在tiled_based GPU上，但这个结果可能在帧粒度一下都不会有任何作用。这是因为，在tiled-based GPU上，命令不一定会按照他们提交的顺序来执行：所有的顶点处理会在第一遍完成，然后像素处理会按照tile的顺序依次执行。因此，性能分析需要依靠更多的干预技术，例如切换场景的启用/禁用开关，来观察性能的下降。硬件提供商提供的读取内部性能计数器的特供工具也能为确定渲染流水线的性能瓶颈提供很大的帮助。

不同于后期优化，在开始开发时就引入一个性能衡量基准，来帮系统决定三角形，纹理，着色器复杂度的预算是一个通常的好办法。在这样做的时候，一定要记住，当提交了过多的三角形而没有交换，会导致一个性能消耗的陡然提升，这在第五节中已经提过。同时，确保哪些渲染命令真正被执行了也是十分重要的，在渲染调用后放置一个glClear在他们到达GPU前取消这些渲染调用（因为他们根本不起作用）。

### 总结                              

每一个GPU，每一个设备驱动都是不同的。而在优化和尝试中的不同选择意味着唯一能够真正决定性能瓶颈的选择就是去实际的测试它。然而，下面的这些没有科学根据的规则，可能是在tile-based GPU上发现高效率渲染的一个起点。

* 在一帧的开始清空或抛弃整个渲染内容：包括颜色，深度，模板缓存。
* 对于每一个framebuffer object，一帧中只绑定一次，并且，在解绑或使用framebuffer object的结果之前，要确保所有影响它的渲染指令提交完毕。
* 在使用遮挡查询或其他取得渲染结果的机制时，记得渲染迟滞效应。并且，如果该程序在之前为立即模式GPU优化过，那么，在tile-based GPU上需要重新优化。
* 将三角面数量控制在一个合适的水平，尽量避免大量的精细模型。
* 在拥有内建的隐藏面提出的硬件上（POWER VR），就没有必要消耗CPU计算能力去从前往后的排序不透明物体了，在其他硬件上，这个步骤很有必要。同时，可以考虑快速深度渲染（depth only pass）。
* 利用代价较低的多采样。
* 记住，在移动平台上，性能必须和电量消耗做一个平衡。</content:encoded><category>tech</category></item><item><title>gkENGINE渲染优化</title><link>https://gameknife.github.io/tech/2013/06/11/gkengine-opt/</link><guid isPermaLink="true">https://gameknife.github.io/tech/2013/06/11/gkengine-opt/</guid><description>gkENGINE上次总结了一篇开发总结（上）。然后二进制DEMO发到了OPENGPU上，算是引起了挺多的关注。 效率这个问题被很多大牛提及，因此，正在撰写的开发总结（下）被我停了下来。认真的分析渲染流程，找到性能瓶颈，尝试修改，突破。…</description><pubDate>Tue, 11 Jun 2013 00:00:00 GMT</pubDate><content:encoded>gkENGINE上次总结了一篇开发总结（上）。然后二进制DEMO发到了OPENGPU上，算是引起了挺多的关注。

&gt; 友情链接一下，openGPU帖子：

&gt; http://www.opengpu.org/forum.php?mod=viewthread&amp;tid=15246

效率这个问题被很多大牛提及，因此，正在撰写的开发总结（下）被我停了下来。认真的分析渲染流程，找到性能瓶颈，尝试修改，突破。再分析新的性能瓶颈，尝试修改，突破...

整个优化完成下来，渲染器结构也重构了不少，接下来继续总结渲染流程应该更加得心应手。

&gt; 题外话：gkENGINE之前的二进制DEMO已经上传至项目主页

&gt; https://gkengine.codeplex.com/

gkENGINE 项目已经决定开源，代码正在整理中，准备工作完毕后就上传至codeplex托管，欢迎到时各位朋友捧场，相互学习！

终于，经过两周的业余时间，把DEMO的开场镜头的效率提升到了240%。期间的一些有意思的分析和优化方法，值得记录一下。

 

 

MISSON START                                                                                    
---
当时是一个周末，先在程序中将DEMO中截图的那个镜头锁定了下来。然后开始用Intel GPA这个图形分析工具开始分析。

插一句，GPA是Intel出品的一款图形分析工具。类同DirectX SDK自带的PIX和 Nv著名分析工具PerfHUD。GPA的最大优势是使用便捷，稳定性高。个人认为这是PIX和perfHUD的弊端。因此一般的简单分析任务我都选用GPA来完成。

&gt; 工具的下载页面在这里：http://software.intel.com/en-us/vcsource/tools/intel-gpa

不过要说的一点是，虽然GPA已经更新到了2013 R2版本。但是个人觉得最好用的还是历史的4.3版。对nvidia, ati的显卡支持得不错。后继版本的支持就显得不那么友好了。并且坑爹的是，好像Intel官方不再提供以前的版本，需要在其他网站搜索下载...

接下来，便得到了精确的GPU时间数据和各阶段的详细资源：

![placeholder](../../assets/blog/blog-add/10221141-ed14483323a544f89394b02b80427333.jpg)

&gt; *渲染效率：GTX560  104frame/s   9.6ms/frame*

这是对0425DEMO版本，第一个镜头的GPA时间分析。简要分析，将一些不太合理的瓶颈标红，如下：

* SSAO：全屏的AO计算，占用了整体场景着色时间的一半。
* SHADOWMASKGEN：阴影mask的生成，总体消耗的时间和SSAO等同。
* POSTPROCESS：FOG, HDR, DOF等后处理效果，消耗时间也很高。
* REFLECTMAP:  反射图的生成，在此场景中没有水面，REFLECT MAP的生成应该被裁剪掉。

 

第一轮，与分辨率的战斗
---
以上的瓶颈，都是后处理算法，都是像素计算密集型的渲染算法，因此，如果能够在基本保证渲染质量的情况下，显著的降低像素计算复杂度，那么性能将会得到成倍的提升。

##### 1.SSAO优化
SSAO的shader，汇编指令169, 11个采样指令。1280x720分辨率，每帧执行92W次。根据场景复杂度，优化了一下旋转纹理的采样方式，使用半分辨率的SSAO处理（降采样）。可以将计算量降低为1/4，而质量下降很低。
##### 2.ShadowMask优化
采用相同策略，使用半尺寸渲染，将计算量降为1/4。不过这时在后面的着色阶段，会产生渲染错误。

![placeholder](../../assets/blog/blog-add/10230714-29e84348faa6456880d594dbec32723e.jpg)

如图，由于MASK采用半尺寸，因此在全尺寸的着色阶段，会由于线性采样，在阴影和受光的交界像素处采样到非阴影值（树干的边缘，后面的茅屋阴影有非阴影白边）。

解决的办法是，在着色阶段，在采样点都右下方像素多采样一个阴影值，两者取最小值作为当前像素的阴影值，过滤掉这个渲染错误。这个解决方案也有必然的弊端：可能会在本身没有阴影的地方产生一定程度的阴影黑边。但是相对白边，黑边造成的瑕疵完全可以接受。
##### 3.POST PROCESS优化
之前的POSTPROCESS有很多RT来回倒腾的操作（紫色的矩形块）。经过RT的合理分配和顺序调整，可以去掉一些RT STRETCH的操作，提高POST-PROCESS的效率。

##### 4.终极优化 - 可变渲染分辨率
之前都是针对各种特性渲染的降采样，来降低像素计算压力。但是在移动平台上，提升依然不是太明显，因此，需要一个终极的解决方案。经过PHOTOSHOP里测试，使用3/4的尺寸渲染，然后“放大”到全尺寸。最终的画面质量下降不是太大。但是像素计算量能直接下降为接近1/2。带来的性能提升是十分显著的。因此在之前的texture管理器中添加了一个scale属性。对除BACKBUFFER之外的所有纹理进行降采样处理。渲染完成后，STRETCH到BACKBUFFER上。

 
第二轮，解决新问题
---
第一轮优化暂告一段落，和分辨率的斗争结束了。渲染效率直接提升了接近100%。当时就在OPENGPU上补充了一个跟帖，效率的确是提上去了，不过也有坛友提出质量下降的问题。

##### 1.为低分辨率渲染添加锐化pass

![placeholder](../../assets/blog/blog-add/11123125-9b4e09866b8248b19a9369ceb3edd615.jpg)

如上图所示，的确，使用3/4的渲染分辨率，少了近一半的像素，质量下降难免，如坛友所说，结果像是图片经过了质量压缩。但是这个下降是否可以再补偿回来一些呢？

经过一些调研，轻微的尺寸缩放可以通过锐化来补偿。于是开始尝试之前做过的锐化算法，将图像进行微弱的高斯模糊，再利用模糊结果和原图进行线性外插，强化像素的“反差”，达到锐化目的。

color = lerp( blur, curr, sharpvalue ); // sharpvalue取值大于1

##### 2.使用手动MIPMAP解决地形颗粒感过重的问题

对于有坛友提出的颗粒感过重的问题，因为terrian的多层混合是直接在shader中计算的，由于纹理的重复采样是直接在shader总通过frac得出，因此打开mipmap会有采样错误（由于frac计算出的texcoord不连续），之前图省事直接关闭了mipmaping。因此这里用了一个简单的方法，解决这个问题：利用像素的线性深度，手动计算应该采样的mipmap层数（避免使用自动的ddx计算，造成地块间的不连续值），然后使用texlod来取得对应Mipmap的值。

这时再观察GPA的数据。

![placeholder](../../assets/blog/blog-add/11123721-3e6bbe86c8dd47329b33faf7a0e078e1.jpg)

这时之前的几个瓶颈已经被压到合理的消耗范围。渲染时间大部分集中在了shadowmap生成，zpass, general pass上。这算是一个合理的渲染管线消耗分配了。

但是也可以注意到，图标中标黄的两个消耗块，占据了相当大的时间，已经超过了SSAO和SHADOWMASK的消耗。

这两个消耗，就是地形系统中占屏幕像素最大的那个block。分析他们的shader assembly，发现每个像素采样的次数达到了惊人的26次！（zpass 7次, general pass 19次）

 

##### 3.tex2dlod和tex2dgrad的选择

而仔细看却发现tex_ld, tex_ldl的次数却没有那么多，通过搜素发现，原来tex2dlod这个函数可以显式的指定lod层数，消耗比tex2d, tex2dgrad都要大。在GPA中会显示两次采样指令。

因此，将tex2dlod的方式改为tex2dgrad, 手动计算ddx送入插值而非直接指定mip层数。像素采样次数直接砍半。

##### 4.高光纹理合并到diffuse的alpha通道

同时，发现地形纹理使用高光贴图稍微有些浪费，索性直接将高光值做成单色存入diffuse的alpha，砍掉采样高光纹理的消耗。

至此，地形block的采样次数从26次降低到了9次，地形block的渲染消耗直接降到了50%。

 

第三轮，引入混合渲染管线
---
经过前两轮优化，基本上性能已经被榨干至极限了。不开启锐化的情况下，在720p分辨率在GTX560上已经能跑到260FPS以上，接下来想要在保证画面质量的情况下，进一步尝试一下优化，突破点大概只有几点了：

1. 降低DP
2. 降低shader复杂度

对于第二点，要保证渲染效果不变，这将是个漫长的优化-测试的循环过程。

对于第一点，目前渲染管线是Deferred Lighting（延迟光照），CryEngine3使用的是这个方式。

优点：在拥有延迟渲染解耦光照运算的优势下，能保证主光源丰富的材质效果，同时获得一个低带宽开销的渲染流程。

缺点：所有不透明物体需要渲染两次：Zpass一次，输出法线和线性深度，GeneralPass一次，利用生成好的光照数据，和主光源，进行传统的材质运算。

而Crytek在GDC2013的演讲中提出了Hybird Deferred Shading的概念，将之前实现的延迟光照和传统的延迟渲染进行混合，对于普遍材质使用延迟渲染方式，只需要一次渲染调用。而对于复杂材质，和以前一样走延迟光照的渲染流程。

因此，我决定先引入传统的DeferredShading实现，先观察效率，并做成可以实时灵活切换渲染管线的架构，再考虑进一步的混合渲染方式。

而要引入混合可切换的延迟渲染管线，就需要对之前的渲染流程进行改造。之前的流程是

shadowmapgen -&gt; zpass -&gt; ssao -&gt; deferred lighting -&gt; shadowmask -&gt; general pass -&gt; fog/hdr/dof -&gt; msaa -&gt; output

对于传统延迟渲染，大体流程都是需要的，但是在zpass, deferred lighting, general pass阶段是需要有不同的算法流程

于是我将各个阶段抽象成IBasePipe, 通过策略模式，将算法包装进Pipe的派生类，然后主渲染流程调用各个pipe执行，选择对应的pipe实现进行渲染算法的组织。

因此，渲染流程变为了

&gt; pipe[shadowmapgen] -&gt; pipe[zpass] -&gt; pipe[ssao] -&gt; pipe[shadowmask] -&gt; pipe[deferred lighting] -&gt; pipe[general pass] -&gt; pipe[postprocess] -&gt; output

同时，延迟渲染还需要新的zpass和最后的合成pass的shader，同时，因为不能使用独立的generalpass了，因此之前的一些特殊材质效果一定需要损失。

对于G-BUFFER，之前的配置是DEPTH|R32F + NORMAL+GLOSS|RGBA8。而延迟渲染需要记录物体的材质信息，所以至少需要多添加一层ALBETO颜色信息，所以GBUFFER在原有的基础上扩展了一个ALBETO DIF+SPEC|RGBA8的MRT。用于输出ALBETO颜色和单色的高光。

对于物理属性，诸如：FRESNEL等，暂时没有写入。后期考虑压缩NORMAL，在NORMAL中多开辟一个通道来存储。

 

渲染流程改为deferred shading后，DP少了一半，G-BUFFER的带宽压力增加了50%。但总体下来效率还是提高了5%左右。

可惜的是，deferred shading要求更加统一的材质属性设置。所以，之前为延迟光照设置的材质属性，在延迟渲染下表现有了差异。考虑到提升并不明显，因此还是默认使用deferred lighing渲染管线。

 

 

 

MISSION ACCOMPLISHED                                                                  
---

任务完成，总结一下最终的效率。

###### 渲染配置：

1280 x 720分辨率, 35.9W三角面, 362 DrawCall

###### 特效全开

0.75的渲染尺寸，ssao, shadowmask一倍降采样

###### 测试结果：

测试平台	帧率	帧时间
Intel i5 2500K &amp; Nvidia GTX560	241FPS	4.14ms
Intel i7 3720QM &amp; Nvidia GT650M	140FPS	7.14ms
Intel i5 2500K &amp; Intel HD Graphics 3000	30FPS	33.33ms
 

 

 

效果对比：

![placeholder](../../assets/blog/blog-add/11193150-6b5ac60ba21d44dc90354890826f8bb3.jpg)

最下面的两张渲染结果分别是全渲染尺寸 和 shadow, ssao全尺寸的渲染结果。基本代表了优化开始时的渲染质量。</content:encoded><category>tech</category></item></channel></rss>