Unity3D Shader编程之一 夏威夷篇:游戏场景的创建 & 第一个Shader的书写
Posted 浅墨_毛星云
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity3D Shader编程之一 夏威夷篇:游戏场景的创建 & 第一个Shader的书写相关的知识,希望对你有一定的参考价值。
本系列文章由@浅墨_毛星云 出品,转载请注明出处。
文章链接: http://blog.csdn.net/poem_qianmo/article/details/40723789
作者:毛星云(浅墨) 微博:http://weibo.com/u/1723155442
作为一个系统介绍Unity3D中Shader编写的系列文章的开篇,本文的第一部分为系列文章的前言,然后第二部分介绍了这个系列文章中我们会使用的游戏场景创建方式,最后一部分讲解了如何在Unity中创建和使用Shader,为后面专注于介绍如何在Unity中进行Shader编程打好了基础。
因为后面推出的系列文章会着重介绍各种Shader的写法和实现,不会再具体讲解如何创建场景和写出Shader代码后如何使用,相信这篇文章作为本系列的开篇,发表出来肯定还是会对大家多少有些帮助的。大家以后阅读稍后推出的Unity Shader系列文章的时候,有场景创建或者Shader代码写好了如何使用方面的疑问的话,可以随时回过头来查阅这篇文章。
OK,就让我们从这篇文章开始一趟精彩万分的Shader游戏编程旅途。
依旧国际惯例,看几张文章中实现的场景美图先:
上图中展示的文本配套Unity工程的可运行exe浅墨也为大家准备好了,有兴趣的朋友们可以点击 这里进行下载、运行和探索:
PS:文章配套的三个unitypackage和最终的工程源码在文章末尾处提供下载。
一、系列文章前言
在这个系列开头,浅墨想说的是,其实这个系列文章中我们学的主要是着色器编程技术,重点不是学Unity。
甚至可以这样说,我们学的是HLSL——没错,就是DirectX中的那个HLSL。
为什么这样讲,让我们来捋一捋。
首先,Unity中编写Shader的语言叫做ShaderLab,而ShaderLab说白了就是裹着一层皮的CG着色器语言而已。Cg,即C forgraphics,即用于图形的C语言,是微软Microsoft和英伟达NVIDIA相互协作在标准硬件光照语言的语法和语义上达成的一种一致性协议。
HLSL和CG其实是同一种语言(参见Cg教程_可编程实时图形权威指南29页的致谢部分)。很多时候,我们会发现用HLSL写的代码可以直接当中Cg代码使用。
Microsoft和NVIDIA联手推出CG语言,想在经济和技术上实现双赢,从而通过这种方式联手打击他们共同的对手GLSL。
既然Unity主打Shader编程的语言ShaderLab是CG语言披上一层皮,而CG语言又约等于HLSL。这就是说,在Unity中写Shader约等于用HLSL写Shader,也就约等于给DirectX写Shader。虽然有点绕orz,最后总结一下也就是:
在Unity中写Shader约等于给DirectX写Shader
而Unity又是这样一个集万千宠爱于一身的可见即所得的目前在移动互联网时代火到不行的游戏引擎。可以说,Unity可见即所得的开发环境非常适合Shader的学习,而且在Unity分分钟可以创建出来一个漂亮的场景里面写写Shader,心情都会好很多。不再是苦逼地面朝代码背朝天了。
所以浅墨决定开始在Unity中进行这个shader学习系列,毕竟Unity、CG、HLSL不分家。
另外促成这个系列文章的成型的一个原因是Unity的Asset store。浅墨很喜欢Unity引领的Asset store这样一站式的游戏素材商店,里面有数不尽的游戏插件、素材、资源、脚本代码参考、shader资料等等,甚至现成的游戏工程范例。Asset store给开发者们带来了很大的便利,简直就是游戏开发界的大宝库。
但需要说明的是,Unity这款引擎的缺点也是很明显的。比如Unity不对外开源,我们看不到源码,坑比较多,遇到坑了看不到
源码给解决带来了很大难度。执行效率还是不太行,渲染大场景和做高级渲染的时候还是显得力不从心。比如渲染大片的近乎
真实的动态水面时,帧数就立马降下来了,需要后期做很多的优化。就画面质量来说和Unreal Engine和cryEngine等次时代
引擎比还是有差距。是最近几年互联网的浪潮和得屌丝者的天下商业模式促进了其最近几年如此的成功。
说了这么多,总结一下。
Unity只是我们学习CG、HLSL编程可见即所得的好帮手好工具而已。我们主要还是利用它来更好的学计算机图形学和Shader编程,顺便掌握目前热门的Unity引擎的基本使用和研发思路。
我们还是忘不了C++和DirectX,我们还是渴望通过自己的努力,最终有能力用C/C++一句一句写出自己的游戏引擎,我们还是想从零开始造轮子,毕竟那样一句一句写出来的代码都是自己的,而不是那些别人为我们准备好的现成的API函数。
谨以此前言,与诸君共勉。
二、用Unity创建第一个美丽的游戏场景
首先要说明的是,作为一个从入门内容开始逐渐深入介绍的系列教程,这一部分在Unity下创建场景的内容是为还不太熟悉Unity的朋友们准备的,如果你已经熟悉了Unity的使用,这部分可以快速跳读。
OK,正式开始吧。
2.1 【第一步】当然是新建一个项目
大家肯定都知道,每次新建项目后或者新建场景后,会得到一片Unity中默认为全蓝的场景。想要场景变得有生机,一般都是去菜单栏的GameObject里面新建Terrain(地形)然后进行编辑。Terrain的制作很耗时而且水很深,想要精通也是得花一些功夫。
甚至Unity Asset Store中有各种可以辅助快速生成AAA级大作风格的真实场景的插件,如Terrain Composer,配合着Unity中有名的Relief Terrain Pack v3地形辅助着色渲染工具,可以生成近乎以假乱真的三维自然风光出来。
漂亮地形的创建当然不属于我们讲解的重点,网络上有数不清的文章和视频讲这方面的内容,有需要的话,大家去学一些基础,或者直接用Asset Store中现成的各种漂亮场景。反正浅墨是各种在网上下载AssetStore中美工大牛们做场景,然后简单的修改,为自己测试和平常调数值所用。
这不,浅墨为了写这篇博客,就为大家再加工“创作“了一个夏威夷风格的场景来:)
2.2 【第二步】导入Hawaii Environment.unitypackage场景包
上文讲到,为了节约时间,浅墨提前为大家修改好了一个场景,然后这个场景已经打包,叫做“HawaiiEnvironment.unitypackage “,在文章末尾提供下载。(限于Unity对中文的支持拙计,无奈只能取英文名,不然直接导入就报错)。
【HawaiiEnvironment.unitypackage单独下载请点我】
双击这个包,导入到我们空空如也的工程中,经过一段时间的读条,就导入完毕了。然后我们双击打开出现在Project面板中Assets文件夹下的场景文件Hawaii Environment.unity
接着便打开了场景,如果打开成功,Scene面板中应该便出现了如下类似的场景画面:
因为略去了场景编辑部分,直接导入,所以过程是非常简单的。但是,这还完全不够。让我们在场景中添加一个可以自由控制的摄像机吧。
2.3 【第三步】添加第一人称摄像机
浅墨准备的这个场景包是没有摄像机的,单单就是场景,所以我们还需要在场景中添加一个摄像机。
大家应该清楚,比较常见添加摄像机的做法是通过菜单栏中的GameObject->CreateOther->Camera来添加。但这种方式的摄像机是固定的,不合我们的要求。我们想添加的是一个在游戏运行时可以自由移动视角的第一人称摄像机。其实Unity自带的资源包中刚好可以满足我们的要求。于是我们在Project面板中右键【Import Package】,或者菜单栏中Assets->ImportPackage->Character Controller来导入官网为我们准备的的角色控制资源包。如下图:
点击之后,会弹出如下的资源导入确认窗口,我们直接点确定就行了:
因为这个包很小,所以很快就导入完成,这时Assets根文件夹下会出现一个名为Standard Assets的文件夹,展开它或者点进去,就是名为【CharacterControllers】的文件夹,继续点进去,发现了一个胶囊状的叫【First PersonController】的家伙,这就是我们需要的了。
然后我们先在Scene面板中利用【右键+键盘W、A、S、D】以及滚轮等操作调整好场景,然后在我们刚才的【CharacterControllers】下点击这个胶囊装的【First Person Controller】按住不放,拖动到Scene场景中,选到合适的地方(如草坪上)后就放手,操作如下:
Unity会自动将这个【CharacterControllers】的中心位置依附到地形表面。
这个时候我们会发现之前是黑屏的Game面板中也有了画面:
这时我们还要将这个 First Person Controller的底部向上拖动一点,不然运行游戏时我们会不停的往下掉,因为在Unity默认情况下First Person Controller的中心位于中部,这会照成它的底部已经穿透地形,悬空位于地形的下方,自然一运行就往下掉。
我们Hierarchy面板中选中First Person Controller,工具栏中选择【移动】工具
然后对着场景中胶囊上的代表Y轴的绿色箭头向上适当拖动,让胶囊的底部确保位于草地之上就行了。
这时候我们点击unity中间三角尖的【运行】按钮,就可以自由地在场景中观察和移动了~
Unity第一人称控制器默认操作方式类似CS,CF一类的FPS游戏。W、A、S、D前后左右移动,空格跳跃,鼠标移动调整视角,非常有亲切感有木有~
这就很好地体现了Unity的入门容易的特点,只用点点鼠标一个漂亮的场景就展现在眼前。
2.4 【第四步】在游戏场景中加入背景音乐
话说这么美丽的场景怎么能没有音乐?
不妨就让我们添加一段优美的钢琴曲吧。曲子浅墨都为大家准备好了,上文已经导入的HawaiiEnvironment.unitypackage包中,在Assets根目录下包含了一首林海的《日光告别》。
我们要做的只要是把这个音乐文件拖拽到第一人称摄像机First PersonController上就可以了,就像箭头中指的这样:
拖拽完成后,First Person Controller的Inspector面板中就应该会多了一个Audio Source组件。
运行场景,伴随着美丽的场景,“吹着海风”,优美的钢琴曲入耳,非常怡人。
先放两张测试过程中的场景美图,再继续我们下一部分的讲解吧:
非常逼真的水效,采用大名鼎鼎的NGUI工作室Tasharen Entertainment出品的水面插件:
三、导入QianMo’s Toolkit并使用
3.1 认识QianMo's Toolkit
所谓的QianMo's Toolkit,其实就是浅墨为场景测试写的一个小脚本工具集,打包成一个unitypackage方便多次使用而已。若有需要,浅墨会在其中添加更多的功能。
以后我们每次新建工程的时候,只要导入这个小工具就可以使用我们之前已经写好的各种特性,非常便捷。
【QianMo's Toolkit v1.0.unitypackage单独下载请点我】
QianMo's Toolkit v1.0版的内容如下:
也就是包含了五个脚本文件,两张图片。这五个脚本文件的功能分别为:
ShowFPS:在游戏运行时显示帧率相关信息
ShowObjectInfo:在场景中和游戏窗口中分别显示添加给任意物体文字标签信息。隐藏和显示可选,基于公告板技术实现。
ShowGameInfo:在游戏运行时显示GUI相关说明
ShowLogo:在游戏运行时显示Logo
ShowUI:在游戏运行时显示简单的镶边UI。
这个五个脚本的代码浅墨都已经详细注释,在后续文章中有机会我们会介绍其具体写法。这篇文章中就先简单的认识一下他们就好。PS:下文第四节中贴出了ShowGameInfo脚本的全部代码。
3.2 使用QianMo's Toolkit
上文已经说了,既然这是一个unitypackage,那么只用双击它导入到我们当前的项目中就行了。导入完成之后。Assets文件夹下就又多了一个名为” QianMo's Toolkit v1.0“的文件夹,内容就是我们刚才介绍的5个脚本文件两张图:
暂时我们要使用的是ShowGameInfo、ShowLogo、ShowUI这三个脚本文件,把它们一起拖到我们之前创建的第一人称摄像机First Person Controller上就行了:
拖动完成后,我们在First Person Controller的Inspector面板中发现其多了三个组件,就是我们给他添加的这个三个脚本:
其实Show Logo无关紧要,就是显示了浅墨自己的logo而已,当然你可以换成自己的logo。show UI也无关紧要,就是显示了一个顶部的镶边png。主要的是这个ShowGameInfo,它是用于显示帧率等相关文字消息的。(其实简约党会觉得三个都无关紧要,orz)
拖动完成之后,再次运行,我们来看一看效果:
可以发现,游戏窗口的边边角角多了一些说明和图片,以及有了帧率的显示。
四、书写和使用第一个Shader
上文讲解的都是一般的场景构建过程,接下来就要正式开始我们的核心部分,书写第一个Shader了。在完成上文中讲解的创建好漂亮的场景之后,我们首先可以在Assets根目录下创建一个文件夹,用于以后Shader和Material文件的存放。创建过程可以是在Project面板的空白处右键->Create->Folder,也可以是点击Project面板中的Create下拉菜单->Folder
给新出来的这个文件夹取名Shaders,然后回车。然后再用同样的方法在Assets根目录下创建一个名为Textures的文件夹,用于稍后素材图片的存放。那么,如果你按照浅墨按照目前描述的步骤来的话,Assets根目录到现在就是这样的5个文件夹:
接着,进去到我们创建的Shader文件夹。同样在空白处右键->Create->Shader,或者是直接点Create下拉菜单->Shader,创建一个Shader文件,取名为 “0.TheFirstShader”。然后双击打开它,Unity会默认使用名为MonoDevelop的编辑器打开这个Shader文件。
小tips:可以在菜单栏中Edit->Preferences->ExternalTools中调成默认用Visual Studio打开,但未经修改配置文件的Visual Studio对Shader后缀的文件是不支持语法高亮的,浅墨修改了部分配置文件才让Visual Studio支持了Unity Shader书写的语法高亮。对于不太清楚如何修改的朋友,可以善用搜索引擎,或者过些天浅墨会单独发一篇名为《Unity中使用Visual Studio编写shader并设置代码高亮》的文章来专门讲解。
作为初次写Shader,我们暂且先用MonoDevelop顶一顶,后面的文章再换用修改了配置文件的Visual Studio。
好了,用MonoDevelop打开我们新建的这个Shader文件,发现Unity已经为我们写好了很多代码。
我们不妨自己重新写点不一样的东西。删掉原本的这些代码,拷贝浅墨写的如下代码到编辑器中:
//-----------------------------------------------【Shader说明】---------------------------------------------- // Shader功能: 凹凸纹理显示+自选边缘颜色和强度 // 使用语言: Shaderlab // 开发所用IDE版本:Unity4.5 06f 、Monodevelop // 2014年11月2日 Created by 浅墨 // 更多内容或交流请访问浅墨的博客:http://blog.csdn.net/poem_qianmo //--------------------------------------------------------------------------------------------------------------------- Shader "浅墨Shader编程/0.TheFirstShader" //-------------------------------【属性】----------------------------------------- Properties _MainTex ("【纹理】Texture", 2D) = "white" _BumpMap ("【凹凸纹理】Bumpmap", 2D) = "bump" _RimColor ("【边缘颜色】Rim Color", Color) = (0.17,0.36,0.81,0.0) _RimPower ("【边缘颜色强度】Rim Power", Range(0.6,9.0)) = 1.0 //----------------------------【开始一个子着色器】--------------------------- SubShader //渲染类型为Opaque,不透明 Tags "RenderType" = "Opaque" //-------------------开始CG着色器编程语言段----------------- CGPROGRAM //使用兰伯特光照模式 #pragma surface surf Lambert //输入结构 struct Input float2 uv_MainTex;//纹理贴图 float2 uv_BumpMap;//法线贴图 float3 viewDir;//观察方向 ; //变量声明 sampler2D _MainTex;//主纹理 sampler2D _BumpMap;//凹凸纹理 float4 _RimColor;//边缘颜色 float _RimPower;//边缘颜色强度 //表面着色函数的编写 void surf (Input IN, inout SurfaceOutput o) //表面反射颜色为纹理颜色 o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; //表面法线为凹凸纹理的颜色 o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); //边缘颜色 half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal)); //边缘颜色强度 o.Emission = _RimColor.rgb * pow (rim, _RimPower); //-------------------结束CG着色器编程语言段------------------ ENDCG //“备胎”为普通漫反射 Fallback "Diffuse"
由于这是第一篇Shader系列文章,已经涉及到很多内容了,所以浅墨不可能展开讲解这段代码的具体思路和写法,不过已经详细注释,大家应该会多少有点明白。随着稍后文章的深入,这段代码就显得很简单易懂了。
拷贝完成,保存一下这段代码,unity会自动检测和编译被保存的代码,我只需返回Unity窗口,等待编译完成即可。若没有错误,在“0.TheFirstShader”的inspector面板中得到的结果应该是有红色的错误提示的。
需要注意的是,Shader想要使用到游戏物体上,一般得有个媒介,这个媒介就是我们的老朋友——材质(Material)。我们把Shader作用于材质,接着再把材质对应地作用于给游戏物体,这样写的Shader就间接地给物体表面使用了。
而这一层关系,在Unity中完全可以通过点点鼠标,拖动来完成。下面我们就来讲一讲如何将一个着色程序的结果显示到物体表面上。
知道以上原理了就很简单了,在“0.TheFirstShader.shader”的同一目录下创建一个Material。同样是可以通过Create下拉菜单->Material或者空白处右键->create->Material来完成。
为了到时候方便对应,我们将这个材质也取名为0.TheFirstShader。
接着,将0.TheFirstShader.shader拖动到0.TheFirstShader材质身上然后释放。
拖动完成后,我们单击0.TheFirstShader材质,打开他的面板,发现他已经和一开始不一样了,泛着蓝光:
还没完,接下来我们还得给这个材质添加两张纹理图片。图片浅墨也已经提前准备好了,在名为Textures01 by QianMo.unitypackage的Unity包中,同样双击这个包然后打开导入到项目中。
【Textures01 by QianMo.unitypackage单独下载请点我】
我们在Textures文件夹下找到这两张纹理,接下来做的就是将他们拖动到0.TheFirstShader材质对应的纹理区域中,如下:
或者点击这里的Select分别选择,操作如下:
两张纹理选择完毕后,我们的材质就准备好了,最后的结果,有点黑科技,如各种科幻游戏和电影中发光的矿石,非常炫酷:
OK,那么就只剩下最后一步了,就是在场景中创建一个物体,然后将我们做好的材质拖拽到物体身上赋给这个物体就行了。
菜单栏【GameObject】->【Create Other】->【Capsule】或者【Create】下拉菜单->【Capsule】来在场景中创建一个胶囊装的物体。把他拖动到和我们的第一人称摄像机【First Person Controller】很近的地方,这样方便观察,接着就可以把我们的“0.TheFirstShader”材质直接拖拽给场景中的这个胶囊,或者Hierachy面板中【Capsule】名字上就行了,操作如下图中的箭头所示:
经过拖拽,Capsule加上Material后的效果如下:
4.1 给使用Shader的物体加上文字说明
为了以后介绍多个Shader写法时能更清晰更方便,浅墨专门在QianMo’s Toolkit中做了一个可以在场景中和游戏窗口中分别显示附加给任意物体文字标签信息的工具脚本,叫做ShowObjectInfo,其详细注释的代码如下:
//-----------------------------------------------【脚本说明】------------------------------------------------------- // 脚本功能: 在场景中和游戏窗口中分别显示给任意物体附加的文字标签信息 // 使用语言: C# // 开发所用IDE版本:Unity4.5 06f 、Visual Studio 2010 // 2014年10月 Created by 浅墨 // 更多内容或交流,请访问浅墨的博客:http://blog.csdn.net/poem_qianmo //--------------------------------------------------------------------------------------------------------------------- //-----------------------------------------------【使用方法】------------------------------------------------------- // 第一步:在Unity中拖拽此脚本到某物体之上,或在Inspector中[Add Component]->[浅墨's Toolkit v1.0]->[ShowObjectInfo] // 第二步:在Inspector里,Show Object Info 栏中的TargetCamera参数中选择需面向的摄像机,如MainCamera // 第三步:在text参数里填需要显示输出的文字。 // 第四步:完成。运行游戏或在场景编辑器Scene中查看显示效果。 // PS:默认情况下文本信息仅在游戏运行时显示。 // 若需要在场景编辑时在Scene中显示,请勾选Show Object Info 栏中的[Show Info In Scene Editor]参数。 // 同理,勾选[Show Info In Game Play]参数也可以控制是否在游戏运行时显示文本信息 //--------------------------------------------------------------------------------------------------------------------- //预编译指令,检测到UNITY_EDITOR的定义,则编译后续代码 #if UNITY_EDITOR //------------------------------------------【命名空间包含部分】---------------------------------------------------- // 说明:命名空间包含 //---------------------------------------------------------------------------------------------------------------------- using UnityEngine; using UnityEditor; using System.Collections; //添加组件菜单 [AddComponentMenu("浅墨's Toolkit v1.0/ShowObjectInfo")] //开始ShowObjectInfo类 public class ShowObjectInfo : MonoBehaviour //------------------------------------------【变量声明部分】---------------------------------------------------- // 说明:变量声明部分 //------------------------------------------------------------------------------------------------------------------ public string text="键入你自己的内容 by浅墨";//文本内容 public Camera TargetCamera;//面对的摄像机 public bool ShowInfoInGamePlay = true;//是否在游戏运行时显示此信息框的标识符 public bool ShowInfoInSceneEditor = false;//是否在场景编辑时显示此信息框的标识符 private static GUIStyle style;//GUI风格 //------------------------------------------【GUI 风格的设置】-------------------------------------------------- // 说明:设定GUI风格 //------------------------------------------------------------------------------------------------------------------ private static GUIStyle Style get if (style == null) //新建一个largeLabel的GUI风格 style = new GUIStyle(EditorStyles.largeLabel); //设置文本居中对齐 style.alignment = TextAnchor.MiddleCenter; //设置GUI的文本颜色 style.normal.textColor = new Color(0.9f, 0.9f, 0.9f); //设置GUI的文本字体大小 style.fontSize = 26; return style; //-----------------------------------------【OnGUI()函数】----------------------------------------------------- // 说明:游戏运行时GUI的显示 //------------------------------------------------------------------------------------------------------------------ void OnGUI( ) //ShowInfoInGamePlay为真时,才进行绘制 if (ShowInfoInGamePlay) //---------------------------------【1.光线投射判断&计算位置坐标】------------------------------- //定义一条射线 Ray ray = new Ray(transform.position + TargetCamera.transform.up * 6f, -TargetCamera.transform.up); //定义光线投射碰撞 RaycastHit raycastHit; //进行光线投射操作,第一个参数为光线的开始点和方向,第二个参数为光线碰撞器碰到哪里的输出信息,第三个参数为光线的长度 collider.Raycast(ray, out raycastHit, Mathf.Infinity); //计算距离,为当前摄像机位置减去碰撞位置的长度 float distance = (TargetCamera.transform.position - raycastHit.point).magnitude; //设置字体大小,在26到12之间插值 float fontSize = Mathf.Lerp(26, 12, distance / 10f); //将得到的字体大小赋给Style.fontSize Style.fontSize = (int)fontSize; //将文字位置取为得到的光线碰撞位置上方一点 Vector3 worldPositon = raycastHit.point + TargetCamera.transform.up * distance * 0.03f; //世界坐标转屏幕坐标 Vector3 screenPosition = TargetCamera.WorldToScreenPoint(worldPositon); //z坐标值的判断,z值小于零就返回 if (screenPosition.z <= 0)return; //翻转Y坐标值 screenPosition.y = Screen.height - screenPosi
以上是关于Unity3D Shader编程之一 夏威夷篇:游戏场景的创建 & 第一个Shader的书写的主要内容,如果未能解决你的问题,请参考以下文章
Unity3D Shader编程之二 雪山飞狐篇:Unity的基本Shader框架写法&颜色光照与材质
浅墨Unity3D Shader编程之二 雪山飞狐篇:Unity的基本Shader框架写法&颜色光照与材质
Unity3D Shader编程之四 热带雨林篇: 剔除深度测试Alpha测试以及基本雾效合辑
Unity3D Shader编程之三 光之城堡篇:子着色器通道与标签的写法 & 纹理混合