shader总结二

以下所有内容都是摘自冯乐乐《shader入门精要》


总结一里面在vert函数中取得每个顶点我们叫逐顶点,当然如果是写在了frag函数中叫逐像素,逐像素产生的光照渐变更加细腻,当然也更消耗资源

上个例子漫反射中,当在光源背部看物体时,物体时全黑的,就分不清到底是啥物体了,要想改成不是那么黑,称为半兰伯特光照模型

如果float4变量,要想取得前3个值,那么变量名.xyz即可


上一个总结讲了通过mul()的方式乘一个矩阵从世界空间转模型空间当然也可以直接调用函数来获得,当然,通过函数的得到光源方向没有单位化,还要记得加上normalize

单位化的意义:把长度取1后就只关注方向不关注长度了



对于多种光的混合,比如当前的物体填充一层白色,那么要把白色光和漫反射光相乘,做乘法运算,但是最后的输出的光是漫反射光加上高光反射加上环境光的叠加


贴图纹理:给物体贴上一层皮肤让它显示出真正的样子

纹理坐标:也即UV坐标,由于要计算每个像素的颜色因此要在片元函数中进行,一般可以通过纹理的颜色代替漫反射的颜色达到效果,在顶点函数中把纹理坐标传递给片元函数,片元函数利用函数tex2D来取得纹理的颜色来代替漫反射颜色值,第一个参数是定义的纹理2D的名字,第二个参数是传给片元函数的纹理坐标,只去前两个值,所以.xy


纹理添加偏移和缩放:

Tilling:可以将贴图压缩n倍,也即压缩多个相同的贴图到一个贴图中

offset:将图片进行偏移,整体左转右转上转下转

具体做法:先声明一个2D纹理,然后声明一个变量float4 ,命名有规则2D纹理名字_ST,必须这样命名,最后再计算纹理坐标的时候用纹理坐标乘以缩放值(2D纹理名_ST.xy)再加上偏移值(2D纹理名_ST.zw)


单位矢量=归一化矢量

点积的几何意义(内积):两个向量的点积a*b等于b在a方向的投影值,再乘以a的长度,            

a*b=|a||b|cos夹角

叉积的几何意义(外积)叉积的长度 |a×b| 可以解释成这两个叉乘向量a,b共起点时,所构成平行四边形的面积。

|a×b|=|a||b|sin夹角,   a×b==

正交矩阵:
如果AA
T=E(E为单位矩阵,AT表示“矩阵A的转置矩阵”)或ATA=E,则n阶实矩阵A称为正交矩阵。


齐次坐标:这个平移矩阵告诉我们了xyz是个三维坐标要想通过矩阵相乘实现平移,就必须加一个维度。把3维矩阵转成4维,比如一个三维坐标(1,4,5)转成齐次为(1,4,5,1)但是如果坐标乘以2,那么最后一个维度也乘以2


旋转矩阵:



绕z轴旋转



从子空间(C)转到父空间(P)或从父空间(P)转到子空间(C):


M c->p指的是从子空间转到父空间的变换矩阵,Mp->c是逆矩阵,我们只需得到一个矩阵,另一个矩阵求逆即可

根据正交矩阵的性质,它的逆矩阵为它的转置矩阵因此从A空间到B空间的矩阵M如果是正交矩阵,那么M矩阵的第一列为A空间的x轴在B空间的表示,M矩阵的第一行为B空间x轴在A空间下的表示


世界空间转观察空间的矩阵求法:

第一种求法:计算观察空间在世界空间的3个坐标轴的表示,得到观察空间转世界空间的变换矩阵,求逆

第二种求法:移动整个观察空间,移动到原来的世界空间的位置上重合即可(这里有个疑问,这样得到的是观察空间转世界空间的矩阵啊,为什么这里求的是世界转观察的?)

通过Transform组件可知,相机在世界空间先按照(30,0,0)进行旋转,也即绕x轴旋转30度,然后按(0,10,-10)平移,得到了观察空间,那么我们要求观察空间转回世界空间,逆向变换,先按(0,-10,10)平移,再(-30,0,0)旋转,这里注意的是,反方向移动的话原本是平移矩阵*旋转矩阵*缩放矩阵,那么现在要反过来相乘,得到变换矩阵,还要记得观察方向使用右手坐标系,要对z方向取反。


这样来对一个矩阵的z方向取反,这样最后结果就得到了


模型:model     投影:projection   观察:observe    转置:transposition    

 因此mvp代表模型,观察,投影矩阵         T代表转置



通常把一些自定义的数据从顶点着色器传递到片元着色器,一般选用TEXCOORD0等,SV_Target的意思是输出值会存储在渲染目标中

TANGENT是切线的意思


Phong光照模型:

上一节那4个标准光照模型叫做Phong光照模型,后来简化计算改成了Blinn—Phong光照模型

要计算自发光,只需在片元着色器输出前,把材质的自发光颜色添加到输出颜色上即可

要得到环境光,通过变量UNITY_LIGHTMODEL_AMBIENT 得到环境光的颜色和强度


在得到了世界空间的法线和光源方向后,需要对它俩进行归一化,得到点积结果后,为了防止结果为负可以使用saturate函数取(0,1)范围内,最后光源的颜色和强度以及材质的漫发射颜色相乘可得到最终漫反射光照

unity中提供了内置变量_LightColor0来访问光源的颜色和强度信息(要定义合适的LightMode标签)


关于纹理的代码:



这里注意的是最后用了texcoord的缩放值乘以2D纹理的缩放值,加上2D纹理的偏移值


使用了CG的tex2D函数对纹理进行了采样,第一个参数是2D纹理名第二个参数是纹理坐标float2,它将返回得到纹素值,采样结果乘以颜色属性作为材质的反射率并把它和环境光相乘得到环境光部分。最后通过物体材质反射率albedo相乘计算漫反射光照,最后将漫发射,环境光照,高光反射相加后返回



纹理导入面板中另一个属性是Filter Mode属性,决定了纹理由于变换产生拉伸采用哪种滤波模式,有3种滤波模式

1,Point :使用最近邻滤波,它的采用像素数目通常只有一个

2,Bilinear: 使用线性滤波,对于每个像素,它首先会找到4个邻近像素,进行线性插值混合后得到最终像素。因此图像看上去被模糊了

3,Trilinear:和Bilinear差不多

它们得到的滤波效果依次提升,耗费的性能依次提升,纹理滤波会影响放大缩小纹理时得到的图片质量。比如把64*64的纹理贴在512*512的平面上就需要放大纹理就可以通过如上的3中滤波模式

纹理缩小更加复杂,它是将原来的多个像素对应到目标的一个像素上。纹理缩放更加复杂的原因是我们要处理抗锯齿问题,最常使用的方式是多级渐远纹理(MipMapping)

多级渐远纹理:将原来的纹理通过滤波得到许多更小的图像这样运行时可快速得到结果像素,比如物体原理相机的时候,可以直接使用较小的纹理缺点是要耗费一定的空间来存储多级渐远纹理。这是一种空间换取时间的方法

做法:在Unity中将纹理导入面板,将纹理类型(Texture Type)选为advanced,再勾选Generate Mip Maps即开启多级渐远纹理技术



凹凸映射:

使用一张纹理修改模型表面的法线,为模型提供更多的细节,让模型看起来凹凸不平

有两种方法来进行凹凸映射,一种方法是高度纹理来模拟表面位移,然后得到修改后的法线值,这种方法叫做高度映射

另一种方法是使用一张法线纹理直接存储表面法线,也叫做法线映射,在法线纹理中又分为模型空间下的法线纹理和切线空间下的法线纹理


在法线贴图技术中,我们就是通过把墙面的每个像素的法线存储在一张纹理中,渲染的时候根据每个像素的法线确定他们的阴暗程度


对于模型顶点自带的法线,它们定义在模型空间中,因此最直接的想法是将修改后的模型空间的表面法线存储到一个2D纹理中,这种纹理叫做模型空间的法线纹理,但是实际往往会采用另一种坐标空间,即模型顶点的切线空间来存储法线(这里应该是用一个空间也就是另一个坐标系来保存法线值)。这种纹理叫做切线空间下的法线纹理。切线空间很多情况都优于模型空间,所以使用切线空间下的法线纹理

模型空间下的法线纹理是看起来五颜六色的,因为模型空间下的法线是向各个方向上的切线空间下的法线纹理看起来全部是蓝色的。

做法:

1,在切线空间下进行光照计算,把光照方向,视角方向变到切线空间下

步骤:构造一个模型空间转切线空间的矩阵,矩阵第一行为切线向量,第二行为副切线向量,第三行为法线向量,副切线向量为切线向量叉积法线向量乘以v.tangent.w也即乘以切线向量的第四个分量来决定叉积后的向量是里面还是外面的。也可以使用内置宏TANGENT_SPACE_ROTATION得到变换矩阵

构造完矩阵后通过该矩阵求得切线空间下的光方向和视角方向,单位化,

这句话的意思是通过BumpScale这个变量(可以在面板上拖动)来控制凹凸程度。

2,世界空间下进行光照计算,把法线方向变到世界空间下,再和世界空间下的光照方向和视角方向进行计算

步骤:在顶点函数中计算切线空间转世界空间的变换矩阵,该矩阵由世界空间下的顶点切线,副切线,法线组成,副切线这样求


在片元函数中把法线纹理的法线方向从切线空间变换到世界空间即可,虽然用了过多的计算,但是使用cubeMap就用到这种方法

构造矩阵这样构造:这里第四个分量保存了顶点的坐标,这里怎么用的列向量我以为应该是横向量


得到切线转世界的矩阵,


在片元函数中使用UnpackNormal对法线纹理进行采样,使用_BumpScale对其缩放,用TtoW0,TtoW1,TtoW2把法线从切线空间转到世界空间下


UnpackNormal函数的作用:把法线纹理的纹理类型表识成Normal map时,使用UnpackNormal来得到正确的法线方向


兰伯特光照模型:

计算方法:漫反射光的强度近似地服从于Lambert定律,即漫反射光的光强仅与入射光的方向和反射点处表面法向夹角的余弦成正比


原理公式:diffuse = I*cosθ;

diffuse:反射光线的的光强;

I:入射光线的光强,        cosθ:入射光线和该顶点法线的余弦,如上图所示;cosθ = L*N;最后的数学表达式为:diffuse = I*(L*N);

半兰伯特光照模型:

用于解决漫反射光无法到达区域无任凭明暗变化,丢失模型细节表现的问题。

其公式如下:

     //将dot结果从(-1,1)映射到(0,1)范围,n为表面法线,I为光源方向,代码如下



Fallback:是一个指令,告诉unity如果上面的所有subShader在这块显卡上都不能运行,那就使用这个最低级的shader吧



这里面的标签参数看下,面试会问的



剪裁空间的目标是对渲染图元进行剪裁,位于空间内部的图元会被保留,否则剔除,这块空间是如何决定的呢?答案是视锥体,它有6个面组成,这些平面也称为剪裁平面,视锥体分为

1,正交投影  :2D游戏中使用

2,透视投影:模拟了人看世界的方式,适用于3D游戏中

距离相机最近的平面叫近剪裁平面

距离相机最远的平面叫远剪裁平面



float4 vert(float4 v : POSITION) :SV_POSITION{    //这句话的意思是将模型的顶点坐标填充到参数v中,SV_POSITION将告诉unity顶点着色器的输出是剪裁空间的顶点坐标

fixed4 frag() :SV_Target {    //意思是用户的输出颜色存储到一个渲染目标中


填充POSITION ,TANGENT,NORMAL这些数据从哪里来呢?在unity中使用材质的meshRender组件提供,在调用每帧的Draw Call的时候,Mesh Render会把每一个三角面片里面的顶点位置,法线,切线,纹理坐标,顶点颜色发送给shader


写shader的时候不要除以0,会出错


各项同性:Blinn Phone模型是各项同性,也即用固定视角和光源方向旋转表面,反射不会改变

各项异性:反之


顶点着色器的最基本任务就是把顶点位置从模型空间转换到剪裁空间,因此用unity内置的模型世界投影矩阵UNITY_MATRIX_MVP来完成坐标变换,通过unity内置的UNITY_LIGHTMODEL_AMBIENT得到环境光部分


纹理映射:纹理最初的目的是通过一张图片来控制模型的外观,使用纹理映射,把一张图片贴在模型表面,把纹理映射坐标存储到每个顶点上。使用二维变量(u,v)表示,纹理映射坐标也叫uv坐标


OpenGL和DirectX在二维纹理中的坐标系差异:OpenGL中,纹理空间的原地位于左下角,DirectX中原点位于左上角,Unity中通常只有一种坐标系,是OpenGL风格的,


通常会用一张纹理来代替物体漫反射的颜色


lightmap:光照贴图,把场景烘焙一下光照就不用去实时计算了,节省性能

NormalMap:法线贴图