- A+
又是新的一天,狄霸哥收拾心情开始新的旅程。奇幻的神秘的马赛克大陆等着他去探索。今天的他的目的地是人们口中称为最穷的地方-黄金谷。在昨天,他去了趟武侠小酒馆,听说书老人说起了这个地方,这个地方的人们穷得没有地方住也吃得不好,但是人们却是无比的善良和无私,想要寻找可以帮助他们的人。狄霸哥一听,这个可以帮助他们的人不就是他了吗,拥有无限创造力的他,于是有了这一天的旅途。
黄金谷是一个种满了庄家的地方,不怪得叫做黄金谷,这名字真的是一点都没有起错。这时真是秋分时刻,狄霸哥走在黄金谷的小道上,看着这遍野的黄金,心情大好。突然,狄霸哥看到面前有一个小女孩被一头有三个角的牛追,狄霸哥身为正义的一方,立刻利用程序之力,创建出了一个大盒子把牛困住了。看着眼前被救的小女孩,狄霸哥扬起了头说:小妹妹,你没事把!小妹妹:大哥哥,我,我,我.....你能不能别欺负大牛啊?大牛是我朋友,在跟我玩呢!
为了补偿小女孩,狄霸哥决定帮助村里面解决居住问题,据了解,原先居民都是住在山洞里面的,非常不好。于是狄霸哥想利用他的程序之力创建很多很多个小房子,要求房子简单但又美丽。要求简单是因为这里的人很多,如果制作大量复杂的房子,需要耗费大量的程序之力,要求美丽是因为帮助别人怎么也不能寒酸。
首先,狄霸哥脑袋想着房子需要墙,那就先把墙做出来吧!首先创建一个Cube给它一个合适的名字叫做Wall,设置它的Transform属性为:
这里设置z为-0.5是为了快速调节4块墙并在一起,聪明的狄霸哥再创建一个空的物体,使Reset它为默认值,再将Wall拖进空物体中作为它的子物体。
好了,现在只有一块墙,组件一个房子需要4块墙,所以Ctrl+D复制GameObject出3个,再分别调节复制出来的空物体的Rotation-Y为:90,180,270,神奇的墙围在了一起,好了,创建一个空物体叫做Walls,注意Reset一下Transform美观一点,最后将4个Walk一起拉到Walls下就大功告成啦!Unity会自动计算它们的本地坐标来保持它们所在的世界坐标不变,所以不用担心墙会被改变。
就差房顶了啊,什么,窗和门呢?这我可不管了,然他们喜欢哪里开洞就开哪里吧!新建一个Cube设置如下盖住房顶,这时你会发现这也太单调了,旋转一下,面面都是一样的,好吧,加个烟冲(圆柱体),啧啧啧,整个画面都不一样了好吗,简直是人间美景啊!
如此强的创造力,也只有我狄霸哥了吧!不过白白的一片不是那么完美,添加一点木质感和纹理感吧。可是程序之力有限啊,制造纹理的凹凸感需要修改增加模型的顶点,这样我肯定不能造出这么多房子的。绞尽脑汁的狄霸哥想起了法线贴图。
法线贴图就是在原物体的凹凸表面的每个点上均作法线,通过RGB颜色通道来标记法线的方向,你可以把它理解成与原凹凸表面平行的另一个不同的表面,但实际上它又只是一个光滑的平面。对于视觉效果而言,它的效率比原有的凹凸表面更高,若在特定位置上应用光源,可以让细节程度较低的表面生成高细节程度的精确光照方向和反射效果。
好了,既然有了解决方案,那么那么,还是一步一步慢慢地来吧!首先创建一个Shader,打开删除原来的代码。开始我们的法线贴图Shader。首先要给它一个美好的名字:
1 |
Shader "chicai/nor" { } |
这样子,Unity编辑器就知道它是在哪儿的啦,再创建一些属性让Unity知道这个Shader需要什么,一个法线贴图材质当然不能少了法线贴图啦,有了法线贴图自然不会少了和它配合的普通贴图了,再加一个系数用来控制凹凸感的强度,这样房子就可以个性化发展了。狄霸哥兴奋了起来,写下:
1 2 3 4 5 6 7 |
Shader "chicai/nor" { Properties { _MainTex("MainTex",2D) = "white"{} _NormalTex("NornalTex",2D) = "white"{} _AoTu("AoTu",float)= 1 } } |
好了,外部可以传入数据,现在需要定义一个子Shader并建立通道和外部赋值的数据对接上。狄霸哥熟练的写下了对应的代码。_ST后缀对应的是前缀贴图的缩放和偏移。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
Shader "chicai/nor" { Properties { _MainTex("MainTex",2D) = "white"{} _NormalTex("NornalTex",2D) = "white"{} _AoTu("AoTu",float)= 1 } SubShader { Pass{ Tags{"RenderMode"="ForwardBase"} CGPROGRAM sampler2D _MainTex;//纹理贴图 float4 _MainTex_ST; sampler2D _NormalTex;//法线贴图 float4 _NormalTex_ST; float _AoTu; ENDCG } } } |
接下来,就是子Shader了,建立处理通道,定义两个结构体和两个函数,一个结构对应传递给一个函数,这两个函数就是大家都知道的顶点和片元了。首先是顶点函数,需要传入些什么呢,那就是原始的数据,顶点信息当然需要啦,渲染房子的位置需要它呢,法线和切线用来计算切线空间的转换矩阵,为了和法线贴图的法线方向空间保持一致也是需要的。uv,啊,没有它怎么把贴图贴上去啊,你知道贴哪里啊!轮到片元函数呢,他又需要什么东西呢。房子最终的位置信息,确认每个点的颜色,需要法线贴图和普通贴图的信息(用uv取出来),光照的方向(切线空间的)。
1 2 3 4 5 6 7 8 9 10 11 12 |
struct a2v { float4 vertex:POSITION; float3 normal:NORMAL; float4 tangent:TANGENT; float4 texcoord:TEXCOORD0;//模型展开的UV }; struct v2p{ float4 position:SV_POSITION; fixed3 lightDir:COLOR1; float4 uv:TEXCOORD0; }; |
终于要到两个关键的函数了,前面的准备,一定会让后面的制造顺畅起来。声明,引入光照相关的宏门。
1 2 3 |
#include "Lighting.cginc" #pragma vertex vert #pragma fragment frag |
接下来顶点函数,传入a2v的数据,构造出v2p的数据给片元函数,这就是它需要做的。rotation这个就是切线空间的转换矩阵了,他是通过TANGENT_APACE_ROTATION计算出来的,这里面有着计算的代码,需要模型的切线和模型的法线。
1 2 3 4 5 6 7 8 9 10 11 |
v2p vert(a2v v){ v2p o; o.position = UnityObjectToClipPos(v.vertex); TANGENT_SPACE_ROTATION; o.lightDir = normalize( mul(rotation,ObjSpaceLightDir(v.vertex))).xyz; o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; o.uv.zw = v.texcoord.xy * _NormalTex_ST.xy + _NormalTex_ST.zw; return o; } |
为什么要转换成切线空间。因为法线贴图数据的保存是用的切线空间。使用切线空间保存之后,相比使用世界空间来说,它不需要依赖世界空间上的数据了,同样的相对模型空间,他也不需要依赖模型的数据,而它就是它,解耦了出来,就可以在不同的地方使用了。
最后就是片元函数了,一口气完成吧。程序力运用到极限的狄霸哥。快速的转换思维,啪啪啪。
1 2 3 4 5 6 7 8 9 10 |
fixed4 frag(v2p i):SV_Target{ fixed3 tangentNormal = tex2D(_NormalTex,i.uv.wz)* 2 -1;//取出法线信息 tangentNormal.xy = tangentNormal.xy * _AoTu; tangentNormal = normalize(tangentNormal); fixed3 texCol = tex2D(_MainTex,i.uv.xy); fixed3 diffuseCol = _LightColor0.rgb * texCol * (dot(tangentNormal,i.lightDir)/2 + 0.5); fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; return fixed4(diffuseCol + ambient,1); } |
为什么只对取出来的法线的xy进行凹凸强度的影响。因为在切线空间中,代表原来法线的是(0,0,1),也就是说最后的z元素其实是他自身的法线的方向,那么它就是代表原来的不凹凸度,所以对另外的两个值进行影响。
终于大功告成了,狄霸哥看着已经完成的...码,感概了一声,衡量起程序之力可以制造多少个房子出来,1,2,3....99....999999,我真是强大啊,全村999990人,每个人都有房子住啦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
Shader "chicai/nor" { Properties { _MainTex("MainTex",2D) = "white"{} _NormalTex("NornalTex",2D) = "white"{} _AoTu("AoTu",float)= 1 } SubShader { Pass{ Tags{"RenderMode"="ForwardBase"} CGPROGRAM sampler2D _MainTex;//纹理贴图 float4 _MainTex_ST; sampler2D _NormalTex;//法线贴图 float4 _NormalTex_ST; float _AoTu; #include "Lighting.cginc" #pragma vertex vert #pragma fragment frag struct a2v { float4 vertex:POSITION; float3 normal:NORMAL; float4 tangent:TANGENT;//计算rotation用,切线 float4 texcoord:TEXCOORD0; }; struct v2p{ float4 position:SV_POSITION; fixed3 lightDir:COLOR1; float4 uv:TEXCOORD0; }; v2p vert(a2v v){ v2p o; o.position = UnityObjectToClipPos(v.vertex); TANGENT_SPACE_ROTATION;//计算切线坐标系的转换矩阵的rotation o.lightDir = normalize( mul(rotation,ObjSpaceLightDir(v.vertex))).xyz; o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; o.uv.zw = v.texcoord.xy * _NormalTex_ST.xy + _NormalTex_ST.zw; return o; } fixed4 frag(v2p i):SV_Target{ fixed3 tangentNormal = UnpackNormal(tex2D(_NormalTex,i.uv.zw));//解析成法线方向 tangentNormal.xy = tangentNormal.xy * _AoTu; tangentNormal = normalize(tangentNormal); fixed3 texCol = tex2D(_MainTex,i.uv.xy); //计算漫反射系数时,用的是法线贴图的法线方向和经过转换的光照方向,两者都在切线空间中 fixed3 diffuseCol = _LightColor0.rgb * texCol * (dot(tangentNormal,i.lightDir)/2 + 0.5); fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; return fixed4(diffuseCol + ambient,1); } ENDCG } } } |
在999990个村名的感谢声下,狄霸哥开始了回城,因为,大家都有地方住了,他没有啊!刚刚少复制了一份,没办法了,只能拒绝村名的好意不能留下来吃饭了。走在回程的大森林中,忽然出现一头猛虎狂奔过来。吓得狄霸哥坐在了地上看着,猛虎在自己脸上狂奔,张开血盆大口,但是就是咬不到自己,因为急中生智的狄霸哥,利用剩下的程序之力,写下了一个死循环,交换眼前的两块土地。
1 2 3 4 5 |
public void exchange(string floor1,string floor2){ string temp = floor1; floor1 = floor2; floor2 = temp; } |
真是好险啊,幸好节约了程序之力,不然不然,看着这血盆大口,狄霸哥站了起来,扭了扭屁股,潇洒的继续他归程。