2016.9.17

 • 

刚刚检查了下教务系统,补考三门过了两门。(Edit :之前没更新完全,现在看是三门都过了)按照之前的计划,过了两门及以上准备让公司 HR 帮我和学校争取下,争取不了的话只能 Remote。我还在准备另一个事情,如果可行的话理论上可以让我最不济的情况下拿到毕业证。
罢了我为什么要在博客上详细地写这种事情。说些其他的。


第一个事情。有两个小软件上架 App Store 了。之所以说小,因为这两个软件都是一个晚上完工,而且其中一个只有 555 KB。我个人觉得比较有意义的是 Contributions for GitHub,这个软件可以让你在 iOS 的今日视图中查看 GitHub 的贡献图,有二维和三维模式(iOS 10 支持在 Today Extension 使用 SceneKit)。

因为是一晚上写的,有很多地方没有做错误处理。以及我觉得维度切换可以做的更炫酷,这个就等哪天有空修下了。我真应该改下我挖坑不填完的习惯。


第二个事情。这几天闷声写 Epoch,继续优化了 Shader,买了 RTP,地形终于达到了一个我比较满意的效果:



(我知道现在看起来效果还是很简陋,不过东西都会有的,老版本 Epoch 的内容都会渐渐重写到新的工程里)


看了两部电影,星际迷航和 War Dogs。后一部给我的感觉和华尔街之狼非常像,简直就是 Wolf of Wall Street With Guns,我非常喜欢这类题材。以及这部电影的预告片非常带感,可以戳 Youtube

2016.9.11

 • 

Unity Editor
花了两个晚上,把 LibNoise 的 Perlin Noise / Spherical Map Builder 移植到了 Unity Fragment Shader 里面。
毕竟是在 Shader 里面写,修改了一些内容。LibNoise 原先自带的一组 Randoms[1024] 被一个 lookup table 替代了,否则直接嵌在 shader 里会溢出。然后 C++ 版本里面奇奇怪怪的位运算(比如 >>,^=,&=)有的使用了数学等价的代码解决,有的就直接丢精度不管了(LibNoise 原库所有运算都是 double 为基准,这种精度在手机着色器上是达不到的,所以丢失精度也无所谓吧)。
基本功能实现后,速度提升是有的,相比 LibNoise.Net 版本在 iPhone 6S 上提速了至少 50 倍,虽然时间没有上篇文章提到的那样在几十毫秒内,但也基本满足我的要求了。现在要解决的问题就是如何把 LibNoise 的其余部分实现出来,同时保证 Shader 编译后的大小不会太大,速度不会太慢。以及如果可以的话我希望能在 Shader 里面顺便把 ColorMap 和 SplatMap 的生成工作给做了。


回校补考周五结束了。说不上有多好,但我本来也就没有抱多大希望。倒不如说,我盼望着大家都赶紧对我放弃希望,这样我就能去做我喜欢的事情了。
有一个非常有趣的现象是如果你说自己不想干了,大家的态度都是“你都上了两年学了 / 你都寒窗苦读十几年了就差这一两年么 / 你怎么这么急”。倒不论说话者究竟是什么意思,但这般说辞实在是乏味可陈。我实在想不出为何时间一长,任何事情就都成为了一件众人眼里道义上正确的东西。
按照这个逻辑。如果有两个人结婚后才发现不合适,那我应该说什么,
“你俩都结婚一两年了 还差后面几十年么 将就一下 一辈子不就过去了。”
或者说,
“现在不能离婚,等到你俩快死的时候爱怎么折腾怎么折腾。”

glslViewer

 • 

需要使用比较先进的浏览器看。IE 和 RSS 阅读器估计是是没办法了。后面会用这个 js 插件来写一些关于 Noise Shader 的开发内容。
<canvas class="glslCanvas" width="250" height="250" data-fragment="

ifdef GL_ES

precision mediump float;

endif

uniform vec2 u_resolution;
uniform float u_time;

void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
st.x *= u_resolution.x/u_resolution.y;

st += vec2(.0);
vec3 color = vec3(1.);
color = vec3(st.x,st.y,abs(sin(u_time)));

gl_FragColor = vec4(color,1.0);

}" style="top: 10px; background-color: rgb(1, 1, 1);">

Just A Quick Update

 • 

用 Shader 来做 Noise 比 CPU 做快太多了。
举个例子:

CGINCLUDE

		void FAST32_hash_2D( float2 gridcell, out float4 hash_0, out float4 hash_1 )	//	generates 2 random numbers for each of the 4 cell corners
		{
			//    gridcell is assumed to be an integer coordinate
			const float2 OFFSET = float2( 26.0, 161.0 );
			const float DOMAIN = 71.0;
			const float2 SOMELARGEFLOATS = float2( 951.135664, 642.949883 );
			float4 P = float4( gridcell.xy, gridcell.xy + 1.0 );
			P = P - floor(P * ( 1.0 / DOMAIN )) * DOMAIN;
			P += OFFSET.xyxy;
			P *= P;
			P = P.xzxz * P.yyww;
			hash_0 = frac( P * ( 1.0 / SOMELARGEFLOATS.x ) );
			hash_1 = frac( P * ( 1.0 / SOMELARGEFLOATS.y ) );
		}

		float2 Interpolation_C2( float2 x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }

		float Perlin2D( float2 P )
		{
			float2 Pi = floor(P);
			float4 Pf_Pfmin1 = P.xyxy - float4( Pi, Pi + 1.0 );
		
			float4 hash_x, hash_y;
			FAST32_hash_2D( Pi, hash_x, hash_y );
		
			float4 grad_x = hash_x - 0.49999;
			float4 grad_y = hash_y - 0.49999;
			float4 grad_results = rsqrt( grad_x * grad_x + grad_y * grad_y ) * ( grad_x * Pf_Pfmin1.xzxz + grad_y * Pf_Pfmin1.yyww );
		
			grad_results *= 1.4142135623730950488016887242097;		//	(optionally) scale things to a strict -1.0->1.0 range    *= 1.0/sqrt(0.5)
			float2 blend = Interpolation_C2( Pf_Pfmin1.xy );
			float2 res0 = lerp( grad_results.xy, grad_results.zw, blend.y );
			return lerp( res0.x, res0.y, blend.x );

		}
		float PerlinNormal(float2 p, int octaves, float2 offset, float frequency, float amplitude, float lacunarity, float persistence)
		{
			float sum = 0;
			for (int i = 0; i < octaves; i++)
			{
				float h = 0;
				h = Perlin2D((p + offset) * frequency);
				sum += h*amplitude;
				frequency *= lacunarity;
				amplitude *= persistence;
			}
			return sum;
		}

	ENDCG

这段代码,取自 briansharpe,在 Unity 上写成 Shader 后使用 RenderTexture (需要 Unity Pro) 渲染到 Texture 上就可以拿到 float[4096][2048] 的数据了,而且在 Update() 里面可以实时更改种子。而相比之下 LibNoise 的 .Net Port 需要半分钟来做到类似的事情。
这个发现让我很兴奋,因为 LibNoise 实在是太慢了,即使是 C++ 版本作者自己也说生成一个星球贴图需要二十分钟。之前尝试了各种加速生成的方法,甚至开始准备写一个 LibNoise 在 Swift 上使用 Accelerate.Framework 来通过 SIMD 加速的移植版本,但是效果都不是很理想。如果一开始就使用 Shader 来做计算的话,这个坎早就过了。

Epoch Development Blog 6 - Float in Physics Engine Issues

 • 

Unity 的物理引擎使用的基础是 float - 无论是 Vector3,Vector2,Quaternion... 等等,都使用 float。而 float 有一个非常大的问题就是精度丢失,特别是在做 Epoch 这种大尺度规模的游戏的时候 —— 尽管我们可以所有东西都缩小到 0.01 倍,但不可避免的是 float 仍然限制 Unity 内同时出现非常大和非常小的物体 —— 例如星球和战斗机。
如果读过 EVE Online 的 Dev Blogs,会发现他们也提到过类似的问题,Titan 舰长 18 千米(更不用提最近新出的 Citadel ),而小型飞船只有 52 米。在用同样的坐标系的情况下,如何保证两种飞船都能在渲染和物理引擎上有同样的表现,本身就是一件非常有挑战性的事情。
回到 Epoch 上,Unity 的物理引擎使用 float,2^23 = 8388608,一共七位,这意味着最多能有 7 位有效数字。如果我们设定 1 unit = 10 meter,那么整个宇宙最多只能有 10^2 - 10^3 km 的大小。而一个超级缩小版星球的半径为 10^2 km —— 星球自身就已经填满了场景,游戏基本上没办法玩了。有关场景大小的讨论还可见 Unity: coordinates and scales

Vector3(100,100,100) 100
Vector3(10000,10000,10000) 100
除此以外,对于物理引擎来说,小数位是绝对不能低于 3 位的,否则就会出现各种 jittering,比如摄像机追踪飞船的时候,飞船本身如果使用 AddRelativeForce() 进行加速,就会有非常剧烈的抖动(如图),并且也不能通过 Rigidbody.Interpolate 或者 Vector3.SmoothDamp 消除,这对于 Motion Blur 是完全不能忍受的。而要保证小数位是不低于 3 位,整数位也就只有 3 - 4 位可以用了 —— 场景大小瞬间砍去一半。 所以,解决方法就是 The Floating Origin Solution —— 即之前提过的 World Streamer 使用的方法:隔一段距离就重置世界坐标到新的 (0,0,0)。这个方法的描述可见 [Unite 2013 - Building a new universe in Kerbal Space Program](https://www.youtube.com/watch?v=mXTxQko-JH0) 以及 [C# - Is a custom coordinate system possible in Unity](http://gamedev.stackexchange.com/questions/110349/is-a-custom-coordinate-system-possible-in-unity)。 [wiki.unity3d.com](http://wiki.unity3d.com/index.php/Floating_Origin) 上有一个现成的示例脚本,其中的操作是在 LateUpdate() 中完成的。这个脚本目前来说唯一的问题就是会影响系统自带的 Trail Render,因为自带的 Trail Render 并没有整体移动的功能,也没有办法让 Trail Mesh 作为某个 Transform 的 child,所以解决办法只能是等学校补考过后自己写一个 Trail Render 好了。

Unity & Doxygen

 • 

三个脚本。
Doxygen 生成的文件位置在与 Assets 同级的 Documentation 文件夹内,Doxyfile 与 GenDoc.command 同级,都在 Assets/FinGameWorks/Scripts/Documentation/ 内


JZDoxygenManager.cs

using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Diagnostics;

namespace FinGameWorks.Editor
{
	public class JZDoxygenManager : EditorWindow{
		
		[MenuItem ("Window/FinGameWorks/Documentation/Gen Doc")]
		static void Init () 
		{
			RegenDoc();
		}
		
		public static void RegenDoc ()
		{
			Process proc = new System.Diagnostics.Process ();
			proc.StartInfo.FileName = "open";
			proc.StartInfo.Arguments = "-b com.apple.terminal "+ Application.dataPath +  "/FinGameWorks/Scripts/Documentation/GenDoc.command";
			proc.Start();
		}
	}
}

GenDoc.command

cd "$(dirname "$([ -L $0 ] && readlink -f $0 || echo $0)")"
doxygen Doxyfile
echo “Generation Done”
sleep 1
killall Terminal

JZ_DocumentationWindow.cs

using UnityEngine;
using System.Reflection;
using System.IO;

namespace FinGameWorks.Documentation
{
	#if UNITY_EDITOR
	using UnityEditor;
	/// <summary>
	/// 文档浏览器窗口
	/// </summary>
	[InitializeOnLoad]
	public class JZ_DocumentationWindow : ScriptableObject
	{
		static BindingFlags Flags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;

		[MenuItem("Window/FinGameWorks/Documentation/Show Doc")]
		static void Open()
		{

		#if (UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0)

		 var type = Types.GetType ("UnityEditor.Web.WebViewEditorWindow", "UnityEditor.dll");
        var methodInfo = type.GetMethod ("Create", Flags);
        methodInfo = methodInfo.MakeGenericMethod (typeof(JZ_DocumentationWindow));

		#elif UNITY_5_4

		var type = Types.GetType ("UnityEditor.Web.WebViewEditorWindowTabs", "UnityEditor.dll");
        var methodInfo = type.GetMethod ("Create", Flags);
        methodInfo = methodInfo.MakeGenericMethod (type);

		#endif

			string path = Directory.GetParent(Application.dataPath).FullName + "/Documentation/html/index.html";
			methodInfo.Invoke(null, new object[]
			{
				"Documentation",
				path,
				200, 530, 800, 600
			});
		}
	}
	#endif
}

Unity Inspector Custom Tabbar

 • 

效果类似于 Terrain Inspector 的顶层 Tabbar,不过按钮之间有间距,这个如果需要完全一样也可以改 GUIStyle 实现。

public class JZSpaceShipControllerEditor : Editor
	{
		/// <summary>
		/// 飞船控制器编辑器 Tab 类别枚举
		/// </summary>
		enum JZSpaceShipControllerEditorTab
		{
			General,
			Control,
			Weapon
		};

		/// <summary>
		/// Tab Button Inactive State 样式
		/// </summary>
		private static GUIStyle ToggleButtonStyleNormal = null;
		/// <summary>
		/// Tab Button Active State 样式
		/// </summary>
		private static GUIStyle ToggleButtonStyleToggled = null;
		/// <summary>
		/// 飞船控制器编辑器当前选中的 Tab
		/// </summary>
		private JZSpaceShipControllerEditorTab currentTab;

		public override void OnInspectorGUI()
		{
			JZSpaceShipController spaceShipController =
				(JZSpaceShipController) target;

			if (ToggleButtonStyleNormal == null || ToggleButtonStyleToggled == null)
			{
				ToggleButtonStyleNormal = "Button";
				ToggleButtonStyleToggled = new GUIStyle(ToggleButtonStyleNormal);
				ToggleButtonStyleToggled.normal.background = ToggleButtonStyleToggled.active.background;
			}

			GUILayout.BeginHorizontal();
			foreach (JZSpaceShipControllerEditorTab tab in Enum.GetValues(typeof(JZSpaceShipControllerEditorTab)))
			{
				if (GUILayout.Button(tab.ToString(),
					currentTab == tab ? ToggleButtonStyleToggled : ToggleButtonStyleNormal))
				{
					currentTab = tab;
				}
			}
			GUILayout.EndHorizontal();

			switch (currentTab)
			{
				case JZSpaceShipControllerEditorTab.General:
				{
					EditorGUILayout.HelpBox("Space Ship Controller",MessageType.Info);
				}
					break;
				case JZSpaceShipControllerEditorTab.Control:
				{
				}
					break;
				case JZSpaceShipControllerEditorTab.Weapon:
				{
				}
					break;
			}
		}
	}

2016.8.31

 • 

在学校上课有三天了,还没看到大一新生的影子。新的学期自己已经大三了。按理说一般大学生这个时候也该确定自己要去做什么了,不过我是再也不想去谈这个事情了 —— 谈到这个总是有和父母不断的毫无意义和结果的争吵,我已经够烦的了。
总之,在我和父母这么长时间的拉扯之后我逐渐明白了,要想达到自己理想的地步,就只能通过吵架来解决问题。听起来很不正常,但是在父母不把自己的观点当一回事的情况下,自己取得的成果总是会被轻视(比如仍然觉得我走了弯路),所以只能通过吵架来取得父母的让步,即使后果是父母伤心,但这是必要的。
人都是趋利的,而获得对自信也算是对自己利好的一种,特别是对于本来就不甚自信的人来说。所以,考虑到在学校参加所谓的软件比赛两次都没有什么好结果,然而去参加黑客松却能有几连冠的时候,我会做出什么决定似乎也没有什么需要解释的地方。

2016.8.22

 • 

在推上吐槽了下 微信 for Mac 2.0,竟然在一天世界的会员通信中被引述作为讨论微信的开头。不鳥萬如一在讨论的最后如是说:“执意要用户使用内建浏览器最终一定是为了实现更好的控制,但如果介意这个,完全不用微信方为正途。”


Star Citizen: 2016 Gamescom Live Alpha 3.0 Demo
Star Citizen 这个视频展示的估计是太空游戏在某种层面的顶尖水平了:星球是真正 1:1 scale 的,而不是 No Man's Sky 里行走面积只有一个地级市那么大的星球(i-Novae Studios 的 Battlescape 其实也能达到类似的效果,而且是最早达到类似效果的
1
,但是毕竟资源不足,开发进度比星际公民慢了很多)。而做到这个,想必在物理引擎和渲染引擎的数字精度上花了不少力气。至于 Unity 中能不能实现类似的效果,我在 Asset Store 里面见过 World Streamer,大概原理是隔一段距离就重置世界坐标到新的 (0,0,0) 并且 Unload 掉无用的场景,这样能避免物理引擎在较大 unit 上出现非常奇怪的精度丢失,但问题是,这个插件仍然是需要在编辑器内预先做好场景然后切割为很多用于 load / unload 的小部分放置于 Unity Build Scenes 里,而不是实时切割。


昨天刚回到家,估计在家呆几天,然后回学校补考。至于什么时候回公司,要看和辅导员,和院里面的老师怎么聊了,然而不幸的是我及其不擅长和大学老师聊天。

  1. 2010 年 i-Novae Studios 就发布过无缝登录的巨大星球登录视频

Epoch Dev Blog 5

 • 

上一次 Epoch Dev Blog 还是三月,中间因为 Unity 在超大 unit 尺度上频繁崩溃的原因几乎就 AFK 了。不过最近 Unity 崩溃的情况有所转好,而且在北京的这段时间几乎每个人看到我的这个 Epoch 原型都在问我为什么不继续开发。但我并不准备在二月的原型上继续开发了,因为当时的代码效率非常低,结构也很乱,现在再看几乎是恨不得重写一遍。
所以我的确重写了 —— 而且我打算分两步走,第一步先基于 Epoch 的核心代码重写一个新的 Epoch Core,包括过程生成的大部分代码,然后生成一个类似于 Mountain 的小工程。然后再基于新的 Epoch Core 填充各类素材,设计故事,等等等。
Teaser Time
最近和 Epoch 比较相关的的事情就是 No Man's Sky 的发售了。差评较多,主要还是集中在核心玩法和优化上:一个具有完整 FPS 视角的太空游戏,大部分时间的操作和 Minecraft 一样,却并没有像 Minecraft 一样直观的反馈,并且不断崩溃。这和很多玩家的想象偏差(也和 Hello Games 在游戏展上演示的效果偏差)很多。过程生成,包括贴图,地形,音效等都只能解决素材问题,但还是不能解决玩法问题。Elite Dangerous 的思路或许更为正常些:它一直宣传自己是一个纯正的太空战斗游戏,而不是什么第一人称射击游戏。