分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow
本系列文章由@浅墨_毛星云 出品,转载请注明出处。
文章链接: http://blog.csdn.net/poem_qianmo/article/details/42215079
作者:毛星云(浅墨) 微博:http://weibo.com/u/1723155442
本文主要讲解了Unity中SurfaceShader的具体写法,以及几个常用的CG函数的用法。
在这里先说明一下,表面着色器将分为两次讲解,本文介绍表面着色器的基本概念和一些写法,用内置的兰伯特光照模式来进行Surface Shader的书写,而下次将介绍Surface Shader+自定义的光照模式的写法。
PS:最近几天,在完美世界、腾讯互娱工作多年的几个朋友们问了浅墨一些表面着色器相关的Shader写法,浅墨当时回答他们的时候自己也是似懂非懂。通过这篇文章的书写,现在算是对这方面知识有了进一步的理解。所以说嘛,写作是总结自己所学的一种很好的方式~
OK,言归正传,依然是先来看看本文配套的游戏场景截图。
运行游戏,音乐响起,首先是一个欧式风格的集市映入眼帘:
雨淅沥沥、天空、晚霞、海平面:
天空中,一群飞鸟飞过:
峡谷:
暗黑城堡:
在小集市中逛荡:
城堡概况图:
OK,图先就上这么多。文章末尾有更多的运行截图,并提供了源工程的下载。可运行的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)的标准输出结构是这样的:
struct SurfaceOutput { half3 Albedo; //反射率,也就是纹理颜色值(r,g,b) half3 Normal; //法线,法向量(x, y, z) half3 Emission; //自发光颜色值(r, g,b) half Specular; //镜面反射度 half Gloss; //光泽度 half Alpha; //透明度};
而这个结构体的用法,其实就是对这些需要用到的成员变量在surf函数中赋一下值,比如说这样:
//表面着色函数的编写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)。 比如说这样:
#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函数讲解
<