百人计划(程序)

第一章  基础夯实

一、 渲染流水线 (作业)

  • 应用阶段(CPU)

        1.准备基本数据

                ①场景物体数据(物体变换数据:位置、旋转、缩放,物体网格数据:顶点位置、uv贴图等)

                ②摄像机数据(位置、方向、远近裁剪平面,正交/透视,视口比例/尺寸等)

                ③光源及阴影数据(光源类型:方向光/点光/聚光等,颜色、位置、方向、角度等参数;是否需要阴影、阴影参数)

                ④其他全局数据等

        2.加速算法,粗粒度剔除

                碰撞检测,加速算法,

                遮挡剔除:通过判断物体位置遮挡关系对被不透明物体完全遮挡的物体进行剔除。

        3.设置渲染状态

                绘制设置(不同物体使用不同的着色器、不同的选择对象使用合批方式),

                绘制顺序(以相对摄像机的距离/RenderQueue/UICanvas等渲染顺序),

                渲染目标(FrameBuffer/RenderTexture),

                渲染模式(前向渲染/延迟渲染)

        4.调用DrawCall,输出渲染图元到显存

                顶点数据,其他数据

  • 几何阶段(GPU)

        1.顶点着色器(可编程):模型空间→世界空间→摄像机空间→裁剪空间 通过MVP矩阵对顶点的坐标进行变换

        2.曲面细分(可选)

        3.几何着色器(可选)

        4.顶点剪裁(CVV视锥体/正面或背面剔除)

        5.屏幕映射

  • 光栅化阶段(GPU)

        1.三角形设置

        2.三角形遍历

        3.片元着色器

        4.逐片元操作

                片元着色、颜色混合(Alpha Test、Depth Buffer Test、Stencil Test、Blending)、目标缓冲区(FrameBuffer、RenderTexture)

  • 后处理

 二、数学基础

  • 向量运算

        1.向量:是指n维空间中一种包含了模 (magnitude) 和方向 (direction) 的有向线段。

        2.向量运算

                     ①向量和标量的乘法/除法:kv=(kv_{x},kv_{y},kv_{z}) /  

                     ②向量的加法和减法:

                     ③向量的模:向量的模是一个标量,可以理解为是矢量在空间中的长度。

                     ④单位向量:单位向量指的是那些模为1的向量。对任何给定的非零向量,把它转换成单位向量的过程就被称为归一化(normalization)。零向量(即向量的每个分量值都为0, 如v=(0,0,0)是不可以被归一化的。

                    ⑤向量的点积(dot product):

                        · 意义:现在有一个光源,它发出的光线是垂直于\vec{a}方向的,那么b\vec{a}方向上的投  影就是b方向\vec{a}上的影子。                                                             

                        · 性质:(1)点积可结合标量乘法。(kab=a·(kb)=k(a·b)

                                     (2)点积可结合向量加法和减法。a·(b+c)=a·b+a·c

                                     (3)一个向量和本身进行点积的结果,是该向量的模的平方。

                    ⑥向量的叉积(cross product):

                        · 意义:对两个向量进行叉积的结果会得到一个同时垂直于这两个向量的新向量。

  • 矩阵运算

        1.矩阵:它是由mXn个标量组成的长方形数组。

        2.矩阵运算

                ①矩阵和标量的乘法:

                ②矩阵和矩阵的乘法:不满足交换律/满足结合律

        3.特殊矩阵

                ①方块矩阵/方阵

                ②单位矩阵

                ③转置矩阵

                        · 性质一:矩阵转置的转置等于原矩阵。 (M^{T})^{T}=M

                        · 性质二:矩阵串接的转置,等于反向串接各个矩阵的转置。 (AB)^{T}=B^{T}A^{T}

                 ④逆矩阵:

                         MM^{-1}=M^{-1}M=I (并非所有的方阵都有对应的逆矩阵。)

                        · 性质一:逆矩阵的逆矩阵是原矩阵本身。 (M^{-1})^{-1}=M

                        · 性质二:单位矩阵的逆矩阵是它本身。     I^{-1}=I                           

                        · 性质三:转置矩阵的逆矩阵是逆矩阵的转置。(M^{T})^{-1}=(M^{-1})^{T}

                        · 性质四:矩阵串接相乘后逆矩阵等于反向串接各个矩阵的逆矩阵。(AB)^{-1}=B^{-1}A^{-1}

                ⑤正交矩阵:

                        MM^{T}=M^{T}M=I  / M^{T}=M^{-1}

        4.矩阵的几何意义:变换

                线性变换包括:缩放、旋转、错切、镜像、正交投影

                ①平移矩阵:

                                                 

                ②缩放矩阵:

                                        

                ③旋转矩阵:

                                      

                                                            

                ④复合变换:

                                P_{new}=M_{translation}M_{rotation}M_{scale}P_{old}

                             (由于上面我们使用的是列矩阵, 因此阅读顺序是从右到左。)

        5.左乘右乘

        参数的位置会直接影响结果值。通常在变换顶点时,我们都是使用右乘的方式来按列矩阵进行乘法。这是因为, Unity 提供的内置矩阵(如 UNITY_MATRIX_MVP等)都是按列存储的。但有时,我们也会使用左乘的方式,这是因为可以省去对矩阵转置的操作

  • MVP矩阵运算                

        1.M:模型空间 → 世界空间

        2.V:世界空间 → 观察空间(以摄像机为原点)

        3.P:观察空间 → 裁剪空间

三、纹理介绍

  • 纹理概述

        1.纹理的概念:一种可供着色器读写的结构化存储形式。

        2.采用纹理的原因:①牺牲几何细节→ 减少建模工作量 ②减小存储空间 ③提升读取速度

  • 纹理管线

模型空间位置→投影函数→纹理映射→纹理坐标→通讯函数→新纹理坐标→纹理采样→纹理值                                  (展UV)                                                                        (避免依赖纹理读取)

        1. 投影函数:将表面坐标投影到纹理坐标空间,通常得到是一个二维坐标 (u,v) ,投影函数把三维的坐标投影到二维UV坐标上。(一般uv基本在建模的时候就处理好了,储存在顶点中)

            

        2.通讯函数:规定纹理只有一部分可以被用来显示,或者进行一些矩阵变化来任意的应用纹理;定义纹理的应用方式,比如当uv超出0-1的范围时,在OpenGL称为Wrapping Mode(包装模式),DX则是Texture Addressing Mode(纹理寻址模式)。

                ①Wrap(DX)/ Repeat(GL)or tile :舍弃整数部分只用小数部分。

                ②Mirror:超过1的时候反过来,超过2的时候再反回来。

                ③Clamp:超过1,就是1。

                ④Border超过1时用一个定义的边缘颜色渲染。

  •  纹理采样设置——Filter Mode 纹理过滤(作业)

        1.Filter Mode:过滤设置,当屏幕上的方形区域尺寸和图片像素尺寸差不多时,基本是还原整张图片的,但当区域很大时,就会发生放大(magnification),反之则是缩小(minification)。

        2.Magnification放大:

                ①nearest neighbor (最邻近):只使用放大后最接近纹理中心的那个像素值,会有一种块状(像素化)的效果。

                ②bilinear interpolation(双线性插值):对每个像素访问周边的四个相邻像素然后混合。效果更模糊,但是少了很多锯齿感。

                ③quilez的光滑曲线插值:使用一个平滑曲线来插值2x2的纹素组。常见的两个是smoothstep曲线和quintic曲线。

                ④cubic convolution(三次卷积插值):比较贵,牺牲性能提高表现。

        3.Minification 缩小:

                 ①最邻近/双线性插值:颜色丢失与闪烁。

                ②Mipmap:通过预处理纹理,创建数据结构,来有助于在实时计算中快速计算一组纹理对一个像素效果的近似值,将 2x2 的4个相邻的纹理值作为下一级的新的纹理值,所以新一级的纹理是上一级的1/4大。直到 1x1 ,所以在存储空间上多了1/3。

                缺点:比如整体会偏模糊,特别当一个像素沿u方向和v方向包含的纹素数量差别很大的时候(比如从边缘观察一个物体)。

                解决方法:各向异性过滤(Ripmap)

  •  GPU渲染优化方式(作业)

        1.纹理图集/数组(降低DrawCall)

        2.纹理压缩:减少了资源在CPU中进行解压缩的过程,减小了包体大小,减少了数据量级,减轻了带宽计算的压力,内存的使用效率更高。

  • 立方体贴图 Cubemap

         由六张正方体纹理组成,每个代表立方体的一个面。 立方体问题由一个三维的纹理坐标访问,其代表的是方向,由立方体中心指向外侧。

  •  凹凸贴图 Bump Mapping

        不使用纹理来改变光照方程中的颜色分量,而是使用纹理来修改表面法线。曲面的几何法线保持不变,只是修改了照明方程中使用的法线。

  • 位移贴图 Displacement Mapping

       真正移动了顶点的位置

补充:https://blog.csdn.net/zheliku/article/details/124464638

第二章 光照基础

一、色彩空间

  • 色彩发送器

        1.光的概述

                ①光源(产生光的物体)

                ②光(本质是一种处于特定频段的光子流)可见光的波长:(紫外线) 400-700 (红外线)

        2.分光光度计:将成分复杂的光,分解为光谱线的科学仪器。

                常用的波长范围为:(1)200~380nm的紫外光区,(2)380~780nm的可见光区(作业),(3)2.5~25μm(按波数计为4000cm<-1>~400cm<-1>)的红外光区。

        3.光的传播:直射光、折射光、反射光、光线追踪

  • 光源接收者

        1.相对亮度感知

        2.人眼HDR:人眼既可以分辨出高亮度的云彩的不同层次区别,还可以分辨出阴影中不同物体的异同。但是人眼的能力不能保证两个功能同时生效。

        3.人眼感光细胞分布

  • 色彩空间的定义(作业)

        1.色域(三个基色的坐标,可以形成三角形)

        2.Gamma(如何对三角形内进行切分)

        3.白点(色域三角形中心)

  • 常用色彩模型与色彩空间

        1.色彩模型:使用一定规则描述(排列)颜色的方法。例:RGB、CMYK、LAB

        2.色彩空间:需要至少满足三个指标:色域、白点、Gamma。例:CIE XYZ、Adobe RGB、sRGB等。

二、模型与材质基础 

  • 图形渲染管线

模型的实现原理:点→线→面→模型        

  • UV  

        1.在建模软件中进行UV展开,UV会放在一个横向为U纵向为V,范围在(0-1)的二维坐标系中。展开后的UV在sp中绘制贴图。

        2.模型包含的信息(obj):①顶点坐标数据(模型空间中单个顶点的XYZ坐标)②贴图坐标(水平方向是U,垂直方向是V)③顶点法线 ④顶点颜色(单个顶点的RGBA通道颜色信息)

  • 材质基础

        1.漫反射:漫反射是最容易模拟的模型。最简单的Lambert模型认为光线均匀的反射出去。

        2.镜面反射:将入射光线根据表面法线进行反射,并且只有在反射方向有能量,其他方向能量均为0。

        3.折射:对于某些物质,除了反射外还会根据物体的折射率折射一部分光线进入物体之中,反射和折射能量的多少根据Fresnel定律决定。

        4.粗糙镜面反射:法线偏移较小,反射依然集中在一个区域,形成磨砂的质感。

        5.粗糙镜面折射

        6.多层材质:物质表面有透明的物质附着。

        7.次表面散射:半透明的物体(玉石、蜡烛、牛奶、皮肤等)光线在物体内部多次反射。

  • 模型数据在渲染中的作用

        1.顶点动画:在顶点着色器中,修改模型的顶点位置,使模型运动。(顶点动画需要一定数量的顶点,效果才会比较明显。)

        2.纹理动画:在片元着色器中,修改模型的UV信息,使得采样贴图时,UV发生位移而产生运动效果。

        3.顶点色:在渲染时,能控制颜色范围。

        4.顶点法线、面法线:存储方式不同,顶点法线:每个顶点都有一个法线,插值结果不同;面法线:未使用平滑时,三个顶点共用一个法线,插值结果相同。

  • 扩展

  • 作业

1.顶点色的作用:

 来源:顶点色的其他作用 - 知乎 (zhihu.com)

2.模型光滑组对法线的作用:

        ①先搞清楚光滑组是什么

  • 没有真正的光滑面,所有面都是三角形
  • 光滑组的含义:下面图标出了面的亮度,纯属打比方,不是确切数字,两面之间的过渡就是两面亮度和的平均值,光滑组处理面之间的光照信息,提高它们的亮度、饱和度。

    • 如果两个面一个光滑组是1,另一个是2,就不进行计算
    • 如果他们的光滑组都是1,就会进行光照计算,产生光滑效果,影响最终渲染。

  • 自动光滑:所有面的夹角小于45度的进行光滑
  • 光滑组:通过处理面之间的光照信息来达到光滑效果,是用来设置边缘线的光滑显示的。
  • 网格平滑和涡轮平滑:通过增加面,把面分的更细腻来表达曲度
  • 我们平常说的布线合理,拓朴其实是保持两个三角面的一致性(构成一个四边面的俩三角面)

②光滑组对法线的影响

法线

  • 烘焙法线的意义,就是把高模的法线方向,用一张图(RGB)来存储法线信息,存到低模的表面上。贴上法线贴图的低模,就会在视觉上产生凹凸不平、增加细节的渲染效果,从而看起来像高模一样。Normal Mapping 法线贴图本质上就是一种图片,只是这张图片的用途比较特殊而已。
  • 没有光滑组的话,烘出来的法线贴图是一棱一棱的。一般情况下最少也要给一个光滑组
  • 参考链接中的例子:渐变色的法线贴图在substancepainter会出现黑边情况(光滑组的问题)

光滑组(软硬边)和UV对法线的影响

  • 光滑组相连的模型,法线贴图都存在大渐变色,导致模型的法线效果会很奇怪(平面上有发暗发亮的光影)。当你发现你的模型出现这种渐变时,一定是出现了光滑组的问题。
  • 中间的两个模型出现了不同程度的接缝(第三个模型的接缝非常明显,第二个模型则弱一些)。光滑组和uv统一相连或断开,是不会出现明显接缝的,当遇到接缝问题,优先考虑模型的光滑组和uv是否统一。

来源: 模型与材质基础 - 知乎 (zhihu.com)

 三、HLSL常用函数介绍

  • 基本数学运算

  • 幂指对函数

  • 数据范围类

  • 类型判断类

  • 三角函数与双曲线函数

  •  向量与矩阵类

  •  光线运算类

  •  纹理查找类

 

 

  • 作业 

ddx 和 ddy的实际使用测试:

1.简单的边缘突出应用:

Shader "Unlit/27_ddxddy"
{
    Properties
    {
        [KeywordEnum(IncreaseEdgeAdj, BrightEdgeAdj)] _EADJ("Edge Adj type", Float) = 0
        _MainTex ("Texture", 2D) = "white" {}
        _Intensity("Intensity",range(0,20)) = 2
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Cull Off
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _EADJ_INCREASEEDGEADJ _EADJ_BRIGHTEDGEADJ

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Intensity;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i , float f : VFACE) : SV_Target //在片断着色器中还有些特殊的语义的可以识别,比如VFACE,如果渲染表面朝向摄像机,则Face节点输出正值1,如果远离摄像机,则输出负值-1。
            {
                fixed a = 1;
                if ( f < 0 ) a = 0.5;
                fixed3 col = tex2D(_MainTex, i.uv).rgb;
                #if _EADJ_INCREASEEDGEADJ //边缘调整:增加边缘差异调整
                col += (ddx(col)+ddy(col))*_Intensity;//使边缘的像素亮度差异变大,增加边缘突出
                #else //边缘调整:增加边缘亮度调整
                col += (abs(ddx(col))+abs(ddy(col)))*_Intensity;//即fwidth(col),边缘变亮
                // fwidth func in HLSL: https://docs.microsoft.com/zh-cn/windows/desktop/direct3dhlsl/dx-graphics-hlsl-fwidth
                #endif
                return fixed4(col,a);
            }
            ENDCG
        }
    }
}

Increase: 

Bright:

2.高度生成法线应用(?):

Shader "Unlit/28_DDX&HeightMap"
{
    Properties
    {
        [KeywordEnum(LMRTMB,CMRCML,NAVDDXPOSDDY)] _S ("Sample Type", Float) = 0
        _Color("Main Color", Color) = (1,1,1,1)
        _MainTex ("Texture", 2D) = "white" {}
        _HightMap("Hight Map", 2D) = "white" {}
        _Intensity("Intensity", Range(0, 20)) = 5
        _SpecuarlIntensity("Specular Intensity", Range(0, 100)) = 80
        _SpecuarlStrengthen("Specular Strengthen", Range(0, 1)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _S_LMRTMB _S_CMRCML _S_NAVDDXPOSDDY

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 lightDir : TEXCOORD1;
                float3 viewDir : TEXCOORD2;
                float3 normal : TEXCOORD3;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;
            sampler2D _HightMap;
            float4 _HightMap_TexelSize; // 1/w, 1/h, w, h
            float _Intensity;
            float _SpecuarlIntensity;
            float _SpecuarlStrengthen;

            inline float3x3 getTBN (inout float3 normal, float4 tangent) {
				float3 wNormal = UnityObjectToWorldNormal(normal);		   
				float3 wTangent = UnityObjectToWorldDir(tangent.xyz);		
				float3 wBitangent = normalize(cross(wNormal, wTangent));	
                normal = wNormal;
				return float3x3(wTangent, wBitangent, wNormal);			    
            }
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                float3x3 tbn = getTBN(v.normal, v.tangent);
                o.lightDir = mul(tbn, normalize(_WorldSpaceLightPos0.xyz));
                o.viewDir = mul(tbn, normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex))); 
                o.normal = mul(tbn, v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 c = tex2D(_MainTex, i.uv);
                #if _S_LMRTMB
                //参考来源:https://blog.csdn.net/puppet_master/article/details/53591167
                float offsetU = tex2D(_HightMap, i.uv + _HightMap_TexelSize * float2(-1, 0)).r - tex2D(_HightMap, i.uv + _HightMap_TexelSize * float2(1, 0)).r;
                float offsetV = tex2D(_HightMap, i.uv + _HightMap_TexelSize * float2(0, 1)).r - tex2D(_HightMap, i.uv + _HightMap_TexelSize * float2(0, -1)).r;
                #elif _S_CMRCML
                fixed cr = tex2D(_HightMap, i.uv).r;
                float offsetU = (cr - tex2D(_HightMap, i.uv + _HightMap_TexelSize * float2(1, 0)).r) * _Intensity;
                float offsetV = (cr - tex2D(_HightMap, i.uv + _HightMap_TexelSize * float2(0, -1)).r) * _Intensity;
                #else
                fixed h = tex2D(_HightMap, i.uv).r;
                float offsetU = -ddx(h); // 右边像素采样 - 当前像素采样 = U的斜率,这里我们取反向,因为我们需要的是当前-右边的值,而ddx是固定的right-cur,所以我们只能取反
                float offsetV = ddy(h); // 下边像素采样 - 当前像素采样 = V的斜率,这里我们不用取反向,斜率方向刚刚好是我们需要的
                #endif // end _S_LMRTMB
                 // 调整tangent space normal
                float3 n = normalize(i.normal.xyz + float3(offsetU, offsetV, 0) * _Intensity);
                // 为了测试法线,添加了diffuse与specular的光照因数
                // diffuse
                float ldn = dot(i.lightDir, n) * 0.5 + 0.5;
                fixed3 diffuse = _LightColor0.rgb * _Color * ldn * c.rgb * tex2D(_MainTex, i.uv);
                // specular
                float3 halfAngle = normalize(i.lightDir + i.viewDir);
                float3 hdn = max(0, dot(halfAngle, n));
                fixed3 specular = _LightColor0.rgb * _Color * pow(hdn, 100 - _SpecuarlIntensity) * _SpecuarlStrengthen;
                fixed3 combined = diffuse + specular;
                return fixed4(combined, 1);
            }
            ENDCG
        }
    }
}

         其中有三种算法方式,前两种基本一样,支持采样坐标不太一样,最后一种就是使用偏导函数DDX,DDY来处理的。

 将高度图中相邻的像素灰度值的插值作为高度斜率。

3.flat shading应用:

来源: Unity Shader - ddx/ddy偏导函数测试,实现:锐化、高度图、Flat shading应用、高度生成法线_Jave.Lin的博客-CSDN博客_unity ddx ddy

四、传统经验光照模型

  • 光照模型

        1.概念:用于计算物体某点处的光强(颜色值)。从算法理论基础而言,光照模型分为两类:一是基于物理理论的,另一种是基于经验模型的。

        2.基于物理理论的光照模型(PBR):偏重于使用物理的度量和统计方法,效果真实,但是计算复杂。

        3.经验模型:对光照的一种模拟,通过实践总结出简化的方法,简化了真实的光照计算,达到想要的效果。

  • 局部光照模型

        1.概念:只计算直接光照部分,即直接从光源发出并照射到物体表面并一次反射至摄像头的光线。

        2.局部光照=漫反射+高光反射+环境光+自发光

        ①漫反射

        定义:在光照模型的定义中,当光线从光源照射到模型表面时,光线均匀被反射到各个方向;在漫反射的过程中,光线发生了吸收和散射,因此改变了颜色和方向。

        计算:漫反射光照符合Lambert定律,反射光强与法线和光源方向之间之间的夹角的余弦值成正比。 dot(n,l)=\left | n \right | \left | l \right | cos\theta=1*1*cos\theta=cos\theta 

        ②高光反射(镜面反射)

        定义:当光线到达物体表面并发生了反射,观察视线在反射光线的附近时,便能够观察到高光反射。(光强不变,方向改变)

         ③环境光

         ④自发光

        物体自身发射的光线,通常作为单独的一项加入光照模型。一般使用一张发光贴图描述物体的自发光。

  • 经典光照模型

        1.Lambert模型

        2.Phong模型

        3.Blinn-Phong模型

        *Phong模型和Blinn-Phong模型区别

        4.Gourand模型

       

        5.Flat模型

  • 总结

  • 作业

1.能量守恒的理念在基础光照模型中的作用。

出射光线的能量不会超过入射光线的能量。

2.基于能量守恒的理念,写完整的光照明模型。包含环境光照。

庄懂的技术美术入门笔记_羡桜的博客-CSDN博客

五、Bump Mapping 凹凸映射

  • 凹凸映射介绍

        1.目的:使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。(不改变模型的顶点位置)

        2.分类

        ①高度映射:使用一张高度纹理(height map)来模拟表面位移(displacement),得到一个修改后的法线值。

        ②法线映射:使用一张法线纹理(normal map)来直接存储表面法线。

  • 法线映射 Normal Mapping

        1.原理:使用一张存有物体局部表面法线信息的纹理,计算光照时,程序会读取法线图,进行光照计算。

        2.TBN矩阵:

         3.法线信息存储在切线空间的优点

        ①自由度高(切线空间下的是相对法线信息,是对当前物体法线的扰动)

        ②可以通过移动uv坐标来实现uv动画

        ③因为纹理记录的信息是对物体法线的扰动,可以共用纹理贴图

        ④切线空间下的贴图中法线的z方向总是正方向(模型空间下是可以负的),可以只存xy(切线和副切线)

      4.法线贴图在unity中的压缩格式

        非移动平台上,Unity会把法线贴图转换成DXRT5nm格式(只有两个有效通道GA通道,节省空间);移动平台上,Unity使用传统的RGB通道。

解码法线贴图:

            v2f vert (a2v v)
            {
                v2f o;
                o.posCS = UnityObjectToClipPos(v.vertex);
                o.posWS = mul(unity_ObjectToWorld, v.vertex);
                o.normalDirWS = normalize(UnityObjectToWorldNormal(v.normal));
                o.tangentDirWS = normalize(mul(unity_ObjectToWorld, v.tangent).xyz);  //切线
                o.bitangentDirWS = normalize(cross(o.normalDirWS,o.tangentDirWS) * v.tangent.w);  //副切线
                o.uv0 = v.texcoords;
            
                return o;
            }
            
            float4 frag (v2f i) : SV_Target
            {
                // 准备向量
                half3x3 TBN = half3x3(i.tangentDirWS, i.bitangentDirWS, i.normalDirWS);

                half3 viewDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS.xyz);
                half3 viewDirTS = normalize(mul(TBN, viewDirWS));

                half3 normalDirTS = UnpackNormal(tex2D(_NormalTex, pm_uv)).xyz;
                half3 normalDirWS = normalize(lerp(i.normalDirWS,mul(normalDirTS, TBN),_NormalScale));
            }

  • 视差映射 Parallax Mapping

        1.概念

        提高模型表面细节(改变纹理坐标)并赋予其遮挡关系的技术,并可以和法线贴图一起使用提供令人信服的逼真的效果。需要引进新的贴图,高度图(利用模型表面高度信息来对纹理进行偏移),但要包含大量的三角形才能获得不错的效果。

         2.原理

       把视线向量v转到tangent中那么前文算出来的v'的x,y分量会和表面的切线、副切线对齐,这样这个表面无论如何旋转都没问题了,否则一旦表面被任意旋转以后,很难指出v'的x,y到底在哪儿。   

        viewDir.xy / viewDir.z,除以z分量,是因为视线向量大致平行于表面时为了获取较大的v'而做的,这时的z接近于0,所以v'的x,y分量都会比较大。但也有人不除以z分量,因为在某些角度看会不好看所以不除,不除的话这这种技术就叫有偏移量限制的视差贴图(Parallax Mapping with Offset Limiting)

        以采样的深度值,计算视线偏移的距离。深度越深,采样的距离越远,准确度较差。

理解:视差贴图(Parallax Mapping)以及浮雕贴图(Relief Mapping)在Unity中的实现 - 简书 (jianshu.com)

// 视差映射
            float2 ParallaxMapping(float2 texcoords, float3 viewDirTS)
            {
                // 高度采样
                float height = tex2D(_HeightTex, texcoords).r;

                // 视点方向越接近法线 UV偏移越小
                float2 offuv = viewDirTS.xy / viewDirTS.z * height * _HeightScale;

                return texcoords - offuv;
            }

  • 陡视差映射 Steep Parallax Mapping

         分层机制,分层多,性能开销会大(根据视角v和法线n的角度限定采样层数);分层小,渲染锯齿会很明显。

        从A点开始采样,计算该点的视线的高度和采样得到的高度,视线的深度小于采样高度贴图的深度,就采样下一个点,即寻找视线与物体表面的交点的uv坐标,然后取代a点的原本uv。

// 陡峭视差映射
            float2 SPM(float2 texCoords, float3 viewDirTS)
            {
                // 高度层数  
                /* trick--分层次数由视点方向与法向夹角来决定,当视点的方向和法线方向越靠近时,
                那么采样需要的偏离也越小,那么就可以采用较少的高度分层,反之则需要更多的分层。*/
                float minLayers = 20;
                float maxLayers = 100;
                float numLayers = lerp(maxLayers, minLayers, abs(dot(float3(0.0,0.0,1.0), viewDirTS)));
                // 每层高度
                float layerHeight = 1.0 / numLayers;
                // 当前层级高度
                float currentLayerHeight = 0.0;
                // 视点方向偏移量
                float2 offsetuv = viewDirTS.xy / viewDirTS.z * _HeightScale;
                // 每层高度偏移量
                float2 deltaTexCoords = offsetuv / numLayers;
                // 当前 UV
                float2 currentTexcoords = texCoords;
                // 当前UV位置高度图的值
                float currentHeightMapValue = tex2D(_HeightTex, currentTexcoords).r;

                while(currentLayerHeight < currentHeightMapValue)
                {
                    // 按高度层级进行UV偏移
                    currentTexcoords += deltaTexCoords;
                    // 从高度贴图采样获取当前UV位置的高度
                    currentHeightMapValue = tex2Dlod(_HeightTex, float4(currentTexcoords, 0, 0)).r;
                    // 采样点高度
                    currentLayerHeight += layerHeight;
                }

                return currentTexcoords;
            }

  • Parallax Occlusion Mapping(视差遮蔽映射)

        视差遮蔽贴图的算法则和陡峭视差贴图原则相同,但在它基础上进行插值。利用陡峭视差贴图得到最靠近交点的两点后,根据这两者的深度与对应层深度的差值作为比例进行插值。

// 视差遮蔽映射
                // 前一个采样点
                float2 preTexcoords = currentTexcoords - deltaTexCoords;

                // 线性插值
                float afterHeight = currentHeightMapValue - currentLayerHeight;
                float beforeHeight = tex2D(_HeightTex, preTexcoords).r - (currentLayerHeight - layerHeight);
                float weight = afterHeight / (afterHeight - beforeHeight);
                float2 finalTexcoords = preTexcoords * weight + currentTexcoords * (1.0 - weight);

                return finalTexcoords;
            

  • 浮雕映射 Relief Mapping

        1.概念:

         2.优点:

        使用更大的UV偏移,视差映射就会导致失真。浮雕映射更容易提供更多的深度,还可以做自阴影以及闭塞效果。

float2 ReliefMapping(float2 texCoords, float3 viewDirTS){
                float3 startPoint = float3(texCoords,0);
                float h = tex2D(_HeightTex, texCoords).r;
                viewDirTS.xy *= _HeightScale;
                int linearStep = 40;
                int binarySearch = 8;
                float3 offset = (viewDirTS/viewDirTS.z)/linearStep;
                for(int index=0;index<linearStep;index++){
                    float depth = 1 - h;
                    if (startPoint.z < depth){
                        startPoint += offset; 
                    }
                }
                float3 biOffset = offset;
                for (int index=0;index<binarySearch;index++){
                    biOffset = biOffset / 2;
                    float depth = 1 - h;
                    if (startPoint.z < depth){
                        startPoint += biOffset;
                    }else{
                        startPoint -= biOffset;
                    }
                }
                float2 finalTexCoords = startPoint.xy;

                return finalTexCoords;
            }

  • 总代码:

        参考来源:

视差贴图(Parallax Mapping)以及浮雕贴图(Relief Mapping)在Unity中的实现 - 简书 (jianshu.com)

2.5 Bump Map的改进 (yuque.com)

Shader "Unlit/29_SnowStone"
{
    Properties
    {
         [KeywordEnum(PM,SPM,CSPM,RM)] _S ("Sample Type", Float) = 0
        _MainTex ("基本颜色贴图", 2D) = "white" {}
        _NormalTex ("法线贴图", 2D) = "bump" {}
        _HeightTex ("高度贴图", 2D) = "white" {}
        _Cubemap ("环境贴图", 2D) = "_Skybox" {}


        _MainCol ("基本色", color) = (0.5,0.5,0.5,1.0)
        _Diffuse ("漫反射颜色", color) = (1,1,1,1)
        _Specular ("高光颜色", color) = (1,1,1,1)

        _Gloss ("光泽度", range(1,255)) = 50
        _HeightScale ("高度图扰动强度", range(0,0.15)) = 0.5
        _NormalScale ("法线贴图强度", range(0,1)) = 1
        
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {
            Name "StudyLM"
            Tags {
                "LightMode"="ForwardBase"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _S_PM _S_SPM _S_CSPM _S_RM

            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"
            #pragma multi_compile_fwdbase_fullshadows
            #pragma target 3.0

            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TEXCOORD0;
                float2 texcoords : TEXCOORD1;
            };

            struct v2f
            {
                float4 posCS : SV_POSITION;
                float4 posWS : TEXCOORD0;
                float3 normalDirWS : TEXCOORD1;
                float3 tangentDirWS : TEXCOORD2;
                float3 bitangentDirWS : TEXCOORD3;
                float2 uv0 : TEXCOORD4;
            };

            uniform sampler2D _MainTex;     uniform float4 _MainTex_ST;
            uniform sampler2D _NormalTex;   uniform float4 _NormalTex_ST;
            uniform sampler2D _HeightTex;
            uniform sampler2D _Cubemap;

            uniform half4 _MainCol;
            uniform half4 _Diffuse;
            uniform half4 _Specular;

            uniform half _Gloss;
            uniform half _HeightScale;
            uniform half _NormalScale;

            v2f vert (a2v v)
            {
                v2f o;
                o.posCS = UnityObjectToClipPos(v.vertex);
                o.posWS = mul(unity_ObjectToWorld, v.vertex);
                o.normalDirWS = normalize(UnityObjectToWorldNormal(v.normal));
                o.tangentDirWS = normalize(mul(unity_ObjectToWorld, v.tangent).xyz);  //切线
                o.bitangentDirWS = normalize(cross(o.normalDirWS,o.tangentDirWS) * v.tangent.w);  //副切线
                o.uv0 = v.texcoords;
            
                return o;
            }

            // 视差映射
            float2 ParallaxMapping(float2 texcoords, float3 viewDirTS)
            {
                // 高度采样
                float height = tex2D(_HeightTex, texcoords).r;

                // 视点方向越接近法线 UV偏移越小
                float2 offuv = viewDirTS.xy / viewDirTS.z * height * _HeightScale;

                return texcoords - offuv;
            }

            // 陡峭视差映射
            float2 SPM(float2 texCoords, float3 viewDirTS)
            {
                // 高度层数  
                /* trick--分层次数由视点方向与法向夹角来决定,当视点的方向和法线方向越靠近时,
                那么采样需要的偏离也越小,那么就可以采用较少的高度分层,反之则需要更多的分层。*/
                float minLayers = 20;
                float maxLayers = 100;
                float numLayers = lerp(maxLayers, minLayers, abs(dot(float3(0.0,0.0,1.0), viewDirTS)));
                // 每层高度
                float layerHeight = 1.0 / numLayers;
                // 当前层级高度
                float currentLayerHeight = 0.0;
                // 视点方向偏移量
                float2 offsetuv = viewDirTS.xy / viewDirTS.z * _HeightScale;
                // 每层高度偏移量
                float2 deltaTexCoords = offsetuv / numLayers;
                // 当前 UV
                float2 currentTexcoords = texCoords;
                // 当前UV位置高度图的值
                float currentHeightMapValue = tex2D(_HeightTex, currentTexcoords).r;

                while(currentLayerHeight < currentHeightMapValue)
                {
                    // 按高度层级进行UV偏移
                    currentTexcoords += deltaTexCoords;
                    // 从高度贴图采样获取当前UV位置的高度
                    currentHeightMapValue = tex2Dlod(_HeightTex, float4(currentTexcoords, 0, 0)).r;
                    // 采样点高度
                    currentLayerHeight += layerHeight;
                }

                return currentTexcoords;
            }

            // 视差遮蔽映射
            float2 Custom_SPM(float2 texCoords, float3 viewDirTS)
            {
                // 高度层数
                /* trick--分层次数由视点方向与法向夹角来决定,当视点的方向和法线方向越靠近时,
                那么采样需要的偏离也越小,那么就可以采用较少的高度分层,反之则需要更多的分层。*/
                float minLayers = 20;
                float maxLayers = 100;
                float numLayers = lerp(maxLayers, minLayers, abs(dot(float3(0.0,0.0,1.0), viewDirTS)));
                // 每层高度
                float layerHeight = 1.0 / numLayers;
                // 当前层级高度
                float currentLayerHeight = 0.0;
                // 视点方向偏移量
                float2 offsetuv = viewDirTS.xy / viewDirTS.z * _HeightScale;
                // 每层高度偏移量
                float2 deltaTexCoords = offsetuv / numLayers;
                // 当前 UV
                float2 currentTexcoords = texCoords;
                // 当前UV位置高度图的值
                float currentHeightMapValue = tex2D(_HeightTex, currentTexcoords).r;

                while(currentLayerHeight < currentHeightMapValue)
                {
                    // 按高度层级进行UV偏移
                    currentTexcoords += deltaTexCoords;
                    // 从高度贴图采样获取当前UV位置的高度
                    currentHeightMapValue = tex2Dlod(_HeightTex, float4(currentTexcoords, 0, 0)).r;
                    // 采样点高度
                    currentLayerHeight += layerHeight;
                }

                // 前一个采样点
                float2 preTexcoords = currentTexcoords - deltaTexCoords;

                // 线性插值
                float afterHeight = currentHeightMapValue - currentLayerHeight;
                float beforeHeight = tex2D(_HeightTex, preTexcoords).r - (currentLayerHeight - layerHeight);
                float weight = afterHeight / (afterHeight - beforeHeight);
                float2 finalTexcoords = preTexcoords * weight + currentTexcoords * (1.0 - weight);

                return finalTexcoords;
            }

            float2 ReliefMapping(float2 texCoords, float3 viewDirTS){
                float3 startPoint = float3(texCoords,0);
                float h = tex2D(_HeightTex, texCoords).r;
                viewDirTS.xy *= _HeightScale;
                int linearStep = 40;
                int binarySearch = 8;
                float3 offset = (viewDirTS/viewDirTS.z)/linearStep;
                for(int index=0;index<linearStep;index++){
                    float depth = 1 - h;
                    if (startPoint.z < depth){
                        startPoint += offset; 
                    }
                }
                float3 biOffset = offset;
                for (int index=0;index<binarySearch;index++){
                    biOffset = biOffset / 2;
                    float depth = 1 - h;
                    if (startPoint.z < depth){
                        startPoint += biOffset;
                    }else{
                        startPoint -= biOffset;
                    }
                }
                float2 finalTexCoords = startPoint.xy;

                return finalTexCoords;
            }

            float4 frag (v2f i) : SV_Target
            {
                // 准备向量
                half3x3 TBN = half3x3(i.tangentDirWS, i.bitangentDirWS, i.normalDirWS);

                half3 viewDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS.xyz);
                half3 viewDirTS = normalize(mul(TBN, viewDirWS));
                // 视差映射
                #if _S_PM
                float2 pm_uv = ParallaxMapping(i.uv0, viewDirTS);
                // 陡峭视差映射
                #elif _S_SPM 
                float2 pm_uv = SPM(i.uv0, viewDirTS);
                // 视差遮蔽映射
                #elif _S_CSPM
                float2 pm_uv = Custom_SPM(i.uv0, viewDirTS);
                #else
                float2 pm_uv = ReliefMapping(i.uv0, viewDirTS);
                #endif
            
                
                half3 normalDirTS = UnpackNormal(tex2D(_NormalTex, pm_uv)).xyz;
                half3 normalDirWS = normalize(lerp(i.normalDirWS,mul(normalDirTS, TBN),_NormalScale));
                half3 lightDirWS = normalize(_WorldSpaceLightPos0.xyz);
                half3 halfDirWS = normalize(lightDirWS + viewDirWS);
                half3 reflectDirWS = normalize(reflect(-lightDirWS, normalDirWS));

                // 准备向量积
                half NdotL = dot(normalDirWS, lightDirWS);
                half NdotH = dot(normalDirWS, halfDirWS);
                half VdotR = dot(viewDirWS, reflectDirWS);

                // 光照模型
                // 环境光
                half4 MainTex = tex2D(_MainTex, pm_uv) * _MainCol;
                half3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse * MainTex.rgb;
                
                half3 diffuse = lerp(ambient * _Diffuse.rgb * MainTex.rgb, max(0, NdotL) * _LightColor0.rgb * _Diffuse.rgb * MainTex, _Gloss / 255);
                // blinnphong
                half3 specular = pow(max(0, NdotH), _Gloss) * _LightColor0.rgb * _Specular.rgb;
                // phong
                //specular = pow(max(0, VdotR), _Gloss) * _LightColor0.rgb * _Specular.rgb;

                specular = lerp(diffuse * specular, specular, _Gloss / 255);

                half3 BlinnPhong = ambient + diffuse + specular;

                float3 finalRGB = BlinnPhong;
                return float4(finalRGB,1.0);
            }
            ENDCG
        }
    }
}

六、伽马校正

  • Gamma校正

        1.颜色空间

         2.传递函数:①光转电传递函数,负责把场景线性光转到非线性视频信号值。②电转光传递函数,负责把非线性视频信号值转换成显示光亮度。

        3.Gamma校正的概念

        即,Gamma是指对线性三色值非线性视频信号之间进行编码解码的操作。

         举例:

        

         4.为什么需要Gamma校正

        ①非线性转换的目的主要是为了优化存储空间和带宽,传递函数能够更好地帮我们利用编码空间。

        ②由于我们用于显示图像数据都是8bit,且人眼对暗部变化更敏感,如果要充分利用带宽,那么就需要使用更多位置去存储暗部值。也就是说暗部使用更高精度保存,而亮部使用更低精度保存。

  • 韦伯定律

 

         人眼感受亮度时,对于暗部变化明显比亮部变化更敏感。

        1.概念:即感觉的差别阈限随原来刺激量的变化而变化,而且表现为一定的规律性,用公式来表示,就是△Φ/Φ=C,其中Φ为原刺激量,△Φ为此时的差别阈限,C为常数,又称为韦柏率。

        (当所受刺激越大时,需要增加的刺激也要足够大才会让人感觉到明显变化,但是只适用于中等强度的刺激。)

        2.结论

        ①人眼对暗部变化比亮部更加敏感。

        ②我们目前所使用的真彩格式RGBA32,每个颜色通道只有8位用于记录信息,为了合理使用带宽和存储空间,需要进行非线性转换。

        ③目前我们所普遍使用的sRGB颜色空间标准,它的传递函数gamma值为2.2(2.4)。

        sRGB很好,它能很高效的应用数据显示图像,但是在我们进行图形操作的时候会出问题。因为sRGB是非线性空间。 因为他把更多的值给了暗部,0.5并不代表0.5而是0.21左右。

  • CRT(阴极射线显像管)

        1.概念:早期我们使用的图像显示器都是CRT,人们发现这种设备的亮度与电压并不成线性关系,而是gamma值约为2.2类似幂律的关系。(刚好可以将变亮的图片压暗至显示正确)

         2.中灰值:中灰值也并非一个固定的具体数值,而是取决于视觉感受。

  • 线性工作流程

        1.概念:一种通过调整图像Gamma值,来使得图像得到线性化显示的技术流程。

        *如果使用Gamma空间的贴图,在传给着色器之前需要从Gamma空间转到线性空间。

        2.如果不在线性空间下渲染

        ①亮度叠加:可以看到非线性空间下亮度叠加出现了过曝(亮度>1的)的情况,因为Gamma空间经过gamma编码后的亮度值相对之前会变大。

        ②颜色混合:如果在混合前没有非线性的颜色进行转换,就会在纯色的边界出现一些黑边。

​        ③光照计算:在光照渲染结算时,如果我们把非线性空间下(视觉上的)的棕灰色0.5当做实际物理光强为0.5来计算时,就会出现左边这种情况。在显示空间下是0.5,但在渲染空间下它的实际物理光强为0.18(如右图)。

  •  Unity中颜色空间

        1.设置:

        ①当选择Gamma Space时,Unity不会做任何处理 

        ②当选择Linear Space时,引擎的渲染流程在线性空间计算,理想情况下项目使用线性空间的贴图颜色,不需要勾选sRGB,如果勾选了sRGB的贴图,会通过硬件特性采样时进行线性转换。

        2.硬件支持: 

        3.硬件特性支持: 

  •  资源导出设置

         1.SP

        当Substance的贴图导出时,线性的颜色值经过伽马变换,颜色被提亮了,所以需要在Unity中勾选sRGB选项,让它在采样时能还原回线性值。

        2.PS  

        ①如果使用线性空间,一般来说Photoshop可以什么都不改,导出的贴图只要勾上sRGB就可以了。如果调整PhotoShop的伽玛值为1,导出的贴图在Unity中也不需要勾选sRGB了。

  

        ②Document Color Profile:

        PhotoShop对颜色管理特别精确,Unity里看到的颜色要经过显示器的伽玛变换,而PhotoShop不会,PhotoShop会读取显示器的Color Profile,反向补偿回去。

        PhotoShop有第二个Color Profile,叫做Document Color Profile。通常它的默认值就是sRGB Color Profile,和显示器的Coor Profile一致,颜色是被这个Color Profile压暗了,所以PhotoShop中看到的结果才和Unity中一样。

        ③半透明混合:Unity中的混合是线性混合,Photoshop的图层和图层之间做混合的时候,每个上层图层都经过了伽马变换,然后才做了混合。在设置中更改,选择“用灰度系数混合RGB颜色”,参数设置为1,这样图层才是直接混合的结果。  

七、LDR与HDR

  • 基本概念

        1. HDR = High Dynamic Range 

                ①8位精度(即 2^{8}= 256(0~255))

                ②单通道0-1

                ③拾色器、一般的图片、电脑屏幕

                ④常用LDR图片存储格式jpg/png等 

        2.LDR = Low Dynamic Range

                ①远高于8位的精度

                ②单通道可超过1

                ③HDRI、真实世界 

                ④常用HDR图片存储的格式有hdr/tif/exr/raw等(其中很多是相机常用格式)

        3.动态范围(Dynamic Range) = 最高亮度 / 最低亮度

               Tone mapping:用于将颜色从原始色调(通常是高动态范围,HDR)映射到目标色调(通常是低动态范围,LDR),映射的结果通过介质进行显示,在人眼的视觉特性的作用下,达到尽可能复原原始场景的效果。    

  • 为什么需要HDR

        1.能有更好的色彩,更高的动态范围和更丰富的细节, 并且有效的防止画面过曝,超过亮度值1的色彩也能很好的表现,像素光亮度变得正常,视觉传达更加真实。

        2.HDR才有超过1的数值,才有光晕(bloom)的效果, 高质量的bloom能体现画面的渲染品质。

        3.HDR的来源图

  • Unity中的HDR

        1.Camera-HDR设置

         2.Lightmap HDR设置

        选择 High Quality 将启用 HDR 光照贴图支持,而 Normal Quality 将切换为使用 RGBM 编码。

        RGBM编码:将颜色存储在RGB通道中,将乘数(M)存储在Alpha通道中。

         3.拾色器的HDR设置:

         4.HDR的优缺点

       优点:

        ①画面中亮度超过1的部分不会被截掉,增加了亮部的细节,减少了曝光 

        ②减少画面暗部的色阶感   

        ③更好的支持bloom效果

        缺点:

        ①渲染速度慢,需要更多显存

        ②不支持硬件抗锯齿

        ③部分低端手机不支持

  • HDR与Bloom

        1.实现过程:

渲染出原图 → 计算超过某个阈值的高光像素 → 对高光的像素进行高斯模糊 → 叠加光晕、成图

        2.UnityBloom流程

  •  HDR与ToneMapping

        1.概念:

左图是线性映射示例,但不符合我们认知的真实效果。

         2.ACES曲线:

        ACES是Academy Color Encoding System学院颜色编码系统,最流行、最被广泛使用的Tonemapping映射曲线,效果:对比度提高,很好地保留暗处和亮处的细节。

        

 

  • LUT(Lookup Table)

        1.概念:就是滤镜,通过LUT,在LDR图里,你可以将一组RGB值输出为另一组RGB值,从而改变画面的曝光与色彩。

        2.3D LUT:调整RGB三个通道的LUT

 

        3.可以在ps中调整LUT,导出的LUT作为滤镜调整画面。

  • 作业 

IBL:基于图像的光照Image based lighting, IBL)是一类光照技术的集合。其光源不是可分解的直接光源,而是将周围环境整体视为一个大光源。现代渲染引擎中使用的IBL有四种常见类型:

        ①远程光探头,用于捕捉"无限远"处的光照信息,可以忽略视差。远程探头通常包括天空, 远处的景观特征或建筑物等。它们可以由渲染引擎捕捉, 也可以高动态范围图像的形式从相机获得.
        ②局部光探头,用于从特定角度捕捉世界的某个区域。捕捉会投影到立方体或球体上, 具体取决于周围的几何体。局部探头比远程探头更精确,在为材质添加局部反射时特别有用.
        ③平面反射,用于通过渲染镜像场景来捕捉反射。此技术只适用于平面,如建筑地板,道路和水。
        ④屏幕空间反射,基于在深度缓冲区使用光线行进方法渲染的场景,来捕捉反射。SSR效果很好,但可能非常昂贵。

        
原文链接:https://blog.csdn.net/JMXIN422/article/details/123180206(暂时不是很理解)

八、FlowMap的实现 流动效果

  • FlowMap

        1.概念:一张记录了2D向量信息的纹理,Flow map上的颜色(通常为RG通道)记录该处向量场的方向,让模型上某一点表现出定量流动的特征。通过在shader中偏移uv再对纹理进行采样,来模拟流动效果。

        2.优点:容易实现,且运算量较小。类似UV动画,而非顶点动画。换言之,无需对模型顶点进行操作,易实现,运算开销小。不仅仅是水面,任何和流动相关的效果都可以采用flowmap。

        3.例子

        4.前置了解:UV映射:        

 

         *不同的引擎uv坐标可能不同

  • FlowMap shader

        1.基本流程

                ①采样Flow map获取向量场信息

                ②用向量场信息,使采样贴图时的UV随时间变化

                ③对同一贴图以半个周期的相位差采集两次,并线性插值,使贴图流动连续

        2.实现方法

                ①目标:根据flowmap上的值,使纹理随时间偏移。

                    实现方法:uv-time(改变的不是顶点的位置)

                    uv+time:模型上某个点,随着time增加,采样到的像素越远,更远距离的像素偏移向该点,视觉效果和我们直观认识到的运算法则是相反的。

                ②目标:由flow map获取流动方向和方向

                    实现方法:flow map不能直接使用,要将flow map上的色值从[0,1]的范围映射到方向向量的范围[-1,1]。

//调整采样时的UV为: adjust_uv = uv - flowDir * time
//在这里我们用FlowSpeed来控制向量场的强度

//从flowmap获取向量
float3 flowDir = tex2D(_FlowMap, i.uv) * 2.0 - 1.0;

//flowSpeed影响向量强度,值越大,不同位置流速差越明显
flowDir *= -_FlowSpeed;

                ③目标:随着时间进行,变形越来越夸张,把偏移控制在一定范围内;周期性,无缝循环

                    实现方法:用相位差半个周期的两层采样进行加权混合,使纹理流动一个周期重新开始时的不自然情况被另一层采样覆盖

         3.用flow map修改法线贴图

  • Flow map的制作

        1.Flowmap painter:

下载地址:http://teckartist.com/?page_id=107

         2.Houdini Labs:

 

 

 

 

  •  作业

参考:https://blog.csdn.net/weixin_51327051/article/details/123577801

学习了#pragma shader_feature _REVERSE_ON的用法

左图为顶点动画,只能设置一个方向的流动;flowmap可以修改任意一点的流动方向。 

Shader "30_Flowmap"
{
    Properties
    {
        _MainTex ("MainTex", 2D) = "white" {}
        _FlowMap ("FlowMap", 2D) = "black" {}
        _FlowSpeed ("Intensity", range(0,10)) = 1.0
        _TimeSpeed ("FlowSpeed" , float) = 50.0
        [Toggle]_reverse("Reverse", Int) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Cull off
        Lighting off 
        ZWrite On

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            #pragma shader_feature _REVERSE_ON
            #include "UnityCG.cginc"

            sampler2D _MainTex; float4 _MainTex_ST;
            sampler2D _FlowMap; float4 _FlowMap_ST;
            float _FlowSpeed;
            float _TimeSpeed;

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                //UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                //UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //从flowmap获取向量,uv范围在0到1,向量范围在-1到1
                float4 flowDir = tex2D(_FlowMap, i.uv) * 2.0 - 1.0;
                //强度修正
                flowDir *= -_FlowSpeed;

                //正负修正
                #ifdef _REVERSE_ON
                    flowDir *= -1;
                #endif

                //周期
                float phase0 = frac(_Time * 0.1 * _TimeSpeed);
                float phase1 = frac(_Time * 0.1 * _TimeSpeed + 0.5);

                //设置平铺uv
                float2 uv = i.uv * _MainTex_ST.xy + _MainTex_ST.zw;

                //用波形函数周期化向量场方向,用偏移后的uv对材质进行偏移采样
                half3 tex0 = tex2D(_MainTex, uv - flowDir.xy * phase0);
                half3 tex1 = tex2D(_MainTex, uv - flowDir.xy * phase1);

                //构造函数计算随波形函数变化的权值,要使MainTex采样值在最大偏移时,其权值为0
                float flowLerp = abs((0.5 - phase0) / 0.5);
                half3 finalColor = lerp(tex0, tex1, flowLerp);
                float4 col = float4(finalColor,1.0);
                // apply fog
                //UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

九、GPU硬件架构

  • GPU

        1.GPU概述:Graphics Processing Unit,图形处理单元。它的功能最初与名字一致,是专门用于绘制图像处理图元数据的特定芯片,后来渐渐加入了其它很多功能。

        GPU是显卡最核心的部件,显卡还包括:散热器、通讯元件、与主板和显示器连接的各类插槽。

        2.GPU物理架构:由于纳米工艺的引入,GPU可以将数以亿记的晶体管和电子器件集成在一个小小的芯片内。从宏观物理结构上看,现代大多数桌面级GPU的大小跟数枚硬币同等大小,部分甚至比一枚硬币还小。显卡不能独立工作,需要装载在主板上,结合CPU、内存、显存、显示器等硬件设备,组成完整的PC机。

        3.GPU微观物理架构共同点:               

                GPC(图形处理簇)、TPC(纹理处理簇)、Thread(线程)、SM,SMX,SMM(Stream Multiprocessor,流多处理器)Warp线程束、Warp Scheduler(Warp编排器)、SP(Streaming Processor,流处理器)、Core(执行数学运算的核心)、ALU(逻辑运算单元)、FPU(浮点数单元)、SFU(特殊函数单元)、ROP(render output unit,渲染输入单元)、Load/Store Unit(加载存储单元)、L1 Cache(L1缓存)、L2 Cache(L2缓存)、Shared Memory(共享内存)、Register File(寄存器)

        *GPU为什么会有这么多层级且有这么多雷同的部件?答案是GPU的任务是天然并行的,现代GPU的架构皆是以高度并行能力而设计的。

        核心组件结构:GPC-->TPC-->SM-->CORE

                 SM包含Poly Morph Engine(多边形引擎)、L1 Cache(L1缓存)、Shared Memory(共享内存)、Core(执行数学运算的核心)等。

                CORE中包含ALU、FPU、Execution Context(执行上下文)、(Detch)、解码(Decode)。

        

猜你喜欢

转载自blog.csdn.net/weixin_56784984/article/details/127940509