unity-shader相关
unity shader 相关记录
相关资料
有很多酷炫shader效果的网站 - https://www.shadertoy.com/view/XllcR4
- 卡通渲染: http://sorumi.xyz/posts/unity-toon-shader/
- Unite 2018 | 《崩坏3》:在Unity中实现高品质的卡通渲染
Unity Shader-深度相关知识总结与效果实现(LinearDepth,Reverse Z,世界坐标重建,软粒子,高度雾,运动模糊,扫描线效果) - https://blog.csdn.net/puppet_master/article/details/77489948
油管up, 有不错的shader教程 - https://www.youtube.com/channel/UCGQVeHxXnvjFlU-bnrVv-OA/playlists
OpenGL 矩阵变换(讲的太好了~!) - https://blog.csdn.net/lyx2007825/article/details/8792475
3D图形学基础 - https://zhuanlan.zhihu.com/p/27846162
计算机图形学的光学基础&BRDF公式的推导 ( 好文 ) - https://www.bilibili.com/read/cv548776/
Unity Shader学习笔记(15)立方体纹理、反射、折射、菲涅尔反射 - http://gad.qq.com/article/detail/38332 ( 底部有一系列的图形学教程 )
unity standard shader 详解 : http://geekfaner.com/unity/blog16_UnityStandardShader.html
油管上看到的很好的shader练习: Shaders Laboratory - https://www.youtube.com/channel/UCDk9-aPr8zQzwi4ylnuoJ6w
GPU Gems 官网 - https://developer.nvidia.com/gpugems/GPUGems/gpugems_pref01.html
DOOM (2016) - Graphics Study - http://www.adriancourreges.com/blog/2016/09/09/doom-2016-graphics-study/
Unity引擎HDRP高清渲染管线制作,其中首次实现了高质量的毛发效果 - https://www.bilibili.com/video/av55323450
里面很多图形书籍的总结, 非常不错. 如: GPU精粹三部曲
- Addison-Wesley出版社的《GPU GEM I~III》
- CRC Press出版社的《GPU PRO 1~7》
- Black Cat Publishing出版社的《GPU Zen》
感觉不错的仓库
尽管已经 star, 但还是记录一下哪些要看
unity 官方 后处理 - https://github.com/Unity-Technologies/PostProcessing
常用的一些shader - https://github.com/QianMo/Awesome-Unity-Shader
日常练习 - https://github.com/przemyslawzaworski/Unity3D-CG-programming
ShaderToy to Unity HLSL/CG - https://github.com/smkplus/ShaderMan
包含各种各样的shader - http://www.shaderslab.com/shaders.html
keijiro
虽然效果不错, 但几乎都是2d的效果. 应该是 shadertoy 练习用的 - https://github.com/JiepengTan/FishManShaderTutorial
3D Game Shaders For Beginners - https://github.com/lettier/3d-game-shaders-for-beginners
Boat Attack - https://github.com/verasl/boatattack
在线画图工具, 数学公式
https://www.geogebra.org/graphing (推荐)
不支持复合公式, 报错
https://www.desmos.com/calculator (推荐)
支持复合公式
todo
特效 及其 shader 研究
Unity Shader - 这个人的专栏不错 , 很多 后处理 案例:
景深, 参考总结: unity-shader-后处理-景深.md
模糊
径向模糊
屏幕特效之径向模糊 - https://blog.csdn.net/u011047171/article/details/48630227
后处理:径向模糊效果 - https://blog.csdn.net/puppet_master/article/details/54566397
运动模糊, 参考: 运动模糊
均值模糊
- Unity Shader-后处理:简单均值模糊 - https://blog.csdn.net/puppet_master/article/details/52547442 . (工程内的 PostProcBlurJunzhi)
高斯模糊
Unity Shader-后处理:高斯模糊 - https://blog.csdn.net/puppet_master/article/details/52783179
迭代次数越大, 批次越大. 也可以做 毛玻璃 效果 (磨砂)
learnopengl-cn - https://learnopengl-cn.readthedocs.io/zh/latest/05%20Advanced%20Lighting/07%20Bloom/#_3
屏幕扭曲
- Unity Shader-后处理:时空扭曲效果 - https://blog.csdn.net/puppet_master/article/details/71437031
UnityShader-BilateralFilter(双边滤波,磨皮滤镜)- https://blog.csdn.net/puppet_master/article/details/83066572
Unity Shader-Decal贴花 (Projector原理) - https://blog.csdn.net/puppet_master/article/details/84310361
描边, 参考总结: unity-shader-模板测试-描边.md
透视, 参考总结: unity-shader-深度测试-透视xray.md
体积光, 参考总结: unity-shader-体积光VolumetricLighting.md
残影效果实现
Unity角色残影特效 - https://blog.csdn.net/qq992817263/article/details/52994907
这里只针对SkinnedMeshRenderer的网格(也就是带蒙皮的网格)残影,主要原理是根据设定的间隔时间连续的截取当前SkinnedMeshRenderer的网格数据并使用Graphics.DrawMesh画出网格
View dir 和 eye vec 区别
只是方向不同
渲染管线
下图显示了 前向渲染管线 中各个阶段主要完成的工作,蓝色部分代表的是我们可以定义自己的着色器。
在上图中,我们以数组的形式传递3个3D坐标作为渲染管线的输入,用它来表示一个三角形,这个数组叫做顶点数据(Vertex Data);这里顶点数据是几个顶点的集合。每个顶点是用顶点属性(vertex attributes)表示的,它可以包含任何我们希望用的数据,下面我们来看看渲染管线中各个阶段主要完成的工作:
- 渲染管线的第一个部分是顶点着色器(vertex shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标(投影坐标),同时顶点着色器允许我们对顶点属性进行一些基本处理。
- 图元组装(primitive assembly)阶段把顶点着色器的表示为基本图形的所有顶点作为输入,把所有点组装为特定的基本图形的形状;上图中是一个三角形。
- 图元组装阶段的输出会传递给几何着色器(geometry shader)。几何着色器把基本图形形成的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其他的)基本图形来生成其他形状。
- 细分着色器(tessellation shaders)拥有把给定基本图形细分为更多小基本图形的能力。这样我们就能在物体更接近玩家的时候通过创建更多的三角形的方式创建出更加平滑的视觉效果。
- 细分着色器的输出会进入光栅化(rasterization)阶段,这里它会把基本图形映射为屏幕上相应的像素,生成供像素着色器(fragment shader)使用的fragment(OpenGL中的一个fragment是OpenGL渲染一个独立像素所需的所有数据。)。在像素着色器运行之前,会执行裁切(clipping)。裁切会丢弃超出你的视图以外的那些像素,来提升执行效率。
- 像素着色器的主要目的是计算一个像素的最终颜色,这也是OpenGL高级效果产生的地方。通常,像素着色器包含用来计算像素最终颜色的3D场景的一些数据(比如光照、阴影、光的颜色等等)。
- 在所有相应颜色值确定以后,最终它会传到另一个阶段,我们叫做alpha测试和混合(blending)阶段。这个阶段检测像素的相应的深度(和stencil)值,使用这些来检查这个像素是否在另一个物体的前面或后面,如此做到相应取舍。这个阶段也会查看alpha值(alpha值是一个物体的透明度值)和物体之间的混合(blend)。所以即使在像素着色器中计算出来了一个像素所输出的颜色,最后的像素颜色在渲染多个三角形的时候也可能完全不同。
虽然渲染管线有多个阶段,每个阶段都需要对应的着色器,但其实对于大多数场合,我们必须做的只是顶点和像素着色器,几何着色器和细分着色器是可选的,通常使用默认的着色器就行了。现在的OpenGL中,我们必须定义至少一个顶点着色器和一个像素着色器(因为GPU中没有默认的顶点/像素着色器)。
Unity把每一frame绘制的事件进行了拆分,然后在其中定义了一些点,在这些点处,可以通过command buffer嵌入一些事件(比如设置RT,绘制一些物件等)。比如在延迟渲染中,可以当G-Buffer中绘制完毕后,往里面绘制一些额外物件。通过下图可以看到Unty的渲染顺序,并可以清晰的看到在绿色点标记的地方可以嵌入command buffer去执行自定义的命令:
RenderingMode 渲染模式
渲染模式总共有四种:
渲染模式 | 意思 | 适用对象举例 | 说明 |
---|---|---|---|
Opaque | 不透明 | 石头 | 适用于所有的不透明的物体 |
Cutout | 镂空 | 破布 | 透明度不是0%就是100%,不存在半透明的区域。 |
Fade | 隐现 | 物体隐去 | 与Transparent的区别为高光反射会随着透明度而消失。 |
Transparent | 透明 | 玻璃 | 适用于像彩色玻璃一样的半透明物体,高光反射不会随透明而消失。 |
变量命名规则
以法线为例, 一般会以某个空间为前缀.
1 | // 切线空间下的法线 |
顶点法线
顶点->图元->像素
- DirectX 11—从空间变换来看3D场景如何转化到2D屏幕 - http://reedhong.lofter.com/post/18b79b_49bf11
Wrap mode 循环模式
在贴图边界以设置贴图的重复模式的方式避免不真实的情
使用强制贴图边界拉伸 TextureWrapMode.Clamp
贴图重复平铺 TextureWrapMode.Repeat
这种方式下, uv 超过 1 的时候, 又会从 0 开始, 假设这种模式下做的扫光特效, 会一直往复循环
UnityShader之空间变换解析
- http://blog.csdn.net/qq984786645/article/details/73467282
- https://www.cnblogs.com/X-Jun/p/7241839.html
模板测试
- http://www.hiwrz.com/2016/07/09/unity/246/
- http://www.open-open.com/lib/view/open1484468851059.html
- http://blog.csdn.net/wangdingqiaoit/article/details/52143197
混合
unity 内置的 Unlit-Texture,渲染类型 Opaque (不透明物体),所以不用开 Blend,默认也是关闭了混合( Blend Off)
Alpha混合 http://blog.sina.com.cn/s/blog_471132920101d8z5.html
Alpha Test和Alpha Blending http://blog.csdn.net/candycat1992/article/details/41599167
总结一下,就是使用Alpha Test看似更简单,但其实在大多数平台上,相比与Alpha Blending,只有一点小小的性能提升。但是!!!在iOS和某些Android设备上,由于它们使用了PowerVR GPUs,因此Alpha Test的性能消耗反而会更大。因此,一个忠告就是,**尽可能使用Alpha Blending,而不要使用 **Alpha Test。
透明度测试与透明度混合详解 : https://blog.csdn.net/u013354943/article/details/52797568
屏幕上显示颜色计算规则
语法
Blend Off 不混合
Blend SrcFactor DstFactor SrcFactor是源系数,DstFactor是目标系数
最终颜色 = (Shader计算出的点颜色值 * 源系数)+(点累积颜色 * 目标系数)
属性(往SrcFactor,DstFactor 上填的值, 也就是 系数)
值 | 解释 |
---|---|
one | 1 |
zero | 0 |
SrcColor | 源的RGB值,例如(0.5,0.4,1) |
SrcAlpha | 源的A值, 例如0.6 |
DstColor | 混合目标的RGB值例如(0.5,0.4,1) |
DstAlpha | 混合目标的A值例如0.6 |
OneMinusSrcColor | (1,1,1) - SrcColor |
OneMinusSrcAlpha | 1- SrcAlpha |
OneMinusDstColor | (1,1,1) - DstColor |
OneMinusDstAlpha | 1- DstAlpha |
运算法则示例:
(注:r,g,b,a,x,y,z取值范围为[0,1])
计算方式 |
---|
(r,g,) * a = (ra, ga, b*a) |
(r,g,) * (x,y,z) = (rx, gy, b*z) |
(r,g,) + (x,y,z) = (r+x, g+y, b+z) |
(r,g,) - (x,y,z) = (r-x, g-y, b-z) |
源颜色 为 该次绘制物体颜色, 目标颜色 为 缓冲区颜色
Blend SrcAlpha OneMinusSrcAlpha
最终颜色 = 源颜色 * 源透明值 [SrcAlpha] + 目标颜色*(1 - 源透明值)[OneMinusSrcAlpha]
结论:贴图alpha值越大,颜色越偏向贴图;alpha值越小,颜色越偏向混合目标
矩阵
在线 矩阵计算器 :http://www.yunsuanzi.com/matrixcomputations/solvematrixmultiplication.html
矩阵乘法
A的列 一定要等于 B的行 ( 不能使用交换律 )
正交矩阵
http://www.mashangxue123.com/%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0/2077705030.html
先来看一下正交矩阵是如何定义的,若方阵M是正交的,则当且仅当M与他的转置矩阵M^T的乘积等于单位矩阵,那么就称矩阵M为正交矩阵.
$$
M^{T} M=I
$$
在矩阵的逆中我们知道,矩阵的逆和矩阵的乘积为单位矩阵I,由此推理,我们可以知道,如果该矩阵为正交矩阵,那么矩阵的逆和转置矩阵是相等的.
$$
M^{T}=M^{-1}
$$
那么正交矩阵存在的意义是什么呢?其实如果一个矩阵是正交矩阵,那么矩阵的逆和转置矩阵是相等的.转置矩阵是非常简单计算的,而计算矩阵的逆如果使用代数余子式计算是非常的麻烦,所以我们可以直接计算转置矩阵然后直接得到该矩阵的逆.
底下是一些重要的性质:
- 正交矩阵:
- 正交、逆、转置 矩阵:http://ios.jobbole.com/89931/
单位矩阵 I
在矩阵的乘法中,有一种矩阵起着特殊的作用,如同数的乘法中的1,这种矩阵被称为单位矩阵。它是个方阵,从左上角到右下角的对角线(称为主对角线)上的元素均为1。除此以外全都为0。
$$
I_{1}=[1], I_{2}=\left[ \begin{array}{cc}{1} & {0} \ {0} & {1}\end{array}\right], I_{3}=\left[ \begin{array}{ccc}{1} & {0} & {0} \ {0} & {1} & {0} \ {0} & {0} & {1}\end{array}\right], \cdots, I_{n}=\left[ \begin{array}{cccc}{1} & {0} & {\cdots} & {0} \ {0} & {1} & {\cdots} & {0} \ {\vdots} & {\vdots} & {\ddots} & {\vdots} \ {0} & {0} & {\cdots} & {1}\end{array}\right]
$$
正交矩阵
但事实上,一个坐标系能用任意3个基向量定义,当然这三个基向量要线性无关(也就是不在同一平面上),
如果这三个基向量(归一化单位向量) 相互垂直 ,那么构成的矩阵是一个正交矩阵
正交矩阵 求 逆矩阵
1
2 // 求 TBN 矩阵的逆矩阵,因为 TBN 矩阵由三个互相垂直的单位向量组成,所以它是一个正交矩阵
// 正如前面所说,正交矩阵的逆矩阵等于它的转置,所以无需真的求逆矩阵
效果
Unity Shader-非主流纹理采样研究(流光,溶解,隐身效果)
Unity Shader-热空气扭曲效果 (GrabPass 抓屏)
Unity Shader-遮挡处理(X-Ray,遮挡描边,遮挡半透,遮挡溶解)
复古风格
切线空间
使用 法线贴图 是因为在 低模 下想获得 高模 凹凸表面光照效果。(也就是面数不够,法线来凑,基于面的法线建立一个 虚拟坐标系 ,通过 法线贴图 在面上 加多点法线 )
由于需要将同一份法线纹理贴到不同模型的表面,或者同一个模型中不同角度的表面,那么当初生成这份法线纹理时所使用的面元角度(所谓模型local坐标系),就无法适用在其他角度的面元上。所以,人们不记录当时用模型坐标系生成的法线,而使用z轴与该点所在面元的法线平行的一个虚拟的相对坐标系来记录该点法线在该坐标系中的x/y/z的值,相当于记录了该点法线与所在平面法线之间的相对关系,而不是记录绝对坐标。由于点的法向量基本不会偏移面法向量太多,也就是凹凸程度一般不会太夸张,所以最终的法向量值中,z的值总是比x和y的大一些(也就是说 点法向量 与平面的 的夹角一般会大于45度),用(法向量值+1)/2转换为RGB后,就成了蓝色为主的色调了。
经过这样记录下来的法向量,贴到哪个模型表面,就按照当前表面的法向量,计算出该点法向量在世界坐标空间中的值,然后计算光照就可以了。
但是有个问题,看下图,竖轴同样是面法向量,但是有多种不同的x和y轴组合,每种组合生成的点法向量是不一致的,所以需要规定一套固定的x和y轴,大家遵守同样的规则。怎么规定呢?就用纹理的uv坐标来定。具体做法是,取该点所在的三角形的三个顶点P1.P2.P3的纹理U和V坐标,然后x轴的方向就是P3指向P1,又称T轴;y轴方向是P3指向P2,又称B轴。 (完)
参考资料
- https://www.zhihu.com/question/23706933
- 切线空间(Tangent Space) 的计算与应用
- http://docs.cryengine.com/display/SDKDOC4/Tangent+Space+Normal+Mapping
- http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping/
法线向量 使用
参考:https://blog.csdn.net/u013354943/article/details/52779991
1. 切线空间下使用 (常用,比较简单)
- 直接使用 顶点的 法线normal 和 切线tangent,叉乘 算出 副切线binormal,然后三个向量 构建一个 切线空间 的 矩阵,然后将 世界空间 下的 光向量lightDir 和 观察向量viewDir。全过程都在 顶点着色器 中进行
2. 世界空间下使用
- 先在 顶点着色器 中,算出 世界空间 下的顶点 法线worldNormal 和 切线worldTangent , 叉乘算出 副切线worldBinormal ,然后 构建 切线空间到世界空间的变换矩阵,
- 然后在 片段着色器 中,将 切线向量 变换 到 世界空间下
法线贴图
http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping/
法线纹理(Normal Mapping)的实现细节 https://blog.csdn.net/candycat1992/article/details/41605257
unity中 法线纹理 设置
通过矩阵变换空间
参考 《Unity Shader入门精要》 4.8.1 和 4.9.2
1 | v2f vert(a2v v) { |
内置变量
unity 矩阵构建
unity 中的 xyz 坐标轴在矩阵中的排列为 横向 排列
所以构建矩阵或变换xyz分量时要要用 横向 数据
比如
1 | // csharp |
变换某个分量
以变换 顶点 从 模型空间 -> 剪裁空间 为例. 以下三种方式等价
1 | v2f o; |
踩坑
顶点 变换, 要用 float4 类型, 四个分量. 因为使用到平移, 用的矩阵是 float4x4.
法线 变换, 用 float3 类型, 三个分量, 因为法线是向量, 不存在平移的概念, 所以用的矩阵是 float3x3.
坐标系
齐次坐标
- 齐次坐标(Homogeneous Coordinate)的理解 - https://blog.csdn.net/winbobob/article/details/38829001
标准化设备坐标 ( NDC )
OpenGL希望在所有顶点着色器运行后,所有我们可见的顶点都变为标准化设备坐标(Normalized Device Coordinate, NDC)。也就是说,每个顶点的x,y,z坐标都应该在-1.0到1.0之间,超出这个坐标范围的顶点都将不可见。我们通常会自己设定一个坐标的范围,之后再在顶点着色器中将这些坐标转换为标准化设备坐标。然后将这些标准化设备坐标传入光栅器(Rasterizer),再将他们转换为屏幕上的二维坐标或像素。
将坐标转换为标准化设备坐标,接着再转化为屏幕坐标的过程通常是分步,也就是类似于流水线那样子,实现的,在流水线里面我们在将对象转换到屏幕空间之前会先将其转换到多个坐标系统(Coordinate System)。将对象的坐标转换到几个过渡坐标系(Intermediate Coordinate System)的优点在于,在这些特定的坐标系统中进行一些操作或运算更加方便和容易,这一点很快将会变得很明显。对我们来说比较重要的总共有5个不同的坐标系统:
- 局部空间(Local Space,或者称为物体空间(Object Space))
- 世界空间(World Space)
- 观察空间(View Space,或者称为视觉空间(Eye Space))
- 裁剪空间(Clip Space)
- 屏幕空间(Screen Space)
这些就是我们将所有顶点转换为片段之前,顶点需要处于的不同的状态。
标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。下面你会看到我们定义的在标准化设备坐标中的三角形(忽略z轴, z轴有深度转化而来):
后处理中的ndc坐标构建
1 | //使用宏和纹理坐标对深度纹理进行采样,得到深度值 |
透视矩阵推导
已知变量
AE: 近剪裁面 用 n 代替这个已知量
AD: 远剪裁面,用 u 来代替;
∠a:就是摄像机的张角,也就是FOV
$$
\theta=\frac{\angle a}{2}
$$
$$
k=\frac{F L}{F G}=\frac{h e i g h t}{\text {width}} (k是宽高的比例系数,已知条件 aspect)
$$
- 最后推导出的透视矩阵
$$
\begin{array}{cccc}{\frac{k^{2}}{\tan (\theta)}} & {0} & {0} & {0} \ {0} & {\frac{e^{2}}{\tan (\theta)}} & {0} & {0} \ {0} & {0} & {-\frac{u+n}{u-n}} & {0} \ {0} & {0} & {-\frac{2 f n}{u-n}} & {0}\end{array}
$$
将顶点从 view 坐标系转换到 NDC 下
这里面包含了两个步骤,将 view坐标系 下的顶点乘以透视矩阵,转换到 Clip坐标系,得到 Clip坐标,
$$
\left( \begin{array}{l}{x_{clip}} \ {y_{clip}} \ {z_{clip}}\end{array}\right)=M_{p r o j e c t i o n} \cdot \left( \begin{array}{c}{x_{e y e}} \ {y_{e y e}} \ {z_{c y e}} \ {w_{e y e}}\end{array}\right)
$$
然后统一除以w,得到NDC 坐标。
$$
\left( \begin{array}{l}{x_{n d c}} \ {y_{n d c}} \ {z_{n d c}}\end{array}\right)=\left( \begin{array}{l}{x_{c l i p} / w_{c l i p}} \ {y_{c l i p} / w_{c l i p}} \ {z_{c l i p} / w_{c l i p}}\end{array}\right)
$$
ddx, ddy 函数
可以参考:
- shader中ddx/ddy偏导数的原理和简单应用 - http://blog.sina.com.cn/s/blog_7cb69c550102xvog.html
- Unity3d ddx ddy 法线 - http://www.dreamfairy.cn/blog/2016/06/15/unity3d-ddx-ddy-normal/
ddx,ddy 只能在 Fragment Shader下使用, 因为它是用来计算相邻像素的某些属性值之差. ( 属性值比如 worldPos )
求出法线的实例. 利用 worldPos 的 x,y 之差查, 得到两个矢量, 通过 叉乘 得到垂直于这两个矢量组成面的 第三个矢量, 也就是法线.
1 | v2f vert (appdata v) |
那么ddx,ddy 可以用在哪里呢。
- 计算出该像素的法线
- 计算mipmap level 计算当前纹理在屏幕分辨率下的大小是否小于某个mipmap等级的纹理大小,是的话就切换纹理
- 视差贴图 从视野方向计算像素挤出的位置的uv值
OpenGL中的坐标变换、矩阵变换
六种常见坐标系:
- Object or model coordinates(模型坐标系)
- World coordinates(世界坐标系)
- Eye (or Camera) coordinates(视坐标系)
- Clip coordinates(裁剪坐标系)
- Normalized device coordinates(归一化设备坐标系)
- Window (or screen) coordinates(屏幕坐标系)
剪裁坐标
在我们来解释一下这个坐标系为什么叫做“裁剪坐标系”,看看哪里来的“裁剪”二字:
需要特别注意的是,模型坐标系、世界坐标系、视点坐标系中的第四个分量w都是1,但是经过了投影变换之后的坐标的w分量不再为1,这时候就可以发挥w分量的作用了。在我们手动编程计算完gl_Position之后,进入GPU自身的流水管线,GPU会根据裁剪坐标gl_Position中 xyz分量 与 w分量 绝对值的大小进行比较进行裁剪。具体的过程是:GPU依次将gl_Position中x、y、z的绝对值与w的绝对值分别比较,只要有一个分量的绝对值大于w的绝对值 (也就是 Xclip/ Wclip 的值 >1 或 <-1
) ,GPU就认为该点不在视景体内,就会被裁减掉,也就是说裁剪的过程是GPU自己进行的,没有被裁减掉的坐标 xyz分量 的绝对值都小于 w分量 的绝对值 (也就是 -1< Xclip/ Wclip 的值 <1
) 。所以现在应该知道经过投影之后的坐标是为了让GPU进行裁剪用的,所以才叫做“裁剪坐标”。
线性空间与GAMMA校正
Gamma计算很简单,只是个power而已,也就是:
$$
\text { Color }{out}=\text { Color }{i n}^{\gamma}
$$
其中的γ就是用来校正的gamma值。
可惜,现实是残酷的,显示器的gamma为2.2,所以如果相机仍然是线性的,那么结果就会变成:
这样在显示器上看到的就会有明显的色彩失真。解决方法是把相机的gamma设成1/2.2,这样两次调整之后又能得到真实场景的色彩了:
对渲染的意义
前面讲的输入是对相机拍的照片而言。而对渲染来说,情况又如何呢?渲染中用到的光照都是在线性空间的。因为在设计光照的时候都是认为1的亮度是0.5的2倍。光照如此,texture又如何呢?渲染中用到的 texture一般有两个来源,一个是照片,一个是artist手工画的。前文提到了,照片是gamma = 1/2.2的。一般图象处理软件也都是在gamma空间工作的,所以artist画的图一般也可以认为是gamma = 1/2.2的。所以,我们在pixel shader常可以见到这样的代码:
1 | float4 diff = tex2D(diffuse_texture, uv); |
这样的代码对吗?不对也对。
说其不对,是因为这里没显式地做gamma校正。做校正的话应该是这样的:
1 | float4 diff = pow(tex2D(diffuse_texture, uv), 2.2f); |
也就是说,gamma校正的过程就是把输入的texture都转换到线性空间,并把输出的调整到gamma = 1/2.2的空间。
总结
总之,计算 都要发生在 线性空间,所以输入和输出需要进行gamma校正。最佳选择是采用sRGB格式,这样pow是 硬件内自动实现 ,速度更快,代码也简单。鉴于目前很多texture的数据是gamma = 1/2.2的,而纹理格式却被错误地标记成没有sRGB的,所以需要修改它们的格式标记,并重新建立mipmap。
贴图格式是sRGB, 也就是该贴图是gamma空间的.
HDR
泛光 bloom
下面这几步就是泛光后处理特效的过程,它总结了实现泛光所需的步骤。
首先我们需要根据一定的阈限提取所有明亮的颜色。我们先来做这件事。
GPU和GLSL并不擅长优化循环和分支
然而事实上,你的GPU和GLSL并不擅长优化循环和分支。这一缺陷的原因是GPU中着色器的运行是高度并行的,大部分的架构要求对于一个大的线程集合,GPU需要对它运行完全一样的着色器代码从而获得高效率
注意问题
矩阵行列优先级
在 CG 中, 对 float4x4 等类型的变量是 按行有限 的方式进行填充。(入门精要 4.9.2)
单位精度
类型 | 精度 |
---|---|
float | 最高精度。32位存储 |
half | 中等精度。16位存储,范围 -60 000 ~ +60 000 |
fixed | 最低精度。11位存储,范围 -2.0 ~ +2.0 (常用来存储颜色值(0~1之间)) |
参考 : Cg/HLSL中的数据类型 - https://zhuanlan.zhihu.com/p/48530294
float
高精度类型,32位,通常用于世界坐标下的位置,纹理UV,或涉及复杂函数的标量计算,如三角函数、幂运算等。
half
中精度类型,16位,数值范围为[-60000,+60000],通常用于本地坐标下的位置、方向向量、HDR颜色等。
fixed
低精度类型,11位,数值范围为[-2,+2],通常用于常规的颜色与贴图,以及低精度间的一些运算变量等。
在PC平台不管你Shader中写的是half还是fixed,统统都会被当作float来处理。half与fixed仅在一些移动设备上有效。
比较常用的一个规则是,除了位置和坐标用float以外,其余的全部用half。主要原因也是因为大部分的现代GPU只支持32位与16位,也就是说只支持float和half,不支持fixed。
技巧
替代 if else 判断
可以参考: unity-shader-GPU优化_step函数替代if-else - https://blog.csdn.net/yangxuan0261/article/details/89852627
GPU 中尽量减少使用 if else 判断逻辑,执行消耗非常大,可以使用以下方式替代
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 fixed4 frag(v2f i) : SV_Target
{
float factor = i.objPos.x - _DissolveThreshold;
fixed3 color = tex2D(_MainTex, i.uv).rgb;
fixed4 retClr;
//方式a, 方式a 与 方式b 等价, 但 方式b 性能更好
//if (i.objPos.x > _DissolveThreshold) {
// retClr = _DissolveColor;
//} else {
// retClr = _DissolveColor;
//}
//方式b
fixed lerpFactor = saturate(sign(i.objPos.x - _DissolveThreshold));
//fixed lerpFactor = step(_DissolveThreshold, i.objPos.x); // 与上一行等价
retClr = lerpFactor * _DissolveColor + (1 - lerpFactor) * fixed4(color, 1);
//retClr = lerp(fixed4(color, 1), _DissolveColor, lerpFactor); // 与上一行代码等价
return retClr;
}sign 约束 值 只能是 -1, 0, 1
saturate 约束值 只能是 [0, 1]
step(a, b) 表示 如果 a <= b,返回 1 ;否则,返回 0 。
所以只要 i.objPos.x > _DissolveThreshold,就取 颜色 _DissolveColor
pass 复用
参考:
- 测试工程内的 PassName
- Unity中Shader LOD控制 - https://zhuanlan.zhihu.com/p/21316674
定义 pass 逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29Shader "test/PassNameBase"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Name "PassNameBase01"
CGPROGRAM
// 逻辑 1
ENDCG
}
Pass
{
Name "PassNameBase02"
CGPROGRAM
// 逻辑 2
ENDCG
}
}
}复用 pass 逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Shader "test/PassNameTest01"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "IgnoreProjector"="True"}
LOD 100
UsePass "test/PassNameBase/PassNameBase01"
// UsePass "test/PassNameBase/PassNameBase02"
}
}然后为该 shader 创建材质球就可以使用了.
几何意义解析
- 点乘(又名 点积,dot):dot(a, b) 求出 向量 a、b夹角的 余弦值,值越大,夹角越小
- 叉乘(又名 叉积,cross): cross(a, b) 求出 与向量 a、b组成平面 垂直的 c 向量
- 变换,mul(A, n) 用 矩阵A 将 n 向量变换 到 A空间 下。(A是一个坐标系,可能是 3x3坐标系, 可能是 4x4 齐次坐标系) ,等价与 mul(n, A的逆矩阵) (矩阵相乘不满足交换律)
专栏
- http://blog.csdn.net/puppet_master/article/category/6441122
- http://blog.csdn.net/column/details/unityshadertutorial.html
_Time 的单位
名称 | 类型 | 说明 |
---|---|---|
_Time | float4 | t 是自该场景加载开始所经过的时间,4个分量分别是 (t/20, t, t2, t3) |
_SinTime | float4 | t 是时间的正弦值,4个分量分别是 (t/8, t/4, t/2, t) |
_CosTime | float4 | t 是时间的余弦值,4个分量分别是 (t/8, t/4, t/2, t) |
unity_DeltaTime | float4 | dt 是时间增量,4个分量的值分别是(dt, 1/dt, smoothDt, 1/smoothDt) |
XX_TexelSize
XX纹理的像素相关大小width,height对应纹理的分辨率,x = 1/width, y = 1/height, z = width, w = height
也就是 : Vector4(1 / width, 1 / height, width, height)
后处理
后处理 需要注意贴图的平台差异
1 | //dx中纹理从左上角为初始坐标,需要反向(在写rt的时候需要注意) |
默认传 _MainTex 给 shader
要注意写法
1 | Shader "test/PostProcCircle" { |
运动模糊
相关资料
- Unity Shader实现运动模糊(一) : 摄像机运动产生模糊 - https://blog.csdn.net/h5502637/article/details/84986197
运动模糊是个经常会用到的效果,常见的实现步骤是:
- 对深度纹理进行采样,取得当前片元的深度信息
- 根据深度信息建立当前片元的NDC空间的坐标curNDCPos
- 把curNDCPos乘以当前VP矩阵的逆矩阵(即View*Projection)-1,得到当前片元的世界空间坐标WorldPos
- 把WorldPos乘以上一帧的VP矩阵(即View*Projection),得到上一帧在裁切空间中的位置 lastClipPos
- 把lastClipPos除以其w分量,得到NDC空间位置lastNDCPos
- 用当前片元NDC空间位置 减去 上一帧NDC空间位置(即 curNDCPos-lastClipPos),得到速度的方向speed
- 沿speed方向进行多次采样,求出平均值作为当前片元的颜色
法线的变换
参考 :
- UNITY_MATRIX_IT_MV[Matrix] - https://blog.csdn.net/cubesky/article/details/38682975
- https://forum.unity.com/threads/_object2world-or-unity_matrix_it_mv.112446/
官方的解释
- MV transforms points from object to eye space
- IT_MV rotates normals from object to eye space
And similarly:
- Object2World transforms points from object to world space
- IT_Object2World (which, as you point out, is the transpose of World2Object) rotates normals from object to world space
If it is orthogonal, the upper-left 3x3 of Object2World will be equal to that of IT_Object2World, and so will also rotate normals from object to world space.
上面这里很好的描述了UNITY_MATRIX_IT_MV的使用场景,专门针对法线进行变换。但是为什么法线的变换和定点不一样呢?让我们来看一篇推导的文章。
注:之所以法线不能直接使用UNITY_MATRIX_MV进行变换,是因为法线是向量,具有 方向,在进行空间变换的时候,如果发生非等比缩放,方向会发生偏移。为什么呢?拿上面的例子来说,我们可以简单的把法线和切线当成三角形的两条边,显然,三角形在空间变换的时候,不管是平移,还是旋转,或者是等比缩放,都不会变形,但是如果非等比缩放,就会发生拉伸。所以法线和切线的夹角也就会发生变化。(而切线在变换前后,方向总是正确的,所以法线方向就不正确了)。
结论 : 法线的变换需要用 xxx_IT
shader示例1
1 | // 变换 法线 从 模型空间 -> 观察空间 |
shader示例2 - 切线空间
1 | v2f2 vert2 (appdata2 v) |
法线混合算法
相关文章
- Blending in Detail - https://blog.selfshadow.com/publications/blending-in-detail/?tdsourcetag=s_pcqq_aiomsg
引擎实现
unity
unity 的 shader graph 的节点 Normal Blend Node
ue4
ComputeScreenPos 屏幕空间位置
参考:
- ComputeScreenPos 详解 - https://chengkehan.github.io/ComputeScreenPos.html
- Unity Shader中的ComputeScreenPos函数 - https://www.jianshu.com/p/df878a386bec
获取像素在屏幕空间的位置
内置 ComputeScreenPos 实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14inline float4 ComputeNonStereoScreenPos(float4 pos) {
float4 o = pos * 0.5f;
o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;
o.zw = pos.zw;
return o;
}
inline float4 ComputeScreenPos(float4 pos) {
float4 o = ComputeNonStereoScreenPos(pos);
o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
return o;
}内置 tex2Dproj 实现
1
half4 tex2Dproj(sampler2D s, in half4 t) { return tex2D(s, t.xy / t.w); }
简单的使用
1 | v2f vert (appdata v) { |
一般采样贴图是都是顶点中的uv值, 是个固定值的, 所以不会随着顶点位置的变化而变化, 就会造成拉伸.
可以做图片不被拉伸的效果, 因为采样的 uv 值是 0-1, 所以只用当四个顶点完后只能覆盖屏幕四个角落时, 才会显示出完整的图片.
因为 mesh 的所有的顶点在屏幕空间下的值都会在 (0, 0) 到 (1, 1) 区间内.
表面着色器
参考:
1 | # 指定 表面函数 和 光照函数 |
雾效使用
unity 中使用雾效的姿势. 主要是四个地方, 加下面注释处.
1 | Pass |
shader 可视化编辑
比较热门的两款 可视化 插件 Amplify Shader vs Shader Forge
Amplify Shader vs Shader Forge
- https://www.reddit.com/r/gamedev/comments/802tuy/amplify_shader_vs_shaderforge_vs_unity_20181/
- https://www.zhihu.com/question/266358345
- https://www.zhihu.com/question/59642795
Shader Forge 貌似更新太慢等很多抱怨
Amplify Shader 的可视化 和 ue4 的材质编辑器 很相似,编辑出来的是 surface着色器 的源码
Shader Forge 编辑出来的是 vertex着色器与fragment着色器 的源码
关于 geometry shader
- Geometry Shader Examples - https://www.khronos.org/opengl/wiki/Geometry_Shader_Examples
- Shader Model 4 - https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl-sm4
Shader Model 4 supports a new pipeline stage—the geometry-shader stage—which can be used to create or modify existing geometry.
要 sm4.0 才支持, 所以手机上还还不能使用
unity Shader Compilation Target Levels - https://docs.unity3d.com/Manual/SL-ShaderCompileTargets.html
sm2.0 才能支持移动平台
GI shader中的使用方式
可以参考这个仓库: https://github.com/keijiro/AdamPlaneReflection
GLSL 转 UnityShader
- ShaderToy 转 unity shader - https://github.com/smkplus/ShaderMan
着色器数据类型和精度
Unity 中的标准着色器语言为 HLSL,支持一般 HLSL 数据类型。但是,Unity 对 HLSL 类型有一些补充,特别是为了在移动平台上提供更好的支持。
着色器中的大多数计算是对浮点数(在 C# 等常规编程语言中为 float
)进行的。浮点类型有几种变体:float
、half
和 fixed
(以及它们的矢量/矩阵变体,比如 half3
和 float4x4
)。这些类型的精度不同(因此性能或功耗也不同):
高精度:float
最高精度浮点值;一般是 32 位(就像常规编程语言中的 float
)。
完整的 float
精度通常用于世界空间位置、纹理坐标或涉及复杂函数(如三角函数或幂/取幂)的标量计算。
中等精度:half
中等精度浮点值;通常为 16 位(范围为 –60000 至 +60000,精度约为 3 位小数)。
半精度对于短矢量、方向、对象空间位置、高动态范围颜色非常有用。
低精度:fixed
最低精度的定点值。通常是 11 位,范围从 –2.0 到 +2.0,精度为 1/256。
固定精度对于常规颜色(通常存储在常规纹理中)以及对它们执行简单运算非常有用。
整数数据类型
整数(int
数据类型)通常用作循环计数器或数组索引。为此,它们通常可以在各种平台上正常工作。
根据平台的不同,GPU 可能不支持整数类型。例如,Direct3D 9 和 OpenGL ES 2.0 GPU 仅对浮点数据进行运算,并且可以使用相当复杂的浮点数学指令来模拟简单的整数表达式(涉及位运算或逻辑运算)。
Direct3D 11、OpenGL ES 3、Metal 和其他现代平台都对整数数据类型有适当的支持,因此使用位移位和位屏蔽可以按预期工作。
Queue
- 零基础入门Unity Shader - https://zhuanlan.zhihu.com/p/51080323
Queue渲染队列,用来指定当前shader作用的对象的渲染顺序:
Unity中的几种内置的渲染队列,按照渲染顺序,从先到后进行排序,队列数越小的,越先渲染,队列数越大的,越后渲染。
- Background(1000) 最早被渲染的物体的队列。
- Geometry (2000) 不透明物体的渲染队列。大多数物体都应该使用该队列进行渲染,也是Unity Shader中默认的渲染队列。
- AlphaTest (2450) 有透明通道,需要进行Alpha Test的物体的队列,比在Geomerty中更有效。
- Transparent(3000) 半透物体的渲染队列。一般是不写深度的物体,Alpha Blend等的在该队列渲染。
- Overlay (4000) 最后被渲染的物体的队列,一般是覆盖效果,比如镜头光晕,屏幕贴片之类的
3d 场景透明贴图渲染合批优化
渲染队列中, Transparent (透明, 默认: 3000) 渲染队列不能 gpu instancing,遮挡关系因为 go 的顺序间隔打断 instance
Geometry (不透明, 默认: 2000), 就不会因为 顺序打断,不透明物体的遮挡关系取决于深度
想要渲染 透明物体,又要使用 instance,就需要把 透明物体 的队列设置到 2000-2450 之间, 如:
"Queue"="Geometry+20"
, 让 unity 识别到要在 不透明渲染队列在中渲染, 同时不要写入深度,因为如果 物体x 的渲染队列更高, 同时深度又小于这个 透明物体 的话, 物体x 就不会渲染出来
RenderType
- a
URP/SRP
- Unity URP/SRP 渲染管线浅入深出 - https://zhuanlan.zhihu.com/p/353687806
- Unity3D项目升级URP(步骤及问题解决) - https://www.bilibili.com/read/cv11464272?from=search
- URP配置—保姆级新手指南 - https://zhuanlan.zhihu.com/p/396397334
常见问题
(1) TRANSFORM_TEX是做什么的
(2)float4 _MainTex_ST 中的_MainTex_ST变量也没有用到,为啥非要声明一下?
答:
(1)简单来说,TRANSFORM_TEX主要作用是拿顶点的uv去和材质球的tiling和offset作运算, 确保材质球里的缩放和偏移设置是正确的。 (v.texcoord就是顶点的uv)
下面这两个函数是等价的。
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
所以使用他有两个前提:
\1. #include “UnityCG.cginc”
\2. 定义name##_ST
(2)而_MainTex_ST的ST是应该是SamplerTexture的意思 ,就是声明_MainTex是一张采样图,也就是会进行UV运算。 如果没有这句话,是不能进行TRANSFORM_TEX的运算的。_MainTex_ST.xy为 下图中的Tiling,zw为下图中的offset.
如果Tiling 和Offset你留的是默认值,即Tiling为(1,1) Offset为(0,0)的时候,可以不用
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
换成o.uv = v.texcoord.xy;也是能正常显示的;相当于Tiling 为(1,1)Offset为(0,0),但是如下图自己填的Tiling值和Offset值就不起作用了
【风宇冲】Unity3D教程宝典之Shader篇:特别讲 常见问题解答
clip 函数
1 | // alpha test |
clip(x) : 如果输入向量中的任何元素小于0,则丢弃当前像素。
也就是说,是通过 Alpha 值 减去 指定的 _CutOff ,也就是,Alpha 值 低于 _CutOff 的片段都会被丢弃。
根据书上所说,这次学习的 透明裁剪着色器,因为要逐像素判断 Alpha 是否小于 _CutOff ,所以是比较消耗 GPU的,所以在手机并不推荐。
但是在工作中,会发现,其实很大程度上都是一个度的问题。
像这中溶解效果,用在副本里面,不可能几十个怪同时死亡而同时显示溶解的效果的。
所以并没有什么在手机上就不能用这种事情。想用就用。
SV_Target、SV_POSITION
SV其实是system-value(系统数值)的意思,需要注意的是SV开头的语义考虑到扩平台的问题了,所以能用SV开头的语义的尽量使用SV开头的语义,免去跨平台的烦恼
http://blog.csdn.net/a958832776/article/details/70847033
http://blog.csdn.net/zhao_92221/article/details/46797969
Q: Duplicated input semantics can’t change type, size or layout. (TEXCOORD1)
重复使用了 TEXCOORD1 语义,可能类型 UNITY_FOG_COORDS(1) 这样的unity宏里使用了,所以改成 TEXCOORD2\3\4\5 即可
Q: 法线图偏蓝紫色 (rgb(0.5,0.5,1))
法线贴图每个像素存的都是每点(当然这些点是离散化的)的局部法向量坐标,所以没有扰动法向量的时候,图片全部像素应该是rgb(0.5,0.5,1) 大概就是蓝紫色那样 (假设z轴就是法向)。因为单位法向量的xy分量取值在(-1,1)但是颜色分量取值在(0,1),所以把这个区间映射一下就会发现未被扰动的向量(0,0,1)会被映射成(0.5,0.5,1)这个偏蓝紫色的颜色
映射公式: pixel = (normal + 1) / 2
通常情况下,美工会做出一张使用的RGB三个通道用来存放法线的XYZ三个轴向的坐标的法线贴图。但是Unity在导入法线贴图的时候会自动将法线贴图压缩成 DXT5nm 格式,这个格式的好处是 它只使用AG(透明和绿色)两个通道来存放两个轴向的坐标值。而Z轴向的坐标值由于是 单位坐标 可以通过 1 减去另外前两个轴向坐标的 平方和 来得到(已知2个分量,可求第三个),从而可以以同样的容量存放更大尺寸的法线贴图,我们都知道图片的颜色通道存的都是非负数(法线贴图生成的时候已经把[-1,1]压缩为[0-1]),而我们的三维空间是[-1,1],所以我们要把它解析放大一下,方法就是对对应的颜色通道值乘以2再减1。这就能存储游戏物体所有的法线信息了。例如,一个RGB值为(0.5,0.5,1)或#8080ff(16进制)的颜色向量,它所存储法线向量值为(0,0,1),代表该图向上的法向量,即模型没有凹凸现象。对比本页面之前的模型平面区域,您就会发现这种颜色基调。
参考 : 法线贴图详解之三—-凹凸,法线以及高度图的区别及法线贴图蓝紫色的原因 - http://manew.com/thread-90210-1-1.html
因此, 如果切线空间下的法线 和 顶点 ( 插值后的) 法线 重合 (平行) , 则这该点像素值为 ( 0.5, 0.5, 1), 也就是对应的法线值 ( 0, 0, 1), 也就是切线空间坐标系的z轴, 也就是顶点的法线.
Q: worldNorm.xy * 0.5 + 0.5 是几个意思
等价与 上面那个问题的 映射公式,都是将 法线值转为 像素值
会使用到这个公式 一般都是 使用了 fixed3 normals = UnpackNormal(tex2D(_BumpMap, i.uv_bump)); 将法线值 从 法线图 解包后
1
2
3
4
5
6 pixel = (normal + 1) / 2
//等价与
pixel = (normal * 0.5 + 1 * 0.5
//推导出
normal = pixel * 2 - 1
Q: unity_ObjectToWorld 和 unity_WorldToObject 是互逆矩阵?
对,所以用法可以互换,
1
2
3 // Transform the normal from object space to world space
// 转换法线 从 模型空间 到 世界空间
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject); // 内置 UnityObjectToWorldNormal 的实现, 相当于 mul(unity_ObjectToWorld的逆转矩阵, v.normal);此方式只能用在向量上,不能用在顶点上(v.vertex),用在顶点上会得到不一样的结果. 因为 法向量是方向向量, 所以不管怎么平移都是一样的, 所以才使用 float3x3 矩阵 (去掉了平移)
推到公式可以参考: 法向量矩阵 - https://www.qiujiawei.com/linear-algebra-18/, 他也是参考 3D游戏与计算机图形学中的数学方法 这本书的.
变体 shader 丢失
表现上 editor 正常, 移动端 丢失, 经测试是 移动端变体丢失
解决办法参考: unity-shader变体ShaderVariant.md
Shader Forge shader 材质移动端显示黑色 (丢失)
将对应的 shader 里面 only_renderers 等指定 渲染硬件的 注释掉即可
1 | // #pragma only_renderers d3d9 d3d11 glcore gles |
参考: Shader Forge shader材质移动端显示黑色bug修复 - https://blog.csdn.net/u014670984/article/details/114364246