最近又开心又担心,因为我的书马上就要上市了,开心当然是因为等了这么久终于可以如愿了,担心是因为不少人对它的期待都很大,我第一次写书,能力也有限,不知道能不能让大家满意,让大家也都喜欢上它。不过有不少朋友和前辈鼓励我,还是很开心滴~
之前有一些朋友觉得这次封面很漂亮,问我书的封面是不是我渲染的,以及怎么渲染的:
其实这封面并不是在Unity里渲染的,是男朋友在C4D里面渲染的。我特别喜欢他上学时候的一次作品(有机会放个视频哈哈),就是这种low polygon的风格,所以当时想要设计书的封面的时候就想要用这个素材。构图是我们讨论后的结果,我特意要他把Unity的Logo放到旗帜上面,然后让画面中间的男孩停在走进屋子的位置上,以此来寓意“入门”。之后就发给一些朋友看,问他们觉得怎么样,会不会太粉嫩、女生化?大家反馈说,这样放在书架上会很显眼,让人有想看的欲望,还有一些人说挺好的,不少游戏都这种风格,不过大部分技术人员说无所谓……技术书内容好就行~Anyway,之后发给了编辑,经过出版社美编同学的一些修改后就成了现在这个样子,希望大家还满意 :)
说到游戏里面low polygon风格的渲染,一直都是一种很有特色的渲染。因为男朋友是设计师,所以他总会拿一些美图来熏陶我,我每次看到这种风格的就会想多看几眼,大概是因为我还没度过小女生的年纪吧……例如下面这张(更多这种风格的设计图案可以参见这个网站,略)
Low polygon风格其实是一种复古风格,在早期的不支持插值渲染的年代,这种风格随处可见,因为模型简单、渲染也不复杂,在早期游戏中被大量使用。
回到技术上来,那么这种low polygon风格在游戏里怎么渲染呢?
Low polygon风格的渲染也被称为flat shading。虽然把这篇文章归到Shader类别里,但其实是完全可以用非Shader的方法来解决的。下面两张图片,左边是我们不希望得到的结果,而右边是我们想要得到的效果。
做美术的同学都知道上面模型的区别就是“硬边”和“软边”的问题。左图里就是软边的效果,软边意味着相邻三角形之间共用顶点,这些被共用顶点的法线(蓝线)通常是根据毗连三角形的面法线加权平均计算得到的,如下图所示(图片来源略)
由于共用顶点的存在,光栅化阶段的插值使得这些三角面片中间每个点的点法线都是有三个顶点的法线插值得到的,而这三个法线各不相同, 使得面片有平滑过渡的效果,从而形成软边。在其他风格的渲染中,模型大多都会使用软边,这不仅可以减少顶点数目提高性能,还能让渲染效果更加平滑。
但flat shading显然不想要平滑的渲染效果,我们希望就算光栅化插值后同一个三角面片中每个点的法线都应该是相同的(都等于面法线),只有这样它们光照计算的结果才会是相同的。如下图所示(图片来源略)
要实现这样的效果就是把所有相连的边在建模软件中设置成硬边,Hard Edge。这样软件背后就会进行拆顶点的工作,每个三角形有各自属于自己的三个顶点,不与他人共用,这三个顶点的点法线计算不会受毗连三角形的影响,而仅仅是由该三角面片的面法线决定。 因此, 我们有了解决方案一:在建模软件中把边设置成硬边。
值得高兴的是,就算你在建模软件里忘记了设置硬边,Unity也是可以直接帮我们做到软硬边的转换的。它的原理其实就是重新拆点、重新计算点法线。
在我们导入一个模型后,可以在模型的导入面板中的Normals & Tangents块中,把Normals设置成Calculate模式、把Smoothing Angle设置成0就可以得到硬边的效果。如上图所示。
这样的模型可以直接使用任何普通的Shader进行渲染,就会得到flat shading的效果。
之前群里有人问程序产生的网格怎么实现这种效果。原理也是一样的,我们只需要保证不要共用顶点、正确计算顶点法线即可。在我的NPR项目里,有这样一个例子,把任何模型强制转化成没有共用顶点的模型。其中关键代码是长这个样子的:
Vector3[] oldVerts = mesh.vertices; Vector4[] oldTangents = mesh.tangents; Vector2[] oldUVs = mesh.uv; int[] triangles = mesh.triangles; Vector3[] newVerts = new Vector3[triangles.Length]; Vector4[] newTangents = new Vector4[triangles.Length]; Vector2[] newUVs = new Vector2[triangles.Length]; for (int i = 0; i < triangles.Length; i++) { newVerts[i] = oldVerts[triangles[i]]; newTangents[i] = oldTangents[triangles[i]]; newUVs[i] = oldUVs[triangles[i]]; triangles[i] = i; } mesh.vertices = newVerts; mesh.tangents = newTangents; mesh.triangles = triangles; mesh.uv = newUVs; mesh.RecalculateBounds(); mesh.RecalculateNormals();
因此,如果你是通过程序方法产生网格的,可以使用类似上面的代码来进行网格转换,再使用普通的Shader即可。
上面转化硬边的方法会增加顶点数目,有一种完全不需要对网格进行任何处理的方法就是使用Geometry Shader。这种方法的原理就是在光栅化前、在Geometry Shader里给每个顶点增加一个属性,面法线faceNormal。由于Geometry Shader中可以知道同一个三角面片中的所有三个顶点的信息,因此我们可以为它们计算一个相同的面法线值。这样,即便在经过光栅化插值后,同一个三角面片中的面法线也是一样的。关键代码如下:
[maxvertexcount(3)] void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream) { float3 A = IN[1].worldPos.xyz - IN[0].worldPos.xyz; float3 B = IN[2].worldPos.xyz - IN[0].worldPos.xyz; float3 fn = normalize(cross(A, B)); g2f o; o.pos = IN[0].pos; o.uv = IN[0].uv; o.worldPos = IN[0].worldPos; o.faceNormal = fn; triStream.Append(o); o.pos = IN[1].pos; o.uv = IN[1].uv; o.worldPos = IN[1].worldPos; o.faceNormal = fn; triStream.Append(o); o.pos = IN[2].pos; o.uv = IN[2].uv; o.worldPos = IN[2].worldPos; o.faceNormal = fn; triStream.Append(o); }
这种方法有点小题大做,因为geometry shader是SM 4.0中的特性,移动终端大多达不到这样的要求。
如果大家真的想做low polygon风格的游戏,可以考虑一个插件PolyWorld,看着很不错的样子。 最后,希望大家喜欢《Unity Shader入门精要》这本书(源码下载),任何问题欢迎联系我的邮箱(lelefeng1992 # gmail DOT com)或在Github上发issue。再次感谢大家的支持 :)
这是一本讲述 《Unity Shader入门精要》 的封面故事,让你了解作者在封面中是如何用渲染做出这样的效果,一起来学习下吧。
《Unity Shader入门精要》 不仅要教会读者如何使用Unity Shader,更重要的是要帮助读者学习Unity中的一些渲染机制以及如何使用Unity Shader实现各种自定义的渲染效果,希望这本书可以为读者打开一扇新的大门,让读者离制作心目中优秀游戏的心愿更近一步。
(1)内容独特。填补了Unity Shader和渲染流水线之间的知识鸿沟,对Unity中一些渲染机制的工作原理进行详细剖析,帮助读者解决“是什么”“为什么”“怎么做”这三个基本问题。本书配合大量实例,来让读者在实践中逐渐掌握Unity Shader的编写。
(2)结构连贯。在内容编排上破费心思,从基础到进阶再到深入,解决读者长期以来的学习烦恼。
(3)充分面向初学者。在本书的编写过程中,我们一直在问自己,这么写到底读者能不能看懂?为此,提供了大量的图示配合以文字说明,并在一些章节后提供了“答疑解惑”小节来解释那些含糊不清而初学者又经常疑问的问题。
(4)包含了Unity 5在渲染方面的新内容。如多次介绍Unity 5中的新工具帧调试器(Frame Debugger),并借助该工具的帮助来理解Unity中的渲染过程。
(5)补充了大量延伸阅读资料。在本书一些章节后提供了“扩展阅读”小节,让那些希望更加深入学习某个方向的读者可以在提供的资料中找到更多的学习内容。