Unity Shader 学习之旅
unity
shader
图形图像
纸上学来终觉浅,绝知此事要躬行
美丽的梦和美丽的诗同样 都是可遇而不可求的——席慕蓉 html
1、渲染流水线
示例图
Tips:什么是 GPU 加速计算? 编程
VIDEO
1.1Draw Call
CPU过Draw Call来g告诉GPU开始一个渲染过程。一个Draw Call会指向本次调用须要渲染的图元列表。 通俗的讲咱们能够把CPU理解成一群专家,他们有着超强和快速的计算能力,能解决各类各样的问题。GPU则是许许多多个流水线上的工人,尽管它们只能作简易单一的任务,可是成千上万个工人一块儿开动能够迅速处理大量的工做。可是专家和这些工人协同工做的过程当中须要交接任务,此时交接的过程就是Draw Call调用的过程。专家告诉工人们这些东西大家须要把这些零件加工成什么样,交接沟通是须要代价,这也就是为何Draw Call过多会影响帧率。琐碎的零部件一点点的交接给工人是很浪费资源的,工人成千上万个对于100个零部件加工和一万个零部件加工耗费的代价是同样的,这样专家也忙不过来,这时专家忙的焦头烂额,工人还在等待下一批零部件,零部件组装成成品的过程就出现了卡顿。因此Draw Call优化的过程也就是尽量一次由专家给工人一大批须要统一操做组装的零件(合并网格),和减小这些零件的类型(避免过多材质,尽量多的公用材质)。数组
2、最简单的顶点片元着色器
2.1.顶点片元着色器基本结构
Unity Shader基本结构:Shader ,Properties,SubShder,Fallback等。数据结构
2.1.1结构
Shader "ShaderName"{
Properties{
}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
SubShader{
}
Fallback "VertexLit"
}
2.1.2一个简单的示例
Shader "UnityShaderBoook/SimplerShader"{
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert(float4 v:POSITION):SV_POSITION{
return UnityObjectToClipPos(v);
}
fixed4 frag():SV_TARGET{
return fixed4(1.0 ,1.0 ,1.0 ,1.0 );
}
ENDCG
}
}
}
当咱们把使用该shader的材质赋给场景物体时,物体仅仅表现出单纯的白色。咱们在shader中没有赋予它更丰富的内容,如法线,纹理等,因此他不会表现出任何深度,阴影等,固然也不会受到光照的影响。app
注意: 编程语言
POSITION 和SV_POSITION 都是CG/HLSL 中的语义,他们是不可省略的,这些语义用来告诉系统输入值和输出值的含义。POSTION 告诉Unity这里将模型的顶点数据传入到v参数,而SV_POSITION 则告诉Unity顶点着色器输出的是裁剪空间 中的顶点坐标。
UnityObjectToClipPos(v) 是mul(UNITY_MATRIX_MVP,v)更新后的函数,他是Unity内置函数,用来帮咱们进行顶点坐标从模型空间 转换到裁剪空间 的转换。
SV_TARGET 是HLSL 中的系统语义,它输出的是一个float4类型的变量,它等同于告诉渲染器,把用户的输出颜色存储到一个渲染目标中,有时也使用COLOR 代替,考虑到平台的通用性最好使用SV_TARGET 。
模型空间 :模型空间咱们能够理解为模型自身的局部坐标,一个模型上的顶点位置信息,都是依托自身的局部坐标的,但当咱们把它放到场景中的时候,就须要咱们转换这些信息来使用。
裁剪空间 :能够简单的理解为Unity中摄像机视椎体包围的空间,不在其空间内的信息将会被剔除,保留的信息后续会被投影到屏幕上被看到。
2.2.顶点着色器与片元着色器之间通讯
片元着色器是没法直接获取陈模型的顶点信息的,这时就须要咱们经过使用结构体做为媒介,有顶点着色器向片元着色器传递一些数据,如:模型法线,纹理坐标等。ide
2.2.1示例
Shader "UnityShaderBoook/vert2frag"{
Properties {
_Color ("Color Tint", Color) = (1 , 1 , 1 , 1 )
}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
uniform fixed4 _Color;
struct a2v{
float4 vertex:POSITION;
float4 normal:NORMAL;
float4 texcoord:TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
fixed3 color:COLOR0;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.color = v.normal * 0.5 + fixed3(0.5 , 0.5 , 0.5 );
return o;
}
fixed4 frag(v2f i):SV_TARGET{
fixed3 c = i.color;
c *= _Color.rgb;
return fixed4(c, 1.0 );
}
ENDCG
}
}
}
注意: 顶点着色器是逐个顶点调用的,片元着色器是逐片元调用的。片元着色器中的输入实际是吧顶点着色器的输出插值后获得的结果。函数
2.3.Unity内置文件及变量
2.3.1 名词解释
ShaderLab 是unity本身封装调用CG/HLSL/GLSL的接口。能够理解为C#与C,一个方便咱们使用,一个更加接近底层。 语义 是CG/HLSL这些底层提供的用于限定参数含义的字符串。而unity为了方便的对模型的数据进行传输,对一些语义进行了特别规定。如:顶点着色器中用TEXCOORD0来描述texcoord,unity会自动识别出TEXCOORD0的语义,并把模型的第一套纹理坐标信息填充到texcoord中。学习
2.3.2 Unity支持的经常使用语义
从应用阶段传递模型数据到顶点着色器 优化
语义
描述
POSITION
模型空间中的顶点位置,一般是float4类型
NORMAL
顶点法线,一般是float3类型
TANGENT
顶点切线,一般是float4类型
TEXCOORDn
如TEXCOORD0、TEXCOORD1该顶点的纹理坐标,TEXCOORD0表示第一组纹理坐标,依此类推,一般是float2或float4类型
COLOR
顶点颜色,一般是fixed4或float4类型
注意: 其中TEXCOORDn中n的数目是和Shader Model有关的,例如通常在Shader Model 2(即Unity默认编译到的Shader Model版本)和Shader Model 3中,n等于8,而在Shader Model 4和Shader Mode 5中,n等于16。一般状况下,一个模型的纹理坐标组数通常不超过2,即咱们每每只使用TEXCOORD0和TEXCOORD1。在Unity内置的数据结构体appdata_full中,它最多使用了6个坐标纹理组。 从顶点着色器传递数据给片元着色器
语义
描述
SV_POSITION
裁剪空间中的顶点坐标,结构体中必须包含一个用该语义修饰的变量。等同于DirectX9中的POSITION,但最好使用SV_POSITION
COLOR0
一般用于输出第一组顶点颜色,但不是必须的
COLOR1
一般用于输出第二组顶点颜色,但不是必须的
TEXCOORD0~TEXCOORD7
一般用于输出纹理坐标,但不是必须的
注意 上面的语义中,除了SV_POSITION是有特别含义外,其余语义对变量的含义没有明确要求,也就是说,咱们能够存储任意值到这些语义描述变量中。一般,若是咱们须要把一些自定义的数据从顶点着色器传递给片元着色器,通常选用TEXCOORD0等。 从片元着色器输出时Unity支持的语义
语义
描述
SV_Target
输出值将会存储到渲染目标(render target)中。等同于DirectX9中的COLOR语义,但最好使用SV_Target
注意 一个语义可使用的寄存器只能处理4个浮点值(float)。所以咱们想要定义矩阵类型,如float3×4,float4×4等变量是就须要使用更多的空间。一种方法是,把这些变量拆分红多个变量,例如对于float4×4的矩阵类型,咱们能够拆分红四个float类型的变量,每一个变量存储矩阵中的一行数据。
3、Unity中三种shader类型
3.1固定管线着色器
3.1.1简介
固定功能着色器为固定功能渲染管线的具体表现。实现较为简单,可是功能单一,效果较差。Unity5.2及之后备抛弃,全部的固定管线着色器都会别Unity编译成对应的顶点片元着色器。
3.1.2效果
固定功能
3.1.3示例代码
Shader "ShaderCookbook/固定功能着色器"
{
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("基本纹理",2 D)="white"{}
_SecTex("副纹理",2 D)="white"{}
}
SubShader
{
Tags{"Queue"="Transparent"}
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
Material
{
Diffuse[_Color]
Ambient[_Color]
Shininess[_Shininess]
Specular[_SpecColor]
Emission[_Emission]
}
Lighting On
SeparateSpecular On
SetTexture[_MainTex]
{
Combine texture * primary DOUBLE,texture * primary
}
SetTexture[_SecTex]{
Combine texture * previous double ,texture * previous
}
}
}
}
3.2表面着色器
表面着色器介绍
3.2.1简介
Unity包装过一层的着色器类型,须要较少的代码量就能达到很好的效果,但因为Unity背后会作不少工做,渲染的代价比较大。
3.2.2效果
表面着色器
3.2.1示例代码
Shader "ShaderCookbook/简单表面着色器"
{
Properties
{
_MainTex ("【纹理】Texture", 2 D) = "white" {}
_BumpMap ("【凹凸纹理】Bumpmap", 2 D) = "bump" {}
_RimColor ("【边缘颜色】Rim Color", Color) = (0.17 ,0.36 ,0.81 ,0.0 )
_RimPower ("【边缘颜色强度】Rim Power", Range(0.6 ,9.0 )) = 1.0
}
SubShader
{
Tags { "RenderType" = "Opaque"}
Blend SrcAlpha OneMinusSrcAlpha
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));
o.Emission = _RimColor.rgb * pow (rim, _RimPower);
}
ENDCG
}
Fallback "Diffuse"
}
3.3顶点片元着色器
3.3.1 简介
更为复杂,但也更为灵活,能够完成不少复杂的效果,可是须要咱们控制渲染的实现细节。
3.3.2效果
顶点片元着色器
3.3.3示例代码:
shader "ShaderCookbook/简单顶点片元着色器"{
Properties
{
_Color ("Color", Color) = (1.0 ,1.0 ,1.0 ,1.0 )
_MainTex("MainTex",2 D)="white"{}
_SpecColor ("Specular Color", Color) = (1.0 ,1.0 ,1.0 ,1.0 )
_Shininess ("Shininess", Float) = 10
}
SubShader
{
Tags { "LightMode" = "ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _SpecColor;
float _Shininess;
float4 _LightColor0;
struct vertexInput
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct vertexOutput
{
float4 pos : SV_POSITION;
float4 col : COLOR;
float2 uv :TEXCOORD0;
};
vertexOutput vert(vertexInput v)
{
vertexOutput o;
o.uv= v.texcoord * _MainTex_ST.xy + _MainTex_ST.zw;;
float3 normalDirection = normalize ( mul( float4(v.normal, 0.0 ), unity_WorldToObject ).xyz );
float3 viewDirection = normalize ( float3( float4( _WorldSpaceCameraPos.xyz, 1.0 ) - mul(unity_ObjectToWorld, v.vertex).xyz ) );
float3 lightDirection;
float atten = 1.0 ;
lightDirection = normalize (_WorldSpaceLightPos0.xyz);
float3 diffuseReflection = atten * _LightColor0.xyz * max ( 0.0 , dot ( normalDirection, lightDirection ) );
float3 specularReflection = atten * _LightColor0.xyz * _SpecColor.rgb * max ( 0.0 , dot ( normalDirection, lightDirection ) ) * pow ( max ( 0.0 , dot ( reflect ( -lightDirection, normalDirection ), viewDirection ) ), _Shininess );
float3 lightFinal = diffuseReflection + specularReflection + UNITY_LIGHTMODEL_AMBIENT;
o.col = float4(lightFinal * _Color.rgb, 1.0 );
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
float4 frag(vertexOutput i) : COLOR
{
fixed4 tex = tex2D(_MainTex,i.uv);
i.col*=tex;
return i.col;
}
ENDCG
}
}
Fallback "Diffuse"
}
3.3.4 效果
在世界坐标系中顶点坐标超过限定值则不显示,相似切面效果
切面
3.3.5 示例代码
Shader "ShaderCookbook/VertexWorldPos" {
Properties {
_Color ("Color", Color) = (1 ,1 ,1 ,1 )
_MainTex ("Albedo (RGB)", 2 D) = "white" {}
_YLimit("YLimit", float )= 0.0
}
SubShader {
Tags { "RenderType"="AlphaTest" "IgnoreProjector"="True" "Queue"="Transparent"}
Pass{
Tags{"LightMode"="ForwardBase"}
ZWrite off
Blend SrcAlpha OneMinusSrcAlpha
Cull off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
float _YLimit;
struct a2v{
float4 vertex:POSITION;
float4 texcoord:TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
float4 uv:TEXCOORD;
float3 worldPos:TEXCOORD1;
};
v2f vert(a2v i){
v2f v;
v.pos=UnityObjectToClipPos(i.vertex);
v.uv.xy=i.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
v.worldPos=mul(unity_ObjectToWorld,i.vertex);
return v;
}
fixed4 frag(v2f v):SV_TARGET{
fixed4 Col=_Color*tex2D(_MainTex,v.uv.xy);
if (v.worldPos.y>_YLimit)
discard ;
return Col;
}
ENDCG
}
}
FallBack "Diffuse"
}
3.3.6 效果
这里咱们再次使用顶点片元着色器造一个黑洞同样的效果。物体靠近黑洞时会被黑洞吸引拉扯,逐渐缩成一点,在另外一端则会逐渐变大出现。
BlankHole
3.3.7 示例代码
Shader "ShaderCookbook/黑洞" {
Properties {
_Color ("Color", Color) = (1 ,1 ,1 ,1 )
_MainTex ("Albedo (RGB)", 2 D) = "white" {}
_YLimit("YLimit", float )= 0.0
_Length("Length",float )=1
}
SubShader {
Pass{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
float _YLimit;
float _Length;
struct a2v{
float4 vertex:POSITION;
float4 texcoord:TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
float4 uv:TEXCOORD;
float3 worldPos:TEXCOORD1;
};
v2f vert(a2v i){
v2f v;
v.worldPos=mul(unity_ObjectToWorld,i.vertex);
if (distance (v.worldPos.y,_YLimit)<_Length)
{
float s=(distance (v.worldPos.y,_YLimit)/_Length);
i.vertex.xz*=s;
}
v.pos=UnityObjectToClipPos(i.vertex);
v.uv.xy=i.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
return v;
}
fixed4 frag(v2f v):SV_TARGET{
fixed4 Col=_Color*tex2D(_MainTex,v.uv.xy);
return Col;
}
ENDCG
}
}
FallBack "Diffuse"
}
開簾頓覺春風暖 滿紙淋漓白雲聲——杨玉香
X Shader实例
X.1.1 热度图
X.1.1.1 效果
运行结果:
渐变颜色效果
梯度颜色效果
热度图纹理:
渐变ramp图片
梯度颜色效果
X.1.1.2 示例代码
Shader "Ellioman/Heatmap"
{
Properties
{
_HeatTex("Texture", 2 D) = "white" {}
}
SubShader
{
Tags{ "Queue" = "Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct vertInput
{
float4 pos : POSITION;
};
struct vertOutput
{
float4 pos : POSITION;
fixed3 worldPos : TEXCOORD1;
};
uniform int _Points_Length = 0 ;
uniform float3 _Points[100 ];
uniform float2 _Properties[100 ];
sampler2D _HeatTex;
vertOutput vert(vertInput input)
{
vertOutput o;
o.pos = UnityObjectToClipPos(input.pos);
o.worldPos = mul(unity_ObjectToWorld, input.pos).xyz;
return o;
}
half4 frag(vertOutput output) : COLOR
{
half h = 0 ;
for (int i = 0 ; i < _Points_Length; i++)
{
half di = distance (output.worldPos, _Points[i].xyz);
half ri = _Properties[i].x;
half hi = 1 - saturate(di / ri);
h += hi * _Properties[i].y;
}
h = saturate(h);
half4 col = tex2D(_HeatTex, fixed2(h, 0.5 ));
return col;
}
ENDCG
}
}
Fallback "Diffuse"
}
X.1.1.3 分析
首先,咱们须要将ramp图片的wrapMode格式设为Clamp。 Texture.wrapMode 循环模式:
TextureWrapMode.Clamp:设置纹理充满拉伸使用
TextureWrapMode.Repeat:纹理重复平铺使用
若是采用Repeat,那么等于U>=1的状况就会用纹理图在右边在平铺一张图。
咱们使用了相似使用ramp纹理制做toon卡通风格shader效果的方式,来用uv中的u指得到颜色的强度。 这里在对blend混合命令作一个笔记: Blend SrcAlpha OneMinusSrcAlpha // 传统透明度 Blend One OneMinusSrcAlpha // 预乘透明度 Blend One One // 叠加 Blend OneMinusDstColor One // 柔和叠加 Blend DstColor Zero // 相乘——正片叠底
那海和天空之间星星消失的地方 连时间也没有确切的命运——杨炼
X.1.2 山崖
X.1.2.1 效果
这里主要实现了山崖上草地覆盖面积,高度还有山石高光,平滑度,法线强度的控制。
X1.2.2 示例代码
Shader "Unlit/Cliff"
{
Properties
{
_MainTex ("Texture", 2 D) = "white" {}
_MainNormal("MainNormal",2 D)="white"{}
_GrassTex ("GrassTex", 2 D) = "white" {}
_GrassNormal ("GrassNormal", 2 D) = "white" {}
_Smooth("smooth ",Range(0 ,1 ))=1
_Gloss("Gloss",Range(0 ,128 ))=96
_Height("Height",float )=0.5
_Offset("Offset",float )=0.5
_BumpScale("bumpscale",float )=0.5
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float4 uv : TEXCOORD0;
float3 normal:NORMAL;
float4 tangent:TANGENT;
};
struct v2f
{
float4 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 lightDir:TEXCOORD1;
float3 worldPos:TEXCOORD2;
float3 tanView:TEXCOORD3;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _GrassTex;
float4 _GrassTex_ST;
sampler2D _MainNormal;
sampler2D _GrassNormal;
float _Offset;
float _BumpScale;
float _Height;
float _Gloss;
float _Smooth;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv.xy, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.uv.zw, _MainTex);
TANGENT_SPACE_ROTATION;
o.worldPos=UnityObjectToWorldDir(v.vertex);
o.lightDir=normalize (mul(rotation,ObjSpaceLightDir(v.vertex)).xyz);
o.tanView=normalize (mul(rotation,ObjSpaceViewDir(v.vertex)));
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 halfDir=normalize (i.tanView+i.lightDir);
fixed4 col = tex2D(_MainTex, i.uv.xy);
fixed4 grass=tex2D(_GrassTex,i.uv.zw);
float3 colNormal=UnpackNormal(tex2D(_MainNormal,i.uv.xy))*_BumpScale;
float3 GrassNormal=UnpackNormal(tex2D(_GrassNormal,i.uv.zw))*_BumpScale;
colNormal.z=sqrt (1.0 -saturate(dot (colNormal.xy,colNormal.xy)));
GrassNormal.z=sqrt (1.0 -saturate(dot (GrassNormal.xy,GrassNormal.xy)));
float angleY=1 -saturate(dot (UnityObjectToWorldDir(colNormal),float3(0 ,1 ,0 ) )+_Offset);
angleY-=i.worldPos.y>_Height?0 :i.worldPos.y-_Height;
fixed4 finalColor=lerp(grass,col,angleY);
float3 diffuse=finalColor*max (0.0 ,dot (i.lightDir,colNormal))*_LightColor0.xyz;
float3 specular= _LightColor0.rgb * pow (saturate(dot (halfDir, colNormal)), _Gloss)*_Smooth;
return fixed4(diffuse+specular+UNITY_LIGHTMODEL_AMBIENT.xyz*finalColor,1 );
}
ENDCG
}
}
Fallback "Diffuse"
}