基础知识
Unity 渲染需要两个组件 Mesh Filter 和 Mesh Renderer
Mesh Filter:存储一个Mesh(网格,模型的网格,就是模型的由哪些三角面组成,组成一个什么样子的模型,三角面的一些顶点信息)
Mesh Renderer: 用来渲染一个模型外观,按照Mesh的顶点,颜色以及Material(材质)控制模型渲染的样子
Material(材质):简单理解为贴图(Texture) + Shader组成(贴图可以没有也可以是单纯的颜色,但是Shader必须要有)
Shader:着色器,一种GPU编程语言,可由opengl或者dx来进行解析,来控制渲染
C#代码 -> CPU(准备好顶点数据等) -> Shader(Shader嵌入GPU的可编程流水线) -> 调用DX或者OpenGl -> GPU
Direct3D是微软推出的底层图形API,只能在Windows平台使用,显卡厂商在对应的驱动程序上实现DX接口,开发者便可以使用DX进行图形学开发
OpenGl 是专门的图形库,具有较好的移植性,主要在移动端使用广泛
DirectX 使用HLSL语言编写Shader
OpenGl 使用GLSL语言编写Shader
CG语言:Microsoft和 NVIDIA公司联手推出的GPU编程高级着色器语言。全称(C for Graphic)
Cg 是一个可以被 OpenGL 和 Direct3D 广泛支持的图形处理器编程语言。Cg 语言和OpenGL DirectX并不是同一层次的语言
而是OpenGL和DirectX的上层,即Cg 程序是运行在 OpenGL 和 DirectX 标准顶点和像素着色的基础上的
其二,Cg语言是Microsoft和NVIDIA相互协作在标准硬件光照语言的语法和语义上达成了一致,标题就是“Microsoft and NVIDIA’s Collaboration to Develop Cg and HLSL”,所以HLSL和Cg其实是同一种语言。很多时候,你会发现用 HLSL 写的代码可以直接当中 Cg 代码使用。也就是说,Cg 基于知识联盟(Microsoft 和 NVIDIA),且拥有跨平台性,选择 Cg 语言是大势所趋。有心的读者,可以注意市面上当前的 GPU 编程方面的书籍,大部分是基于 CG 语言的。
(附:Microsoft 和 NVIDIA 联手推出 Cg,应该是一种经济和技术上的双赢,通过这种方式联手打击 GLSL)
此外,Cg,即 C for Graphics,用于图形的 C 语言,这其实说明了当时设计人员的一个初衷,就是“让基于图形硬件的编程变得和 C 语言编程一样方便,自由”。正如 C++ 和 Java 的语法是基于 C 的,Cg 语言本身也是基于 C 语言的。如果您使用过 C、C++、Java 其中任意一个,那么 Cg 的语法也是比较容易掌握的。Cg 语言极力保留了 C 语言的大部分语义,力图让开发人员从硬件细节中解脱出来,Cg 同时拥有高级语言的好处,如代码的易重用性,可读性提高等。使用 Cg 还可以实现动画驱动、通用计算(排序、查找)等功能
CG编写shader 因为他是跨平台的 可以用OpenGL解析也可以使用DX解析
Unity中的Shader,是Unity封装了一层,即Shader Lab,其SubShader中的Pass使用CG语言来实现的,使得Unity开发的软件可以跨平台
Unity中的Shader分类:
- Surface Shader
Unity3d推崇的Shader类型,使用Unity预制的光照模型来进行光照运算(CG/HLSL语法)
- Vertex and Fragment Shader
最强大的Shader类型,属于可编程渲染管线。(CG/HLSL语法)
- Fixed function shader
属于固定渲染管线 Shader。在老显卡无法显示时则Fallback
编写Shader
Unity Shader使用的是ShaderLab语法,这是Unity自家创造的说明性语言,除了里面内嵌着色器代码(Cg/HLSL)外,最重要的是可以方便程序员去设置着色器的属性和状态,如Properties语句块中定义了着色器所需要的各种属性(颜色、纹理等),这些属性将会出现在属性面板中,在Editor里输入数据,而不用像传统的Shader中需要编写冗长的代码来设置着色器的输入和状态。设计上类似于CgFX和Direct3D Effect(.FX)语言,它们都定义了一个着色器所需要的输入数据和状态,而不仅仅是着色器代码。让Unity开发者绝大多数时候只需要和Unity Shader打交道便可以编写出色的游戏效果
演示Shader基本语法:
Shader"myshader/myshader01"{
// Properties里写属性 (固定写法)
Properties{
// 第一个 _color 代表属性的名字 是自定义的
// ("_color",Color) 里面的字符串是在Inspector面板显示的名字 是自定义的
// ("_color",Color) 后面的Color代表定义的属性 是固定的
// = (1,1,1,1) 这个()里面设置默认值 R G B A
// 颜色类型
_Color("_Color",Color) = (1,1,1,1)
// 四维向量类型 X Y Z W
_Vector("_Vector",Vector) = (1,2,3,4)
// 整数类型
_Int("_Int",Int) = 100
// 小数类型 默认值不加F shader里面没有double类型
_Float("_Float",Float) = 12.3
// 范围类型 从哪个范围到哪个范围 这里设置的范围是1 - 100 默认值设置为了55 可以取到1和100
_Range("_Range",Range(1,100)) = 55
// 指定图片 类型 如果{}里指定图片了会显示图片的颜色 如果没有指定图片会显示为设置的纯色 这里为white
_2D("_Texture",2D) = "white"{}
// 矩形纹理属性
_Rect("RectTex", Rect) = "white" {}
// 立方体纹理 立方体贴图
_Cube("_Cube",Cube) = "white"{}
// 3D 纹理
_3D("_Texure",3D) = "White"{}
}
// SubShader 可以有很多个 里面编写的是渲染的代码 利用属性编写一些 控制 渲染出来的效果
// 为什么创建多个SubShader 因为不同的SubShader可以实现不同的效果 在不同的显卡上运行时需要
// 比如不同的显卡对应不同的SubShader 好一点的对应 一个SubShader 差一点的对应另外一个SubShader
// 1. 显卡运行效果时,从第一个SubShader开始,如果里面的效果都可以实现,那么就使用第一个SubShader
// 2. 如果这个SubShader满足不了这个显卡的需求,它会自动跳到下一个,若所有的SubShader均不能执行,则
// 3. 调用 Fallback"VertexLit" 默认 (Legacy Shaders中)
// 一般第一个SubShader是显示效果最好的 第二个比第一个次一点 以此类推 这样会增加shader的适应能力
SubShader{
// SubShader 里面必须有一个 Pass块 也可以有多个
// 一个Pass块代表一个方法
Pass{
// 在这里编写Shader代码
CGPROGRAM
// 使用CG语言编写shader代码
// 使用属性需要先定义
// 语法: 类型 名字 这里的名字需要和属性里面的一致
// 不需要附默认值 会按 Properties 里面的来
// 结尾需要带 ; 号
float4 _Color;
// float4 可以使用 float half fixed 代替
// 还有 float3 float2 float 分别代表里面有几个值
// 主要看是要怎么用 比如 float4 可以存储值 也可以存储向量 也可以使用float3存储向量
// 这里的float 的使用主要是看想存储几个值 和 怎么用
// 在任何使用float的地方都可以使用 half 和 fixed 代替 比如 half3 half2 fixed3 fixed2
// float 和 half 的区别:精度范围(可在优化时调整)
// 值 二进制位 范围
// float 32 -2147483648 到 2147483647
// half 16 -6万 到 +6万
// fixed 11 -2万 到 +2万
// 一般颜色都使用fixed存储 位置使用float存储 half使用的较少
float4 _Vector;
float _Int;
float _Float;
float _Range;
sampler2D _2D;
samplerCube _Cube;
sampler3D _3D;
ENDCG
}
}
// 上面的SubShader都不执行的时候 执行Fallback
// Fallback用来指定一个已经存在了的Shader
Fallback"VertexLit"
}
类型使用技巧扩展:
float 32位浮点数
half 16位浮点数
int 32位整形数
fixed 12位定点数
bool 布尔数据
float2×4 matrix; //表示2×4阶矩阵,包含8个float类型数据
1.精度够用就好
2.颜色和单位向量,使用fixed
3.其他情况,尽量使用half(即范围在[-6万,+6万]、精确的小数点3.3位);否则才使用用float
定义顶点函数和片元函数:
Shader"myshader/myshader02"{
// 如果不需要任何属性的话 是可以不设置属性的
SubShader{
Pass{
CGPROGRAM
// 定义两个系统函数 由系统自己调用的函数
// 顶点函数 声明了顶点函数的函数名
#pragma vertex vert // 顶点函数定义语法 #pragma vertex 是固定的 vert为函数名 是自己设置的
// 顶点函数的基本作用是: 完成顶点坐标到剪裁空间的转换(从游戏环境转换到视野相机屏幕上)
// 定义和使用顶点函数 (float4 v:POSITION) 系统传递过来参数给v 后面加:POSITION 的意思是把顶点坐标传给v
float4 vert(float4 v:POSITION):SV_POSITION
// 通过语义告诉系统,我这个参数是干嘛的,比如POSSITION是告诉系统我需要顶点坐标
// SV_POSITION这个语义用来解释说明返回值,意思是返回值是剪裁空间下的顶点坐标
// 语义写法是固定的 不同的语义代表不同的效果
{
// mul()是一个方法可以完成一个 模型空间坐标到剪裁空间的转换 一个矩阵和一个postition的乘法运算
// 第一个参数是一个矩阵 这里使用的是 矩阵宏(固定写法 代表的是一串数字) 第二个是顶点坐标
// 这时V里面的W就有了值 但是在坐标上没有实际意义 就是用来辅助坐标转换的
float4 pos =mul(UNITY_MATRIX_MVP,v);
return pos;
}
// 片元函数 这里是声明了,片元函数的函数名
#pragma fragment frag // 片元函数定义语法 #pragma fragment 是固定的 frag为函数名 是自己设置的
// 片元函数的基本作用:返回模型对应屏幕上的每一个像素的颜色值
// 定义和使用片元函数
// 这里返回一个颜色值 所以这个方法返回 fixed4类型 现在这个参数不需要 以后可能会写参数进去
fixed4 frag():SV_Target // :SV_Target 这个语义代表模型对应到屏幕上的颜色
{
// 返回的颜色 R G B A 但是需要跟上类型 这里是fixed4类型
return fixed4(1,1,1,1);
}
ENDCG
}
}
Fallback "VertexLit"
}
正弦相位水波:
Shader "Water/waterShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
// 相位
float dist = distance(v.vertex.xyz, float3(0, 0, 0));
//float h = sin(dist + _Time.z);
float h = sin(dist * 2 + _Time.z) / 5;
o.vertex = mul(unity_ObjectToWorld, v.vertex);
o.vertex.y = h;
o.vertex = mul(unity_WorldToObject, o.vertex);
o.vertex = UnityObjectToClipPos(o.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
模型物体表面发射波光,光效等:
Shader "Water/ShipShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {} // 物体的纹理
_SubTex("Texture", 2D) = "white" {} // 需要反射的波光的纹理 背景为黑色 只显示闪耀波光
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
sampler2D _SubTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 uv_Offset = float2(0.0, 0.0);
uv_Offset.x = _Time.y * 0.5; // t
uv_Offset.y = _Time.y * 0.5;
// sample the texture
//纹理越界问题 不需要考虑
// 纹理已经设置成 Wrap Mode -> Repeat
fixed4 colLight = tex2D(_SubTex, i.uv + uv_Offset); // 随时间随机采样对应位置的像素颜色
fixed4 col = tex2D(_MainTex, i.uv);
//fixed col = fixed4(1.0, 0.0, 0.0, 1.0);
col += colLight;
return col;
}
ENDCG
}
}
}
GrabPass截屏通道:
Shader "Unlit/GrabPassShader"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType" = "Opaque" "Queue" = "Overlay"} //最后被绘制
LOD 100
GrabPass {}
//GrabPass {} // 截图通道, 后面使用_GrabTexture访问截屏纹理
Pass
{
name "GRABPASS"
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
sampler2D _GrabTexture;
float4 _MainTex_ST;
float4 _GrabTexture_ST;
v2f vert(appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _GrabTexture);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_GrabTexture, i.uv);
return col;
}
ENDCG
}
}
}
UsePass:
Shader "Unlit/UsePass"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 100
UsePass "Unlit/GrabPassShader/GRABPASS"
}
}