Advertisement

Unity Shader入门精要——笔记

阅读量:

第1篇 基础篇

第2章:渲染流水线

GPU流水线

该系统采用分层架构实现图形处理流程:首先对输入的3D模型进行几何信息解析与转换;随后通过一系列图形处理单元完成模型的细分、法线计算以及最终的渲染准备;整个过程由四大部分组成:首先是预处理模块将原始顶点数据转换为适合后续运算的形式;其次是光线追踪系统中的光线采样过程;最后是图形输出接口用于将计算结果呈现给最终用户。

顶点着色器:其核心功能是将模型的顶点坐标从模型空间实现到齐次裁剪坐标(通常通过构建一个矩阵来完成这一过程, 即 MVP 矩阵)。

第4章:学习Shader所需的数学基础

数学基础

矢量减法 b-a :理解为b点相对于a点的向量,再坐标系中以a点为尾,b点为头

点积(内积):满足交换律结合律

  1. 可以用来求b在单位向量a上的投影长度【能够计算出相对于单位向量a的标量投影

叉积(外积):只满足反交换律(axb=-bxa)

  1. |axb|表示a与b这两个向量所形成的正方形面积;若a与b平行,则其叉积为零。
  2. axb是两个向量之间的垂直方向,并且其方向由右手法则确定。

矩阵乘法:不满足交换律,满足结合律

顶点的坐标空间变换过程

  • 第一步:通过将顶点坐标从模型空间经过世界空间的转换来完成模型变换。
  • 第二步:通过将顶点坐标从世界空间经过观察空间的转换来完成观察变换。
  • 第三步:通过将顶点坐标从观察空间经过裁剪空间(齐次裁剪坐标系)的转换来完成投影变换。
  • 第四步:首先,在齐次裁剪(Normalized Device Coordinates, NDC)生成的过程中,在齐次裁剪坐标系下对各顶点坐标的w分量进行齐次除法运算以获得归一化设备坐標;随后,在屏幕上应用该过程计算出相应的#D像素位置。

第2篇 初级篇

第6章:标准光照模型

环境光

cambient=gambient c_{ambient}=g_{ambient}

自发光

cemissive=memissive c_{emissive}=m_{emissive}

漫反射

兰伯特光照模型

c_diffuse = (c_light · m_diffuse) \cdot \max(0, \widehat{n} \cdot I)
其中n̂表示表面法线方向单位向量,I代表单位向量朝向光源方向,m_diffuse为材质的漫反射颜色,c_light对应于光源的颜色值。为了避免法向量与光源方向的点积可能出现负值的情况,我们采用最大值函数将结果截取在非负数范围内,从而确保物体不会因来自后方光源的照射而被错误地点亮。

半兰伯特光照模型

广义的半兰伯特光照模型公式如下:
cdiffuse=(clight⋅mdiffuse)(α(n^⋅I)+β) c_{diffuse}=(c_{light}\cdot m_{diffuse})(\alpha(\widehat{n}\cdot I)+\beta)
与原兰伯特模型相比,半兰伯特光照模型没有使用maxmax操作来防止nn和II的点积为负值,而是对其结果进行了一个α\alpha倍的缩放再加上一个β\beta大小的偏移。绝大多数情况下,α\alpha和β\beta的值均为0.5,即公式为:
cdiffuse=(clight⋅mdiffuse)(0.5(n^⋅I)+0.5) c_{diffuse}=(c_{light}\cdot m_{diffuse})(0.5(\widehat{n}\cdot I)+0.5)
通过这样的公式,可以把n^⋅I\widehat{n}\cdot I的结果范围从[-1,1]映射到[0,1]范围内,也就是说,对于模型的背光面,在原兰伯特光照模型中点积结果将映射到同一个值,即0值处;而在半兰伯特模型中,背光面也可以又明暗变化,不同的点积结果会映射到不同的值上。

值得注意的是,在目前的研究中所指的半兰伯特方法并不存在明确的物理依据支撑;它本质上是一种依赖于视觉效果的技术手段

高光反射

在硬件实现过程中, 当摄像机与光源与场景间的距离足够远时, Blinn光照算法将显著优于Phong算法, 其原因在于此时计算出的v_hat与I_hat均为恒定值, 因而h_hat成为一个固定值. 然而, 在某些情况下(如当v_hat或I_hat并非固定值时), 反而可能使得Phong算法成为更为高效的选择. 需要注意的是, 这两种光照算法本质上均属于经验性的工具, 即不应误以为Blinn算法是针对"理想"情况下的Phong算法近似. 实际应用中, 我们发现这种差异在特定条件下更为显著.

Phong光照模型

确定反射方向:
r = 2(n̂·I)n̂ - I r = 2(\widehat{n} \cdot I)\widehat{n}-I
基于Phong模型计算高光反射部分:
cc_specular = (c_light ⋅ m_specular) * max(0, ĉ_r ⋅ v̂)^{m_gloss} c_{specular} = (c_{light} \cdot m_{specular}) \times \text{max}(0, \widehat{r}\text{refl} \cdot \widehat{v})^{m\text{gloss}}
其中n̂\widehat{n}\代表表面法线向量;而ĉ_r\widehat{r}\text{refl}\代表单位化的入射光线;I表示光源的方向向量;cc_specular\ c{specular}\则存储材质对高光反射的颜色和强度信息;当m_gloss值越大时(即当m_\text{gloss}值越大时),该材质对于强光的响应会更加显著;而当该值较小时,则会对弱光更加敏感。另外需要注意的是,在处理光照效果时需要注意避免出现负值的结果。

Blinn-Phong光照模型

该方法的核心理念在于避免直接计算反射方向\widehat{r}。为了实现这一目标,Blinn模型引入了一个新的矢量\widehat{h}。这一矢量是通过将\widehat{v}\widehat{I}取平均后再进行归一化得到的。具体来说:

\widehat{h} = \frac{\widehat{v} + \widehat{I}}{\left|\widehat{v} + \widehat{I}\right|}

随后,则基于\widehat{n}\widehat{h}之间的夹角来进行后续计算(而非直接使用\widehat{v}\widehat{r}之间的夹角)。具体来说:

c_{\text{specular}} = (c_{\text{light}} \cdot m_{\text{specular}})^{\max(0, \ widehat{n} \cdot \ widehat{h})^{m_{\text{gloss}}}}

Shader 常用函数

复制代码
    //取得小数部分  
    frac(x);  
      
    //将x限制在[0,1]范围中  
    saturate(x)  
      
    //返回一个布尔值,如果输入的向量的任何分量为非零,返回true,否则返回false  
    any(x)  
    
    //环境光颜色
    UNITY_LIGHTMODEL_AMBIENT
    
    //世界空间下的光源方向
    _WorldSpaceLightPos0
    
    //光源的颜色与强度信息(注意,想要的到正确的值需要定义合适的LightMode标签)
    _LightColor0
    
    //用于高光反射中计算反射方向reflectDir,CG的reflect函数的入射方向要求是由光源指向交点处的,因此需要对worldLightDir取反后再传给reflect函数
    reflect(-worldLightDir, worldNormal));
    
    
    shaderlab
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-18/xNw3ZO7GqDIjRM8krA290XumfJge.png)

第7章:基础纹理

凹凸映射

  1. 高度映照:通过一张高度纹理模拟表面的形变,并计算出一个修正后的法线值。
  2. 法向量映照:通过一张法线纹理直接编码表面的法向量信息。
高度纹理

高度图中所存储的数据代表不同区域间的地形起伏情况,在计算机图形学中被广泛应用于表现三维模型表面的几何细节。具体而言,在模型表面局部的位置上设置不同的色彩值能够直观地反映其相对于周围环境的空间关系:当某区域的颜色较浅时,则表示该区域向外凸出的程度较大;反之,则意味着该区域向内凹陷的程度更为显著。

法线纹理

法线纹理存储的就是表面的法线方向,由于法线方向的分量范围在[-1,1],而像素的分量范围为[0,1],因此需要做一个映射:
pixel=normal+12 pixel = \frac{normal +1}{2}
在Shader中对法线纹理进行纹理采样后,还需要对结果进行一次反映射的过程,以得到原先的法线方向。反映射的过程实际就是使用上面映射函数的逆函数:
normal=pixel∗2−1 normal=pixel*2-1

Shader 常用函数

复制代码
    //_MainTex_ST可以得到该纹理的缩放和平移值,_MainTex_ST.xy存储的是缩放值,_MainTex_ST.zw存储的是偏移值;首先使用缩放属性对纹理坐标进行缩放,然后再使用偏移属性对结果进行偏移。
    o.uv=v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
    //Unity提供了一个内置宏TRANSFORM_TEX来计算上述过程。
    o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
    
    //使用内置的UnpackNormal函数对法线进行采样和解码(需要把法线纹理的格式标识成Normal map)
    fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));  
    bump.xy *= _BumpScale;  
    bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
    
    
    shaderlab
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-18/Qgy5YnbdvRAISO4KfqtTk6xFawuE.png)

第8章:透明效果

基础概念

透明度测试 :它采用一种“霸道极端”的机制,只要一个片元的透明度不满足条件(通常是小于某个阀值),那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理也不会对颜色缓冲产生任何影响:否则,就会按照普通的不透明物体的处理方式来处理它,即进行深度测试、深度写入等。也就是说,透明度测试是不需要关闭深度写入的,它和其他不透明物体最大的不同就是它会根据透明度来舍弃一些片元。虽然简单,但是它产生的效果也很极端,要么完全透明,即看不到,要么完全不透明,就像不透明物体那样。
透明度混合 :这种方法可以得到真正的半透明效果。它会使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。但是,透明度混合需要关闭深度写入(我们下面会讲为什么需要关闭),这使得我们要非常小心物体的渲染顺序。需要注意的是,透明度混合只关闭了深度写入,但没有关闭深度测试。这意味着,当使用透明度混合渲染一个片元时,还是会比较它的深度值与当前深度缓冲中的深度值,如果它的深度值距离摄像机更远,那么就不会再进行混合操作。这一点决定了,当一个不透明物体出现在一个透明物体的前面,而我们先渲染了不透明物体,它仍然可以正常地遮挡住透明物体。也就是说,对于透明度混合来说,深度缓冲是只读的。

混合命令

复制代码
    //混合因子命令
    Blend SrcFactor DstFactor
    Blend SrcFactor DstFactor,SrcFactorA DstFactorA

进行加法时使用的混合公式:

O_{rgb}等于源因子乘以S_{rgba}加上目标因子乘以D_{rgba}

混合操作

复制代码
    BlendOp BlendOperation
    
    //如
    //将混合后的源颜色和目的颜色相加。默认的混合操作。
    BlendOp Add
    //将混合后的源颜色减去目的颜色。
    BlendOp Sub
    //将混合后的目的颜色减去源颜色。
    BlendOp RevSub
    //使用源颜色和目的颜色中较小的值,是逐分量比较的。
    BlendOp Min
    //使用源颜色和目的颜色中较大的值,是逐分量比较的。
    BlendOp Max
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-18/7Bf4VruI1tC8LPTbvdG9We3ajOol.png)

常见的混合类型

复制代码
    //正常(Wormal),即透明度混合
    Blend SrcAlpha OneMinusSrcAlpha
    
    //柔和相加(Soft Additive)
    Blend OneMinusDstColor One
    
    //正片叠底(Multiply),即相乘
    Blend DstColor Zero
    
    //两倍相柔(2x Multiply)
    Blend DstColor SrcColor
    
    //变暗(Darken)
    BlendOp Min
    Blend One One
    
    //变亮(Lighten)
    BlendOp Max
    Blend One One
    
    //滤色(Screen)
    Blend OneMinusDstColor One
    //等同于
    Blend One OneMinusSrcColor
    
    //线性减淡(Linear Dodge)
    Blend One One
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-18/hYpeQ5WVBXyu3txdNS2MjmOb6D4c.png)

Shader 常用函数

复制代码
    //透明度测试
    SubShader{
    	Tag
    	{
    		//在AlphaTest渲染队列
    		"Queue"="AlphaTest"
    		//意味着这个SubShader不会受到投影器(Projectors)的影响
    	    "IgnoreProjector"="True"
    	    //加入提前定义的组中
    	    "RenderType"="TransparentCutout"
    	}
    	Pass{
    		···
    	}
    }
    
    //透明度混合
    SubShader{
    	Tag
    	{
    		//在Transparent渲染队列
    		"Queue"="Transparent"
    		//意味着这个SubShader不会受到投影器(Projectors)的影响
    	    "IgnoreProjector"="True"
    	    //加入提前定义的组中
    	    "RenderType"="Transparent"
    	}
    	Pass{
    		//关闭深度写入
    		ZWrite Off
    		//设置Blend的混合模式
    		Blend SrcAlpha OneMinusSrcAlpha
    		···
    	}
    }
    
    函数: void clip(foat4 x); void clip(float3 x); void clip(float2 x); void clip(floatl x); void clip(float x);
    参数:裁剪时使用的标量或矢量条件。
    描述:如果给定参数的任何一个分量是负数,就会舍弃当前像素的输出颜色。它等同于下面的代码:
    void clip(float4 x)
    {
    	if( any( x < 0 ) )
    		discard;
    }
    
    //渲染命令,用于设置颜色通道的写掩码(write mask)
    //当ColorMask设为0时,意味着该Pass不写入任何颜色通道,即不会输出任何颜色。
    ColorMask RGB | A | 0 | 其他任何R、G、B、A的组合
    
    
    
    shaderlab
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-18/Fjm5CcDrLvNH7sPdwJRI6iX1TE3h.png)

第3篇 中级篇

第9章:更复杂的光照

Shader 常用函数

复制代码
    //阴影计算三剑客
    #include "AutoLight.cginc"
    //声明一个用于对阴影纹理采样的坐标,参数是下一个可用的插值寄存器的索引值
    SHADOW_COORDS(2)
    //在顶点着色器种计算声明的阴影纹理坐标
    TRANSFER_SHADOW(o)
    //在片元着色器种计算阴影值
    SHADOW_ATTENUATION(i)
    
    //Unity内置的用于计算光照衰减和阴影的宏,它会将光照衰减和阴影值相乘后的结果存储到第一个参数种;第二个参数是结构体v2f;第三个参数是世界空间的坐标,它会用于计算光源空间下的坐标,再对光照衰减纹理采样来得到光照衰减。
    UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
    
    
    shaderlab
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-18/1QksHhyXaGLRWgd2i9MI7BTem8Pc.png)

第10章:高级纹理

Shader 常用函数

复制代码
    //Compute th reflect dir in world space  
    o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);
    
    //Compute the refract dir in world space  
    o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);
    
    
    shaderlab

第11章:让画面动起来

Shader 常用函数

复制代码
    //ShadowCaster Pass
    Tags {"LightMode"="ShadowCaster"}
    
    #pragma multi_compile_shadowcaster
    
    struct v2f  
    {
    	//在v2f结构体中定义阴影投射需要定义的变量
    V2F_SHADOW_CASTER;  
    };
    
    //顶点着色器中
    TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
    
    //片元着色器中,把结果输出到深度图和阴影映射纹理中
    SHADOW_CASTER_FRAGMENT(i);
    
    
    shaderlab
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-18/yHrn6NXLGFqcVDl2EgdC0vUxaSmP.png)

全部评论 (0)

还没有任何评论哟~