Unity3D学习笔记(三十四):Shader着色器(1)
一、GPU:图形处理器,Graphics Processing Unit
显卡的处理器就是图形处理器。与CPU类似。
GPU和CPU的区别?
1.CPU主要是为了串行指令设计,GPU则是为了大规模的秉性的计算而设计。
2.从并行的角度来看,CPU并行针对于指令集并行,而GPU的并行是针对大规模运算的。
3.同样面积的芯片:CPU上更多的放置缓存和控制部件,而GPU上放置的是更多的运算单元。
二、渲染管线
渲染管线也叫渲染流水线,就是告诉GPU一堆数据,然后得到一个二维的图像。
渲染管线主要分三个阶段:应用程序阶段、几何阶段、光栅化阶段
1.应用程序阶段(CPU)
主要是CPU将模型身上的顶点信息(顶点坐标、纹理坐标、法线等),组成一个图元,告诉GPU。
2.几何阶段(GPU)(顶点函数在这个阶段)
几何阶段的主要工作是“变换三维顶点坐标”。主要是将图元信息的顶点坐标变化到屏幕坐标中,传给光栅化阶段。
几何阶段涉及的几个空间:
Object Space(模型坐标空间),以模型原点为参考
World Space(世界坐标空间),三维
Eye Space(观察坐标空间)
Clip anf Project Space(剪裁和屏幕坐标空间),二维
1)从Object Space到World Space
模型在导入到场景中,只有一些模型身上的顶点信息。需要把顶点坐标转换到世界坐标空间下,我们才知道模型在世界中的哪一个位置。
2)从World Space到Eye Space
我们模型显示到屏幕是需要摄像机的,所有的观察坐标空间,就是以摄像机为原点,
摄像机的右方为观察坐标系的+X轴的方向,
摄像机的上方为观察坐标系的+Y轴的方向,
摄像机的正前方为观察坐标系的-Z轴的方向。
Unity的观察坐标系是右手坐标系。
3)从Eye Space到Clip anf Project Space
就是一个裁剪(视锥体以外的部分)和投影的过程
3.光栅化阶段(GPU)(片元函数在这个阶段)
使用上一个阶段生成的数据渲染出最终的图像。
三、Shader着色器:GPU上运行实现图像渲染的程序
![](https://img2018.cnblogs.com/blog/1517599/201902/1517599-20190212003504824-1116447821.png)
图像编程接口
OpenGL,苹果
DirectX,微软
![](https://img2018.cnblogs.com/blog/1517599/201902/1517599-20190212003517223-1885761570.png)
着色器的语言:
按照3D渲染的图像编程接口来区分
GLSL:OpenGL Shading Language(支持OpenGL API)
HLSL:High Level Shader Language(支持DX API)
Cg语言:C for Graphics(支持两种编程接口)
![](https://img2018.cnblogs.com/blog/1517599/201902/1517599-20190212003528819-1517841150.png)
着色器的分类:
固定管线着色器:效果差,难度小,适用各种硬件,老旧显卡,手机显卡
顶点/片段着色器(可编程顶点/片段处理器):Cg语言,效果好,新型显卡,可编程,更灵活
表面着色器(固定功能着色器):Unity自己的,对Cg语言的封装,较顶点/片段着色器复杂,包括光影的运算
着色器的结构:
1.需要一个定义
Shader"着色器的名字"{ },这个名字是可以带有层级化结构的,并且名字可以与Shader的名字不一致。
2.向引擎公布一些自己的公共的属性(可以存在,也可以不存在)
Properties{ //属性的集合},Properties向下融合
3.至少包括一个计算过程
SubShader{ //计算过程}
计算过程可以是多个,也可以是一个,SubShader多个的情况下,机器会从上至下往下找,只有一个会执行,这个可以执行的SubShader是机器可以容忍的着色器代码,从上至下找到一个自己可以容忍可以执行的SubShader,那么就执行这个。
表面着色器的代码不需要写在Pass块里,但固定管线着色器和顶点/片段着色器的代码都需要写在pass块里
一个SubShader可以有多个Pass块,Pass块效果会叠加。
4.最后是在所有的SubShader都失效的情况下执行的一个方案
FallBack"最好写默认的着色器代码",编写时最好把FallBack注释掉,避免系统默认调用,无法显示粉色错误
FallBack "Diffuse"//Unity自带的LegacyShaders的Diffuse
5.对于多个Pass块的情况,机器会依次向下执行每一个Pass,一般情况下最后一个Pass的效果会覆盖前面的Pass,但是也有特殊的情况。
ShaderLab结构
![](https://img2018.cnblogs.com/blog/1517599/201902/1517599-20190212003557859-268175075.png)
![](https://img2018.cnblogs.com/blog/1517599/201902/1517599-20190212003604182-2046653363.png)
注意:
shader依赖材质球,材质球依赖物体身上的Mesh Render
材质球的粉色:是报错的颜色
着色器的属性:
定义:<属性的变量名>("Inspactor显示的名字", 数据类型) = <必须初始化>
类型:
Color:颜色
_Color ("颜色", Color) = (1,1,1,1)
Float:数字
_Float ("数字", float) = 0.1//不用加f
Vector:四维向量
_Vector ("向量", Vector) = (0,0,0,0)
Range:区间
_Range ("区间", Range(0, 10)) = 0//注意区间里要设置最大值和最小值
2D:2D纹理
_2D ("2D纹理", 2D) = "white"{}//纹理使用字符串加花括号形式赋初值
Rect:矩形纹理(少用)
_Rect ("矩形纹理", Rect) = "white"{}
Cube:立方体纹理(一般用于天空盒)
_Cube ("立方体纹理", Cube) = "white"{}
对于纹理来说,没办法对其进行指定的纹理作为默认值,可以使用{}表示空纹理,{}前的字符串表示的是当使用空纹理时,使用的颜色,"white"、"red"、"gray"。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SetMat : MonoBehaviour { public Texture tex; //如果要修改Shader属性面板的值 //首先需要获取使用这个Shader材质 private Material mat; private MeshRenderer mr; // Use this for initialization void Start () { mr = GetComponent<MeshRenderer>(); //获取MeshRenderer里的材质 mat = mr.material; //修改颜色,第一个参数是属性面板的变量名,第二个参数是想要设置的值 mat.SetColor("_Color", Color.blue); //修改数字、区间 mat.SetFloat("_Float", 100); mat.SetFloat("_Range", 5); //修改向量 mat.SetVector("_Vector", Vector4.one); //修改纹理 mat.SetTexture("_2D", tex); //修改纹理的Tiling和Offset mat.SetTextureScale("_2D", Vector2.zero);//Tiling mat.SetTextureOffset("_2D", Vector2.one);//Offset } }
SubShader
Unity提供的一些渲染的控制命令
注意:
1.所有的命令后都不可以带分号结尾
2.所有的命令必须给值,值分为常量值和变量值。如果想要使用属性面板的变量,命令[变量名]。使用常量值,命令 (1,1,1,1)或数字
灯光命令
Lighting off/on
on表示支持灯光,off表示不理会灯光
固定颜色
Color(r,g,b,a)或Color[变量名]
r,g,b,a的取值范围0-1
注意:如果开启的灯光控制,该命令无效,如果想要命令有效,那么必须关闭灯光控制。
Shader "Lesson/FixedColor" { Properties{ _Color("Color", Color) = (1,1,1,1) } SubShader{ //固定管线着色器要写在Pass里 Pass { //关闭灯光 Lighting off //固定颜色 //Color (1,0,0,1)//使用常量值的颜色 Color[_Color]//使用变量值的颜色 } //FallBack "Diffuse" }
灯光处理块
Material{//处理各种灯光}
Diffuse:Color控制基本的漫反射的颜色:Diffuse(r,g,b,a)或Diffuse[颜色变量名]
Specular:Color控制镜面反射的颜色:Specular(r,g,b,a)或Specular[颜色变量名]
Shininess:Number控制高光的锐利度,一般取值范围在0-1之间,0高光最大,1高光最小。Shininess 数字或Shininess[区间或float变量名]
Emisson:Color控制物体自发光颜色。Emisson(r,g,b,a)或Emisson[颜色变量名]
Ambient:Color控制环境光颜色。Ambient(r,g,b,a)或Ambient[颜色变量名]
SeparateSpecular on/off 开启独立的镜面反射,只有开启了这个命令,Specular高光命令才有效。
最终颜色 = Ambient 控制的颜色 * 引擎设置的环境光颜色 + 光照颜色 * Diffuse + 光照颜色 * Specular + Emisson
Shader "Lesson/FixedDiffuse" { Properties { _Diffuse("漫反射颜色", Color) = (1,1,1,1) _Specular("高光颜色", Color) = (1,1,1,1) _Shininess("高光系数", Range(0, 1)) = 0.2 _Emission("自发光颜色", Color) = (1,1,1,1) _Ambient("环境光颜色", Color) = (1,1,1,1) } SubShader { Pass { //先把灯光打开 Lighting on //开启独立的镜面反射 SeparateSpecular on //在这个里去写一些灯光处理控制的命令 Material { //灯光产生的漫反射的影响 //漫反射光照命令 Diffuse [_Diffuse] //高光颜色的命令 Specular [_Specular] //高光系数 数字0~1 Shininess [_Shininess] //自发光 相加 Emission [_Emission] //环境光 相乘,乘以Environment Light的数值,一般情况下设置为(1,1,1,1) Ambient [_Ambient] } } } //FallBack "Diffuse" }
环境光设置
纹理显示:
SetTexture[纹理的属性变量名]{//纹理计算命令}
Combine:告诉引擎开始进行颜色混合
texture:纹理贴图的原始颜色
constantColor:定义一个常量的颜色
constant:使用这个常量的颜色
primary:使用灯光的颜色
double:使灯光的颜色变成二倍
quad:使灯光的颜色变成四倍
previous:代表颜色缓冲区(帧缓存)当前的颜色
Combine texture:显示图片的原始色
Combine texture * constant:使用一个常量颜色与纹理的原始颜色混合
Combine texture * primary:使用灯光颜色与纹理的原始颜色混合
Shader "Lesson/FixedDiffuse" { Properties { _Tex("纹理", 2D) = "white"{} } SubShader { Pass { SetTexture[_Tex]//可以有多个,效果叠加 { //计算命令的选项 Combine texture//显示纹理的原始颜色 //如果想与原始颜色进行混合 //定义一个常量色 constantColor[_Color] //常量色与纹理的原始颜色 Combine texture * constant //灯光颜色与纹理的原始颜色混合 //double代表双倍的意思 Combine texture * primary double Combine texture * previous + constant } } } //FallBack "Diffuse" }
Shader "Custom/FixedSetTexture" { Properties { _MainTex("主纹理贴图", 2D) = ""{} _SecTex("主纹理贴图", 2D) = ""{} _Color("颜色", Color) = (1,1,1,1) } SubShader { Pass { SetTexture[_MainTex] SetTexture[_SecTex] { //语法:Combine src1 lerp (scr2)scr3 //使用scr2的alpha值进行颜色插值,当scr2的alpha值为1的时候,显示scr1的颜色,当scr2的alpha值为0的时候,显示scr3的颜色 //如果scr2的某个像素的alpha值为0.5的时候,那么就是scr1的颜色和scr3的颜色进行1:1的混合 Combine texture lerp (texture)previous } SetTexture[_SecTex] SetTexture[_MainTex] { ConstantColor[_Color] //语法:Combine src1 * scr2 + scr3 用scr1的颜色 * scr2的 alpha 值 + scr3 Combine texture * previous + constant } } } FallBack "Diffuse" }
网格面的裁剪
Cull off/back/front
Cull off:关闭网格面的裁剪:双面显示
Cull back:裁剪掉背面网格,默认开启的是裁剪掉背面网格
Cull front:裁剪掉正面网格,可以用作天空盒
渲染队列命令,这个命令与Pass是同级的
控制谁先渲染谁后渲染!渲染队列值,值越大越后渲染,最终显示在屏幕上的颜色是由渲染队列深度深度比较等同时决定的。
"Queue"="BackGround",代表的渲染队列值1000,一般用于背景层,第一批渲染
"Queue"="Geometry",代表的渲染队列值2000,一般用于不透明的游戏物体
"Queue"="AlphaTest",代表的渲染队列值2450
"Queue"="Transparent",代表的渲染队列值3000
"Queue"="Overlay",代表的渲染队列值4000,一般用于镜头的处理
如果想设置3200的渲染队列
"Queue"="Transparent+200",注意加号左右不允许有空格
Shader "Lesson/FixedCull" { Properties { _MainTex("Albedo (RGB)", 2D) = "white" {} } SubShader { //设置渲染队列的值 Tags{ "Queue" = "BackGround" } Tags{ "Queue" = "Geometry" } Tags{ "Queue" = "AlphaTest" } Tags{ "Queue" = "Transparent" } Tags{ "Queue" = "Overlay" } Pass { //网格面的裁剪 Cull off SetTexture[_MainTex] { Combine texture } } } //FallBack "Diffuse" }
什么是深度?
游戏物体距离摄像机的距离我们叫做深度。
深度测试:当前物体的对应像素颜色的深度与深度缓冲区里的深度做比价的过程就是深度测试
深度缓冲:开启了深度缓冲,证明当比较成功之后,要把新的深度写入到深度缓冲区里去
ZTest深度测试
ZTest Less:小于,默认就是小于比较
Greater:大于
Equal:等于
LEqual:小于等于
GEqual:大于等于
NotEqual:不等于
off:关闭,深度测试关闭时,当跟深度缓冲区里做比较时,直接通过。
深度写入
ZWrite off/on:关闭或者开启深度写入
如果深度写入关闭,即使比较成功了,深度不会写入深度缓冲区,但是颜色会写入到深度缓冲区。
1.深度测试通过,深度写入开启,深度写入深度缓冲区,颜色写入颜色缓冲区
2.深度测试通过,深度写入关闭,深度不写入深度缓冲区,颜色写入颜色缓冲区
3.深度测试失败,深度写入开启,深度不写入深度缓冲区,颜色不写入颜色缓冲区
4.深度测试失败,深度写入关闭,深度不写入深度缓冲区,颜色不写入颜色缓冲区
5.深度测试关闭,深度写入开启,深度写入深度缓冲区,颜色写入颜色缓冲区
Shader "Lesson/ZTestR" { Properties { } SubShader { //红色 3000 Tags{ "Queue" = "Transparent" } Pass { //深度测试变为小于比较 ZTest Less //深度写入关闭 ZWrite off Color(1,0,0,1) } } //FallBack "Diffuse" }
补充内容
快速添加材质球的方法:在Shader脚本上右键新建Material,会自动根据Shader脚本的路径命名
![](https://img2018.cnblogs.com/blog/1517599/201902/1517599-20190212003957433-2038039303.png)