- 是运⾏在GPU上的一段代码,控制GPU的运算方式
- 着色器就是给定了输入,然后出输给Material进行着色的代码。
- GameObject里有MeshRenderer
- MeshRenderer里有Material列表
- 每个Material里有且只有一个Shader
- Material在编辑器暴露该Shader的可调属性
Shader的大体格式:
//格式1: -> Shader "路径" 路径名跟Shader的名没有任何关系
Shader "Custom/MyShader/01"
{
//属性
Properties
{
}
//子着色器
SubShader
{
}
//默认Shadr(备胎),当上面的SubShader都不能用的时候,系统会使用这个备胎(渲染方案、Shader)
FallBack "Diffuse"
}
Shader——Properties(属性):
Properties是通过在Inspector面板上提供出你所写出的控件
Properties
{
//代码块
//格式2:-> 变量名("变量显示在编辑器中的名字",变量的类型)=初始值
//语法2:_Name("Display Name", type) = defaultValue[{options}]
_FloatValue("FloatValue",Float) =0.5 //定义一个浮点数
_IntValue("IntValue",Int)=10 //定义一个整数
_Shininess("Shininess",Range(1,10))=5 //范围浮点数 :光泽度
_Vector4("Vector4",Vector)=(1,2,3,4) //四维变量 :四维数
_MainColor("MainColor",Color)=(1,0,0,1) //颜色 :主颜色
_MainTexture("MainTexture",2D) = "white"{} //贴图 :主纹理
_RecTexture("RecTexture",Rect) = ""{} //非2阶贴图变量 :副纹理
_CubeTexture("CubeTexture",Cube) = ""{} //立方体贴图变量 :天空盒子
_3DTexture("3DTexture",3D) = ""{} //3D贴图变量 :3D纹理
}
注:如果写完Shader出错优先检查上图(语法2的说明)的红色标注部分
语法2中type的种类:
类型 | 说明 |
---|---|
Color | 由RGBA(红绿蓝和透明度)四个量来定义 |
2D | 2的阶数大小(256,512之类)的贴图。这张贴图将在采样后被转为对应基于模型UV的每个像素的颜色,最终被显示出来; |
Rect | 非2阶数大小的贴图 |
3D | |
Cube | 立方体纹理(Cube map texture) [6张有联系的2D贴图的组合] 主要用来做反射效果(比如天空盒和动态反射),也会被转换为对应点的采样 |
Range(min, max) | 介于min和max之间的浮点数,一般用来当作调整Shader某些特性的参数(比如透明度渲染的截止值可以是从0至1的值等) |
Vector | 创建4个栏位,让用户可以填入相应的浮点数,代表一个Vecto4(四维数) |
语法2中的defaultValue:(定义了这个属性的默认值)
类型 | 说明 |
---|---|
Color | 以0~1定义的rgba颜色,比如(1,1,1,1); |
2D/Rect/Cube | 对于贴图来说,默认值可以为一个代表默认tint(色彩)颜色的字符串,可以是空字符串或者”white”,”black”,”gray”,”bump”中的一个 —> "白","黑","灰","(碰撞???)" |
Float,Range | 某个指定的浮点数 |
Vector | 一个4维数,写为 (x,y,z,w) |
语法2中的{option}:
它只对2D,Rect或者Cube贴图有关,在写输入时我们最少要在贴图之后写一对什么都不含的空白的{},当我们需要打开特定选项时可以把其写在这对花括号内。如果需要同时打开多个选项,可以使用空白分隔。可能的选择有ObjectLinear, EyeLinear, SphereMap, CubeReflect, CubeNormal中的一个,这些都是OpenGL(https://en.wikibooks.org/wiki/GLSL_Programming/Unity)中TexGen的模式
Shader——SubShader(子着色器)
一个Shader有多个SubShader。每个Shader至少1个SubShader。一个SubShader可理解为一个Shader的一个渲染方案。(SubShader是为了针对不同的渲染情况而编写的)
SubShader
{
Tags{"RenderType" = "Opaque"}
LOD 200
}
Tags
表面着色器可以被若干的标签(tags)所修饰,而硬件将通过判定这些标签来决定什么时候调用该着色器。告诉了系统应该在渲染非透明物体时调用我们。
Queue指定了物体的渲染顺序,预定义的Queue有:
语法:Tags { "RenderType"="Opaque" }
标签类型:
- "IgnoreProjector"="True"(值为"true"时,表示不接受Projector组件的投影。)
- "ForceNoShadowCasting"="True"(从不产生阴影)
- "Queue"="xxx"(指定渲染顺序队列)《--如果你使用Unity做过一些透明和不透明物体的混合的话,很可能已经遇到过不透明物体无法呈现在透明物体之后的情况。这种情况很可能是由于Shader的渲染顺序不正确导致的--》
- Background - 最早被调用的渲染,用来渲染天空盒或者背景
- Geometry - 这是默认值,用来渲染非透明物体(普通情况下,场景中的绝大多数物体应该是非透明的)
- AlphaTest - 用来渲染经过Alpha Test的像素,单独为AlphaTest设定一个Queue是出于对效率的考虑
- Transparent - 以从后往前的顺序渲染透明物体(绝大部分透明的物体、包括粒子特效都使用这个)
- Overlay - 用来渲染叠加的效果,是渲染的最后阶段(比如镜头光晕等特效)
LOD:
LOD很简单,它是Level of Detail的缩写,在这里例子里我们指定了其为200(其实这是Unity的内建Diffuse着色器的设定值)。这个数值决定了我们能用什么样的Shader。在Unity的Quality Settings中我们可以设定允许的最大LOD,当设定的LOD小于SubShader所指定的LOD时,这个SubShader将不可用。Unity内建Shader定义了一组LOD的数值,我们在实现自己的Shader的时候可以将其作为参考来设定自己的LOD数值,这样在之后调整根据设备图形性能来调整画质时可以进行比较精确的控制。
- VertexLit及其系列 = 100
- Decal, Reflective VertexLit = 150
- Diffuse = 200
- Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250
- Bumped, Specular = 300
- Bumped Specular = 400
- Parallax = 500
- Parallax Specular = 600
《重点》
Shader "Custom/MyShader/01"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader
{
CGPROGRAM
#pragma surface surf Lambert
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o)
{
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
CG语言的使用:
编译指令:#pragma surface surf Lambert
它声明了我们要写一个表面Shader,并指定了光照模型。
- surface :声明的是一个表面着色器
- surfaceFunction : 着色器代码的方法的名字
- lightModel : 使用的光照模型。
GLSL贴图类型:
- sampler1D
- sampler2D
- sampler3D
- samplerCube
sampler2D就是和texture所绑定的一个数据容器接口
解释下为什么在这里需要一句对_MainTex的声明
已经在Properties里声明过_MainTex,Shader其实是由两个相对独立的块组成的,外层的属性声明,回滚等等是Unity可以直接使用和编译的ShaderLab;而在CGPROGRAM...ENDCG这样一个代码块中,这是一段CG程序。对于CG程序,要访问在Properties中所定义的变量,必须使用和之前变量相同的名字进行再次声明。sampler2D _MainTex; 就是再次声明并链接了Properties中的_MainTex,使得CG程序能够使用这个变量。
struct结构体
struct中的Input是需要我们去定义的结构,把所需要参与计算的数据都放到这个Input结构中,然后再传入surf函数使用。
UV:
UV mapping的作用是将一个2D贴图上的点按照一定规则映射到3D模型上,是3D渲染中最常见的一种顶点处理手段。规定: 在一个贴图变量(在我们例子中是_MainTex)之前加上uv两个字母,且代表提取它的uv值
(相当于贴图上点的二维坐标)
,这样就可以在surf函数中通过访问uv_MainTex来取得这张贴图当前需要计算的点的坐标值。
surf函数:着色器的工作核心
CG规定了声明为表面着色器的方法(surf)的参数类型和名字,因此我们没有权利决定surf的输入输出参数的类型,只能按照规定写。规定是:它的两个参数:
- 第一个是Input
- 第二个是一个可写的SurfaceOutput。
SurfaceOutput是已经定义好了里面类型输出结构,但是一开始的时候内容暂时是空白的,我们需要向里面填写输出,这样就可以完成着色了**SurfaceOutput是预定义的输出结构,surf函数的目的是根据输入的数据把这个输出结构给填上。
tex2D函数:是CG程序中用来在一张贴图中对一个点进行采样的方法,返回一个half4。这里对_MainTex在输入点上进行了采样,并将其颜色的rbg值赋予了输出的像素颜色,将a值赋予透明度。于是,着色器就明白了应当怎样工作:即找到贴图上对应的uv点,直接使用颜色信息来进行着色
half指的是半精度浮点数,精度最低,运算性能相对比高精度浮点数高一些,因此被大量使用
参考资料:
https://onevcat.com/2013/07/shader-tutorial-1/
https://www.jianshu.com/p/7b9498e58659