15
Jul 12

The Art of Doing Science and Engineering

这本书是根据Hamming在美国海军研究院授课的课堂录音整理成的,主要内容是用自己做计算机相关研究的亲身经历讲述科研和工程的心得。对这本书感兴趣纯粹是因为Bret Victor同学给了一个6星级评分。看下来感觉还可以(虽然稍微贵了点。。。)。计算机和通信相关专业的同学对Hamming应该都不陌生,86年就因为在数值方法,自动编码系统,错误检测和纠错码的贡献获得了图灵奖。这本书因为只是课堂录音整理,并且估计也没什么Marketing之类的,所以影响力似乎并不大。中间的一些部分如果不是专业出身可能也比较枯燥,所以我想发行量不会很大。好在这门课程全部数字化了,大部分内容都可以在这里找到,有些章节只是节选,如果不想花31刀买书的话,就看这里的电子版吧。95年的最后一学期讲课的全部录像在这里也能下到,感觉那时候思维还很敏捷啊,怎么96年就挂了。。。他还有一个很有名的演讲,You and Your research,这里有一篇还不错的翻译。下面是一些很有意思的摘要(Kindle做这个还是很方便的,只是因为我经常在PC/手机/Kindle上同时看一本书,多设备同步还是有bug,有时候会覆盖掉。。。)

Education is what, when, and why to do things, Training is how to do it.

In science if you know what you are doing you should not be doing it. In engineering if you do not know what you are doing you should not be doing it.

All of engineering involves some creativity to cover the parts not known, and almost all of science includes some practical engineering to translate the abstractions into practice.
Often it is not physical limitations which control but rather it is human made laws, habits, and organizational rules, regulations, personal egos, and inertia, which dominate the evolution to the future.

you must try to foresee the future you will face. To illustrate the importance of this point of trying to foresee the future I often use a standard story. It is well known the drunken sailor who staggers to the left or right with n independent random steps will, on the average, end up about steps from the origin. But if there is a pretty girl in one direction, then his steps will tend to go in that direction and he will go a distance proportional to n. In a lifetime of many, many independent choices, small and large, a career with a vision will get you a distance proportional to \(\sqrt{n}\), while no vision will get you only the distance In a sense, the main difference between those who go far and those who do not is some people have a vision and the others do not and therefore can only react to the current events as they happen.

To what extent history does or does not repeat itself is a moot question. But it is one of the few guides you have, hence history will often play a large role in my discussions—I am trying to provide you with some perspective as a possible guide to create your vision of your future.

In forming your plan for your future you need to distinguish three different questions: What is possible? What is likely to happen? What is desirable to have happen? In a sense the first is Science—what is possible. The second in Engineering—what are the human factors which chose the one future that does happen from the ensemble of all possible futures. The third, is ethics, morals, or what ever other word you wish to apply to value judgments.

Lastly, in a sense, this is a religious course—I am preaching the message that, with apparently only one life to live on this earth, you ought to try to make significant contributions to humanity rather than just get along through life comfortably—that the life of trying to achieve excellence in some area is in itself a worthy goal for your life.

It has often been observed the true gain is in the struggle and not in the achievement—a life without a struggle on your part to make yourself excellent is hardly a life worth living.
Notice I leave it to you to pick your goals of excellence, but claim only a life without such a goal is not really living but it is merely existing—in my opinion. In ancient Greece Socrates (469–399) said: The unexamined life is not worth living.
Indeed, one of the major items in the conversion from hand to machine production is the imaginative redesign of an equivalent product. Thus in thinking of mechanizing a large organization, it won’t work if you try to keep things in detail exactly the same, rather there must be a larger give-and-take if there is to be a significant success.

You must get the essentials of the job in mind and then design the mechanization to do that job rather than trying to mechanize the current version—if you want a significant success in the long run.
We must not forget, in all the enthusiasm for computer simulations, occasionally we must look at Nature as She is.

The Buddha told his disciples, "Believe nothing, no matter where you read it, or who said it, no matter if I have said it, unless it agrees with your own reason and your own common sense". I say the same to you—you must assume the responsibility for what you believe.

"Almost everyone who opens up a new field does not really understand it the way the followers do". The evidence for this is, unfortunately, all too good.

It has been said in physics no creator of any significant thing ever understood what he had done. I never found Einstein on the special relativity theory as clear as some later commentators.

The reason this happens so often is the creators have to fight through so many dark difficulties, and wade through so much misunderstanding and confusion, they cannot see the light as others can, now the door is open and the path made easy.

Hence I expect a lot of trouble until we do understand human communication via natural languages. Of course, the problem of human-machine is significantly different from humanhuman communication, but in which ways and how much seems to be not known nor even sought for. Until we better understand languages of communication involving humans as they are (or can be easily trained) then it is unlikely many of our software problems will vanish.

There are many things we can do to reduce "the software problem", as it is called, but it will take some basic understanding of language as it is used to communicate understanding between humans, and between humans and machines, before we will have a really decent solution to this costly problem. It simply will not go away.

"Is programming closer to novel writing than it is to classical engineering?" I suggest yes!

Give the same complex problem to two modern programmers and you will, I claim, get two rather different programs. Hence my belief current programming practice is closer to novel writing than it is to engineering. The novelists are bound only by their imaginations, which is somewhat as the programmers are when they are writing software.

What you learn from others you can use to follow; What you learn for yourself you can use to lead.

Mathematics is nothing but clear thinking. Mathematics is the language of clear thinking.

Platonic school (most) formalists When rigor enters, meaning departs. logical school intuitionists constructionists fallacies: 1) we do not actually "prove" theorems! 2) many important programming problems cannot be defined sharply enough so a proof can be given, rather the program which emerges defines the problem!

Man is not a rational animal, he is a rationalizing animal.

I often suspect creativity is like sex; a young lad can read all the books you have on the topic, but without direct experience he will have little chance of understanding what sex is—but even with experience he may still not understand what is going

An expert is one who knows everything about nothing; A generalist knows nothing about everything. In an argument between a specialist and a generalist the expert usually wins by simply (1) using unintelligible jargon, and (2) citing their specialist results which are often completely irrelevant to the discussion.

All impossibility proofs must rest on a number of assumptions which may or may not apply in the particular situation. "If an expert says something can be done he is probably correct, but if he says it is impossible then consider getting another opinion."

What you did to become successful is likely to become counterproductive when applied at a later date. "There is never time to do the job right, but there is always time to fix it later." especially in computer software.

Hamming’s rule: 90% of the time the next independent measurement will fall outside the previous 90% confidence limits!

Most of the time each person is immersed in the details of one special part of the whole and does not think of how what they are doing relates to the larger picture. It is characteristic of most people they keep a myopic view of their work and seldom, if ever, connect it with the larger aims they will admit, when pressed hard, are the true goals of the system. This myopic view is the chief characteristic of a bureaucrat.

Systems engineering is the attempt to keep at all times the larger goals in mind and to translate local actions into global results.But there is no single larger picture

The first rule of systems engineering is: If you optimize the components you will probably ruin the system performance.

Rule 2: Part of systems engineering design is to prepare for changes so they can be gracefully made and still not degrade the other parts.

Rule 3: The closer you meet specifications the worse the performance will be when overloaded.

Westerman believes, as I do, while the client has some knowledge of his symptoms, he may not understand the real causes of them, and it is foolish to try to cure the symptoms only. Thus while the systems engineers must listen to the client they should also try to extract from the client a deeper understanding of the phenomena. Therefore, part of the job of a systems engineer is to define, in a deeper sense, what the problem is and to pass from the symptoms to the causes.

The deeper, long term understanding of the nature of the problem must be the goal of the system engineer, whereas the client always wants prompt relief from the symptoms of his current problem. Again, a conflict leading to a meta systems engineering approach!

You may think the title means if you measure accurately you will get an accurate measurement, and if not then not; but it refers to a much more subtle thing—the way you choose to measure things controls to a large extent what happens.

For example, in school it is easy to measure training and hard to measure education, and hence you tend to see on final exams an emphasis on the training part and a great neglect of the education part


08
Jul 12

Real Time Shading Model (4)

这周写点实际游戏中用的ShadingModel。目前的趋势是越来越多的PBS(基于物理的模型),BRDF越来越复杂的情况下,PBS确实会好调些。

先从Uncharted系列的头发渲染模型开始

Thorsten Sheuermann Hair Shading

基于Kajiya-Kay和Marschner模型改进。04年的Siggraph Sketch。头发渲染效果还是不错的,海域2就是直接用的这个模型。同样的,分为Diffuse和Specular两部分。先说Specular。

我们都知道头发是一种各向异性(Anisotropic)的材质,一般来说,人的头发由10w到15w根发丝构成,这样的表面充满了细小的沟槽(grooves or strands)。这样的表面不能用传统的各向同性的模型来计算,会显得非常不真实,所以我们需要引入不同的假设来解决这个问题,由于构成表面的每一个Strand都有无数的法线方向,当光线和这样的表面发生交互的时候,只能假定(否则就要对法线方向进行积分了)产生最大反射的法线向量就是和光线的入射向量在Strand法线平面上的投影重合的法线。这样问题就简化很多了,不过Kajiya-Kay模型使用的不是N,而是半法线H,如下图所示:

T为Strand的切线向量,N为当前Strand的最大法线向量,V是视点方向,L是光照方向。所以Specular分量为:

\(J_s= C_s \cdot (\sqrt{1 – (T\cdot H)^2})^e\)

而03年Marschner提出的模型中,对头发材质高光的模拟更进了一步,注意到实际上有两种类型的高光,一种是直接反射的,一种是穿过最初的进入点又被反射出来的,这种Secondary的Specular是带头发颜色的,并且位置上向发根偏移,并且有闪烁的感觉。

要实现Marschener的模型也比较简单,计算公式和Kajiya-Kay是一样的,只是我们用一张Shift Tangent Texture来对T分量进行偏移就可以打破由于T在表面完全一致造成的相似性,这对提升渲染效果是很重要的。如下图所示:

而要获得Secondary高光闪烁的感觉,可以简单的用一张噪声纹理对颜色进行Modulate:

而Diffuse分量没有使用原始的Kajiya-Kay模型,而是用了Warp Lighting,这是一种模拟次表面散射材质的廉价算法。原始的公式是这样的:

\(J_d = saturate( \frac{ N\cdot L + w }{ (1 + w )^2 }\)

其中w是0-1的一个参数,用来加亮远离光线方向的面。而Thorsten Sheuermann用的是这样的近似方程:

\(J_d=max(0,0.75*N\cdot L + 0.25)\)

所以最终的Shader代码是这样的:

 
float HairDiffuseTerm(float3 N, float3 L)
{
    return saturate(0.75 * dot(N, L) + 0.25);
}
 
float HairSingleSpecularTerm(float3 T, float3 H, float exponent)
{
    float dotTH = dot(T, H);
    float sinTH = sqrt(1.0 - dotTH*dotTH);
    return pow(sinTH, exponent);
}
 
float3 ShiftTangent(float3 T, float3 N, float shiftAmount)
{
    return normalize(T + shiftAmount * N);
}
 
float4 main(PS_INPUT i) : COLOR
{
    // shift tangents
    float shiftTex = tex2D(tSpecularShift, i.texCoord) - 0.5;
 
    float3 N = normalize (i.normal);
 
    float3 T1 = ShiftTangent(i.tangent, N, specularShift.x + shiftTex);
    float3 T2 = ShiftTangent(i.tangent, N, specularShift.y + shiftTex);
 
    // diffuse term
    float3 diffuse = hairBaseColor * diffuseLightColor *
                     HairDiffuseTerm(N, i.lightVec);
 
    // specular term
    float3 H = normalize(i.lightVec + i.viewVec);
    float3 specular = specularColor0 *
                      HairSingleSpecularTerm(T1, H, specularExp.x);
 
    float3 specular2 = specularColor1 *
                       HairSingleSpecularTerm(T2, H, specularExp.y);
 
    // modulate secondary specular term with noise
    float specularMask = tex2D(tSpecularMask, i.texCoord * 10.0f);
    specular2 *= specularMask;
 
    // specular attenuation for hair facing away from light
    float specularAttenuation = saturate(1.75 * dot(N, i.lightVec) + 0.25);
 
    specular = (specular + specular2) * specularLightColor *
               specularAttenuation;
 
    // read base texture
    float base = tex2D(tBase, i.texCoord);
 
    // combine terms for final output
    float4 o;
 
    o.rgb = (diffuse + ambientLightColor * hairBaseColor) * base;
    base = 1.5 * base - 0.5;
    o.rgb += specular * base;
    //o.rgb *= i.ambOcc;
    o.a = tex2D (tAlpha, i.texCoord);    // read alpha texture
 
    return o;
 
    return i.ambOcc;
}

参考文献

1.Hair Rendering and Shading

2.Uncharted2 Character Lighting


01
Jul 12

Real Time Shading Model (3)

总结一下基于物理的模型实现上的主要特点:

  • Energy Conserving Model,能量必须守恒,也就是反射的能量不能大于入射的。大部分经验模型这点都是不满足的,通过加入一些Normalization项可以进行修正,获得更可控的Shading Model
  • Punctual Light,假定光源是无穷远无穷小的,这样只用光源颜色和方向就可以完全定义了。
  • HDR Lighting,光照的结果必须有足够高精度,某种基于物理模型就没有意义了
  • Linear Pipeline,必须使用线性渲染管线,Gamma的处理必须正确,参数纹理的颜色空间要统一。

Normalized Blinn-Phong

原始的Blinn-Phong高光并不符合能量守恒,看下面两张图,上面是没有Normalized的,下面是相同的高光参数下符合能量守恒的效果。可见基于物理的模型更容易调整效果,而不用借助各种奇怪的参数。

\(I_{o}=I_{i}((N\cdot L) k_dC_d + k_sC_s(N\cdot H)^e) )\)

其中\(k_s\le\frac{e+8}{8\pi} \)就是保证能量守恒的因子

Ashikhmin-Shirley

各向异性模型。用来表现一些特殊的金属材质。相比其他的模型,可以更好的控制高光的形状。虽然不是基于物理的模型,但是使用了Fresnel函数来获得更真实的反射系数。同时Diffuse项也脱离了Labertian模型,保证了能量守恒,效果还是很不错的,不过计算代价确实很大。如果要用到游戏里面,肯定要做一些简化。

Specular和Diffuse如下:

\(J_s=\frac{\sqrt{(n_u+1)(n_v+1)}}{8\pi}\frac{N\cdot H\frac{n_u (H\cdot T)^2+n_v(H\cdot B)^2}{1-(H\cdot N)^2}}{(V\cdot H)\cdot max(N\cdot L,N\cdot V)}F_r((V\cdot H),C_s)\)

\(J_d=\frac{28}{23}C_d(1-C_s)(1-(1-\frac{N\cdot V}{2})^5)(1-(1-\frac{N\cdot L}{2})^5)\)

下面是原始论文里面的不同的\(n_u,n_v\)值的效果:

当\(n_u = n_v\)时,A.S模型就退化成了各向同性的模型,下面是原始论文里面的效果比较:

\(J_s=\frac{n+1}{8\pi}\frac{(N\cdot H)^n}{V\cdot H \cdot max(N\cdot L,N\cdot V)}F_r(V\cdot H,C_s)\)

注意Diffuse项现在是和视点相关的,Fresnel的加入使得在不同的角度,Diffuse和Specular的比例有所区别,Fresnel采用的是Schlick的简化函数:

\(F_r(u,\rho_s)=\rho_s + ( 1-\rho_s)(1-u)^5\)

加入Fresnel的效果,看下面这张原始论文的图就很直观了:


23
Jun 12

Real Time Shading Model (2)

继续基于物理的光照模型,先从Oren-Nayar开始,这个模型从一开始就是为了改进对Diffuse分量的计算,所以并没有包含Specular分量的计算。也是基于微表面模型,但是和Cook Torrance模型的假设有一些区别:

  • 假定表面是由一些连续的对称的V型凹槽构成的,每一个凹槽由两个对立的面构成
  • 每一个微表面的面积远大于光的波长,所以不用考虑衍射之类的问题
  • 每一个微表面是一个Lambertian面,有相同的Diffuse反射系数
  • 微表面的面积相比物体整个表面积非常小,所以每个像素可以覆盖足够多的微表面,可以使用统计分布模型建模

Oren-Nayar有分析模型,但是由于计算过于复杂,一般使用的是简化模型:

\(J_{d}=C_{d}(A+B\cdot \max(0,(V^{‘},L^{‘}))\cdot\sin\alpha\tan\beta\)

\(A=1-\frac{1}{2}\frac{\sigma^{2}}{(\sigma^2+0.33)}\)

\(B=0.45\frac{\sigma^{2}}{\sigma^2+0.09}\)

\(\alpha=\max(\theta_i,\theta_o),\beta=\min(\theta_i,\theta_o)\)

\(V^{‘},L^{‘}\)分别是视点向量和光照方向向量在切平面的投影。

最重要的可控参数只有一个就是表示粗糙程度的\(\sigma\)。如果这个参数是统一的,那么计算量最大的A和B两个参数都可以用预计算的方式存储在纹理中。当然也可以以参数纹理的方式,让美术可以精确的控制材质表现,对于某些种类的Diffuse材质,Oren-Nayar效果还是不错的,不过大家基本都是做了各种简化(比如战锤40000,以及这里的一个优化,以及GPU Gems的Warp Lighting)。可见了解这些有数学和物理基础的模型的思想,对解决自己游戏中特定的效果需求还是很有帮助的,凑也得有基础啊。

Strauss

这个模型也是很有影响力的,特别是在金属高光材质上。上面这个右边的角色就是用Strauss模型的。顺便说一下作者后来去了Pixar做工具开发,现在在做Google Earth。主要的目的是在达到和Cook-Torrance类似的效果基础上降低计算开销,并提供更直观的材质调节参数:Smoothness(s),Metalness(m),以及Transparency(t)。和Cook-Torrance一样,也包含了Fresnel系数和几何衰减系数,不过算法略有区别。Strauss包含了Diffuse,Specular以及Ambient项。下面逐项来看。

\(J_d=r_d (1-ms)C, r_d =(1-s^3)(1-t)\)

Diffuse分量会随Smoothness,Transparency,Metalness增大而减小.这在物理上是正确的,但是一个缺点是美术会发现自己画的Diffuse纹理色和最后实际的Diffuse颜色差别很大,不好控制。

下面是Specular了:

\(J_s=\frac{1}{(N,L)}r_s C_s\)

其中\(r_s\)是一个Phong+Fresnel+Gemoetry的混合系数,也是这个模型中最复杂的部分。

\(r_s=r_j(V,R)^e,e=\frac{3}{1-s}\)

\(r_j=\min(1,r_n+(rn+k_j)j\)

\(k_j=0.1,r_n=(1-t)-r_d\)

j是Fresnel系数和几何衰减系数的近似函数,注意都是直接以角度为函数参数的。

\(j=F(\theta_i)G(\theta_i)G(\theta_o)\)

其中\(\theta_i=acos(N\cdot L),\theta_o=acos(N\cdot V)\)

\(F(\alpha)=\frac{\frac{1}{(2\alpha/\pi-k_f)^2}-\frac{1}{k_{f}^2}}{\frac{1}{(1-k_f)^2}-\frac{1}{k_{f}^2}},G(\alpha)=\frac{\frac{1}{(1-k_g)^2}-\frac{1}{(2\alpha/\pi-k_g)^2}}{\frac{1}{(1-k_g)^2}-\frac{1}{k_{g}^2}}\)

这部分计算代价很大,估计只能预计算或者做简化吧。不然只能用作Demo里面。

\(C_s=C_w+m(1-F(\theta_i))(C-C_w)\)

最后是高光色的计算,根据Fresnel系数和Metalness系数,在白色和固有色直接做插值获得。

今天写到这里,下周继续。越发觉得遍历一次学术界的各种模型和原始论文,还是很有必要的。参考文献到最后再来整理吧。


10
Jun 12

Real Time Shading Model (1)

暗黑3到炼狱了,作为一个全身乞丐装的巫医已经打不下去了,等1.0.3版本调低炼狱难度和调高掉率之后再继续吧。这个周末可以继续写博客了,这次想写写Shading Model。不像离线渲染,尽可能的使用精确的物理模型,实时渲染的光照方程一般都比较简单,毕竟一帧只有十几毫秒的渲染时间,当然,随着计算能力的增强,实时渲染也在不断的把离线渲染的算法移植过来,追求物理真实的光照方程。下面先从基本的概念和建模方式开始,然后是一些重要的模型。这次没有时间写代码,不过最近正好想学JavaScripts,所以有时间会把这些模型用WebGL做一个交互Demo。应该会比较有意思。

1.基本模型

在去年的一篇基本概念短文中,提到了图形学采用的光的物理模型,物体的材质属性可以用BRDF函数来表示,这个函数决定了入射光和反射光的关系,求解渲染方程可以获得最终被人眼感知的Radiance。BRDF再细化还有用来建模反射的BTDF,建模散射的BSDF,次表面散射的BSSDF….因为后面不准备写太多公式并且实时渲染的Shading Model就是对BxDF的各种简化。所以下面准备从简单的直观模型和术语入手。能把思想说明白就行了。

BxDF是从数学上给出材质的精确描述,但是如果不要求太精确的描述,我们可以把光线照射物体后的Radiance分为高光分量(Specular)和漫反射分量(Diffuse)。不同的Shading Model的区别就在于这两种分量分别是如何计算,如何线性插值获得最终结果。其中Specular是被物体表面直接反射的光照分量,也就是一个白光照射到蓝色的表面,Specular依然是白色的。因为并没有在粒子层面发生作用。而Diffuse则是光线和物体发生微观层面的物理作用后,被散射和吸收之后再反射出来的,两者的重要区别是Specular依然保持极化(Polarized),而Diffuse由于经过电子被激发等物理作用,反射方向随机,极化方向也是随机的,所以也就可以说失去极化特征了。为了比较直观的理解这两种分量,推荐大家看UDK的一个文档和John Hobble的这篇博客,下面的图片是来自John的博客,里面描述了怎样用单反相机+偏振镜+后期处理获得真实物体的Specular和Diffuse,这样也可以帮助我们更好的理解某种材质。很多直接用照片做贴图的美术同学恐怕对这种处理方式早就很熟练了吧。下面来看看:

a.Original

b. Diffuse Only:

c. Specular Only:

之所以我们能够分离出Specular和Diffuse,就在于偏振滤镜(linear polarizing filter)的作用,玩摄影的同学可能都用过这种滤镜,可以有效降低明亮日光下的高光反射,获得更好的质感。分离算法原理也比较简单,大家可以看原贴。从上面分离的Specular和Diffuse可以看到,金属部分基本是没有Diffuse的,全部颜色都来自Specular。因此我们可以把通用的Shading Model表示成这样:

\(I_{o}=I_{i}(N\cdot L)( k_sJ_s+k_dJ_d ) \)

其中\(J_{s}\)是Specular分量,\(J_{d}\)是Diffuse分量。下面来看看对这两种分量的各种计算方法。

2.Empirical Shading Model

Lambert:最古老的模型,只是建模理想漫反射表面,即假定物体表面全部是平滑的,每个方向的Diffuse反射量是一致的。没有Specular分量。

公式:\(I_{o}=I_{i}(N\cdot L)C_{d}\)

注意N.L其实不是LambertBRDF模型的一部分,只是反射方程的几何因子,\(C_{d}\)是DiffuseColor。Lambert模型一般不会单独使用。通常都是其他模型的一部分。

Phong:1975年发布,属于简单的经验模型,加入了高光分量的计算。Phong模型的高光有强烈的塑料感。并且会有强度突变的问题。不能很好的兼容法线贴图。

\(I_{o}=I_{i}((N\cdot L) k_dC_d + k_sC_s(V\cdot R)^e) )\)

Blinn-Phong:用N.H替代了V.R来计算高光强度。H是半角向量,即入射光方向和观察者视线方向的平均值,Blinn-Phong效率更高,因为H只用每盏光计算一次,而R则是每一点都需要重新计算,并且\((N\cdot H)^{4e} \approx (V\cdot R)^{e}\)。Blinn-Phong的理论基础是Microfacet理论,即认为物体表面分布的大量微表面会向视点方向反射光线,这些微表面的法线和H方向一致的时候反射光线才是有效的。

\(I_{o}=I_{i}((N\cdot L) k_dC_d + k_sC_s(N\cdot H)^e) )\)

Phong vs Blinn-Phong

3.Physically Based Shading Model

前面的模型都是基于经验凑的,下面介绍一些基于物理的模型,这些模型计算上更复杂,但是能更方便调试和修改,对真实的材质表现也更好。现在很多游戏都在积极的使用了,这方面有很多文献可以看,比如使命召唤的SIGGRAPH PPT,Tri-Ace的PPT。先从基于微表面模型最有名的Cook-Torrance开始,这个模型重点也是Specular分量的计算。

关于微表面性质的一些假定是,每一个微表面是一个完美的Specular Reflector,只有朝向半角向量的微表面会对BRDF有贡献。公式如下:

\(J_{s}=\frac{F(L,H)G(L,V,H)D(H)}{4(N\cdot L)(N\cdot V)}\)

其中F是菲尼尔函数,用来代表每一个微表面的反射量。G则是几何衰减系数,用来表示微表面之间的Shadowing和Masking。或者说是Visibility函数,即给定L(入射光方向),V(视点方向)和H,哪些微表面是可见的。D则是表示朝向半角向量方向的微表面的概率分布函数,比较流行的是用Beckmann分布函数,Phong NDF。而菲尼尔函数用的比较多的近似是Schlick的简化函数:\(F(L,H)=F_{0} + (1 – F_{0})(1-L\cdot H)^{5}\)


26
May 12

对游戏开发工具的一些思考

本周主要任务是尽快把暗黑3通关,所以博客就简单一些了。。。随便写一点工具方面的思考,我们都知道开发工具是衡量一个团队制作实力的重要标准,很多时候工具是和团队的制作流程绑定的,人和工具必须匹配,一些使用很先进的商业引擎的团队最后却出现各种低级制作失误。不能不说是团队驾驭不了先进的工具和引擎造成的。所以很多工作室也比较喜欢自己开发工具,虽然前期费点事情,但是后面可以任意定制,根据自己的流程来做各种优化还是非常强的核心竞争力积累吧。做了这么几年的MMO,也算是经历了一轮完整的工具积累了,还是有不少问题值得反思的。

1.概念一致性

目前的工具链,缺乏统一视觉识别规划,所以不同的工具,可能都用的不同的色系,不同的按钮图标,并且在术语一致性上有各种小问题,毕竟是不同的程序/美术/策划在制作不同的工具,而这些工具通常是随着制作需求和流程的完善逐渐长出来的,进度和开发压力一直很大,所以不统一也是比较正常的,不过我想下一版工具一定要统一工具的视觉标识,这样会进一步提高工具的易用度,减少培训难度。

除了视觉标识和术语的一致性,工具设计的一致性也很重要,之前项目中,凡是不好用的工具基本都有一个共有的毛病,就是设计思想不统一,比如做一个事情,需要很多步,这些功能分布在很多切页中,并且不同的情况,要用不同的顺序去点这些功能按钮,一旦出错,就前功尽弃,不熟悉的人很难上手。这些工具往往是由不同的人在不同时期负责不同的功能,而由于缺乏统一的规划,所以很多细节就是按不同的假定去进行。导致做出功能OK,但是非常难用的工具。这也是我觉得工具的设计一定是自上而下进行的原因。

2.视野

好的工具应该首先完成功能,然后能够尽量减少繁琐的操作需求,提高使用效率,这并不容易。所以放宽视野,多参考其他工具的设计是非常有必要的,评估相同的功能,其他商业引擎的编辑器和自己家编辑器的生产率差别是必修课。了解工具设计背后的思想。不光是游戏制作的工具,很多其他类型的工具软件也是很好的样板,以后真的要考虑用用Mac平台下的那些工具了。

3.原型迭代

工具开发也是不小的工作量,之前遇到过几次花费1-2个月人力做出来的工具完全达不到设计目标的情况,所以先在纸上设计好UI和使用流程,纸面推演完成后再PS设计实际的界面,最后再进行原型迭代是不错的流程。但是由于工具开发绝对是一个长期的流程,所以要做好准备完成第一版可用原型后就开始进行不断的检讨优化工作。开发人员最好能实际的和用户一起工作,观察工具使用中的行为,有时候还是能发现不少问题的。

4.需求收集与改进

工具的最终目的是提高制作效率,而由于游戏的复杂性,流程和制作方法总是在不断变化的,所以一定要定期进行新需求的收集,以及现有功能的反馈,比如以前就遇到过这样的事情,一开始做场景编辑器的时候,为了提高客户端效率,模型是需要进行同步操作才能在引擎中使用。由于初期资源量少,同步很快,但是一年之后,模型数量巨大之后,同步一次也需要很长时间了,对美术的工作效率有不小的影响。所以以后做工具,一定会加入固定的工具优化讨论,比如让每个人列出Top5 浪费时间的任务等等。

5.反馈速度

对游戏内容的制作者来说,修改内容->IN GAME的循环需要的时间越短,越能更好的进行游戏的调试,如果策划随便修改一个数据,都需要重新启动客户端和服务器才能看到结果,那制作效率是非常低的。工具一定要提供很稳定,快速的热加载和IN-GAME预览功能,但是这里需要注意的是,视内容的不同,IN-GAME预览不一定要100%匹配。因为要做到100%匹配需要的成本是比较高的,很多时候也是不必要的。举个之前项目遇到的问题,我们的预览器是提供给美术的场景制作人员预览场景的IN-GAME效果的,由于想做到和客户端完全一致,于是这个工具就是客户端的一种特殊编译配置,结果在后期这个工具经常会由于客户端改动一些协议而不能使用,比较影响美术正常的工作,并且对于美术的应用来说,并不需要预览逻辑部分的内容。比较好的方法应该是把效果部分的代码共享即可,这样就把和逻辑层网络层的耦合解开了。

6.并行编辑

并行的效率肯定是比串行高的,我们的场景编辑器一开始就考虑到这个问题,能支持大地图的并行拼图。但是策划工具就比较差了,实际上很多策划的填表工具,用Web化的方式实现是很方便的,这样能比较容易的支持并行编辑。所以工具引入HTML5+CSS3+JQuery技术是很有价值的技术积累。这也是之前项目比较欠缺的。


18
May 12

关于Unicode和字符串处理的一些笔记

这篇文章的主要目的是梳理目前的主流Unicode字符串格式相关知识点,包括历史的演化,一些需要注意的事项。

Pre-Unicode

1.ANSI 标准

是最早的单字节编码格式,兼容英语与欧洲语言的显示。0-127是标准字符集,128-255则引入了Code Page的概念。Code Page可以看作一个映射表,不同的国家有不同的Code Page,对相同的128-255的字符有完全不同的映射关系。但是这种编码显然是无法容纳海量的亚洲文字的。

2. DBCS或者MBCS标准

当一个字节已经无法满足字符编码需求的情况下,这种单双字节编码出现了。根据当前系统的Codepage,会约定当遇到某个范围内的字符时,这个字符会被认为是Leading Byte,标记后面的一个字符也是当前表示的一个文字的一部分,需要一起解析,所以使用这种编码,遍历字符串做处理的时候就要小心了,不能直接用s++,s–,否则会出现字符被截断,和后面的字符形成转义字符之类的东西,造成解析错误。由于历史原因,Leading Byte的处理非常繁琐,你不会想自己实现一个的,所以必须使用_mbsinc和_mbsdec等系统API去处理

3.用MBCS支持国际化要考虑的问题

是的,用MBCS也是可以做国际化的,虽然比较麻烦,但是一些遗留的系统和代码如果改成Unicode太痛苦的话,可能用这种方法也是一个Option。主要注意的东西有下面这些:

  • 使用_mbsinc和_mbsdec去做字符串指针的迭代操作
  • 用_mblen获得字符串长度,比如下面的代码是扫描转义字符的,我们需要对字符串数组进行索引操作,就需要考虑到变长的编码
while ( rgch[ i ] != '\\' )
    i += _mbclen ( rgch + i );
  • 用_getmbcp 获得当前的CodePage
  • 使用_mbscpy拷贝字符串
while( *sz2 )
{
    //注意如果不是X,那么要使用_mbccmp!!
    if( *sz2 != 'X' )
    {
        _mbscpy( sz1, sz2 );
        sz1 = _mbsinc( sz1 );
        sz2 = _mbsinc( sz2 );
    }
    else
        sz2 = _mbsinc( sz2 );
}
  • 正确的字符串比较方式是用系统函数进行,if( !_mbccmp( sz1, sz2) ),如果知道比较的是ANSI字符,则可以直接进行,但是不建议这样做
  • 小心Buffer Overflow的陷阱

比如下面这段代码,如果sz是MBCS编码,就有Buffer Overrun的危险

cb = 0;
while( cb < sizeof( rgch ) )
   rgch[ cb++ ] = *sz++;

正确的做法是这样,注意while里面还需要判断最后一个字符的长度!

cb = 0;
while( (cb + _mbclen( sz ))

或者直接使用_mbsnbcpy( rgch, sz, sizeof( rgch ) );

参考文献:

Unicode

简要的发展历史

Unicode是一种希望一种编码搞定地球所有语言字符的标准。但是由于种种原因,Unicode也经历了多种版本才总算把有多少字符搞明白。以至于到后来的标准和前面完全就不一致了。1988年Becker发布了第一版Unicode草案,提出用16 bit来表示所有的字符(也就是当时人们认为65535个字符就够地球人用了),91年正式标准发布,也就是UCS-2标准,有很多新的系统和框架使用了这种标准,比如QT,Widnows NT,Java等。但是实际使用后人们才发现,16位根本不够。96年,又出现了UTF-16标准。允许2-4个字节的编码,到目前为止一共有109449个字符,其中CJK占了74500个.

在Unicode标准中所有的字符都用一个叫做CodePoint的唯一数字来代表。但是具体的编码方案可以各异,目前最常用的是三种方式

  • UCS-2,把所有的字符用2 Bytes保存,在Windows下wchar_t就是UCS-2
  • UTF-16是用2-4 Bytes来保存,为了效率,兼容Big/Little Endian,通过一开头的FE FF Mask位来指定当前文档的字节序。UTF-16是UCS-2的超集。UTF-16的一个字符,有可能在高8位或者低8位上等于0×0,所以不能兼容ANSI标准。这也是Thompson发明UTF-8的主要原因之一
  • UTF-8,是Ken Thompson在贝尔实验室参与Plan 9系统开发的时候设计的,<128的CodePoint用一个Byte来编码,然后大于的依次根据其大小用2-6个Byte进行编码。因为兼容ANSI,并且只要做比较少的改动,就能正确的处理多语言支持的问题,所以是Unix/Linux/Web的主流编码格式
Bits  Hex Min  Hex Max  Byte Sequence in Binary
 
1    7  00000000 0000007f 0vvvvvvv
 
2   11  00000080 000007FF 110vvvvv 10vvvvvv
 
3   16  00000800 0000FFFF 1110vvvv 10vvvvvv 10vvvvvv
 
4   21  00010000 001FFFFF 11110vvv 10vvvvvv 10vvvvvv 10vvvvvv
 
5   26  00200000 03FFFFFF 111110vv 10vvvvvv 10vvvvvv 10vvvvvv 10vvvvvv
 
6   31  04000000 7FFFFFFF 1111110v 10vvvvvv 10vvvvvv 10vvvvvv 10vvvvvv 10vvvvvv

关于Unicode:

  • UTF-8和UTF-16都是变长的编码标准,最大都是4个Bytes(UTF-8定义的时候还有5,6Bytes的情况,不过现在这部分还没有用上,所以可以认为是1-4 Bytes)
  • UTF-8是Endianness的,UTF-16分为UTF-16LE和UTF-16BE
  • wchar_t在一些平台上是2 Bytes,在一些上是4 Bytes
  • UTF-16编码纯中文字符比UTF-8要小一些(2 Bytes,而UTF-8需要3 Bytes),但是一般情况下游戏数据文件还是ANSI字符占大头。所以使用UTF-8编码还是能有效节省带宽和内存的
  • 任何byte oriented的字符串搜索算法都可以直接用于UTF-8,编码方式保证了一个字符的字节序是唯一的
  • UTF-8不会出现FE FF,也就是不会和UTF-16混淆
  • UTF-8可以有BOM用来标志这是一个UTF-8文档,但是现在也有很多文档选择忽略这个标记
  • UTF-8可以编码任何Unicode字符,不需要考虑CodePage之类的,能够同时输出到多种语言
  • 直接把UTF-8字符串当作unsigned byte进行排序的结果和将其当作Unicode CodePoints进行排序,结果是一致的,这是很方便的设计!
  • 没有所谓的Plain Text,必须知道当前的字符串是用什么方式编码的,才能正确的解析。举个例子,对于网页解析来说,首先服务器上由于托管了大量的页面,编码各有不同,所以不能由服务器发编码,那么就交给客户端的浏览器去决定,客户端会先读取页面,去寻找ContentType的字段获得页面的编码,然后再重新Parse整个页面,如果找不到编码方式,不同的浏览器有不同的处理方法,比如IE,就会根据词频之类的特征,猜测当前页面最可能的编码

4.UTF-8实际使用中要考虑的问题

  • 工程全部使用UNICODE编译,避免错误的把Narrow String传给Windows API
  • 除非特别的指定,所有的std::string和char*都当作UTF-8编码处理
  • 可以考虑使用Boost.Locale等高质量的第三方库
  • 上层逻辑代码不使用wchar_t,_T(),L”"等,只在底层使用
  • 由于MSVC的fstream等文件类,不能支持UTF-8编码的文件名,所以只能使用一个非标准的扩展,就是把UTF-8的文件名转换成UTF-16传给fstream,所以对于文件操作,还是由底层进行封装比较好,比如使用_wfopen_s()。
  • 在上层使用的字符串默认都是UTF-8的,底层提供UTF8::Iterator迭代器,供需要进行字符串操作的模块使用。相对MBCS的复杂情况,UTF-8是比较简单的编码,从Leading Byte判定长度是统一的,获得字符的算法也都是字节位操作,还是比较高效的。比如这样:
template <typename octet_iterator>
uint32_t next(octet_iterator& it)
{
    uint32_t cp = internal::mask8(*it);
    typename std::iterator_traits<octet_iterator>::difference_type length = utf8::internal::sequence_length(it);
    switch (length) {
        case 1:
            break;
        case 2:
            it++;
            cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f);
            break;
        case 3:
            ++it; 
            cp = ((cp << 12) & 0xffff) + ((internal::mask8(*it) << 6) & 0xfff);
            ++it;
            cp += (*it) & 0x3f;
            break;
        case 4:
            ++it;
            cp = ((cp << 18) & 0x1fffff) + ((internal::mask8(*it) << 12) & 0x3ffff);                
            ++it;
            cp += (internal::mask8(*it) << 6) & 0xfff;
            ++it;
            cp += (*it) & 0x3f; 
            break;
    }
    ++it;
    return cp;        
}
  • 所有的数据文件也都使用UTF-8编码
  • 代码中不直接出现UTF-8字符串,全部从文件读取,避免源代码文件编码问题

参考文献:


14
May 12

Vision and Art ,The Biology of Seeing 读书笔记(2)

继续上周的读书笔记,今天主要讲色彩的感知原理,为什么战地3可以用Chroma SubSampling来提高渲染效率同时又不对效果有很严重的影响?人眼的感光细胞只有10倍左右的动态范围的情况下,为什么我们能同时看清反差如此之大的各种细节?在颜料能够表现的动态范围很有限的情况下,画家们是如何表现高动态场景的?


色彩的感知原理-三原色(Trichromatic) vs 补色(Color Opponent)


就像光具有波粒二相性一样,对颜色是如何构成的,也有两种理论:
  • 三原色,三色中的任何一色,都不能用另外两种原色混合产生,而其他色可由这三色按一定的比例混合出来,这三个独立的色称之为三原色(或三基色)。视网膜上确实有三种感光细胞,分别对三原色敏感。
  • 补色,这种理论认为人对色彩的感知并不是直接编码三种感光细胞的响应,用三原色合成颜色,而是编码某两种原色的差异,所以叫补色原理,在神经生物学的研究中发现,由于三种感光细胞对光线的反应曲线是有重叠的,所以这种编码差异而不是完整的曲线的方式,信息量更小,通过大脑的处理即可还原所有的信息。
一开始这两种理论被认为是不相容的,并且各自都有实验证据,很有点波粒二相性之争的感觉。后来的科学发展证实,这两种理论适用于人脑处理颜色信息的不同阶段,在第一阶段,感光细胞确实是提供了三原色的响应信号。但是在接下来的Bipolar细胞的处理中,就应用了补色的机制。把三原色信号变成了两元补色(two opponent signals)和亮度(luminance)信号。从处理效率和进化的角度来讲,人类还在低等生物的阶段时,只需要亮度信号就可以进行很多有用的信息处理了(Where系统).这样补色信号和亮度信号的分辨率或者说信息量是不同的,并且由于我们先进化出Where系统,后进化处理对颜色信息进行处理的What系统,所以这样划分也很自然,毕竟对进化来说,推翻重构的成本太高,添加功能式演化是比较自然的。有了这样的认识,很多视觉现象就好解释了,后面很多地方会提到。
比如目前应用非常广的图像压缩算法:Chroma SubSampling。从2011年的SIGGRAPH PPT里面我们可以看到战地3也用了这种算法,通过渲染低分辨率的颜色Buffer来提高效率。所以说,Vision and Art就是做渲染的原点,任何严肃的图形程序员都得好好掌握这些知识。


如何在2D的平面上呈现出3D的效果


视网膜需要从两只眼睛获得的2D图像还原出3D的世界,所以需要不少规则去辅助判定
  • 透视(Perspective)
  • Shading(主要是靠物体的亮度差异,而不是颜色)
  • Occlusion(遮挡关系)
  • Haze(远处的物体会由于雾和大气散射而变得模糊)
  • Steropsis(立体视觉)
  • Relative Motion(相对运动)
对画家来说,需要克服已经被大脑处理好的3D图像,而从中去还原出2D图片,然后让其他有着2D视网膜的人觉得这些平面的画很有3D的效果。而克服直觉是需要花很多功夫的,一些画家借助辅助网格,而一些人本身就是由于生理缺陷,缺乏立体视觉。反而成为画家的一大优势。
而为了增加画作的立体感,也需要模拟人眼感知世界的各种规则。下面列举一些我觉得挺有意思的。


透视

人眼因为要从2D图像重建3D的立体世界,所以透视原理也是被硬编码了。就是我们会认为物体必然是近大远小的,比如下面这个著名的Ames’ Window和Ames’ Room。视频演示在这里。如果想自己做一个纸上模型,可以把这个打印出来


Chiaroscuro(明暗法)


这个术语是来自意大利语,直译就是Light-Dark,是绘画的一种技法,用明暗对比来表现物体的体积感。前面提过,人眼的感光细胞对局部的亮度变化很敏感,并且这部分是古老的高分辨率Where系统所掌管的,所以亮度的对比可以给Where系统很强烈的物体轮廓提示,从而让平面的画作获得立体感。而且由于太阳光总从我们头顶射下,所以“光来自上面”这个规则已经被我们的Where系统硬编码了,所以让物体上部亮,下部暗的时候,我们会觉得这个物体很有立体感,反之则是完全不同的深度感觉,比如下面这些球体:
而一旦我们把球体旋转一个角度,上下的明暗对比没有了:
我们就不会有凹凸的感觉了。
再看一个印象派画家的作品,Monet的Rouen Cathedral:The Portal(in sun)。通过刻意的增加局部的明暗对比,教堂的立体感得到非常大的加强。
再来一张安格尔的:Princess Albert de Broglie,对亮度对比的控制已经牛的不行了。


画家是如何表现HDR(高动态范围)的效果


自然界中,不同的物体之间的亮度差异可能会有成百上千倍,但是我们的眼睛可以毫不费力的同时看到这些东西,而实际上,神经生物学家们发现,感光细胞能够编码的亮度差异也就是10倍左右!(这个我以前还真不知道)人眼会通过局部的对比来自动的调整(上一篇文章提到过的Center/Surround原理)。而对画家们来说,使用的颜料动态范围是非常有限的,显然不可能模拟物理真实的世界如此高的反差,于是画家们也逐渐学会了利用局部的对比来匹配人眼的局部性规则。比如伦勃朗的这幅Philosopher in Meditation。通过使用局部的大反差,用低动态范围的颜料模拟了高动态范围的光照效果。实际上,这中技法在实时渲染里面也是后期效果的标配了。

06
May 12

本博方针说明

只发本人原创文章,欢迎转载但请大家注明出处。基本保持至少每周一文的更新频率。内容主要是游戏制作相关的,包括引擎开发,图形学,工具制作,项目管理,读书笔记等。


06
May 12

Vision and Art ,The Biology of Seeing 读书笔记(1)

这本书入手也有一段时间了,术语比较多,读起来比较头疼,不过习惯了到后面就好了。印刷十分精美,各种插图设计也是一流的,看起来还是很享受的。全书的中心思想就是:Vision is information processing, and visual computations are SIMPLE, LOCAL, and OPPONENT。视觉并不是对外部世界的真实还原,在进化的过程中,人脑还形成了很多信息加工的功能,方便快速的获得某种重要信息,比如人脸的识别,深度的感知,颜色从2元到3元以便能识别出有毒的果实之类的。而很多画家其实就是利用了人脑的这些处理信息模式,创造出了经典作品。下面记录一些有意思的地方。

视网膜的构成

视网膜(Retina)的构成如上图所示,列一下从维基百科上拷贝过来的中英文的术语对照,这样后面好理解一些:

  • 视网膜色素上皮 (retinal pigment epithelium)
  • 感光层 (photoreceptor layer,rod,cone) ,包括视杆和视锥细胞
  • 神经节细胞层 (ganglion cell) ,这个层含有神经节细胞的细胞核,视神经从这里开始
  • 内核层 (inner nuclear layer),又称内颗粒层,由双极细胞(Bipolar Cell)、水平细胞(Horizontal Cell)、无长突细胞、Muller细胞的胞核组成
  • 神经纤维层 (nerve fiber layer),主要为神经节细胞的轴突

比较有意思的地方:

  • 感光层居然在Retina的最外层,也就是最后才接收到光线的,也就是这个原因,在中心的Fovea区域,为了最大化分辨率,这里的三层神经节细胞都被移开了,露出了一小块区域,让光线经历的中间细胞少一些
  • 中间的几层神经元细胞会在感光层接受光线刺激产生信号后,对信号做整合预处理
  • 光感受体传递神经信号的方式和普通的神经并不相同,光感受体平时一直处于Potentiate的高电位状态,光线刺激出现的时候才降低电位
  • 视锥细胞工作在比较亮的环境下,而且可以分辨颜色。视杆细胞工作在比较暗的环境下,其分辨率比较低,而且不能分辨颜色

Center/Surround原理

在感光细胞送出神经元信号后,神经节细胞层会对信号进行横向整合,遵循的是著名的Center/Surround原理,也就是在中央区域的光线刺激是+信号,而在边缘区域的光线刺激是-信号。这样的组织下,人眼的中心视觉灵敏度很高,并且对运动和变化的物体反映十分灵敏,也就是对光的变化敏感,而不是总体的亮度。这恐怕也是进化过程中必需的。而且重点编码运动和变化的信息,而不是整体的信息,也是一种信息压缩的手段吧。下面这张图可以让我们直观的感受到Center/Surround预处理的效果,当把视觉焦点放在白线交汇处时,会发现周围的交汇处有黑点闪烁,并且离中心视觉焦点越远越黑,当视线移动时,还能明显感觉到闪烁,这是因为神经信号传递的时间延迟。

中心视觉和边缘视觉

人眼的中心视觉分辨率很高,相对的,边缘视觉则要粗很多,但是比较擅长分辨大的轮廓和重要的元素,比如人脸。下面这张对蒙娜丽莎的脸部分析图可以很直观的解释为什么她的微笑那么神秘。

当你聚焦到她的脸部之后,视觉分辨率很高,看到的都是细节,也就是最右边的效果,所以微笑并不明显,但稍微移动视线,由于边缘视觉是看轮廓的,看到的就是左边的两种效果,所以就会突然觉得笑容更明显了。很多画家都会运用人眼的这种视觉原理去创造出一种变化感和运动感。特别是印象派的。

莫奈的这张Rue Motorgueil in Paris,Festival of June 30 , 1878.也是运用这种原理制作的,创造动感并不是单单的Blur掉。而是精确的模拟人眼的Peripheral Vision的Spatially imprecise的特点。

What and Where System

如上图所示,大脑中有两套处理视觉信息的系统,其中where系统是比较古老的构造,特点是分辨率不高,对反差很敏感,色盲,主要依靠亮度信息来判定深度和物体的运动,人物和地面的分离。而what系统是后期进化出来的,分辨率高,对颜色敏感,低反差。用来进行对象识别,人脸识别和颜色感知。

利用这个特点,我们就有了Equiluminant Color,指色调(Hue)和饱和度(Satureate)不同,但是亮度(Luminance)相同的色彩,用这种色彩组成的画面,会导致人的Where系统无法对其中的形状进行定位,所以会有闪烁,漂浮不定的感觉,也是很多画家利用的一个特点,比如印象派的很多画家

莫奈的这幅日出印象就是典型,也包括梵高的很多作品。从图上可以看到莫奈的太阳有比较特别的动感,但是把颜色去掉之后,我们就发现太阳的Luminance和背景基本是一致的。不过我觉得网上找的图效果都不明显,因为Where系统灵敏度很高,只要有亮度差异,基本就识别出来了。而且和屏幕的校准也有关系,这里有个网站可以实时的调整看效果

今天先写到这里,剩下的下周继续。

Update 2012.5.16,第二部分在这里