Create a Cube Sphere in SceneKit

 • 

前言

Cube Sphere 是一种奇特的球体,不同于 Geodesic Sphere 和一般 Sphere,更像是一个 Cube 通过某种变换得到的 Sphere。

Cube Sphere 有个好处,就是更适合来做过程生成的星球,因为每个面都可以用 QuadTree 无限细分(详见:https://acko.net/blog/making-worlds-4-the-devils-in-the-details/

过程

1.新建一个 SCNBox
2.对其修改顶点(vertices)
3.重新计算 Normal
4.刷新 SCNode 的 Geometry

1.新建 SCNBox

SCNBox *SCNBoxToSphereMapping = [SCNBox boxWithWidth:60.0f height:60.0f length:60.0f chamferRadius:0.0f];
    SCNBoxToSphereMapping.widthSegmentCount = 16;
    SCNBoxToSphereMapping.heightSegmentCount = 16;
    SCNBoxToSphereMapping.lengthSegmentCount = 16;
    
    SCNNode *PlanetNode = [SCNNode nodeWithGeometry:SCNBoxToSphereMapping];
    [PlanetSceneKitView.scene.rootNode addChildNode:PlanetNode];
    
    [SCNTransaction flush];
  • SegmentCount 是 2 的 n 次方 因为后面(当然不是这篇文章)要做 QuadTree
  • [SCNTransaction flush]; 是很关键的一步,(详见http://stackoverflow.com/questions/17760275/geometry-from-scenekit-primitives?lq=1 简单说来,就是不 flush 的话,获取的 geometry data 就是 SegmentCount = 1 的默认 SCNBox 数据,而默认的 SCNBox 顶点只有八个,不足以变换成球体)

2.对其修改顶点

// Get the vertex sources
    NSArray *vertexSources = [PlanetNode.geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex];
    
    // Get the first source
    SCNGeometrySource *vertexSource = vertexSources[0]; // TODO: Parse all the sources
    
    long stride = vertexSource.dataStride; // in bytes
    long offset = vertexSource.dataOffset; // in bytes
    
    long componentsPerVector = vertexSource.componentsPerVector;
    long bytesPerVector = componentsPerVector * vertexSource.bytesPerComponent;
    long vectorCount = (long)vertexSource.vectorCount;
    
    
    SCNVector3 vertices[vectorCount]; // A new array for vertices
    
    // for each vector, read the bytes
    for (long i=0; i<vectorCount; i++)
    {
        
        // Assuming that bytes per component is 4 (a float)
        // If it was 8 then it would be a double (aka CGFloat)
        
        //xyz 3 componet
        float vectorData[componentsPerVector];
        
        // The range of bytes for this vector
        NSRange byteRange = NSMakeRange(i*stride + offset, // Start at current stride + offset
                                        bytesPerVector);   // and read the lenght of one vector
        
        // Read into the vector data buffer
        [vertexSource.data getBytes:&vectorData range:byteRange];
        
        // At this point you can read the data from the float array
        
        //
        float x = vectorData[0] / SCNBoxToSphereMapping.width * 2.0f;
        float y = vectorData[1] / SCNBoxToSphereMapping.width * 2.0f;
        float z = vectorData[2] / SCNBoxToSphereMapping.width * 2.0f;
        
        float SphereX = x*sqrt(1-pow(y,2)/2.0f-pow(z,2)/2.0f + pow(y*z,2)/3.0f) * SCNBoxToSphereMapping.width / 2.0f;
        float SphereY = y*sqrt(1-pow(z,2)/2.0f-pow(x,2)/2.0f + pow(x*z,2)/3.0f) * SCNBoxToSphereMapping.width / 2.0f;

        float SphereZ = z*sqrt(1-pow(x,2)/2.0f-pow(y,2)/2.0f + pow(y*x,2)/3.0f) * SCNBoxToSphereMapping.width / 2.0f;

        
        // ... Maybe even save it as an SCNVector3 for later use ...
        vertices[i] = SCNVector3Make(SphereX, SphereY, SphereZ);
        
        // ... or just log it
        NSLog(@"x:%f, y:%f, z:%f", x, y, z);
        NSLog(@"SphereX:%f, SphereY:%f, SphereX:%f", SphereX, SphereY, SphereZ);
    }

3.重新计算 Normal

SCNGeometrySource *DeformedGeometrySource = [SCNGeometrySource geometrySourceWithVertices:vertices count:vectorCount];
    NSArray *SCNGeometrySourceArray = [NSArray arrayWithObject:DeformedGeometrySource];
    NSArray *DeformGeometryElement = [PlanetNode.geometry geometryElements];
    SCNGeometry *DeformedGeometry = [SCNGeometry geometryWithSources:SCNGeometrySourceArray elements:DeformGeometryElement];
    
    
    MDLMesh *DeformedGeometryUsingMDL = [MDLMesh meshWithSCNGeometry:DeformedGeometry];
    [DeformedGeometryUsingMDL addNormalsWithAttributeNamed:MDLVertexAttributeNormal creaseThreshold:1.0f];
  • 一个自定义 SCNGeometry 需要两样东西,SCNGeometrySource 和 SCNGeometryElements,SCNGeometryElements 在我的理解中就是用来描述顶点之间连接成三角形的顺序。当然实际肯定并非如此简单,只是便于理解我们只是修改了顶点的位置,但连接顺序并没有改变,因此 SCNBox 的 SCNGeometryElements 是可以继续用在新的 Sphere 上的
  • MDLMesh 是 Model I/O 的东西,用的时候和 SceneKit 一起记得导入以下头文件:
#import <SceneKit/SceneKit.h>
#import <ModelIO/ModelIO.h>
#import <SceneKit/ModelIO.h>

4.刷新 Geometry

DeformedGeometry = [SCNGeometry geometryWithMDLMesh:DeformedGeometryUsingMDL];
    PlanetNode.geometry = DeformedGeometry;

记得打开 Debug 的线框预览功能

PlanetSceneKitView.debugOptions = SCNDebugOptionShowWireframe;

效果如下:

Build libnoise on iOS!

 • 

libnoise 是一个 Cpp 的噪声库 写了一个简易的教程 怎么把这个东西做成一个可以 iOS 用的 lib

1.Download libnoise from http://libnoise.sourceforge.net/
2.Navigate to libnoisesrc-1.0.0/noise/src, and select files like this:

No makefile, just .h and .cpp files, and don't select win32 folder, you are building in Xcode.

3.On your current Xcode project menu, Select File/New/Target/iOS/Frameworks & Library/Cocoa Touch Static Library, hit next and name it "libnoise" or whatever :-)
4.Now, Drag files you selected in Finder to Xcode like this:

Things to remember to check:

  • Copy items if needed
  • Create Groups
  • Add to targets, just select the new library we have created.

And now our project will looks like this: (I made a group from these files so you will see a top folder named libnoise)

5.Switch your target to libnoise (or whatever you named it when creating library target), we will check something.

6.Navigate to Build Phases/Compile Sources, see if all .cpp files are in there.

7.Jump back the target to your app, make sure Build Phases/Compile Sources have these:

  • Xcode's target are on your app
  • Target Dependencies have libnoise
  • Link Binary With Libraries have libnoise.a

8.Add libc++.tbd in General Tab, or in Build Phases.
9.Create a UIViewController (like NoiseDebuggerViewController), and change NoiseDebuggerViewController.m to NoiseDebuggerViewController.mm
10.In NoiseDebuggerViewController.mm:

  • import "noise.h"

  • paste these codes to viewDidLoad():
noise::module::Perlin myModule;
double value = myModule.GetValue(1.25, 0.75, 0.5);
NSLog(@"Value : %f",value);

and Press Run, make sure you have real iDevice plugged in instead of Simulator.
See the Log!

Value : 0.686347

Now you have libnoise on iOS! We are ready to use it for procedural planet generation on iOS!

Epoch Dev Blog 4

 • 


👆用来提交崩溃的 Bug Report 都崩溃

Unity 关于 PrepareShadowMaps 还有 Occlusion Culling 崩溃的 bug 在5.3的最新 Patch 版本上仍然存在 让我的进度延缓了不少 因为没办法实时 Editor 预览了 只能自己写一堆东西 试着 build 到 iPhone 上看
所以最近没做什么事情 大致有这几个
1.加了God Ray(Sun Shafts)

2.让游戏根据不同机型调整分辨率 比如 iPad mini 2 这种 A7+大屏Retina 的组合 就运行在 0.75倍率原始分辨率上 然后更老的机型就使用Non-Retina
3.改进了SpeedTree的树木,准备了很多花花草草还有大树

4.研究了下 Noise 图生成 HeightMap 后怎么在 Runtime 生成 Splatmap 留给地面贴图用
5.试着重写树木生成的脚本(但失败了),等脑子清醒并且 Unity 不会那么容易崩溃的时候再试着重写一遍 主要是准备直接从 HeightMap 中 GetPixels 后 把根据 Alpha 值随机出的树木样式数据固化到某个地方 然后做一个 Pool Manager 来管理 Spawn 和 Destroy

多说几句 因为 Unity 工作不正常的原因 最近又看了会 WWDC 学习用了下 ModelIO里面的 Voxel 感觉可以试着用 ModelIO 和 SceneKit 写一个类似的东西(根据噪声的过程星球生成) 如果能写出来的话倒是有个东西可以提交 WWDC 奖学金了

晚安

2016.3.1

 • 

二月底开学 回到了长沙 热得冒汗 感觉都要到了夏天
EPOCH 放在电脑里面总觉得不放心 而且现在两台电脑也不好协作 于是想着用 git 传到服务器上去 但是文件数目太多 即使是用 git shell 也会在 commmit 的时候卡死 于是作罢 改为买 Dropbox 空间 扔到上面自动同步 好处是全自动 坏处是没办法做特定文件的同步规则 以及潜在的被墙风险
生日也过去了 19岁 早晨醒来看到 Twitter 客户端上飘着满屏幕的生日气球 觉得挺好玩 于是多按了几次
Fin GameWorks 算是有了一个小小的网站 http://fingameworks.github.io/ 花了一晚上乱改模板 CSS 做出来的 惨不忍睹 看来有必要学习下前端
最近在看 Vimeo 上面的一些 VFX 视频 比如特效的 breakdown 或者 工作室的 ShowReel 当然也有一些惊人的 SpeedArt 看的人心痒痒 于是跑去申请 Octane Render 的学生证书 但是 OTOY 并不支持国内教育邮箱 对方也一直没有回复

Epoch Dev Blog 3

 • 

最近在做星球的部分东西
昨天和陈叔说 我终于知道我在做什么了
Epoch将会做成一个手机版的 No Man‘s sky,但是并不具备无限的地图过程生成,也没有动物等等,除此以外,Epoch目前在做的功能和No Man‘s sky几乎一样:无缝登陆,过程生成的地表,太空战斗。
无缝登陆这块目前是没有问题,但是在Visual上表现力太差,因为登陆过程要经过一个大气层,这个大气层略高,不好处理地面的物体。No Man Sky的处理方式是加上了一个缓冲用的云层,等云散去,离地表只有一千个单位左右,做大面积的植被也不会有很多性能上的问题。但是我目前没有做这个云层,所以地面树木的生成会被很明显地看到,这个和LOD没有什么关系,就是单纯的突然冒了出来这种效果,因此估计后面我也会做这么一个trick,用云层来阻隔玩家视线,同时生成树木。
地表植被目前只做了树木和草,种类也只有四五种,我也在收集各种SpeedTree,留着后面使用。目前植被生成不是太好,代码也写的很烂,有很多可以优化的地方,主要问题有这几个:
1.草一般是做成billboard,这种模式在第一人称在地面的时候非常有用,可以用很少的物体大面积填充视线。但是这不适用于飞行游戏,飞机是要从天上飞下来的,这个时候billboard的效果就不是很好,因为billboard会让贴图一直面向摄像机所以看上去草角度有问题。解决这个问题我估计只能从改进地面Shader入手,地面贴图先用草,用2K分辨率都不过分,只要能把地面的草给模拟了就好,等到降落后,地面贴图再改成纯泥土,配合很多billboard的草,看起来应该会好些。
2.草不知为什么,并没有被Dynamic Batch掉,结果就是很多draw call,在iPad上运行的效果不是很好。但是我没法做Static Batching,因为我的草并不是真正的静态物体,一旦摄像机掠过草,草就会被移动到前方更远的地方(我还没做pool),保证摄像机看到的是很多草,这样Unity没有办法合并草的mesh,因为都是动态的。
(#Edit : 找到没有 Dynamic Batch 的原因了:我在飞船上加了一个 WindZone 模拟声速飞过的效果,导致 Speed Tree 不能 Batch(对,我的草都是SpeedTree...),关掉风就能看到几乎所有batch都被save了...我一直以为风没有关系)

3.目前树木的生成还不是依据星球的height map,因此是随机分布的,这个其实不难,但我还没写到。
4.海洋的模拟。一般海洋放一个Plane就可以了,但这是星球,是有弧度的,一般的海洋Shader搭配Sphere都会有些问题,我估计解决方式就是,把Sphere拆成一个1/4的弧面,甚至更少,在登陆过程中不断更改mesh,1/4弧面的时候Shader只有反射,保证不是很卡,然后着陆到地平线后再用很小的弧面配合带折射的Shader来做到海洋的效果。
5.天气。这个再说吧,也不是很难做的样子,和树差不多,反正是手机游戏,要求不是很高,没法做很真实,突出风格化就好。

Epoch Dev Blog 02

 • 

回家后战斗力暴跌,基本上是什么也没干。
脑子里有许许多多的计划和项目,但都只存在于我想象中。而每天最多的事情就是发呆,想着这些未竟的计划,真要开始做起来应该用什么方式。
Stillness 的 SpriteKit 重写计划往后推了一段时间 因为我发现 SpriteKit 的光影效果实在是太弱了(当然也有可能是我没有仔细研究 API) 灯光的 FallOff 聊胜于无 而 Stillness 最大的卖点就在于光影,去除光影就没什么了。但我一直在想因为用 SpriteKit 写的话 就可以提交给 WWDC 的奖学金项目,这么一说还是有动力的,只不过我需要花点时间研究下了。
Epoch 倒是开始写起来了,而且进展不错,不过项目一旦发展起来就发现,原来我以前对 Unity 不吃机器的印象也是错的,Unity 是不怎么占用系统资源,但那是相对于虚幻引擎而言。现在每次打开 Unity Editor 都要卡上十秒钟,每次修改完 Script 后预编译也是。
Epoch
而且我遇到了很多奇奇怪怪的问题,比如更新到 5.3.1 后 一个 Shader 彻底不工作,渲染的 Mesh 变粉红色了,我又不会改 Shader 这种东西,只能先替换着。还有各种 Unity 自己的 Bug,以及今天刚遇到的内存泄漏问题(现在 loadLevel 突然就爆内存),然后每次 import assets 漫长的等待(以及假死)让我明白还是不能用 Macbook 做 3D 游戏,等下学期开学还是要买工作站。

先更新到这
放点图






Epoch Dev Blog (1)

 • 

好长时间没有关于 Epoch 的消息了
放寒假后 因为工作站迟迟没有到位 而我的 Mac 性能又不足以坚持去做虚幻引擎 以及一些其他的原因 我就转换回 Unity 工作了 反正整个游戏一直处于原型的状态 两个引擎的原型版本都做过 也没有什么前功尽弃一说
Freelancer 和 Galaxy on Fire 2 这两款游戏都比较好 我在参考他们的游戏模式 顺带一说 GOF2 HD 这款当初给 iPhone 5 的游戏 现在用 iPad 玩 很明显能看出 Skybox 的分辨率不够 而且 后面的星球是一个平面的贴图 甚至都不是一个 Sphere
这两天的工作结果:




修复坚果2.5系统刷第三方Rec变砖

 •  Filed under Smartisan, 坚果

情况如下:
系统挂了 进入不了Recovery
开机黑屏自动进fastboot 自然adb也挂了
不过fastboot devices有显示设备
fastboot flash recovery的时候 出现如下错误:

target reported max download size of 268435456 bytes
sending 'recovery' (16384 KB)...
OKAY [  0.517s]
writing 'recovery'...
FAILED (remote: unknown reason)
finished. total time: 0.522s

解决:

使用fastboot boot 加载一个系统的Recovery 然后进入系统Rec后就简单多了 开启sideload 后刷进去老版本的坚果Smartisan OS

2016.1.11

 • 

现在在北京T2的肯德基里面 吃着第二份餐

swift大会。
见到了KevinZhow,人挺逗,黑人技术和Martin一个级别的,hhhh,其实要不是上学我挺想去他们那写unity。
见到了MartinRGB,把我黑得太惨了。如果暑假Martin还在RavenTech我就去那里实习。
见到了Cee,和菊苣一个房间~。
见到了烧碱,今年估计还有很多机会去杭州hhhh。
此外,见到了梁杰和SwiftGG的很多人,比如爱画画的MMoary,也算是正式加入了很污的翻译组,哈哈哈。
唐巧大大采访了陈叔和Martin(这个过程Martin还不忘黑我,hhh)。
我没买到喵神新书 不过和喵神拍了合照,挺激动的。喵神这次的ppt很有趣 :-) 此外喵神还对stillness提了些建议,感觉可以寒假做个新版本了。
陈叔是作为大会工作人员一直在忙(当然一直和martin互黑),然后今天结束后很早就睡了。

后面的事情。
再过四个小时飞回长沙。
再过一天去补考大物。
再过几天回到北京参加Uber的hackathon。
然后应该是回家。不过也有可能去厦门,某些外包方面的事宜。
还有很多计划中的,个人或团队的Project。
有趣的生活。