TA-Shader-文件 【浅墨Unity3D Shader编程】之六 暗黑城堡篇(上)
发布于
2017-8-4
2815
29
TA资源类型
TA资源类型: 算法思路 其它 
shader资源类型: 其它 
适用引擎: unity 
资源介绍: -

您需要 登录 才可以下载或查看,没有账号?注册

x

      上一篇地址

还是写在前面我自己的废话,昨天只看了一节,我并没有HLSL或者CG语言的基础,大量的基础对于我来说有些吃力,
但是怎么说呢,路是一步一步走的,我的本子上记满了各种英文以及翻译,
其实我上学时候英语都很少及格,
我只能记住,路既然是自己选的,无论前方的路是如何都要披荆斩棘,遇山开山,遇河渡河,,与君共勉。。。。。

这节的基础单词更多了,还是分2章发,




下面开始是正文
【浅墨Unity3D Shader编程】之六 暗黑城堡篇: 表面着色器(Surface Shader)的写法(一)


本文主要讲解了Unity中SurfaceShader的具体写法,以及几个常用的CG函数的用法。
在这里先说明一下,表面着色器将分为两次讲解,本文介绍表面着色器的基本概念和一些写法,用内置的兰伯特光照模式来进行Surface Shader的书写,而下次将介绍Surface Shader+自定义的光照模式的写法。

PS:最近几天,在完美世界、腾讯互娱工作多年的几个朋友们问了浅墨一些表面着色器相关的Shader写法,浅墨当时回答他们的时候自己也是似懂非懂。通过这篇文章的书写,现在算是对这方面知识有了进一步的理解。所以说嘛,写作是总结自己所学的一种很好的方式~

OK,言归正传,依然是先来看看本文配套的游戏场景截图。

运行游戏,音乐响起,首先是一个欧式风格的集市映入眼帘:



Center.jpg





雨淅沥沥、天空、晚霞、海平面:
Center.jpg



天空中,一群飞鸟飞过:




峡谷:



暗黑城堡:




在小集市中逛荡:




城堡概况图:




OK,图先就上这么多。文章末尾有更多的运行截图,并提供了源工程的下载。可运行的exe下载在这里:


【可运行的exe游戏场景请点击这里下载试玩】



好的,我们正式开始。









一、表面着色器的标准输出结构(Surface Output)


要书写Surface Shader,了解表面着色器的标准输出结构必不可少。此为表面着色器书写的第一个要素。
而定义一个“表面函数(surface function)”,需要输入相关的UV或数据信息,并在输出结构中填充SurfaceOutput。SurfaceOutput基本上描述了表面的特性(光照的颜色反射率、法线、散射、镜面等)。其实还是需要用CG或者HLSL编写此部分的代码。
我们其实是通过表面着色器(Surface Shader)来编译这段CG或者HLSL代码的,然后计算出需要填充输入什么,输出什么等相关信息,并产生真实的顶点(vertex)&像素(pixel)着色器,以及把渲染路径传递到正向或延时渲染路径。
说白了,还是那句话,Surface Shader是Unity微创新自创的一套着色器标准,是Unity自己发扬光大的一项使Shader的书写门槛降低和更易用的技术。

我们之前的文章中已经稍微了解过,表面着色器(Surface Shader)的标准输出结构是这样的:


[cpp] view plain copy


  • struct SurfaceOutput
  • {
  •     half3 Albedo;            //反射率,也就是纹理颜色值(r,g,b)
  •     half3 Normal;            //法线,法向量(x, y, z)
  •     half3 Emission;          //自发光颜色值(r, g,b)
  •     half Specular;           //镜面反射度
  •     half Gloss;              //光泽度
  •     half Alpha;              //透明度
  • };







而这个结构体的用法,其实就是对这些需要用到的成员变量在surf函数中赋一下值,比如说这样:

[cpp] view plain copy


  • //表面着色函数的编写
  • void surf (Input IN, inout SurfaceOutput o)
  • {
  •     //反射率,也就是纹理颜色值赋为(0.6, 0.6, 0.6)
  •        o.Albedo= 0.6;
  • }






        注意到Albedo是half3类型的。那么o.Albedo = 0.6和o.Albedo = float3(0.6,0.6,0.6)是等价的。







        二、表面着色器的编译指令




表面着色器的编译指令为编写表面着色器的第二个要素。
表面着色器放在CGPROGRAM .. ENDCG块里面,就像其他的着色器一样。区别是:

其必须嵌在子着色器(SubShader)块里面。而不是Pass块里面。因为表面着色器( Surface shader)将在多重通道(multiple passes)内编译自己,而不是放在某个Pass中。
我们甚至可以这样说,如果你写表面着色器,用不到Pass代码块,一般直接在SubShader块中完成就行了。


使用的 #pragma surface...指令,以声明这是一个表面着色器。指令的句法是:

#pragma surface surfaceFunction lightModel[optionalparams]


所需参数的讲解:







    • surfaceFunction - 表示指定名称的Cg函数中有表面着色器(surface shader)代码。这个函数的格式应该是这样:void surf (Input IN,inout SurfaceOutput o), 其中Input是我们自己定义的结构。Input结构中应该包含所需的纹理坐标(texture coordinates)和和表面函数(surfaceFunction)所需要的额外的必需变量。
    • lightModel -使用的光照模式。内置的是Lambert (diffuse)和 BlinnPhong (specular)两种,一般习惯用Lambert,也就是兰伯特光照模式。而编写自己的光照模式我们将在下次更新中讲解。



可以根据自己的需要,进阶选这样的一些可选参数:








    • alpha -透明( Alpha)混合模式。使用它可以写出半透明的着色器。
    • alphatest:VariableName -透明( Alpha)测试模式。使用它可以写出 镂空效果的着色器。镂空大小的变量(VariableName)是一个float型的变量。
    • vertex:VertexFunction - 自定义的顶点函数(vertex function)。相关写法可参考Unity内建的Shader:树皮着色器(Tree Bark shader),如Tree Creator Bark、Tree Soft Occlusion Bark这两个Shader。







    • finalcolor:ColorFunction - 自定义的最终颜色函数(final color function)。 比如说这样:


[cpp] view plain copy


  • #pragma surfacesurf Lambert finalcolor:mycolor。



        相关Shader示例可见下文Shader实战部分的第五个Shader(纹理载入+颜色可调)。








    • exclude_path:prepass 或者 exclude_path:forward - 使用指定的渲染路径,不需要生成通道。
    • addshadow - 添加阴影投射 & 收集通道(collector passes)。通常用自定义顶点修改,使阴影也能投射在任何程序的顶点动画上。
    • dualforward - 在正向(forward)渲染路径中使用 双重光照贴图(dual lightmaps)
    • fullforwardshadows - 在正向(forward)渲染路径中支持所有阴影类型。
    • decal:add - 添加贴图着色器(decal shader) (例如: terrain AddPass)。
    • decal:blend - 混合半透明的贴图着色器(Semitransparent decal shader)。
    • softvegetation - 使表面着色器(surface shader)仅能在Soft Vegetation打开时渲染。
    • noambient - 不适用于任何环境光照(ambient lighting)或者球面调和光照(spherical harmonics lights)。
    • novertexlights - 在正向渲染(Forward rendering)中不适用于球面调和光照(spherical harmonics lights)或者每个顶点光照(per-vertex lights)。
    • nolightmap - 在这个着色器上禁用光照贴图(lightmap)。(适合写一些小着色器)
    • nodirlightmap - 在这个着色器上禁用方向光照贴图(directional lightmaps)。 (适合写一些小着色器)。
    • noforwardadd - 禁用正向渲染添加通道(Forward rendering additive pass)。 这会使这个着色器支持一个完整的方向光和所有光照的per-vertex/SH计算。(也是适合写一些小着色器).
    • approxview - 着色器需要计算标准视图的每个顶点(per-vertex)方向而不是每个像索(per-pixel)方向。 这样更快,但是视图方向不完全是当前摄像机(camera) 所接近的表面。
    • halfasview - 在光照函数(lighting function)中传递进来的是half-direction向量,而不是视图方向(view-direction)向量。 Half-direction会计算且会把每个顶点(per vertex)标准化。这样做会提高执行效率,但是准确率会打折扣。


此外,我们还可以在 CGPROGRA内编写 #pragma debug,然后表面编译器(surface compiler)会进行解释生成代码。





三、表面着色器输入结构(Input Structure)



表面着色器书写的第三个要素是指明表面输入结构(Input Structure)。
Input 这个输入结构通常拥有着色器需要的所有纹理坐标(texture coordinates)。纹理坐标(Texturecoordinates)必须被命名为“uv”后接纹理(texture)名字。(或者uv2开始,使用第二纹理坐标集)。
可以在输入结构中根据自己的需要,可选附加这样的一些候选值:








    • float3 viewDir - 视图方向( view direction)值。为了计算视差效果(Parallax effects),边缘光照(rim lighting)等,需要包含视图方向( view direction)值。
    • float4 with COLOR semantic -每个顶点(per-vertex)颜色的插值。
    • float4 screenPos - 屏幕空间中的位置。 为了反射效果,需要包含屏幕空间中的位置信息。比如在Dark Unity中所使用的 WetStreet着色器。
    • float3 worldPos - 世界空间中的位置。
    • float3 worldRefl - 世界空间中的反射向量。如果表面着色器(surface shader)不写入法线(o.Normal)参数,将包含这个参数。 请参考这个例子:Reflect-Diffuse 着色器。
    • float3 worldNormal - 世界空间中的法线向量(normal vector)。如果表面着色器(surface shader)不写入法线(o.Normal)参数,将包含这个参数。
    • float3 worldRefl; INTERNAL_DATA - 世界空间中的反射向量。如果表面着色器(surface shader)不写入法线(o.Normal)参数,将包含这个参数。为了获得基于每个顶点法线贴图( per-pixel normal map)的反射向量(reflection vector)需要使用世界反射向量(WorldReflectionVector (IN, o.Normal))。请参考这个例子: Reflect-Bumped着色器。
    • float3 worldNormal; INTERNAL_DATA -世界空间中的法线向量(normal vector)。如果表面着色器(surface shader)不写入法线(o.Normal)参数,将包含这个参数。为了获得基于每个顶点法线贴图( per-pixel normal map)的法线向量(normal vector)需要使用世界法线向量(WorldNormalVector (IN, o.Normal))。







四、一些本次写Shader用到的CG函数讲解



本次Shader书写用到了四个CG着色器编程语言中的函数——UnpackNormal、saturate、dot、tex2D。下面将分别对其进行讲解。



4.1UnpackNormal( )函数
UnpackNormal接受一个fixed4的输入,并将其转换为所对应的法线值(fixed3),并将其赋给输出的Normal,就可以参与到光线运算中完成接下来的渲染工作了。
一个调用示例:

[cpp] view plain copy


  • o.Normal = UnpackNormal (tex2D (_BumpMap,IN.uv_BumpMap));





4.2saturate( )函数
saturate的字面解释是浸湿,浸透。其作用其实也就是将取值转化为[0,1]之内的一个值。

其可选的原型如下:

[cpp] view plain copy


  • float saturate(float x);
  • float1 saturate(float1 x);
  • float2 saturate(float2 x);
  • float3 saturate(float3 x);
  • float4 saturate(float4 x);
  • half saturate(half x);
  • half1 saturate(half1 x);
  • half2 saturate(half2 x);
  • half3 saturate(half3 x);
  • half4 saturate(half4 x);
  • fixed saturate(fixed x);
  • fixed1 saturate(fixed1 x);
  • fixed2 saturate(fixed2 x);
  • fixed3 saturate(fixed3 x);
  • fixed4 saturate(fixed4 x);




其唯一的一个参数x表示矢量或者标量的饱和值(Vector or scalar to saturate.),也就是将这个x转化为[0,1]之内的值。

其返回值:

  • 如果x取值小于0,则返回值为0.
  • 如果x取值大于1,则返回值为1.
  • 若x在0到1之间,则直接返回x的值。


其代码实现大致如下:


[cpp] view plain copy


  • float saturate(float x)
  • {
  •     return max(0,min(1, x));
  • }





一个调用示例:

[cpp] view plain copy


  • half rim = 1.0 - saturate(dot(normalize(IN.viewDir), o.Normal));






4.3 dot( )函数
dot函数顾名思义,是高等数学中的点积操作,用于返回两个向量的标量积。

可选原型如下:


[cpp] view plain copy


  • float dot(float a, float b);
  • float dot(float1 a, float1 b);
  • float dot(float2 a, float2 b);
  • float dot(float3 a, float3 b);
  • float dot(float4 a, float4 b);
  • half dot(half a, half b);
  • half dot(half1 a, half1 b);
  • half dot(half2 a, half2 b);
  • half dot(half3 a, half3 b);
  • half dot(half4 a, half4 b);
  • fixed dot(fixed a, fixed b);
  • fixed dot(fixed1 a, fixed1 b);
  • fixed dot(fixed2 a, fixed2 b);
  • fixed dot(fixed3 a, fixed3 b);
  • fixed dot(fixed4 a, fixed4 b);





其代码实现大致是这样的:


[cpp] view plain copy


  • float dot(float4 a, float4 b)
  • {
  •     return a.x*b.x +a.y*b.y + a.z*b.z + a.w*b.w;
  • }



一个调用示例:


[cpp] view plain copy


  • float answer= dot (normalize(IN.viewDir),o.Normal);







4.4 tex2D( )函数
让我们看一看CG中用得比较多的用于2D纹理采样的tex2D函数的用法。其备选的原型也是非常之多:

[cpp] view plain copy


  • float4 tex2D(sampler2D samp, float2 s)
  • float4 tex2D(sampler2D samp, float2 s, inttexelOff)
  • float4 tex2D(sampler2D samp, float3 s)
  • float4 tex2D(sampler2D samp, float3 s, inttexelOff)
  • float4 tex2D(sampler2D samp, float2 s,float2 dx, float2 dy)
  • float4 tex2D(sampler2D samp, float2 s,float2 dx, float2 dy, int texelOff)
  • float4 tex2D(sampler2D samp, float3 s,float2 dx, float2 dy)
  • float4 tex2D(sampler2D samp, float3 s,float2 dx, float2 dy, int texelOff)
  • int4 tex2D(isampler2D samp, float2 s)
  • int4 tex2D(isampler2D samp, float2 s, inttexelOff)
  • int4 tex2D(isampler2D samp, float2 s,float2 dx, float2 dy)
  • int4 tex2D(isampler2D samp, float2 s,float2 dx, float2 dy, int texelOff)
  • unsigned int4 tex2D(usampler2D samp, float2s)
  • unsigned int4 tex2D(usampler2D samp, float2s, int texelOff)
  • unsigned int4 tex2D(usampler2D samp, float2s, float2 dx, float2 dy)
  • unsigned int4 tex2D(usampler2D samp, float2s, float2 dx, float2 dy,int texelOff)





参数简介:
samp-需要查找的采样对象,也就是填个纹理对象在这里。
s-需进行查找的纹理坐标。
dx-预计算的沿X轴方向的导数。
dy-预计算的沿Y轴方向的导数。
texelOff-添加给最终纹理的偏移量

而其返回值,自然是查找到的纹理。

最后,看一个综合了本次讲解的四个函数(UnpackNormal、saturate、tex2D、dot)的Surface Shader中surf函数的示例:

[cpp] view plain copy


  • //【2】表面着色函数的编写
  • void surf (Input IN, inout SurfaceOutput o)
  • {
  •        //从主纹理获取rgb颜色值
  •        o.Albedo= tex2D (_MainTex, IN.uv_MainTex).rgb;
  •        //从凹凸纹理获取法线值
  •        o.Normal= UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
  •        //从_RimColor参数获取自发光颜色
  •        halfrim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
  •        o.Emission= _RimColor.rgb * pow (rim, _RimPower);
  • }




下一部分是实例





本帖被以下画板推荐:

还没有设置签名!您可以在此展示你的链接,或者个人主页!

使用道具 举报 登录

回复 <
tp999xc  发表于 2017-8-4 12:06:34  
2#
兄弟啊 我的英语也不好 啊哈哈哈
回复 收起回复
使用道具
大芸彩  发表于 2017-8-4 12:39:46  
3#
tp999xc 发表于 2017-8-4 12:06
兄弟啊 我的英语也不好 啊哈哈哈

共勉共勉
回复 收起回复
使用道具
w695486538  发表于 2018-6-1 13:20:12  
4#
感谢分享。
回复 收起回复
使用道具
吃鱼D.楿櫵  发表于 2018-10-14 18:39:20  
5#
学习~!
回复 收起回复
使用道具
ゞ最初的奔跑ゞ  发表于 2018-10-18 09:50:14  
6#
不错哦,感谢楼主的分享!
回复 收起回复
使用道具
张前进  发表于 2018-10-18 10:13:11  
7#
想要成大触,天天上元素!
回复 收起回复
使用道具
An.w  发表于 2018-10-18 10:30:16  
8#
感谢分享
回复 收起回复
使用道具
Ekko  发表于 2018-10-18 12:57:22  
10#
大神谢谢,够小白研究半月
回复 收起回复
使用道具
小四  发表于 2018-10-18 14:26:04  
11#
酷炫的不得了
回复 收起回复
使用道具
迷路的小星扬  发表于 2018-10-18 20:30:44  
12#
666666666
回复 收起回复
使用道具
<`▽′>  发表于 2018-10-25 10:35:51  
13#

想要成大触,天天上元素!
回复 收起回复
使用道具
fyh420464  发表于 2018-10-25 11:00:14  
14#
元素帖子强,满满正能量!
回复 收起回复
使用道具
瀚哈哈哈  发表于 2018-10-27 12:43:31  
15#
非常感谢66
回复 收起回复
使用道具
qq_wangbingxv_i  发表于 2018-10-27 17:00:01  
16#
多谢分享。
回复 收起回复
使用道具
不能好好起名  发表于 2018-10-27 18:05:29  
17#
很好
回复 收起回复
使用道具
kofyyz  发表于 2018-10-28 01:05:44  
18#
谢谢分享,真是太好了
回复 收起回复
使用道具
李琛琛  发表于 2018-10-30 08:30:23  
19#
很好,我正在看这个
回复 收起回复
使用道具
bili23  发表于 2018-11-5 22:44:12  
20#
很棒 学习了!
回复 收起回复
使用道具
12下一页

快来发表你宝贵的意见吧!

大芸彩 实名

通过了实名认证的内容创造者

  

主题
22
精华
4
超神
0
扩散
288
微金
2471
智慧
11
余额
0
在线时间
651 小时

学徒法袍 长枪 火元素

快速回复 返回顶部 返回列表