赞
踩
shader链接: https://blog.csdn.net/tjw02241035621611/article/details/80038608
shader优化CPU: https://blog.csdn.net/tjw02241035621611/article/details/80090204sahd
UnityShader学习资料推荐 https://blog.csdn.net/wwlcsdn000/article/details/78811394
http://candycat1992.github.io/unity_shaders_book/unity_shaders_book_corrigenda.html //shader 入门精要 勘误
隐函数:如果存在定义域上的子集D,使得对每个x属于D,存在相应的y满足F(x,y)=0,则称方程确定了一个隐函数。
显函数:就是初中学到的函数。
数字图像:以像素为基本单位,以二维数字组形式表示的图像。
位图图像/ 点阵图像 / 数据图像 / 数码图像”: 像素、单元内像素多的分辨率高。
矢量图: 是由数学方式描述的点、线、面构成的图形。
OpenGL: 一个底层图库,不提供几何实体格式,不能直接用于描述场景。但是可以把一些模型文件转换成顶点数组。
英伟达CG编写shader(跨平台) ;Opengl ; DirectX;
ShaderLab 对这三种语言都做了封装 通常使用CG来编写Shader
Unity Shader的分类
使用的是ShaderLab编写Unity中的Shader
1,表面着色器 Surface Shader(Unity独有的 是对,顶点/片元着色器的一种封装 封装的还不全面肯会有一些功能无法实现 编写起来或许会简单点 运行时会被解析成顶点/片元着色器 )
2,顶点/片元着色器 Vertex/Fragment Shader(最基本的 编写起来代码可能会多一点 因为是最基本的所以可以实现所有的效果 比如 表面着色器 无法实现的 顶点/片元着色器可以实现 )
3,固定函数着色器 Fixed Function Shader(现在已经被弃用了 在旧设备上使用)
错切变换: 是在某方向上,按照一定的比例对图形的每个点到某条平行于该方向的直线的有向距离做放缩得到的平面图形
分为三步:
[1] 把数据加载到显存中 [2]设置渲染状态 【3】调用drawcall
渲染的数据需要从硬盘中加载到系统内存[RAM]中<通过cpu>。网格纹理等数据又被加载到显存【VRAM】中,.当数据从内存加载到显存后,RAM中的数据就可以移除了,但是有时候需要使用它【比如纹理需要读写,模型网格需要动态创建,模型需要读写 后等】,且加载到内存的过程非常耗时,所以不想被移除。
DrawCall是一个命令,只是指向一个需要被渲染的图元列表,而不包含材质信息,当给一个命令时,GPU根据给的渲染状态【材质,纹理,着色器】和顶点数据来计算。计算渲染的过程就是GPU流水线。
个人总结下:
<1. 准备模型顶点数据
<2.几何阶段 :根据顶点数据,建立几何数据模型
<3. 光栅化阶段 :根据数据模型,片元着色器处理
<4. 屏幕图像
图元:基本的几何单位,如模型的点,线,面。 片元:像素点,它比像素多一些位置、法向量等
首先接收顶点数据作为输入,这些顶点数据由应用阶段加载到显存中,再通过drawcall指定
1.几何阶段:传给顶点着色器【完全可编程】,实现顶点的空间变换颜色等。【曲面着色器】是一个可选的着色器,用于细分图元,【几何着色器】是一个可选的着色器,用于逐行逐图元着色操作或者产生更多的图元。【裁剪】,控制不在摄像机视野的顶点裁掉,剔除三角图元的面片。【屏幕映射】不可配置,不可编程,把图元坐标转换到屏幕坐标系中。
2.光栅化阶段:【三角形设置和遍历】执行的都是固定的函数、【片元着色器】完全可编程,用于实现逐片操作、【逐片元操作】,修改颜色,深度,混合,它不可编程,但是有很高的配置性【比如说混合的模式】。
概念:流水线的第一个阶段,输入源是cpu,处理进来的每个顶点,无法获取顶点之间的关系,顶点相互独立,所以gpu可以对其并行处理。
基本作用;把顶点坐标从模型空间转换到齐次裁剪空间 o.pos = mul(UNITY_MVP,v.position);
齐次坐标系就是笛卡尔坐标系中的一个坐标(1,2)乘以一个实数(2)得到 (2,4,2)
2.3.3 裁剪
图元在视野内保留,在视野外删除,部分在视野则生成新的顶点,多出部分删除,这部分不可编程,由硬件决定裁剪范围,但是可以自定义一个裁剪操对这一步进行配置【lod】。
2.3.4屏幕映射
屏幕映射得到的屏幕坐标决定了这个顶点对应屏幕上哪个像素以及距离这个像素有多远。
【视口变换屏幕的wide、height 和设置投影矩阵的wide和height比例都是一样的,所以不同分辨率看到的图像缩放是一样的】
注意:OpenGL把屏幕的左下角当成最小的窗口坐标值,微软的窗口都是这样的,DirectX是屏幕的左上角是最小的窗口坐标值。注意图像倒转可能是这个原因造成的。
Unity: 送往GPU 到视口坐标 渲染到屏幕:到屏幕坐标
- //绕x轴翻转
- Texture2D flipTexture = new Texture2D(tex.width, tex.height);
- for (int i = 0; i < tex.height; i++)
- {
- flipTexture.SetPixels(0, i, tex.width, 1, tex.GetPixels(0, tex.height - i - 1, tex.width, 1));
- }
- flipTexture.Apply();
- //绕y轴翻转
- Texture2D flipTexture = new Texture2D(width, height);
-
- for (int i = 0; i < width; i++)
- {
- flipTexture.SetPixels(i, 0, 1, height, texture.GetPixels(width - i - 1, 0, 1, height));
- }
- flipTexture.Apply();
-
2.3.5 三角形设置
光栅化的目标:计算每个图元覆盖了哪些像素,以及为这些计算像素的颜色
光栅化的第一个流水线阶段是三角形设置,计算光栅化一个三角网格所需要的信息
2.3.6 三角形遍历
深度 :
https://baike.baidu.com/item/深度缓冲/16859168?fr=aladdin
当三维图形卡渲染物体的时候,每一个所生成的像素的深度(即z坐标)就保存在一个缓冲区中。这个缓冲区叫作z缓冲区或者深度缓冲区,这个缓冲区通常组织成一个保存每个屏幕像素深度的x-y二维数组。如果场景中的另外一个物体也在同一个像素生成渲染结果,那么图形处理卡就会比较二者的深度,并且保留距离观察者较近的物体。然后这个所保留的物体点深度保存到深度缓冲区中。最后,图形卡就可以根据深度缓冲区正确地生成通常的深度感知效果:较近的物体遮挡较远的物体。这个过程叫作z消隐
该阶段检查每个像素是否被一个三角网格所覆盖,如果被覆盖就生成一个片元。这个阶段也叫扫描变换。 对三个顶点所覆盖的区域的像素做插值。
片元:像素点,它比像素多一些位置、法向量等。片元的状态是对三个顶点的状态做插值。
- //注意大多数的内置渲染器不显示顶点颜色,要改为unity自带的Particles渲染器来查看顶点颜色
- Mesh mesh = GetComponent<MeshFilter>().mesh;
- Vector3[] vertices = mesh.vertices;
- Color[] colors = new Color[vertices.Length];
- colors[0] = Color.red;
- colors[1] = Color.yellow;
- colors[2] = Color.blue;
- colors[3] = Color.black;
- mesh.colors = colors;
UV坐标:射线点击获取uv,坐标,可以传到shader中进行纹理采样。
2.3.7 片元着色器
片元着色器,在Direct中,【片元着色器】被称为【像素着色器】,片元不是真正意义上的像素,包含了其他信息。
片元着色器: 上一个阶段对顶点信息插值得到的结果是此着色器的输入,而输出是一个或者多个颜色值。例如:纹理采样,通常在顶点着色器阶段输出每个顶点对应的纹理坐标,再经过光栅化阶段对三角网格的三个顶点对应的纹理坐标进行插值后,就得到覆盖了片元的纹理坐标。
局限性是片元仅仅可以影响单个片元,也就是说当执行片元着色器时,它不可以将自己的任何结果直接发送给它的邻居们。但有一个情况例外,片元着色器可以访问到导出信息【gradient或者derivative】
运行的顺序: 顶点着色器 —> 片元着色器 —> 模板测试 —> 深度测试。
展示所有属性eg:
结构eg:
状态eg:
Blend SrcFactor DstFactor: 稍微解释一下,这行指令意思就是将本 Shader 计算出的颜色值(源颜色值,即蓝色) * 源Alpha值(0.6) + 目标颜色值(可以理解为背景色) * (1-0.6),从而让蓝色方块展示出了40%的透明度。
SubShader 的标签:
Tags是键值对,键值都是字符串类型,这些键值对是SubShader和渲染引擎之间的沟通桥梁,需要怎样何时渲染这个对象。
标签的结构:
说明:
1)SetReplacementShader("shaderA",""),表示全部shader替换成shaderA进行渲染
2)SetReplacementShader("shaderA","RenderType"),先看场景中的shader中是否有RenderType参数,如果有再看RenderType中的数值是否与shaderA中的RenderType值相等,相等则使用shaderA渲染,否则就不渲染。
pass中可定义的标签类型:
除了普通的Pass定义外,UnityShader还支持特殊的Pass,以便代码复用,实现复杂效果:
3.3.4 FallBack
UnityShader的形式
3.4.1 Unity的宠儿:表面着色器
概述:unity自己创造的一种shader代码类型:代码量少,unity在背后做了许多工作,但渲染代价比较大。可以理解是对顶点和片元着色器更高层的封装。存在的意义是,处理了很多光照细节。
3.4.2 最聪明的孩子: 顶点/片元着色器
和表面着色器不同的是顶点语句块是写在pass内,而非SubShader内,因为表面是不需要关心多少个pass,以及如何渲染的问题。
3.4.3 被抛弃的角落:固定管线着色器
3.4.4 选择哪种UnityShader形式:
二维笛卡尔坐标系:不论轴向,都是可以通过变换成标准的坐标系。都是等价的
三维:左手和右手坐标系
unity中,模型空间使用的是左手坐标系,观察空间使用的是左手坐标系。
矢量的运算:
(1) 和标量的乘除
相乘时,每项都相乘这个标量,相除时,这个标量只能做分母且不为0.
几何意义:就是把向量缩放标量倍。标量是负数的话,则方向取反。
(2)矢量的加减【互未矢量才能加减】
对应分量分别加减。三角形定则:把一个点先偏移a的位置再偏移b的位置。把起点和结束点相连,或者平移。
矢量模长:
4.单位矢量:
模运算:
注意:零矢量不能归一化,分母不能为0.
点乘公式【也叫内积】:
公式一: 对应分量相乘,再相加。
应用:
6.矢量的叉乘【外积】
公式一:
公式二:
应用:计算垂直于一个平面,三角形的矢量。叉积中大拇指代表法线方向。四指是旋转方向。
练习题:【点积和叉积的应用】
答案:叉积---左手坐标系
4.4.3 矩阵运算:
1.矩阵和标量的乘法: 和矩阵中的每个标量相乘
2.矩阵和矩阵乘法:
eg: 矩阵A r * n 和 B n*c 相乘 得到r*c大小的矩阵。
注意: 第一个矩阵的列数必须和第二个矩阵的行数相同。得到的矩阵行数是第一个矩阵的行数,列数是第二个矩阵的列数。
第一个矩阵的行和第二个矩阵的列对应相乘,再相加。
综上:矩阵的运算有两步,1先判断出新矩阵是 几乘几 的矩阵,再求出新矩阵每个项上的值,eg: C23 = A第二行 * B第三列 项对应相乘。所以不满足交换率。
4.4.4 特殊的矩阵
1.方阵:行列相同的矩阵
.对角矩阵:主对角线之外的元素皆为0的矩阵。对角线上元素相等的对角矩阵称为数量矩阵;对角线上元素全为1的对角矩阵 称为单位矩阵。
2.单位矩阵:一个特殊的对角矩阵是单位矩阵。从左上角到右下角的对角线(称为主对角线)上的元素均为1。除此以外全都为0。
3.转置矩阵:行列互换
转置矩阵性质一:矩阵转置的转置是原矩阵。
转置矩阵性质二:矩阵串接的转置,等于反向串接各个矩阵的转置。
4.逆矩阵:
概念:一个n阶方阵A称为可逆的,或者说非奇异的,如果存在一个n阶方阵B,使得
则称B是A的一个逆矩阵。A的逆矩阵记作A-1。
性质一:逆矩阵的逆矩阵是原矩阵本身。
性质二:单位矩阵的逆矩阵是它本身。
性质三:转置矩阵的逆矩阵是逆矩阵的转置。
性质四:矩阵串接相乘后的逆矩阵等于方向串接各个矩阵的逆矩阵。
5. 正交矩阵:
概念:如果一个方阵M和它的转置矩阵的乘积是单位矩阵的话,则M是正交矩阵
如果矩阵是正交的,则转置矩阵和逆矩阵是一样的。
注意:unity中矢量和矩阵相乘时,把矢量放在矩阵的右边。
习题总结:
(1)对称矩阵(Symmetric Matrices)是指元素以主对角线为对称轴对应相等的矩阵。对称矩阵的转置时它本身,因此乘以行矩阵和列矩阵不会对其造成影响
(2)单位矩阵和任何矩阵M相乘都是任何矩阵M。
(3)矩阵M,为了得到和列矩阵相乘相同的结果,在进行矩阵乘法时,对M进行转置。再相乘。
。
4.5矩阵的几何意义:变换2
线性变换:缩放,旋转。镜像,正交投影。3*3矩阵用来描述线性变换。
仿射变换,又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间。
平移,不是线性变换,而是仿射变换,因为平移满足标量乘法但是不满足矢量加法。仿射变换可以使用4*4矩阵来表示。需要把矢量扩展到四维空间下,这个空间就是齐次坐标空间。
图形学常见变换矩阵的名称和特性:
4.5.2 齐次坐标
https://blog.csdn.net/popy007//article/list/2? //关于向量的系列文章
我们3*3矩阵不能表示平移,则需要把这个这个三维矢量转换成四维矢量,也就是齐次坐标。
1表示空间的一点。0表示矢量
对于一个点,从三维坐标到齐次坐标是把w分量设为1,对于方向矢量来说,是把w分量设为0. 目的是为了
*** 对一个点进行平移,旋转,缩放都会施加于该点。而对矢量变换时会忽略平移操作。
既能够用来明确区分向量和点,同时也更易用于进行仿射(线性)几何变换。
4.5.3 分解基础矩阵
4.5.4 平移矩阵 ,左乘
点平移[tx,ty,tz]个单位
矢量平移,不变,矢量没有位置概念,只有方向和模[大小]概念。
4.5.5 缩放矩阵
下边对方向矢量进行缩放:
如果kx =ky= kz,则为同比例缩放,为统一缩放。
缩放矩阵的逆矩阵:使用原缩放系数的倒数来对点和方向矢量进行缩放:
注意:
4.5.6 旋转矩阵:
4.5.7 复合变换:平移,旋转,缩放组合起来,形成一个复杂的变换过程。正确顺序是先缩放,再旋转,再平移
由于我们使用的是列矩阵,因此是阅读顺序是从右到左。表示旋转时,则旋转矩阵是分成两种【1.unity使用的则是从左向右一次乘以旋转矩阵(因为此时旋转时坐标系是不旋转的,从左向右 2.旋转时坐标系也时跟着旋转的,才是从右向左,但是unity使用的时第一种方式】
旋转顺序:先绕z轴再x轴再绕y轴旋转。【列矩阵是从右往左读的】,而旋转,如果旋转时不一起旋转当前的坐标系,则:
4.6 空间坐标
c->p: 把子坐标空间下表示的点Ac转换到父Ap坐标空间,通过矩阵Mc->p 可以表示
p->c: 把父坐标空间下表示的点Bp转换到父Bc坐标空间,通过矩阵Mp->c 可以表示
其中Mp->c是其逆矩阵。
4.6.3 顶点的坐标变换过程。
4.6.4 模型空间
4.6.5 世界空间,
顶点变换的第一步:就是将顶点坐标从模型空间变换到世界空间,这个变换叫做模型变换。
注意: 由于我们使用的是列矩阵,因此是阅读顺序是从右到左。表示旋转时,则旋转矩阵是分成两种【1.unity使用的则是从左向右一次乘以旋转矩阵(因为此时旋转时坐标系是不旋转的,从左向右 2.旋转时坐标系也时跟着旋转的,才是从右向左,但是unity使用的时第一种方式】
4.6.6 观察空间【摄像机空间】
顶点变换的第二步:把顶点从世界空间变换到观察空间中,叫做观察变换。 平移整个观察空间,再旋转。
4.6.7 裁剪空间 :这就是判断一个点在视锥体空间得算法。
<1. 观察空间和屏幕空间不同,观察空间是3D的,屏幕空间是二维空间。观察空间到屏幕空间叫做投影。
<2. 通过齐次除法,来得到二维坐标,而投影矩阵只是在做准备。
<3. 对于视锥体投影来说,想要判断一个点是不是在视锥体是比较麻烦的,所以需要把顶点转换到一个裁剪空间中。
<4. 先了解下基本概念:
Fov(file of view):改变视锥体竖直方向的张开角度。
可以求出视锥体近裁剪和远裁剪平面的高度
纵横比:
脚本可以控制 Camera.aspect, aspect由ViewRect的W、H决定(xy坐标是摄像机渲染屏幕原点,wh是宽高)。
现在可以根据已知值来确定透视投影得投影矩阵:
我们针对的是观察空间是右手坐标系,使用列矩阵在矩阵右侧进行相乘. 可以看出此时得w分量不再是1,而是原来z分量得取反结果。(这里只是作者使用了矩阵左乘的方式,我们写的时候把这个点写成行矩阵来右乘也是可以的)
虚拟的3维坐标变换完成。裁剪投影矩阵变换后:一个顶点在视锥体,则x、y、z必须满足 在-w和w之间。
<5. 齐次除法 / 透视除法
用齐次坐标的w分量分别除以x,y,z分量。OpenGL中,把这一步叫做归一化的设备坐标(NDC).
经过透视投影变换后的裁剪空间,再经过齐次出发后会变到一个立方体内。opengl x、y、z范围是【-1,1】,而directx中是【0,1】
<6. 把这个NDC坐标范围映射到屏幕坐标,得到最终在屏幕上显示的坐标。此时z分量被用于深度缓存,clip(z) / clip(w) 值直接存进深度缓冲中。
Unity选择了OpenGl的齐次裁剪。
*******************************************************************
4.7 法线变换【法矢量】
我们不仅需要变换一个顶点,还需要变换顶点法线。以便在后续中计算光照。
切矢量: 切线空间:基于法线映射。
注意:切线、法线都是方向矢量,不会受偏移的影响。可以直接使用M(a->b)矩阵来变换顶点.
变换后的切线: T(a)和T(b)分别表示在坐标空间a和坐标空间b下的切线方向。
顶点法线:如果和切线同样的方式变换法线,则会得到错误的结果。
法线矩阵的推导:M(a->b)的逆矩阵的转置矩阵。
如果只包含旋转,则这个矩阵就是正交矩阵(正交矩阵的逆矩阵就是转置矩阵)。这时可以乘以逆矩阵(还是这个矩阵),反着变换回去。
如果是统一缩放,则这个矩阵除以统一缩放系数k,是正交矩阵。因为统一缩放会使得每一行(或者每一列)的矢量长度部位1,而是k,不符合矩阵特性,所以要除以k。
Ta点乘Na为0也就是Ta的转置乘上Na(矩阵乘法形式)为0,中间矩阵只要是单位矩阵就可以了,不是单位矩阵则必须交换,但是矩阵没有交换律。单位矩阵的转置是它本身。
4.8 Unity Shader的内置变量(数学篇) unity5.2 UnityShaderVariables.cginc
红线处,只需要再转置一下,就可以得到逆矩阵。可以把顶点又从观察空间到模型空间。
代码: 红线处,只需要再转置一下,就可以得到逆矩阵。可以把顶点又从观察空间到模型空间。
mul函数参数位置不同导致的问题:
4.8.2 摄像机和屏幕参数
4.9 答疑解惑:
CG中矩阵类型的表示: float3X3 float4X4,而对于float3或者float4的变量,我们既可以当作是矢量,又可以当作是一个行矩阵或则列矩阵,这取决于运算的种类和运算的类型。
eg ; float4 a=float4(1.0, 2.2, 3.0 ,4.0)
注意:
<1. 因为unity中的内置矩阵都是按列存储的.
unity中,自己定义的矩阵是先填充第0行,即是行矩阵,所以内置矩阵是按列矩阵来存储的。
<2.CG中我们想自己初始化一个矩阵时,是一行一行的填充矩阵的,因此CG中的库矩阵即是用列来存储的。
eg: 通常在顶点变换中,是使用矩阵右乘的。来按列矩阵进行乘法,有时候也会使用左乘的方式,因为会省去转置的操作。
我们定义的 f4x4 等类型的变量是优先按照行来填充的,给定一串数字时,优先按行。这也是CG语言的特性。
UnityShader中的转换矩阵为行矩阵。所以自己定义的是按列存储的。
4.9.3 Unity中的屏幕坐标
在顶点/片元着色器中,有两种方式获得片元的屏幕坐标。在unity中是等价的。
<1. 在片元着色器的输入中声明 VPOS / WPOS语义。 VPOS是HLSL中对屏幕坐标的语义。而WPOS是CG中对屏幕坐标的语义。
归一化到视口坐标中:
屏幕坐标只是一个坐标点
屏幕分辨率是指屏幕显示的分辨率。显示分辨率就是屏幕上显示的像素个数,分辨率160×128的意思是水平方向含有像素数为160个,垂直方向像素数128个。屏 幕尺寸一样的情况下,分辨率越高,显示效果就越精细和细腻。
(0,0)
,右上角为(1,1)
,我们在设计分屏游戏的时候可以通过设置摄像机所占据的视口空间来控制。即把屏幕坐标归一化。把片元的模型坐标转换为视口坐标。
5.2.1 顶点/片元着色器得基本结构
SubShader:
<1. 每个Unity Shader文件可以包含多个SubShader语义块,但至少要有一个。当Unity需要加载这个Unity Shader时,Unity会扫描所有的SubShader语义块,然后选择一个能够在目标平台上运行的SubShader。如果都不支持的话,Unity就会使用FallBack语义指定的Unity Shader。SubShader类似于if else 选择一个执行,找到针对显卡的SubShader。而Pass则是依次执行。
<2. SubShader中,如果没有进行任何渲染设置和标签,那么SubShader将使用默认的渲染设置和标签设置。
Unity提供这种语义的原因在于,不同的显卡具有不同的能力。一系列Pass以及可选的状态([Render Setup])、标签([Tags])设置。SubShader语义块中包含的定义一般如下.
Pass:
SubShader和Pass的状态设置和标签设置:
. 状态设置 : ShaderLab提供了一系列渲染状态的设置指令,这些指令可以设置显卡的各种状态。Pass中的状态Render Setup语法与SubShader中的相同
如果我们在SubShader中进行了状态设置,那么这些设置将会用于所有的Pass,如果某些Pass不想应用到这些设置,可以在Pass语义块中单独进行设置。pass中渲染指令设置如下表所示:
SubShader的标签Tags以“键值对”形式存在,键和值都是字符串类型。这些键值对是SubShader和渲染引擎Unity之间的桥梁,它们用来告诉Unity:SubShader希望怎样以及何时渲染这个对象。
标签结构为Tags{"TagName1"="Value1" "TagName2"="Value2"},标签类型表如下所示,这些标签仅可以用在SubShader中,不可以在Pass中声明。
Pass中的标签不同于以下标签。下图是SubShader的标签设置:
Pass中的标签类型如下:
5.2.2 模型数据从哪儿来
v包含了顶点的位置,这是由POSITION语义指定的。返回float4的变量,它是该顶点在裁剪空间中的位置。
POSITION:把模型的顶点位置填充到v中。SV_POSITION,意思是顶点着色器的输出是裁剪空间中的顶点坐标。
5.2.3 顶点着色器和片元着色器之间如何通信
注意点: 顶点着色器是逐顶点调用的,片元着色器是逐片元调用的。片元着色器的输入实际上是顶点着色器的输出进行插值后得到的结果。
5.2.4 属性
5.3.1
常见的CGInclude主要的文件以及它们的主要作用:但是有的即使不包含也能使用,下边第二个全局
5.3 Unity提供的内置文件和变量
向矩阵中传数组: https://www.xuanyusong.com/archives/4488
在UnityCG.cginc中找到。理解上述实现.
5.4 Unity提供的CG/HLSL语义
语义让shader知道从哪儿读取数据,并把数据输出到哪儿。目前unity并不支持所有的语义。
DirectX10 以后,有了一种新的语义,系统数值语义,以SV开头,SV代表的就是系统数值。
对于绝大多数平台 POSITION和SV_POISITION 是等价的,但是有的平台PS4则只能使用SV_POISITION,最好用SV开头的
5.4.2 Unity支持的语义
5.4.3如何定义复杂的变量类型
5.5 Debug
5.6 小心:渲染平台的差异
注意:如果使用抗锯齿【Quality->Anti Aliasing】,并此时使用了渲染到纹理技术。unity不会为我们反转。但是如果调用的是Graphics.Blit函数时,unity已经为我们对屏幕图像的采样坐标进行处理,我们只需要按照正常采样过程处理屏幕图像即可。如果是处理多张图,则需要判断是否在direct平台,再判断y的纹素是否<0,来进行翻转。
UNITY_UV_STARTS_AT_TOP //用于判断当前平台是否是DirectX类型的平台。
_MainTex_TexelSize.y是否小于0来检查是否开启了抗锯齿。则对采样坐标进行竖值方向的反转。
5.6.2 Shader的语法差异 【DIRECT/OPENGL】
float v=float(0,0); [在OpenGL中是合法的,返回一个4个分量都是0.0的float4类型,但是在Direct11中报错]
表面着色器out参数需要初始化,则需要UNITY_INITIALIZE_OUTPUT(Input,0)
Direct9/11中不支持顶点着色器使用tex2D函数。而tex2d需要这样的偏导信息。使用tex2Dlod函数代替。我们还需要添加
#pragma target3.0 ,因为是3.0的特性
5.6.3 Shadre的语法差异
更多平台差异造成的错误:到官网中查找
5.7 Shader 整洁之道
PC上都是按照最高精度float来计算,而在移动平台。fixed在比较旧的移动平台用,大多数都是把half和fixed当成同精度来处理。half来处理
5.7.3. 避免不必要的计算
注意:所有的OpenGL的平台(包括移动平台)被当成是支持到shader Modle.3.0的,而WP8/WINRT平台则只支持Shader Model 2.0.
5.7.4 慎用分支和循环语句。
因为:GPU对分支语句的处理和CPU不同,最坏情况下,一个分支语句的运行时间等于运行了所有的分支语句的时间,会降低并性操作。使得性能成倍下降。
解决方案:把片元着色器的计算放到顶点着色器,或者预计算在cpu。必须使用时,分支判断条件最好是常数,在分支中包含的操作指令越少越好,嵌套越少越好。
5.7.5 不要除以0
对可能是0的情况,强制截取到非0的范围。或者用if来判断
6.1
辐射度:用来量化光,光源的方向是L,对于平行光来说,计算垂直于L的单位面积上的单位时间内穿过的能量来得到。
但是实际中,L并不是垂直于物体表面的,所以这个垂直表面可以通过计算光源方向L和表面法线n之间的夹角的余旋值来得到
【注意:这里光源的方向模场都为1】
【说明】:角度越大,cos@越小,辐射强度越弱。cos@和辐射强度成正比。
点积就是投影,则L在法线N上的投影
6.1.2 吸收和散射
高光反射:物体表面如何反射光线
漫反射:表示多少光线被反射和散射出表面。
出射度:根据入射的光线的数量和方向,计算出射光线的数量和方向。通常使用出射度描述。
6.1.3 着色:
着色指的是,根据材质属性(漫反射等)、光源信息(光源方向,辐射度),使用一个等式去计算沿某个观察方向的出射度的过程。
这个等式叫做光照模型。不同的光照模型有不同的作用,例如描述金属表面和粗糙表面。
6.1.4 BRDF【径向分布函数】光照模型 ,基于物体
BRDF主要描述光照射到不透明物体上后,光的反射情况。当给出入射光线的方向和辐射强度后,计算反射光的能量分布。
6.2 标准光照模型
标准光照模型: 只关心直接光照,也就是那些直接从光源反射出来照射到物体表面后,经过物体表面的一次反射后进入摄像机的光线。如果不是全局光照,则自发光自会照亮周围的物体,而只是本身看起来更亮了而已。
基本方法:把进入到摄像机的光线分为4部分,每个部分使用一种方法来计算它的贡献度。自发光、高光反射、漫反射、环境光。
自发光部分【Cmissive】:给定方向时,表面本身会向该方向发射多少辐射量。,直接使用该材质的自发光颜色。
高光反射[C specular]: 描述光线从光源照射到模型表面时。该表面会在完全镜面反射方向散射多少辐射量。
漫反射(diffuse):照到模型表面后,该表面向每个方向散射多少辐射量。
环境光(ambient):其他所有的间接光。
6.2.3 漫反射
漫反射中视角的位置是不重要的,因为反射是完全随机的。但是入射光线的角度是比较重要的,
漫反射符合兰伯特定律: 通俗来解释公式,光源颜色*材质的漫反射颜色*(表面法向量*指向光源的单位矢量)。
6.2.4 高光反射
是一个经验模型,用来计算完全镜面反射方向被反射的光线,这可以让物体看起来有光泽,例如金属材质。
需要知道的信息比较多,表面法线、视角方向、光源方向、反射方向等。下面计算看做是单位向量
而反射可以通过其他信息计算得到:
Phong模型计算高光反射:
Blinn模型: 对视角向量和光源向量取平均。也是经验模型。当摄像机和光源足够远时,模型就看成一个没有体积的质点。可以看成是一个定值
漫反射: 表面散射多少辐射量
环境光: 其他所有的间接光照
6.2.5 逐像素还是逐顶点
在顶点着色器中计算的,就是逐顶点 在片元中的是逐片元
Phong着色:以像素为基础,得到法线。 面片之间对顶点法线进行插值的计算称为Phong着色。
高洛德着色: 逐顶点光照,在每个顶点计算光照,然后再每个渲染图元内部进行插值。
6.3 Unity中的环境光
6.4 Unity中实现漫反射光照模型
staturate(x): 用于防止数位负数。x可以是float float2 float3等类型。把x截取到【0,1】范围内。如果x是矢量,则分别对每个分量进行截取。
- // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
- // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
-
- //***********************实现一个逐顶点的漫反射光照*********************//
- Shader "Custom/DiffuseVertexLeve"
- {
- Properties {
- _Diffuse ("_Diffuse", Color) = (1,1,1,1)
- }
- SubShader
- {
- Pass
- {
- //只有定了正确的光照LightModle ,才能使用Unity的内置光照变量,eg:_LightcColor0
- Tags{"LightMode"="ForwardBase"}
- CGPROGRAM
-
- #pragma vertex vert
- #pragma fragment frag
- #include "Lighting.cginc" //还需要包含unity的内置文件Lighting.cginc
- fixed4 _Diffuse; //用来使用propert中定义的_Diffuse
- //定义顶点着色器的输入
- struct a2v
- {
- float4 vertex : POSITION;
- float4 normal : NORMAL; //NORMAL语义告诉unity要把模型顶点的法线数据存储到normal中。
- };
- //定义片段着色器的输入
- struct v2f
- {
- float4 pos: SV_POSITION;
- fixed3 color :COLOR; //为了把顶点着色器得到的颜色信息传给片元着色器
- };
-
- v2f vert(a2v v)
- {
- v2f o;
- //顶底着色器最基本的任务是把顶点位置从模型空间转换到其次裁剪空间
- o.pos=UnityObjectToClipPos(v.vertex);
- //使用unity内置的宏来得到环境光部分
- fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
-
- //开始计算漫反射
-
- //计算表面法线
- fixed3 worldNormal=normalize(mul(v.normal,(float3x3)unity_WorldToObject));
-
- //光源方向可以由_WorldSpaceLightPos0 来得到【只适用于一个光源,且光源的类型是平行光】
- fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
-
-
- //漫反射 【计算公式6.23.】 光源的颜色和强度信息 * 材质的漫反射颜色* max(0,n*l)[cos@这是辐照度,使用点积求辐照度]
- //_LightColor0来访问该Pass处理的光源的颜色和强度信息
- fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal,worldLight)); //计算漫反射
-
- o.color = ambient+diffuse;
- return o;
- }
- fixed4 frag(v2f i):SV_Target
- {
- return fixed4(i.color,1.0);
-
- }
- ENDCG
- }
- }
- FallBack "Diffuse"
-
- }
注意点:
5)unity提供
逐像素漫反射
- // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
-
- // Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
-
- // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
- // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
-
- //***********************实现一个 逐像素 的漫反射光照, 比片段着色器更加平滑*********************//
- Shader "Custom/DiffusePixelLevel"
- {
- Properties {
- _Diffuse ("_Diffuse", Color) = (1,1,1,1)
- }
- SubShader
- {
- Pass
- {
- //只有定了正确的光照LightModle ,才能使用Unity的内置光照变量,eg:_LightcColor0
- Tags{"LightMode"="ForwardBase"}
- CGPROGRAM
-
- #pragma vertex vert
- #pragma fragment frag
- #include "Lighting.cginc" //还需要包含unity的内置文件Lighting.cginc
- fixed4 _Diffuse; //用来使用propert中定义的_Diffuse
- //定义顶点着色器的输入
- struct a2v
- {
- float4 vertex : POSITION;
- float4 normal : NORMAL; //NORMAL语义告诉unity要把模型顶点的法线数据存储到normal中。
- };
- //定义片段着色器的输入
- struct v2f
- {
- float4 pos: SV_POSITION;
- fixed3 worldNormal :TEXCOORD0; //为了把顶点着色器得到的颜色信息传给片元着色器
- };
-
- //顶点着色器不需要计算光照,只需把世界空间下得法线穿给片元着色器就可以
- v2f vert(a2v v)
- {
- v2f o;
- o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
- //把法线从物体空间到世界空间
- o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
- return o;
- }
- fixed4 frag(v2f i):SV_Target
- {
- //获取环境光
- fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
- //归一化法向量
- fixed3 worldNormal=normalize(i.worldNormal);
- //光源方向 可以由_WorldSpaceLightPos0 来得到【只适用于一个光源,且光源的类型是平行光】
- fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
-
- //漫反射 【计算公式6.23.】 光源的颜色和强度信息 * 材质的漫反射颜色* max(0,n*l)[cos@这是辐照度,使用点积求辐照度]
- //_LightColor0来访问该Pass处理的光源的颜色和强度信息
- fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal,worldLightDir)); //计算漫反射
-
-
- fixed3 color = ambient+diffuse;
- return fixed4(color,1.0);
-
- }
- ENDCG
- }
- }
- FallBack "Diffuse"
-
- }
逐像素光照虽然比逐顶点光滑,但是在光线照射不到的区域是全黑的,可以加环境光来得到非全黑的效果。但是无法解决背面光照明暗的缺点。为此才有了兰伯特模型。
6.4.3 半兰伯特模型
就是把公式中的点积范围从(-1,1)映射到(0,1)的范围。半兰伯特模型没有物理依据,只是为了效果上的更好的展示
6.5 在UnityShader中实现高光反射光照模型
reflect(i,n) i是入射方向 n是法线方向 当给定入射方向i和法线方向n时,reflect函数可以返回反射方向。
6.5.1 实践:
1》逐顶点光照的高光反射效果
- // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
-
- // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
- // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
- //***********************实现一个 逐顶点的高光光照,*********************//
- Shader "Custom/SpecluarVertexLevel" {
- Properties {
- _Diffuse ("Diffuse", Color) = (1,1,1,1)
- _Specluar("Specluar",Color)=(1,1,1,1) //控制高光区域的颜色
- _Gloss("Gloss",Range(8.0,256))=20 //控制高光区域的大小
- }
- SubShader
- {
- Pass
- {
- Tags{"LightMode"="ForwardBase"} //定义正确的LightMode才能使用Unity的内置光照变量
-
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
- #include "Lighting.cginc" //为了使用Unity的内置光源 eg:_LightColor0来访问该Pass处理的光源的颜色和强度信息
-
- fixed4 _Diffuse; //因为颜色的值域是0-1,因此用ifxed4来存储
- fixed4 _Specluar;
- fixed4 _Gloss; //范围很大,因此用float
-
- struct a2v
- {
- float4 vertex:POSITION;
- float3 normal:NORMAL;
- };
-
- struct v2f
- {
- float4 pos:SV_POSITION;
- float3 color:COLOR;
- };
-
- v2f vert(a2v v)
- {
- v2f o;
-
- //把顶点坐标转到齐次坐标
- o.pos=UnityObjectToClipPos(v.vertex);
- //获取环境光
- fixed3 ambient =UNITY_LIGHTMODEL_AMBIENT.xyz;
- //把法线从模型空间转换到世界空间
- fixed3 worldNormal=normalize(mul((float3x3)unity_ObjectToWorld,v.normal));
- //获取世界空间中光源方向 [ 可以由_WorldSpaceLightPos0 来得到【只适用于一个光源,且光源的类型是平行光】
- fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
-
- fixed3 diffuse=_LightColor0*_Diffuse*saturate(dot(worldLightDir, worldNormal));
- //在世界空间中的反射方向 是由光源指向交点处的,因此需要对worldLightDir取反后再传给reflect函数
- // 【从图中可以看出 书中描述的光源方向是由顶点指向光的 视角v也是由顶点指向光源的 】
- fixed3 reflectDir=normalize(reflect(-worldLightDir,worldNormal));
-
- //获取世界空间的视角方向
- fixed3 viewDir=normalize(_WorldSpaceCameraPos.xyz-mul(unity_ObjectToWorld,v.vertex));
-
- //pow(x, y): x的y次方
- fixed3 specluar=_LightColor0.rgb*_Specluar.rgb*pow(saturate(dot(reflectDir,viewDir)),_Gloss);
- o.color=ambient+diffuse+specluar;
- return o;
- }
-
- fixed4 frag(v2f i):SV_Target
- {
- return fixed4(i.color,1.0);
- }
- ENDCG
- }
- }
- FallBack "Diffuse"
- }
总结常用变量数据类型:
1>a2v()
【float4 vertex : POSITION】 【float3 normal : NORMAL】
2>v2f()
【float4 pos : SV_POSITION】 【float color】
[flxed4 _Diffuse] [fixed4 _Specluar] [fixed4 _Gloss]
[fixed3 ambient] [fixed3 worldNormal] [fixed3 diffuse] [fixed3 reflectDir] [fixed3 specluar]
1.概述: 纹理映射,逐纹素(为了区分像素)。
uv: 纹理映射坐标。
注意: 纹理采样时使用的纹理坐标不一定是在【0,1】范围内。与之相关的就是纹理的平铺模式。
<2. //纹理属性的申明:名字不是任意取的,纹理名_ST(Scale.Translation)
//_MainTex_ST.xy 存储的是缩放值【Tiling】 _MainTex_ST.zw 存储的是偏移值【offset】,调整的是图片的偏移和缩放。
float4 _MainTex_ST;
<3. TRANSFORM_TEX函数【先使用_MainTex_SV.xy进行缩放 再_MainTex_SV.zw平移 】
//o.uv=TRANSFORM_TEX(v.texcoord,_MainTex); 【纹理顶点坐标,纹理名】
<4. tex2D(需要被采样的纹理,float2类型的纹理坐标) ==》返回计算得到的纹理素;
<5. albedo[反照率]=采样结果*_Color.rgb.
7.1.2 纹理的属性
<1.贴图导入设置: FilterMode 提供了三种滤波模式,Point ,Bilinear,Trilinear 【耗费性能依次增大】,会影响图片放大和缩小时的图片质量。Point 当图片所在物体变大时会变得模糊。【左point,右Trilinear】
<2.
Point采像素只有一个.
Bilinear有点像素风格iner邻近四个点插值得到,有点模糊效果 .
Trilinear邻近四个点插值,有点模糊效果,同时又进行了多级纹理混合.
<3.纹理使用了非2的幂次,则不能压缩,内存增大,GPU读取慢。有些平台不支持读取。
目的:使用一张纹理改变模型表面的法线。没有真的改变顶点位置
<7.2.1高度纹理【高度映射】,高度纹理中存储的是强度值
,
颜色深向外凸,颜色浅则凹,得到一个修改后的法线值。优点是看起来直观,缺点是计算复杂不能直接得到表面法线
7.2.2 法线纹理
法线纹理直接存储得就是表面的法线方向 .由于法线分量范围是【-1,1】,而像素的分量是【0,1】,即把法线结果映射到像素值。
pixel=(normal+1)*0.5f;
所以在shader中对法线纹理进行采样后,需要对结果进行一次反映射得过程,得到原来的法线方向。
模型空间的法线纹理 和 模型顶点的切线空间
模型空间的法线纹理: 每个法线都在模型空间,每个点存储的法线方向都是各异的。所以映射后是五颜六色的。7.13左图。
模型顶点的切线空间: 每个法线方向所在的空间都是不一样的,即表面每点各自的切线空间。存储了法线在各自空间的法线扰动情况。大多数的坐标是一样的,即(0,0,1)映射后是(0.5,0.5,1)浅蓝色。7.13右图。
注意: 如果一个变换只存在平移和旋转变换,那么这个变换矩阵的逆矩阵就是它的转置矩阵。【模型到切线空间 】
法线纹理中存储的法线方向所在的空间: 模型顶点自带的法线存储在一张纹理中,即模型空间的法线纹理
模型顶点的切线空间的法线纹理: 切线空间是由顶点法线和切线构建出得一个坐标空间,因此我们需要得到顶点得切线信息。
TANGENT语义告诉unity把顶点得切线方向填充到tengent变量中。tengent得类型是float4,因为需要从tangent.w分量来决定切线空间中的第三个坐标轴--副切线得方向性。
<1. 在切线空间中计算光照模型
《1. 大概思路:把其他坐标转换到切线空间,在片元着色器中进行纹理采样,得到切线空间下的法线,再和切线空间下的视角,光照方向等进行计算得到最终的光照结果。
《2. 计算:
(1)传进来第一张纹理
(2)副切线计算:
(3)顶点从模型空间到切线空间:
<==> 构建过程用下边宏代替。
(4)光照方向和视角方向转换到切线空间:
(5)片段中的计算
把纹理中存储的像素值映射回法线反向。获取正确的法线方向。
为了方便Unity对法线纹理进行优化,我们将纹理标识为Normal Map,unity回根据不同的平台选择不同的压缩方法。
<2. 在世界空间中计算光照模型
把采样得到的法线防线转换到世界空间。
7.2.4 UNITY中得法线纹理类型
<1.UppackNormal 采样法线方向
7.3 渐变纹理
注意:把渐变纹理得Wrap mode设置为clamp,为了防止浮点数精度造成得问题。当fixed2(halfLambert,halfLambert)halfLambert得值理论上是【0,1】,但是可能回出现1.0001的情况,Repeat会舍弃整数部分。
如下图
概述:
7.4.1 实践遮罩纹理
效果如下:更加精细的控制光照细节
Unity中的透明效果实现方式2种: 透明度测试【Alpha Test,这个是开深度缓冲的,要不完全透明要不显示】和透明度混合【Alpha Blending,这个是关深度写入,但是不关闭深度测试】。
深读缓冲【z-buffer】: 用于解决可见性问题的。 把物体距离摄像机的值Sc和该物体已经存在深度缓冲的值进行比较,如果大于,则显示,且把新值更新到深度缓冲中【前提是开了深度测试】。如果是使用透明混合时,我们关闭了深度写入不关闭深度测试。
8.1
为什么透明度混合需要关闭深度写入?
因为我们半透明物体后边还需要能看到后边的物体,前面的半透明物体深度写入是关的,深度缓冲中没有数据,此时渲染后的时候,发现深度缓冲中没有数据,则会放心的把数据写入进去,倒是前面的半透明物体看不到了。
半透明物体渲染顺序:
8.4 透明度混合Blend
9.1 Unity的渲染路径
如果Cameraz中设置了新的渲染路径,则会覆盖Projectting中的设置。如果平台不支持该渲染路径,则会过度到低一级别的渲染路径。如果不指定渲染路径则会默认当成一个和顶点照明渲染路径等同的Pass;
设置渲染路径,则告诉unity准备好渲染流程所需要的数据。
9.1.1前向渲染路径【ForwordBase】
原理: 先确认深度缓冲中的片元是否可见,如果可见再更新颜色缓冲区的颜色值。 pass通道数=N个物体*M个光源的影响
光照组件中设置RednerMode为Important,则代表此光源使用逐像素光照. Auto ,Unity会在背后根据重要程度(这个光源很远对模型影响很小等)为我们判断哪些光源会按逐像素处理,而哪些是按逐顶点或者SH(球谐函数) 的方式处理
延迟渲染: 数据是存储在G缓冲区中,数据复杂度和屏幕大小有关. 再通过第二个Pass来统计最终得光照颜色
9.2 Unity的光源类型
9.3 Unity的光照衰减
使用查找表【LOOK UP Table】,只需要用一个参数再纹理进行采样即可。
衰减: 物体离开光源照明的范围时会发生突变。这是因为,如果物体不在该光源的照明范围内,unity就不会为物体执行一个Addtionnal Pass.
9.4 Unity中的阴影
Shadow Map【阴影映射纹理】: 技术原理,它会首先把摄像机的位置放到和光源重合的位置上,那么场景种该光源的阴影区域就是那些摄像机看不到的地方。记录了从该光源的位置出发,能看到的场景中距离它最近的表面位置(深度信息) UNITY用的就是此技术。设置LightMode为ShadowCaster,时的pass,来渲染阴影贴图。
计算流程: unity选择额外的PASS来更新阴影,这个Pass就是LightMode标签被设置为ShadowCaster的Pass.
屏幕空间的阴影映射技术需要显卡支持MRT,有的移动平台不支持。
9.4.2 不透明物体的阴影
案例: 215页
立方体纹理:
天空盒制作: 注意点,WrapMode设置为Clamp,反之在衔接处出现不匹配的情况。
10.1.4 折射
菲尼尔折射
10.2 渲染纹理
渲染目标纹理【RTT】: 把整个三维场景渲染倒一个中间缓冲种区,即渲染目标纹理。
多重渲染目标纹理【Multiple Render Target , MRT】: 把场景透视渲染倒多个纹理种,而不需要为每个纹理渲染完整的场景,eg延迟渲染
eg:模拟玻璃效果 P220
10.3 程序纹理 : 由程序计算生成的纹理
10.3.2 Unity的程序材质 : 使用程序纹理的材质
Substance Designer软件 在unity外部生成的。
11.1 UnityShader中的内置变量(时间篇)
11.2 纹理动画
应用: 使用纹理动画代替复杂的粒子系统等模拟各种动画效果。
eg: Unity种实现序列帧动画 P232
11.2.2 滚动的背景
eg: P233
11.3 顶点动画
11.3.1 流动的河流
eg: P234
11.3.2 广告牌
技术:会根据视角方向来旋转一个被纹理着色的多边形,使得多边形总是面对摄像机 应用:渲染烟雾 云朵 闪光效果
eg: P237
顶点动画的注意事项:
1>. 批处理会破坏这种动画效果。
第12章 屏幕后处理效果
由于在图像处理中我们常常需要使用一个pass的处理结果作为另一个pass的输入,这个时候就需要用到OnRenderImage()
OnRenderImage在所有不透明核透明的pass执行完毕后调用,以便对场景中都产生影响。有时不希望对不透明的物体调用这个(pass<=2500像内置的Background,Geometry,AlphaTest渲染队列均在此范围)在此函数前加ImageEffectOpaque属性来实现。
背景是先渲染,然后一层一层的渲染。
ZTest Always Cull Off ZWrite Off //对于屏幕后处理shader 为了防止它挡住后边被渲染的物体 关闭深度写入.
边缘检测的原理:利用边缘检测算子对图像进行卷积操作。
卷积: 卷积操作指的是使用一个卷积核对一张图像中的每个像素进行一系列操作。
卷积核:是一个四方形网格结构(2X2,3X3,opencv api中发现也可以不是方形结构),该区域内每个方格都有一个权重值,当对图像中的某个像素进行卷积时,我们把卷积核的中心放到这个像素上,顺时针旋转180度,依次计算核中每个元素和其覆盖的图像像素值的乘积再求和。得到该位置(卷积核覆盖的中心位置)的像素值。
https://blog.csdn.net/lucky_yw/article/details/80077443 卷积翻转方式。
像素值: 是原稿图像被数字化时由计算机赋予的值,它代表了原稿某一小方块的平均亮度信息. eg:亮度公式: 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b; 【指定的系数】
只基于颜色图像的边缘检测会收到光照和物体纹理等法线影响。更好的办法是对深度纹理和法线纹理进行边缘检测。下章。
梯度:相邻像素的差值用梯度表示。边缘处的梯度绝对值会比较大。
基于梯度的不同,有以下几种不同的边缘检测算子被先后提出来。
对每个像素卷积得到,Gx1 [第一个像素的x方向的梯度值] Gy1 [第一个像素的y方向的梯度值]
--->卷积后得到两个方向的梯度值:---> 再计算整体的梯度值
--->考虑开根的性能问题,用绝对值代替
--->根据G(梯度值)来判断是否式边缘点。
但是如果只是使用颜色进行边缘检测,则不准,会受到纹理和阴影的影响。因此一般使用屏幕的法线纹理和深度纹理。
- Shader "Unity Shaders Book/Chapter 12/Edge Detection" {
- Properties {
- _MainTex ("Base (RGB)", 2D) = "white" {}
- _EdgeOnly ("Edge Only", Float) = 1.0
- _EdgeColor ("Edge Color", Color) = (0, 0, 0, 1)
- _BackgroundColor ("Background Color", Color) = (1, 1, 1, 1)
- }
- SubShader {
- Pass {
- ZTest Always Cull Off ZWrite Off
-
- CGPROGRAM
-
- #include "UnityCG.cginc"
-
- #pragma vertex vert
- #pragma fragment fragSobel
-
- sampler2D _MainTex;
- //xxx_TexelSize提供的访问xxx对应的每个纹素的大小【512*512,纹素大小是1/512】
- //因为卷积需要对相邻区域内的纹理进行采样,因此需要利用_MainTex_TexelSize来计算各个相邻区域的纹理坐标
-
- uniform half4 _MainTex_TexelSize;
- fixed _EdgeOnly;
- fixed4 _EdgeColor;
- fixed4 _BackgroundColor;
-
- //计算边缘检测时需要用到的纹理坐标
- struct v2f
- {
- float4 pos : SV_POSITION;
- half2 uv[9] : TEXCOORD0;
- };
-
- v2f vert(appdata_img v) {
- v2f o;
- o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
-
- half2 uv = v.texcoord;
- //对应Sobel算子采样是时所需要的9个领域纹理坐标(此处用的并不是算子)。因为都是线性的所以可以从片元移到顶点着色器
- o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
- o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
- o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
- o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
- o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
- o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
- o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
- o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
- o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);
-
- return o;
- }
-
- //计算亮度
- fixed luminance(fixed4 color)
- {
- return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
- }
-
- half Sobel(v2f i)
- {
- //定义水平方向和垂直方向使用的卷积核
- const half Gx[9] = {-1, 0, 1,
- -2, 0, 2,
- -1, 0, 1};
- const half Gy[9] = {-1, -2, -1,
- 0, 0, 0,
- 1, 2, 1};
-
- half texColor;
- half edgeX = 0;
- half edgeY = 0;
- for (int it = 0; it < 9; it++)
- {
- //对9个像素进行采样
- texColor = luminance(tex2D(_MainTex, i.uv[it]));
- //和卷积核对应的权重相乘,叠加到各自的梯度值上
- edgeX += texColor * Gx[it];
- edgeY += texColor * Gy[it];
- }
- //减去水平核垂直方向的梯度值,edge越小越可能是一个边缘点
- half edge = 1 - abs(edgeX) - abs(edgeY);
-
- return edge;
- }
- //片元着色器
- fixed4 fragSobel(v2f i) : SV_Target
- {
- //Sobel()函数计算当前像素的梯度值 ,当前的顶点位置和周围的9个uv坐标
- half edge = Sobel(i);
-
- //利用edge值,计算背景是原图下的颜色值
- fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge);
- //利用edge值,计算背景是纯色下的颜色值
- fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);
- //得到最终的像素值
- return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
- }
-
- ENDCG
- }
- }
- FallBack Off
- }
12.3.2 常见的边缘检测算子
12.3.3 实现描边
eg: P250,也可以对自定义图片描边。或者模型贴图描边。
均值模糊:它使用的卷积核中的各个元素值都相等,且相加等于1,也就是说,卷积后的像素值是相邻区域内各个像素值的平均值。
中值模糊:相邻区域内对所有像素排序后的中值替换掉原颜色。
高级的方法:高斯模糊
也是卷积,也使用卷积核,这个卷积核是叫高斯核,每个元素都是基于高斯方程:
-->高斯方程很好的模拟了邻域每个像素对当前处理像素的影响程度,距离越近,影响越大。
-->高斯核的维数越高,模糊程度越大,N*N的高斯核进行卷积滤波,则需要N*N*W*H【W和H式图像的宽高】次样。
-->由于计算量很大,所以把二维高斯核拆成两个一维高斯核进行滤波。效果一样,但是采样次数减少2*N*W*H. 把y和x方向的权重分别相加。
-->两个一维中包含重复的权重,所以只需要记录3个权重。
-->第一个pass是竖直方向的一维高斯核滤波。第二个pass使用水平高斯核滤波。
-->控制图像缩放,控制滤波次数。控制模糊程度。
uniform half4 _MainTex_TexelSize: 计算相邻像素得纹理偏移量
//xxx_TexelSize提供的访问xxx对应的每个纹素的大小【512*512,纹素大小是1/512】
//因为卷积需要对相邻区域内的纹理进行采样,因此需要利用_MainTex_TexelSize来计算各个相邻区域的纹理坐标
高斯模糊的优点: 很好的模拟了相邻每个像素对当前处理像素的影响程度,距离越近越大
p:254
12.5 Bloom效果: 让画面中较亮的区域“扩散”倒周围的区域。造成一种朦胧的效果。
实现原理: 根据阈值取出图像中较亮的区域。存储在一张纹理中,再利用高斯模糊对这张渲染纹理进行模糊处理,模拟光线扩散的效果。最后于原图像混合,得到最终的效果。可以对自定义图片描边。或者模型贴图描边。
//UsePass 语义指明他们的pass名来实现的,由于unity内部会把所有的pass的Name转成大写字母表示,因此使用时需要大写。
eg: UsePass "Unity Shaders Book/Chapter 12/Gaussian Blur/GAUSSIAN_BLUR_VERTICAL"
由于unity不存在曝光这一功能,使得产生的图像总是棱角分明,缺少运动模糊。
实现方法:
速度缓存 【缓存速度,利用速度决定模糊的大小和方向】
累计缓存:不断地把当前的渲染结果累加到上一帧的渲染图像中。
texture.MarkRestoreExpected(); // 恢复操作发生在 【渲染到纹理而没有被提前清空和销毁的情况下恢复,就是上一帧的图像】
ColorMask R //指定渲染结果的输出通道,而不是通常的 RGBA 四个通道都被写入,写入R通道. ColorMaskR 条件下 frag计算结果由(1,1,1)变为(1,0,0)
Blend SrcAlpha OneMinusSrcAlpha// 源颜色 x 源通道 + 目标颜色 x(1 - 源通道)
详见博文 https://mp.csdn.net/postedit/90513569
13.1 获取深度和法线纹理
camera.depthTextureMode=DepthTextureMode.Depth; 获取深度纹理
float d=SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv) //通过此宏对深度纹理进行采样
13.2 再谈运动模糊
13.3全局雾效Fog : 基于屏幕后处理的全局雾效
13.4 再谈边缘检测
再深度和法线纹理上进行边缘检测,实现描边效果
如果场景中某个物体不需要描边:则需要使用unity提供的Graphice.DrawMesh把需要描边的物体再渲染一次,然后再使用本届提到的边缘检测算法计算深度和法线纹理中每个像素的梯度值。判断他们是否小于某个阀值,如果是则使用clip()函数将像素剔除掉。显示原本颜色
素描风格的渲染:
14.1 卡通风格
P293
15.1 消融效果
原理: 噪声纹理+透明度测试。对噪声纹理进行采样的结果和某个控制消融程度的阀值比较,如果小于阀值,就是用clip函数把对应的像素裁剪掉。镂空区域边缘的烧焦效果则是这两种颜色的混合,再用pow(x,y)[x的y次方]函数处理后,与原纹理颜色混合的效果。
注意点:
#pragma multi_compile_fwdbase //可以保证我们在Shader中使用光照衰减等,光照变量可以被正确赋值。这是不可缺少的。
Cull Off //消融会导致模型内部裸露,所以需要双面显.
1.#pragma multi_compile_fwdbase是unity内置的用于前向渲染的关键字快捷方式,它包含了前向渲染光照计算需要的大多数关键字,因此会被shader带来很多的变体。所以我们在通过shader实现一些效果时,一定要谨慎使用这种内置的快捷方式,它会给shader带来大量的变体,造成量大又不必要的浪费。
2.Cull关闭正面剔除,因为消融会导致内部裸露,两面都得渲染。
3.使用透明度测试的物体的阴影需要特别处理,如果使用普通的阴影Pass,那么被剔除的区域任然会向其他物体投射阴影,造成穿帮。
4.用于投射阴影的Pass的LightMode需要被设置为ShadowCaster,同时还需要使用 #pragma multi_compile_shadowcaster指明它需要的编译指令。
5.定义阴影投射时需要的各种变量,V2F_SHADOW_CASTER。 TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)填充V2F_SHADOW_CASTER在背后声明的变量。
6. unity会完成阴影投射的部分,把结果输出到深度图和阴影映射纹理中。 SHADOW_CASTER_FRAGMENT(i)
7.宏需要需要输入一些特定的输入变量,因此我们需要保证提供了这些变量。
eg: TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)会使用名称v作为输入结构体。v中包含顶点位置v.vertex和v.normal.
- Shader "Unity Shaders Book/Chapter 15/Dissolve" {
- Properties
- {
-
- _BurnAmount ("Burn Amount", Range(0.0, 1.0)) = 0.0
- //模拟烧焦时的线宽,值越大则火焰边缘的蔓延的范围越广
- _LineWidth("Burn Line Width", Range(0.0, 0.2)) = 0.1
- _MainTex ("Base (RGB)", 2D) = "white" {}
- //物体得法线纹理
- _BumpMap ("Normal Map", 2D) = "bump" {}
- //对应了火焰边缘的两种颜色值
- _BurnFirstColor("Burn First Color", Color) = (1, 0, 0, 1)
- _BurnSecondColor("Burn Second Color", Color) = (1, 0, 0, 1)
- //噪声纹理
- _BurnMap("Burn Map", 2D) = "white"{}
- }
- SubShader {
- Tags { "RenderType"="Opaque" "Queue"="Geometry"}
-
- Pass {
- Tags { "LightMode"="ForwardBase" }
-
- Cull Off //消融会导致模型内部裸露,所以需要双面显示
-
- CGPROGRAM
-
- #include "Lighting.cginc"
- #include "AutoLight.cginc"
- //可以保证我们在Shader中使用光照衰减等
- //光照变量可以被正确赋值。这是不可缺少的
- #pragma multi_compile_fwdbase
-
- #pragma vertex vert
- #pragma fragment frag
-
- fixed _BurnAmount;
- fixed _LineWidth;
- sampler2D _MainTex;
- sampler2D _BumpMap;
- fixed4 _BurnFirstColor;
- fixed4 _BurnSecondColor;
- sampler2D _BurnMap;
-
- float4 _MainTex_ST;
- float4 _BumpMap_ST;
- float4 _BurnMap_ST;
-
- struct a2v {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- float4 tangent : TANGENT;
- float4 texcoord : TEXCOORD0;
- };
-
- struct v2f {
- float4 pos : SV_POSITION;
- float2 uvMainTex : TEXCOORD0;
- float2 uvBumpMap : TEXCOORD1;
- float2 uvBurnMap : TEXCOORD2;
- float3 lightDir : TEXCOORD3;
- float3 worldPos : TEXCOORD4;
- SHADOW_COORDS(5)
- };
-
- v2f vert(a2v v) {
- v2f o;
- o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
-
- //TRANSFORM_TEX 计算三张纹理对应的纹理坐标
- o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
- o.uvBumpMap = TRANSFORM_TEX(v.texcoord, _BumpMap);
- o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap);
-
- //为了得到阴影信息,计算世界空间下的顶点位置和阴影纹理的采样坐标
- TANGENT_SPACE_ROTATION;
- o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
-
- o.worldPos = mul(_Object2World, v.vertex).xyz;
-
- TRANSFER_SHADOW(o);
-
- return o;
- }
-
- fixed4 frag(v2f i) : SV_Target {
- fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
-
- //结果<0时会被剔除
- clip(burn.r - _BurnAmount);
-
- float3 tangentLightDir = normalize(i.lightDir);
- fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uvBumpMap));
-
- fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb;
- fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
- fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
- //smoothstep计算混合系数t,t=1时表明该像素位于消融的边界值,t=0时该像素为模型的正常颜色,中间插值为消融的过程
- fixed t = 1 - smoothstep(0.0, _LineWidth, burn.r - _BurnAmount);
- //用t来混合两种颜色
- fixed3 burnColor = lerp(_BurnFirstColor, _BurnSecondColor, t);
- //再处理
- burnColor = pow(burnColor, 5);
- //此时得到的atten就是综合了衰减和阴影的结果,可以用其来作为衰减和阴影的因子与最终的光照颜色相乘。
- UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
- // step(a,b)当a<=b时返回1,否则返回0
- fixed3 finalColor = lerp(ambient + diffuse * atten, burnColor, t * step(0.0001, _BurnAmount));
-
- return fixed4(finalColor, 1);
- }
-
- ENDCG
- }
-
- // Pass to render object as a shadow caster
- Pass
- {
-
- //用于投射阴影的Pass的LightMode需要被设置为ShadowCaster,同时还需要使用 #pragma multi_compile_shadowcaster指明它需要的编译指令
- Tags { "LightMode" = "ShadowCaster" }
-
- CGPROGRAM
-
- #pragma vertex vert
- #pragma fragment frag
-
- #pragma multi_compile_shadowcaster
-
- #include "UnityCG.cginc"
-
- fixed _BurnAmount;
- sampler2D _BurnMap;
- float4 _BurnMap_ST;
-
- struct v2f
- {
- //定义阴影投射时需要的各种变量
- V2F_SHADOW_CASTER;
- float2 uvBurnMap : TEXCOORD1;
- };
-
- v2f vert(appdata_base v) {
- v2f o;
- //填充V2F_SHADOW_CASTER在背后声明的变量
- TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
- o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap);
- return o;
- }
-
- fixed4 frag(v2f i) : SV_Target
- {
-
- fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
-
- clip(burn.r - _BurnAmount);
- //unity会完成阴影投射的部分,把结果输出到深度图和阴影映射纹理中
- SHADOW_CASTER_FRAGMENT(i)
- }
- ENDCG
- }
- }
- FallBack "Diffuse"
- }
15.2 水波效果
噪声纹理: 模拟水面时使用噪声纹理,通常被用作高度图,不断修改水面的法线方向。
水的流动: 使用和时间相关的变量对噪声纹理进行采样。得到法线后再进行正常的反射和折射计算。
菲尼尔反射:
<1. 描述一种光学现象,当光线落在物体表面时,一部分发生反射,一部分进入物体内部,发生折射或者散射。被反射得光和 入射的光存在一定比率关系,这个比率关系可以通过菲尼尔等式计算。
<2. 应用:根据视角方向控制反射程度。 eg:看脚下水中物体时清晰的,看远处睡眠物体指能看到周围的反射物体。
<3. 计算菲尼尔反射:在10.1.4节有具体的公式使用。
319
231
15.3 动态的全局雾
第16章 Unity中的渲染优化技术
渲染统计窗口
16.4.2 静态批处理
内部实现上: Unity首先把这些静态批处理变换到世界空间下(如果shader中有基于模型坐标下的运算,则不能批处理),然后为它们构建一个更大的顶点和索引缓存(占用更大的内存存储合并后的几何结构)。使用了同一个材质的,unity调用一个drawcall就可以绘制全部物体,对于不同材质的物体,静态批处理同样可以提升性能,尽管需要调用多个drawcall,但是静态批处理可以减少这些drawcall的状态切换(往往这些切换比较耗时)。
16.4.3 共享材质
如果两个材质之间只有使用的纹理不同,那么可以把这些纹理合并到一张更大的纹理中,这个更大的纹理就是图集。使用同一张纹理,同一个材质再使用不同的采样坐标进行采样。
但是有时除了纹理不同之外,不同的物体材质上还有一些微笑的参数变化,那么怎么办,可以利用网格的顶点的颜色数据来调整。Renderer.material //unity会创建一个材质的复制品破坏了批处理,使用Renderer.sharedmaterial。
16.4.4 批处理的注意事项
16.5 减少需要处理的顶点数目
16.5.1 优化几何体
(1.美工优化网格结构
(2.渲染统计窗口和美工软件上顶点不同的原因?
<1.三维软件是站在人类的角度理解顶点的(即组成几何体的每一个点就是一个单独的点),而unity是在GPU的角度计算顶点数
<2.GPU有时会把顶点拆分成几个顶点,1.为了分离纹理坐标(比如立方体的一个顶点会被拆分成四个顶点来理解), 2.产生平滑的边界。
几何体优化建议:GPU只关心顶点个数, 移除不必要的硬边和纹理衔接避免边界平滑和纹理分离
16.5.2 模型的LOD技术
16.6 减少需要处理的片元数目
减少overdraw(同一个像素被绘制了多次)
16.6.2 警惕透明物体
半透明物体由于没有开启深度写入,想要得到正确的渲染效果,就必须从后往前渲染。这就意味着半透明物体一定会造成overdraw,例如GUI,他们多视半透明。
16.6.3 减少实时光照,使用烘焙切图。
16.7节省带宽
长宽比2的整数幂,Unity5中即便会把长宽转成最近的2的整数幂,但是还是要美术制作时就处理好,防止造成不好的影响。
16.7.2 利用分辨率缩放
http://www.xuanyusong.com/archives/3205
16.8 减少计算复杂度
eg: LOD允许最大最小的裁剪距离
颜色蔓延效果:
在unity脚本中维护一个ColorBuffer数组,改变颜色。控制R,new Color(R, 0, 0)的慢慢变化。把生成的这个图片传到shader中,
纹理采样,让主纹理中从灰色过渡到主纹理本身的颜色。
当uv.r=0,时然后是灰色,部位0时才慢慢插值。
流光效果:
通过时间偏移世界坐标对flashTex进行采样,然后把流光图片的纹理叠加到主贴图中,调整比照率。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。