本系列文章由@浅墨_毛星云 出品,转载请注明出处。
文章链接: http://hpw123.net/a/C__/kongzhitaichengxu/2014/1222/163.html
作者:毛星云(浅墨) 微博:http://weibo.com/u/1723155442
QQ交流群:330595914
很多其它文章尽在:http://www.hpw123.net
本文介绍了Unity中Shader书写中会用到的剔除、深度測试、Alpha測试以及基本雾效相关的语法知识。然后以6个Shader的书写作为实战内容。最后创建了一个生机勃勃的热带雨林场景进行了Shader的測试。依然是国际惯例。先上本文配套程序的截图。
绿色的海洋:
满眼的生机:
竹林:
參天大树:
飘到脸上的树叶:
OK。图先就上这么多。文章末尾有很多其它的执行截图,并提供了原project的下载。可执行的exe下载在这里:
好的。我们正式開始。
一、剔除与深度測试(Culling & Depth Testing)相关内容
1.1 剔除(Culling)的概念
对于实时交互的3D环境而言,现实的速度和效率是很重要的。尽管如今的硬件能力很的快,可是要想保持30FPS的同一时候处理数十万的三角形,以如今的主流机器来说还是有些困难的。
为了解决这样的问题,人们提出了非常多方法,当中有LOD,有Culling。
这两种方法并不矛盾,并且我们往往须要在culling的基础上再使用LOD进一步解决pipeline的负担。
剔除是一种通过避免渲染背对观察者的几何体面来提高性能的优化措施。全部几何体都包括正面和反面。
剔除基于大多数对象都是封闭的事实;假设你有一个立方体,你不会看到背离你的那一面(总是仅仅有一面在你的前方)。因此我们不须要绘制出背面。
因此也被称做背面剔除。
一言以蔽之,所谓剔除。就是被挡住或视角以外的我们看不到的物体,由于它们无关紧要。所以我们就不去绘制。以节省资源,提高场景的执行效率。
1.2 深度測试(Depth Testing)的概念
在复杂的场景中,通常有多个物体须要绘制。这些物体之间一般会存在遮挡关系,离观察点较远的物体会由于近处物体的者的遮挡而不可见或仅仅有部分可见,Direct3D图形系统提供了深度測试功能来实现这样的效果。深度測试能够简化复杂场景的绘制。确保仅仅有场景内的对象的最靠近的表面參与绘制。
浅墨之前在写DirectX相关博文的时候写过对深度測试的形象化理解,在这边也列出来吧:
把深度測试看做在一口井的井口处向井中观望。把全部物体都赋予一个深度值,放到井中来显示。深度越深的物体,离井口就越远。深度越浅的物体,离井口就越近。井表面的深度值为0。离井口近而深度浅的物体。可能会把离井口远的物体遮挡住。终于显示在屏幕上的开启深度測试后的画面,就如在井口处向井中观望里面物体显示出的遮挡与层次的效果一样。当然,离井口的深度就是每一个物体在世界坐标系中的矩阵的Z坐标值了。
1.3 剔除与深度測试(Culling & DepthTesting)相关句法
语句之中的一个:Cull Back | Front| Off
此语句用于控制几何体的哪一面会被剔除(也就是不被绘制) 。当中:
- Cull Back—— 不绘制背离观察者的几何面
- Cull Front—— 不绘制面向观察者的几何面。用于由内自外的旋转对象
- Cull Off —— 显示全部面。用于特殊效果。
语句之二:ZWrite On | Off
此语句用于控制是否将来之对象的像素写入深度缓冲(默认开启),假设须要绘制纯色物体,便将此项打开,也就是写上ZWrite On。假设要绘制半透明效果,关闭深度缓冲,则用ZWrite Off。
语句之三:ZTest Less |Greater | LEqual | GEqual | Equal | NotEqual | Always
此语句用于控制深度測试怎样运行。
缺省值是LEqual (绘制和存在的对象一致或是在当中的对象。隐藏其背后的对象),含义列举相应例如以下:
Greater |
仅仅渲染大于AlphaValue值的像素 |
GEqual |
仅仅渲染大于等于AlphaValue值的像素 |
Less |
仅仅渲染小于AlphaValue值的像素 |
LEqual |
仅仅渲染小于等于AlphaValue值的像素 |
Equal |
仅仅渲染等于AlphaValue值的像素 |
NotEqual |
仅仅渲染不等于AlphaValue值的像素 |
Always |
渲染全部像素,等于关闭透明度測试。 等于用AlphaTest Off |
Never |
不渲染不论什么像素 |
语句之四:Offset Factor ,Units
此语句用两个參数(Facto和Units)来定义深度偏移。
- Factor參数表示 Z缩放的最大斜率的值。
- Units參数表示可分辨的最小深度缓冲区的值。
于是,我们就能够强制使位于同一位置上的两个集合体中的一个几何体绘制在还有一个的上层。比如偏移量Offset 设为0, -1(即Factor=0, Units=-1)的值使得靠近摄像机的几何体忽略几何体的斜率。而偏移量为-1,-1(即Factor =-1, Units=-1)时,则会让几何体偏移一个微小的角度。让观察使看起来更近些。
二、Alpha測试(Alpha testing)相关内容解说
2.1 Alpha測试的概念
Unity中。Alpha測试(Alpha Testing)是阻止像素被写到屏幕的最后机会。在Pineline中Alpha測试的位置例如以下:
在终于渲染出的颜色被计算出来之后,可选择通过将颜色的透明度值和一个固定值比較。假设比較的结果失败,像素将不会被写到显示输出中。
Alpha測试在渲染凹形物体的透明部分时很实用。
显卡上有着每一个像素写到屏幕上的深度记录。
假设一个新的像素比原来的像素的深度深,那么新的像素就不会被写到屏幕中。
让我们看一幅图:
细致看上图。会发现:
- 左边的树使用了透明度測试(AlphaTest),注意在它的图形上的像素是怎样全然透明或不透明的。
- 中间的树仅仅使用透明度混合(Alpha Blending)来渲染,注意因为深度缓冲的缘故靠近分支的透明部分是怎样覆盖更远的叶子。
- 最右边的树是通过下文实战中的第六个Shader“6.简单的植被Shader”着色器来渲染的,实现了透明度混合和透明度測试的组合。细节没有粗糙之处,浑然天成。
2.2 Alpha測试相关句法
语句之中的一个:AlphaTest Off
此语句用于渲染全部像素(缺省)
语句之二:AlphaTest comparison AlphaValue
此语句用于设定透明度測试仅仅渲染在某一确定范围内的透明度值的像素。当中的comparison取值为下表之中的一个:
Greater |
Only render pixels whose alpha is greater than AlphaValue. 大于 |
GEqual |
Only render pixels whose alpha is greater than or equal to AlphaValue. 大于等于 |
Less |
Only render pixels whose alpha value is less than AlphaValue. 小于 |
LEqual |
Only render pixels whose alpha value is less than or equal to from AlphaValue. 小于等于 |
Equal |
Only render pixels whose alpha value equals AlphaValue. 等于 |
NotEqual |
Only render pixels whose alpha value differs from AlphaValue. 不等于 |
Always |
Render all pixels. This is functionally equivalent to AlphaTest Off. |
Never |
Don't render any pixels. 不渲染不论什么像素 |
而AlphaValue为一个范围在0到1之间的浮点值。也能够是一个指向浮点属性或是一个范围属性。在后一种情况下须要使用标准的方括号写法标注索引名字,如([变量名]).
三、基本雾效(Fog)设置
3.1 雾效相关背景和概念理解
雾效(Fog)參数用于控制雾相关的渲染。
在计算机图形学中,雾化是通过混合已生成的像素的颜色和基于到镜头的距离来确定的终于的颜色来完毕的。雾化不会改变已经混合的像素的透明度值,仅仅是改变RGB值。
Unity中的缺省雾设定是基于Edit->RenderSettings中的设置的,且雾模式既能够是Exp2也能够是关闭;密度和颜色全然取自设定。
注意假设使用了片段着色器的话。着色器中的雾设定仍然是有效的。
假设平台没有对固定管线的雾功能支持,Unity会实时补上着色器以支持须要的雾模式。
3.2 雾效相关的句法
语句之中的一个:Fog { FogCommands }
此语句用于设定雾命令的内容,详细的填在大括号之中的内容见以下的语句。
语句之二:Mode Off | Global | Linear | Exp | Exp2
此语句用于定义雾模式。缺省是全局的,根据雾在渲染设定中是否打开确定可从无变化到平方值
语句之三:Color ColorValue
此语句用于设定雾的颜色值。
语句之四:Density FloatValue
此语句以指数的方式设定雾的密度。
语句之五:Range FloatValue , FloatValue
此语句用于为linear类型的雾设定远近距离值。
OK,本文的基本知识就将这么多,接下来看看QianMo's Toolkit的更新,然后開始基于上面解说的基础知识的Shader实战。
四、QianMo's Toolkit迎来v1.2更新
如前面三篇文章中所言,QianMo's Toolkit是浅墨在Unity中写Shader时会用到的一些脚本小工具,用C#来实现。
此次在QianMo's Toolkit Ver1.2版中增加了新的特性——检測当前系统的cpu与显卡的型号和參数。并显示到游戏窗体中。事实上现代码例如以下:
- //-----------------------------------------------【脚本说明】-------------------------------------------------------
- // 脚本功能: 在游戏执行时显示系统CPU、显卡信息
- // 使用语言: C#
- // 开发所用IDE版本号:Unity4.5 06f 、Visual Studio 2010
- // 2014年12月 Created by 浅墨
- // 很多其它内容或交流。请訪问浅墨的博客:http://blog.csdn.net/poem_qianmo
- //---------------------------------------------------------------------------------------------------------------------
- //-----------------------------------------------【用法】-------------------------------------------------------
- // 方法一:在Unity中拖拽此脚本到场景随意物体之上
- // 方法二:在Inspector中[Add Component]->[浅墨's Toolkit v1.2]->[ShowSystemInfo]
- //---------------------------------------------------------------------------------------------------------------------
- using UnityEngine;
- using System.Collections;
- //加入组件菜单
- [AddComponentMenu("浅墨's Toolkit v1.2/ShowSystemInfo")]
- public class ShowSystemInfo : MonoBehaviour
- {
- string systemInfoLabel;
- public Rect rect = new Rect(10, 100, 300, 300);
- void OnGUI()
- {
- //在指定位置输出參数信息
- GUI.Label(rect,systemInfoLabel);
- }
- void Update ()
- {
- //获取參数信息
- systemInfoLabel = " CPU型号:" + SystemInfo.processorType + " (" + SystemInfo.processorCount +
- " cores核心数, " + SystemInfo.systemMemorySize + "MB RAM内存) " + " 显卡型号:" + SystemInfo.graphicsDeviceName + " " +
- Screen.width + "x" + Screen.height + " @" + Screen.currentResolution.refreshRate +
- " (" + SystemInfo.graphicsMemorySize + "MB VRAM显存)";
- }
- }
加入此脚本到场景中的随意物体后,填一下当中的x和y坐标的值,用于设置输出在屏幕中的哪个位置。
接着点执行,于是在游戏窗体的指定区域中出现了CPU和显卡的信息:
于是。眼下的QianMo's Toolkit v1.2拥有的功能例如以下所看到的:
ShowFPS:在游戏执行时显示帧率相关信息
ShowObjectInfo:在測试过程里。于场景中和游戏窗体中分别显示加入给随意物体文字标签信息。
隐藏和显示可选,基于公告板技术实现。
ShowGameInfo:在游戏执行时显示GUI相关说明
ShowLogo:在游戏执行时显示Logo
ShowUI:在游戏执行时显示简单的镶边UI。
SetMaxFPS :用于突破Unity每秒渲染 60帧的设定,自由设置最大帧率。
ShowObjectInfoInGamePlay:用于公布游戏之后显示文本信息。
ShowSystemInfo:在游戏执行时显示系统CPU、显卡信息
五、写Shader实战
依据上文解说的剔除、深度測试和Alpha測试操作的句法,我们将完毕例如以下六个Shader的写法:
1.用剔除操作渲染对象背面
2.渲染对象背面v2
3.用剔除实现玻璃效果
4.基本Alpha測试
5.顶点光照+可调透明度
6.简单的植被Shader
让我们通过具体凝视的代码一个一个将他们讲清楚。
1.用剔除操作渲染对象背面
先写一个很easy的仅仅会渲染对象的背面的Shader:
- Shader "浅墨Shader编程/Volume4/12.用剔除操作渲染对象背面"
- {
- //--------------------------------【子着色器】--------------------------------
- SubShader
- {
- Pass
- {
- //【1】设置顶点光照
- Material
- {
- Emission(0.3,0.3,0.3,0.3)
- Diffuse (1,1,1,1)
- }
- //【2】开启光照
- Lighting On
- //【3】剔除正面(不绘制面向观察者的几何面)
- Cull Front
- }
- }
- }
我们将此Shader编译后赋给材质,得到例如以下效果,能够发现比原本默认的材质颜色暗。由于我们绘制的是物体的背面:
我们将材质赋给一个立方体,能够看到。通过此Shader我们能够渲染对象的背面,从而看到一个非常奇怪立方体(由于我们看到的是它的内部)。
效果例如以下:
2. 用剔除操作渲染对象背面(第二版)
让我们给上面写的这个Shader加上之前我们已经掌握的顶点光照的一些内容,并通过定义出第二个通道。採用亮蓝色来渲染物体的背面。
代码例如以下:
- Shader "浅墨Shader编程/Volume4/13.渲染对象背面v2"
- {
- //-------------------------------【属性】---------------------------------------
- Properties
- {
- _Color ("主颜色", Color) = (1,1,1,0)
- _SpecColor ("高光颜色", Color) = (1,1,1,1)
- _Emission ("光泽颜色", Color) = (0,0,0,0)
- _Shininess ("光泽度", Range (0.01, 1)) = 0.7
- _MainTex ("基础纹理 (RGB)-透明度(A)", 2D) = "white" {}
- }
- //--------------------------------【子着色器】--------------------------------
- SubShader
- {
- //---------------------------【通道一】------------------------------
- // 说明:绘制对象的前面部分,使用简单的白色材质。并应用主纹理
- //----------------------------------------------------------------------
- Pass
- {
- //【1】设置顶点光照
- Material
- {
- Diffuse [_Color]
- Ambient [_Color]
- Shininess [_Shininess]
- Specular [_SpecColor]
- Emission [_Emission]
- }
- //【2】开启光照
- Lighting On
- // 【3】将顶点颜色混合上纹理
- SetTexture [_MainTex]
- {
- Combine Primary * Texture
- }
- }
- //--------------------------【通道二】-------------------------------
- // 说明:採用亮蓝色来渲染背面
- //----------------------------------------------------------------------
- Pass
- {
- Color (0,0,1,1)
- Cull Front
- }
- }
- }
相同是将Shader先赋给一个材质。得到的效果例如以下:
我们将材质赋给一个立方体,直接看是一个很正常的立方体:
但一旦镜头靠近。看到物体的内部时,就发现了我们定义的蓝色部分了:
3.用剔除实现玻璃效果
非常多时候。控制剔除比背面调试(debugging backfaces)更实用。比方遇到须要渲染透明的物体,常常会想要显示一个对象的背面。假设不做不论什么剔除操作的话。我们会发现有时常有一部分背面会覆盖在前面的一部分之上。以下就是一个解决问题的用于凸物体(球,立方体,车窗)的简单玻璃效果的着色器:
- Shader "浅墨Shader编程/Volume4/14.用剔除实现玻璃效果"
- {
- //-------------------------------【属性】---------------------------------------
- Properties
- {
- _Color ("主颜色", Color) = (1,1,1,0)
- _SpecColor ("高光颜色", Color) = (1,1,1,1)
- _Emission ("光泽颜色", Color) = (0,0,0,0)
- _Shininess ("光泽度", Range (0.01, 1)) = 0.7
- _MainTex ("基础纹理 (RGB)-透明度(A)", 2D) = "white" {}
- }
- //--------------------------------【子着色器】--------------------------------
- SubShader
- {
- //【1】定义材质
- Material
- {
- Diffuse [_Color]
- Ambient [_Color]
- Shininess [_Shininess]
- Specular [_SpecColor]
- Emission [_Emission]
- }
- //【2】开启光照
- Lighting On
- //【3】开启独立镜面反射
- SeparateSpecular On
- //【4】开启透明度混合(alpha blending)
- Blend SrcAlpha OneMinusSrcAlpha
- //--------------------------【通道一】-------------------------------
- // 说明:渲染对象的背面部分
- //----------------------------------------------------------------------
- Pass
- {
- // 假设对象是凸型, 那么总是离镜头离得比前面更远
- Cull Front //不绘制面向观察者的几何面
- SetTexture [_MainTex]
- {
- Combine Primary * Texture
- }
- }
- //----------------------------【通道二】-----------------------------
- // 说明:渲染对象背对我们的部分
- //----------------------------------------------------------------------
- Pass
- {
- // 假设对象是凸型,那么总是离镜头离得比背面更远
- Cull Back //不绘制背离观察者的几何面
- SetTexture [_MainTex]
- {
- Combine Primary * Texture
- }
- }
- }
- }
相同是将Shader先赋给一个材质,得到的效果例如以下,能够发现是透明的黄色玻璃的效果:
再将材质赋给一个物体,就让此物体实现了简单的玻璃效果:
4.基本Alpha測试
先看一个最简单的能用的样例,使用一张带有透明度通道的纹理。
对象仅仅会在透明度大于0.6 时显示:
- Shader "浅墨Shader编程/Volume4/15.基本Alpha測试"
- {
- //-------------------------------【属性】-----------------------------------------
- Properties
- {
- _MainTex ("基础纹理 (RGB)-透明度(A)", 2D) = "white" {}
- }
- //--------------------------------【子着色器】--------------------------------
- SubShader
- {
- //----------------------------【通道】-------------------------------
- // 说明:进行Alpha測试操作,且仅仅渲染透明度大于60%的像素
- //----------------------------------------------------------------------
- Pass
- {
- // 仅仅渲染透明度大于60%的像素
- AlphaTest Greater 0.6
- SetTexture [_MainTex] { combine texture }
- }
- }
- }
此Shader编译后赋给材质,材质又赋给物体的效果例如以下。且由于选取的纹理的透明度除了局部线条以外。其它的都小于了60%,于是就是仅仅剩下这大概的轮廓了:
5.顶点光照+可调透明度
让我们在上面这个Shader的基础上添加一些光照和并让Alpha的阈值能够调节。于是便写出了例如以下的Shader:
- Shader "浅墨Shader编程/Volume4/16.顶点光照+可调透明度"
- {
- //-------------------------------【属性】-----------------------------------------
- Properties
- {
- _Color ("主颜色", Color) = (1,1,1,0)
- _SpecColor ("高光颜色", Color) = (1,1,1,1)
- _Emission ("光泽颜色", Color) = (0,0,0,0)
- _Shininess ("光泽度", Range (0.01, 1)) = 0.7
- _MainTex ("基础纹理 (RGB)-透明度(A)", 2D) = "white" { }
- _Cutoff ("Alpha透明度阈值", Range (0,1)) = 0.5
- }
- //--------------------------------【子着色器】--------------------------------
- SubShader
- {
- Pass
- {
- // 【1】使用Cutoff參数定义能被渲染的透明度阈值
- AlphaTest Greater [_Cutoff]
- //【2】设置顶点光照參数值
- Material
- {
- Diffuse [_Color]
- Ambient [_Color]
- Shininess [_Shininess]
- Specular [_SpecColor]
- Emission [_Emission]
- }
- //【3】开启光照
- Lighting On
- // 【4】进行纹理混合
- SetTexture [_MainTex] { combine texture * primary }
- }
- }
- }
赋给材质后的效果例如以下,能够自由调节Alpha透明度的阈值:
通过调节Alpha透明度的阈值。得到了显示效果差异非常大的材质:
将此材质赋给物体后的效果例如以下:
6.简单的植被Shader
最后一个Shader,让我们实现一个简单的植物渲染Shader的写法。
当渲染树和植物时,透明度測试使很多游戏中出现尖锐的边缘。
解决问题的方法之中的一个是渲染对象两次。首次通道中,我们仅仅渲染超过50%透明度的像素。在第二次通道中,我们使用透明度混合上次我们切除的部分,而不记录像素的深度。我们可能会使一些树枝覆盖近的其它树枝。以实现逼真的效果。于是。简单的植被Shader写法例如以下:
- Shader "浅墨Shader编程/Volume4/17.简单的植被Shader"
- {
- //-------------------------------【属性】-----------------------------------------
- Properties
- {
- _Color ("主颜色", Color) = (.5, .5, .5, .5)
- _MainTex ("基础纹理 (RGB)-透明度(A)", 2D) = "white" {}
- _Cutoff ("Alpha透明度阈值", Range (0,.9)) = .5
- }
- //--------------------------------【子着色器】--------------------------------
- SubShader
- {
- //【1】定义材质
- Material
- {
- Diffuse [_Color]
- Ambient [_Color]
- }
- //【2】开启光照
- Lighting On
- //【3】关闭裁剪,渲染全部面。用于接下来渲染几何体的两面
- Cull Off
- //--------------------------【通道一】-------------------------------
- // 说明:渲染全部超过[_Cutoff] 不透明的像素
- //----------------------------------------------------------------------
- Pass
- {
- AlphaTest Greater [_Cutoff]
- SetTexture [_MainTex] {
- combine texture * primary, texture
- }
- }
- //----------------------------【通道二】-----------------------------
- // 说明:渲染半透明的细节
- //----------------------------------------------------------------------
- Pass
- {
- // 不写到深度缓冲中
- ZWrite off
- // 不写已经写过的像素
- ZTest Less
- // 深度測试中。仅仅渲染小于或等于的像素值
- AlphaTest LEqual [_Cutoff]
- // 设置透明度混合
- Blend SrcAlpha OneMinusSrcAlpha
- // 进行纹理混合
- SetTexture [_MainTex]
- {
- combine texture * primary, texture
- }
- }
- }
- }
我们将一棵树的叶子和树皮的材质替换成刚刚写好的Shader:
然后看看效果:
嗯。还是挺不错的,总体显示效果比用Unity自带的Bumped Diffuse要出色一些(Unity自带的Bumped Diffuse还能够在树叶上看到透过的光影,这部分内容我们以后以后学了再加)。
OK,我们就写如上的这六个Shader作为本次实战的内容吧。最后是构建本文配套的游戏场景,以及一些终于程序执行截图的赞赏。
五、终于游戏场景效果演示——热带雨林
上一次我们处于神奇的光之城堡。这次的场景。最好还是让我们来到充满生机的热带雨林。领略一番不一样的味道。以大师级美工鬼斧神工的场景作品为基础。浅墨调整了场景布局。增加了音乐,并增加了很多其它高级特效。于是便得到了如此这次生机勃勃的场景。
执行游戏,我们来到充满各种绿色植物的热带雨林之中:
满满的绿色植物:
飘落到脸上的树叶:
竹林:
被椰子树包围:
鹤立鸡群的铁树:
冲破云霄的树:
凸起树根的老树:
最后放一张这次Shader的全家福:
OK,美图就放这么多。游戏场景可执行的exe能够在文章开头中提供的链接下载。
本篇文章的演示样例程序请点击此处下载:
【浅墨Unity3D Shader编程】之四 热带雨林篇配套Unityproject下载
浅墨已经完毕《OpenCV3编程入门》一书的技术修订工作。以及OpenCV2、OpenCV3双版本号总计两百多个演示样例程序的凝视和整理工作。书籍估计会在12月底印刷,1月上市。
近期应该能够没什么琐事了,没有特殊情况的话Unity Shader系列博文能够保持每周周一更新到明年2月份过春节前。美剧都冬歇了咱们才刚上线:)
所以本周開始。Unity Shader系列博文宣告正式回归~下周一,我们,不见不散。
版权声明:本文博客原创文章。博客,未经同意,不得转载。