shader 编程入门(一)
本系列文章由@浅墨_毛星云 出品,转载请注明出处。
文章链接: http://blog.csdn.net/poem_qianmo/article/details/40723789
作者:毛星云(浅墨) 微博:http://weibo.com/u/1723155442
- //-----------------------------------------------【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)); //saturate 把输入的值限制在0到1之间
- //边缘颜色强度
- o.Emission = _RimColor.rgb * pow (rim, _RimPower); //pow(x,y)x的y次方
- }
- //-------------------结束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 - screenPosition.y;
- //获取文本尺寸
- Vector2 stringSize = Style.CalcSize(new GUIContent(text));
- //计算文本框坐标
- Rect rect = new Rect(0f, 0f, stringSize.x + 6, stringSize.y + 4);
- //设定文本框中心坐标
- rect.center = screenPosition - Vector3.up * rect.height * 0.5f;
- //----------------------------------【2.GUI绘制】---------------------------------------------
- //开始绘制一个简单的文本框
- Handles.BeginGUI();
- //绘制灰底背景
- GUI.color = new Color(0f, 0f, 0f, 0.8f);
- GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture);
- //绘制文字
- GUI.color = new Color(1, 1, 1, 0.8f);
- GUI.Label(rect, text, Style);
- //结束绘制
- Handles.EndGUI();
- }
- }
- //-------------------------------------【OnDrawGizmos()函数】---------------------------------------------
- // 说明:场景编辑器中GUI的显示
- //------------------------------------------------------------------------------------------------------------------
- void OnDrawGizmos()
- {
- //ShowInfoInSeneEditor为真时,才进行绘制
- if (ShowInfoInSceneEditor)
- {
- //----------------------------------------【1.光线投射判断&计算位置坐标】----------------------------------
- //定义一条射线
- Ray ray = new Ray(transform.position + Camera.current.transform.up * 6f, -Camera.current.transform.up);
- //定义光线投射碰撞
- RaycastHit raycastHit;
- //进行光线投射操作,第一个参数为光线的开始点和方向,第二个参数为光线碰撞器碰到哪里的输出信息,第三个参数为光线的长度
- collider.Raycast(ray, out raycastHit, Mathf.Infinity);
- //计算距离,为当前摄像机位置减去碰撞位置的长度
- float distance = (Camera.current.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 + Camera.current.transform.up * distance * 0.03f;
- //世界坐标转屏幕坐标
- Vector3 screenPosition = Camera.current.WorldToScreenPoint(worldPositon);
- //z坐标值的判断,z值小于零就返回
- if (screenPosition.z <= 0) { return; }
- //翻转Y坐标值
- screenPosition.y = Screen.height - screenPosition.y;
- //获取文本尺寸
- Vector2 stringSize = Style.CalcSize(new GUIContent(text));
- //计算文本框坐标
- Rect rect = new Rect(0f, 0f, stringSize.x + 6, stringSize.y + 4);
- //设定文本框中心坐标
- rect.center = screenPosition - Vector3.up * rect.height * 0.5f;
- //----------------------------------【2.GUI绘制】---------------------------------------------
- //开始绘制一个简单的文本框
- Handles.BeginGUI();
- //绘制灰底背景
- GUI.color = new Color(0f, 0f, 0f, 0.8f);
- GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture);
- //绘制文字
- GUI.color = new Color(1, 1, 1, 0.8f);
- GUI.Label(rect, text, Style);
- //结束绘制
- Handles.EndGUI();
- }
- }
- }
- //预编译命令结束
- #endif
这个脚本的用法倒是很简单,在代码的说明部分已经详细写出,在这里我们再列出一遍:
第一步:在Unity中拖拽此脚本到某物体之上,或在Inspector中[Add Component]->[浅墨's Toolkit v1.0]->[ShowObjectInfo]
第二步:在Inspector里,ShowObject Info 栏中的TargetCamera参数中选择需面向的摄像机,如Main Camera,FirstPerson Controller等
第三步:在text参数里填需要显示输出的文字。
第四步:完成。运行游戏或在场景编辑器Scene中查看显示效果。
也就是拖拽ShowObjectInfo脚本或者直接添加组件给需要附加文字的物体,然后在Inspector中输入需要显示的文字,然后选择其面对的摄像机就可以了。
我们将ShowObjectInfo脚本拖拽给上文中刚刚变得炫酷外形黑科技的Capsule:
那么他在Inspector就多了一个“ShowObject Info(Script)”组件,将该组件的Text项中填上“凹凸纹理+边缘发光效果”,TargetCamera中填上First Person Controller的子物体Main Camera:
最后,得到的效果就是这样:
五、总结、配套资源&最终工程下载
好了,本篇的文章到这里就大概结束了。
今天讲的内容还是非常多的,对于新接触Unity的朋友们来说或许还得好好消化消化,而熟悉Unity的朋友应该很快就可以看懂,或者觉得浅墨讲了一堆废话,orz。
这篇文章的内容说白了就非常简单,也就是新建工程,然后导入三个浅墨提前准备好的unitypackage游戏资源,点一点鼠标拖动拖动脚本,新建一个Shader,写点代码,再创建一个Material,Shader赋给这个Material,最后创建一个胶囊状Capsule,Material赋给这个Capsule,点运行查看最终效果。一切,就是这么简单。:)
本文配套的三个unitypackage打包请点击此处下载:
【浅墨Unity3D Shader编程】之一 配套的三个unitypackage打包下载
本文最终的Unity工程请点击此处下载:
【浅墨Unity3D Shader编程】之一 配套Unity工程
最后放几张最终的场景美图吧。
站在亭子上看世界:
逼真的光晕:
漂亮的天空:
乱真的水面:
蓝天和草地树木交相辉映:
OK,全文到此结束。
新的游戏编程之旅已经开启,下周一,我们不见不散。