阅前提示
记录Unity Shader 学习笔记,拿起这块砖,砸开Shader的门。
适合人群:Shader 初学者
阅读方式:目录顺序阅读
知道的越多,不知道的越多。希望我的文章对你有所帮助
获取更多:我的博客
Shader
渲染管线:
渲染流程主要分为三个阶段,由CPU与GPU完成
1.应用阶段(cpu部分)
此阶段主要是处理数据(硬盘——>内存),设置渲染状态,DrawCall
加载数据到显存
设置渲染状态
DrawCall
将准备的数据(点,线,面,材质,等信息打包传递给GPU【图元数据】,通知GPU开始渲染)
2.几何阶段(gpu)
该阶段主要做的事情是坐标转换,将空间坐标转换为屏幕坐标。
细分下来有如下几个过程:
顶点着色器(Vertex)
实现顶点的空间变化,顶点着色。
曲面着色器(Tessellation)
细分图元
几何着色器(Geometry)
执行逐图元着色操作,或用于产生更多图元
剪裁(Clipping)
剪裁不在摄像机内的顶点,剔除某些三角图元的面片。
屏幕映射(ScreenMapping)
将图元坐标转到屏幕坐标新中
3.光栅化阶段(gpu)
设置三角面,计算每个像素的颜色,最终呈现。
三角形设置
上阶段输出的三角网格顶点数据,计算三角网格。
三角形遍历
找寻被三角网格覆盖的像素(片元)
片元着色器
可编程,完成重要的渲染技术。
逐片元操作
称为输出合并阶段,此阶段片元将进行很多测试工作。
模板测试:用于限制渲染区域,或一些高级用法 渲染阴影,轮廓渲染
深度测试:用于判断遮挡
混合:用于处理透明,半透明
数学
向量/矢量v(x,y,z)
模
点积
v1 在v2上的投影长度 (>0 方向相同 反之)
叉积
向量叉积来计算垂直于该平面的向量
矩阵
单位矩阵
置换矩阵
逆矩阵
高斯-若尔当消元法
逆矩阵的求法
正交矩阵
矩阵变换
基础变化矩阵
法线变换
若一个点T 的变换矩阵为M,则法线N的变换矩阵为M的逆转置矩阵
Unity Shader
四种Shader
-
Standard Surface Shader 表面着色器模板(Unity后台转换为顶点/片元着色器)
Shader "Custom/NewSurfaceShader" { Properties {} SubShader { Tags { "RenderType"="Opaque" } //[RenderSetup] CGPROGRAM ENDCG } FallBack "Diffuse" }
-
Unlit Shader 顶点/片元着色器
Shader "Unlit/NewUnlitShader" { Properties{} SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM ENDCG } } }
Image Effect Shader 屏幕后处理效果模板
Compute Shader 常规渲染流水线无关的计算
第一个Shader
Shader "Unlit/SimpleShader"
{
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,1,0,1);
}
ENDCG
}
}
}
vertex
声明了哪个函数包含顶点着色器代码
顶点着色器:逐顶点执行
fragment
声明了哪个函数包含了片元着色器代码
片元着色器:逐片元执行
顶点与片元的关系
在渲染管线中,GPU执行的开始便是顶点作色器,它的输入来自CPU,主要工作是 坐标转换和逐顶点光照,且可以为后续阶段输出数据
片元着色器作用于光栅化阶段,三角形遍历阶段会检查每个像素是否被三角网络覆盖,如果被覆盖的话就会生成一个片元。
光照
漫反射
_LightColor0.rgb * _Color.rgb * saturate(dot(worldNormal,worldLight))
半兰伯特模型(Half Lambert)
_LightColor0.rgb * _Color.rgb * (dot(worldNormal,worldLight)*0.5 + 0.5)
目的在于把[-1,1]映射到[0,1],模型的背光面也会有明暗变化。因为普通漫反射会与0做比较。
高光反射光照模型(Phong)
Clight是入射光的颜色和强度,材质的高光反射系数Mspecular,视角方向V和反射方向R点乘的结果的光系数Mgloss次方
fixed3 reflectDir = normalize(reflect(worldLight,worldNormal));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld,v.vertex));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(viewDir,reflectDir)),_Gloss);
Blinn
纹理
属性:_MainTex ("Texture", 2D) = "white" {}
声明:
sampler2D _MainTex;
float4 _MainTex_ST;
// float2 uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
float2 uv = TRANSFORM_TEX(v.texcoord,_MainTex);//缩放与平移确定最终纹理坐标
float3 TexelsValue = tex2D(_MainTex,uv);//通过纹理坐标采样纹素值
纹理名_ST 代表纹理属性 ST(scale,translation)
X_ST.xy 代表缩放,X_ST.zw 代表平移
法线纹理
凹凸映射有两种方式:
高度纹理:颜色深浅表示凹凸
法线纹理:通过法线扰动方向确定凹凸
因为法线分量范围[-1,1] 像素的分量范围[0,1]
模型空间的法线纹理
实现简单,直白,计算少。但得到的是绝对法线信息,仅可用于创建时的模型。
切线空间的法线纹理
自由度高,记录的是相对法线信息,适用于不同的网格。
可进行UV动画
可压缩。切线空间下法线纹理的Z方向总是正方向,因此可靠XY方向推导Z方向。
切线空间下的代码:
//把模型空间下切线方向,副切线方向,法线方向 排列得到模型空间到切线空间的旋转矩阵
//副切线由其他两个做叉乘得到,乘w 是为了区分方向(因为有两个)
// float3 binormal = cross(normalize(v.normal),normalize(v.tangent.xyz))*v.tangent.w;
// float3x3 rotation = float3x3(v.tangent.xyz,binormal,v.normal);
// TANGENT_SPACE_ROTATION;//内置宏,效果同上
TANGENT_SPACE_ROTATION;
//计算切线空间下的光线与视角
o.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex)); //模型——>切线
o.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex));
//计算切线空间下的法线坐标
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);
fixed4 packedNormal = tex2D(_BumpMap,i.uv.zw);
fixed3 tangentNromalDir;
tangentNromalDir = UnpackNormal(packedNormal);//法线纹理设置成 normal map 后使用此方法
// tangentNromalDir.xy = (packedNormal*2 - 1)*_BumpScale;
tangentNromalDir.xy *= _BumpScale;
tangentNromalDir.z =
sqrt(1-saturate(dot(tangentNromalDir.xy,tangentNromalDir.xy)));//根据xy推导z
渐变纹理
通过渐变纹理使漫反射轮廓鲜明。
原理是由半兰伯特而来,利用HalfLambert 使法线和光线的点积的范围变为了[0,1],从而在UV上以halfLambert为坐标采样。
fixed halfLambert = 0.5*(dot(worldNormalDir,worldLightDir)) + 0.5;
fixed3 diffuseColor = tex2D(_RampTex,float2(halfLambert,halfLambert)).rgb * _Color.rgb;
fixed3 diffuse = _LightColor0.rgb * diffuseColor;
遮罩纹理
遮罩纹理为我们提供了更为精确(像素级)地控制模型表面的各种性质。
例如:可以通过一张遮罩纹理的R通道的值来控制高光反射的强弱。
fixed specularMask = tex2D(_SpecularMark,uv).r * _SpecularScale;
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(i.worldNormal,blinnDir)),_Gloss) * specularMask;
所以,通过增加遮罩纹理,我们可以控制更多的表面属性。
透明
透明度测试
判断一个片元透明度满不满足条件从而进行舍去,保留的片元将于普通的不透明物体一起处理。无其他特殊开关,即物体要不就是纯透明,要不就是不透明。
clip(texColor.a - _Cutoff);
//当 a - 定义的系数 < 0 是将执行 discard 指令剔除该片元
透明度混合
真正意义上的半透明。它会将当前片元颜色于存储在颜色缓冲中点颜色进行混合。但此混合会关闭深度写入,所以必须要注意渲染顺序。