《Unity Shader入门精要》学习--初级篇--正式见面,Unity Shader

·本专栏文章记录笔者阅读学习《Unity Shader入门精要》的感想与笔记,方便日后复习与查找


一、回顾Unity Shader的基本结构

包含Shader名字、Properties属性、SubShader、FallBack等语义块


二、顶点着色器与片元着色器

1.我的第一个Unity Shader

Shader "Unlit/SImple Shader"
{
   SubShader{  
      Pass{
             CGPROGRAM
             #pragma vertex vert    //编译指令
             #pragma fragment frag

             float4 vert(float4 v : POSITION) : SV_POSITION{
                return UnityObjectToClipPos(v);
             }

             fixed4 frag() : SV_Target{
                return fixed4(1,1,1,1);
             }
             ENDCG
         }
   }

   FallBack "Diffuse"
}

1.1.编译指令

#pragma vertex vert

#pragma frament frag

都是编译指令,用于告诉Unity,哪一个函数对应是顶点/片元着色器

1.2.数据来源

  • 模型顶点的坐标数据来自POSITION语义
  • 模型顶点法线的数据来自NORMAL语义
  • 模型第一套纹理数据来自TEXCOORD0语义(是的,还可以有好几套)

当我们需要同时往顶点着色器中传入这些数据的时候,我们可以使用一个结构体,来包含所有这些需要输入的数据(被语义修饰的变量,Unity会自动填入相关的数据)

2.顶点着色器与片元着色器的通信

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unlit/SImple Shader"
{
   SubShader{  
      Pass{
             CGPROGRAM
             #pragma vertex vert
             #pragma fragment frag

             struct a2v{
                float4 vertx : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
             };


             //这个结构体用来定义从顶点着色器到片段着色器的输出
             struct v2f{
                //SV_POSITION告诉Unity裁剪空间下的顶点位置存放在哪个变量里
                float4 pos : SV_POSITION;
                //COLOR可以用来存储颜色
                fixed3 col : COLOR;
             };


             v2f vert(a2v v){
                //声明输出的结构
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertx);
                o.col = v.normal * 0.5 + 0.5;   //将法线从[-1,1]映射到[0,1]
                return o;
             }

             fixed4 frag(v2f i) : SV_Target{
                return fixed4(i.col, 1);    //将颜色输出到屏幕上
             }
             ENDCG
         }
   }

   FallBack "Diffuse"
}

顶点着色器一般负责输出【顶点变换后的坐标】、【顶点法线数据】、【纹理坐标】等传递给片元着色器

通常要输出这么多数据的时候,我们也是通过一个结构体来进行存储

这个结构体中也需要包含相关的语义

  • SV_POSITION表示裁剪空间中的顶点坐标这个非常重要,必须包含!
  • COLOR0只是一个普通变量,可有可无,用处由用户自己处理

这个着色器的效果如图所示:

3.使用属性

步骤:

①在Properties语句块中声明这个属性变量

②在CGPROGRAM下再定义一个和①中名字和类型都相同的变量

这个表格列出了ShaderLab属性类型与CG变量类型的匹配关系


三、Unity内置文件的函数与变量

1.内置文件的位置

有两种办法查看这些内置文件

方法一:官网下载

Unity 2022.x Unity官方下载_Unity新版_从Unity Hub下载安装 | Unity中国官网

可以在官网去下载这些文件来进行查看

解压后可以看到非常多的这些我们在UnityShader中可以使用的内置文件

方法二:Unity应用程序文件中查找

一般是在 你的Unity安装的位置/Data/CGIncludes 

CGPROGRAM

#include "UnityCG.cginc"           //这样引入使用了

ENDCG

2.常用的内置文件与变量

Unity中常用的包含文件
文件名描述
UnityCGcginc包含了最常用的帮助函数、宏和结构体等
UnityShaderVariables.cginc在编译UnityShader的时候,会被自动包含进来,里面有很多内置的全局变量比如UNITY_MATRIX_MVP等
Lighting.cginc包含了各种内置的光照模型,如果编写的是Surface Shader的话,会自动包含进来
HLSLSupport.cginc在编译Unity Shader的时候,会被自动包含进来,生命力很多用于跨平台编译的宏和定义


UnityCG.cginc中一些常用的结构体
名称描述包含的变量
appdata_base可用于顶点着色器的输入顶点位置、顶点法线、第一组纹理坐标
appdata_tan可用于顶点着色器的输入顶点位置、顶点法线、顶点切线、第一组纹理坐标
appdata_full可用于顶点着色器的输入顶点位置、顶点法线、顶点切线、四组(更多)纹理坐标
appdata_img可用于顶点着色器的输入顶点位置、第一组纹理坐标
v2f_img可用于顶点着色器的输出裁剪空间中的位置、纹理坐标

UnityCG.cginc中一些常用的帮助函数
函数名描述
float3 WorldSpaceViewDir(float4 v)输入一个模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向
float3 ObjSpaceViewDir(float4 v)输入一个模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察方向
float3 WorldSpaceLightDir(float4 v)仅可用于前向渲染。输入一个模型空间中的顶点位置,返回从世界空间中从该顶点到光源的光照方向。没有归一化
float3 ObjSpaceLightDir(float4 v)仅可用于前向渲染。输入一个模型空间中的顶点位置,返回从模型空间中从该顶点到光源的光照方向。没有归一化
float3 UnityObjectToWorldNormal(float3 norm)把法线方向从模型空间转化到世界空间
float3 UnityWorldToObjectDir(float3 dir)把方向向量从世界空间转化到模型空间
float3 UnityObjectToWolrdDir(float3 dir)把方向向量从模型空间转化到世界空间

显然可以看出,顶点一般都是四维表示(float4),而矢量是三维表示(float3)

这是因为顶点包含位置信息,而矢量不需要


四、基础语义

语义的作用是让Unity知道把数据输入到哪里,以及从哪里再获取到数据;让Shader知道把数据读取,以及输出到哪里

常见的读取用的语义有:

1.从应用阶段到顶点着色器阶段的(a2v)

  • POSITION:模型空间的顶点位置(float4)
  • NORMAL:顶点法线(float3)
  • TANGENT:顶点切线(float4)
  • TEXCOORDn(例如TEXCOORD1):顶点的纹理坐标(可以有好几组)(float2或float4)
  • COLOR:顶点颜色(fixed4或者float4)

2.从顶点着色器到片元着色器阶段的(v2f)

  • SV_POSITION:裁剪空间中的顶点坐标。结构体中必须有一个(虽然有的POSITION也可以,但是最好都用SV_POSITION了现在)
  • COLOR0:通常用于输出第一组顶点颜色,但不是必须的
  • COLOR1:通常用于输出第二组顶点颜色,但不是必须的
  • TEXCOORD0~TEXCOORD7:通常用于输出纹理坐标,但不是必须的

其实除了SV_POSITION之外,剩下三个里面装的是什么数据其实都不重要,可以装一些你想要传到片元着色器中的数据


五、如何Debug调试

1.把要调试的变量映射到[0,1]作为颜色显示在材质上

一维分量,那就选择一个单独颜色分量(R)进行调试输出,其他设置为0

多维数据,选择其中一个分量进行单独调试,或者选择多个颜色分量进行输出

虽然有些原始,但是大部分情况下还是可以用的。

通过像素颜色的值判断它对不对

比如这个是把法线值映射到[0,1]之后作为颜色输出到屏幕上

2.VS--调试之神

VS2012以上版本有了Graphics Debugger可以使用。

具体操作可以去官网看的VS对Unity Shader的调试功能手册(需要科学上网)

3.Frame Debugger

Unity中自带的

可以这样打开它,然后可以滑动滑块看它一步一步的渲染过程

这些以Draw开头的就是一个Draw Call


六、不同平台的差异

1.渲染纹理坐标的差异

我们通过渲染纹理来保存输出到不同渲染目标上的 渲染结果

大多数情况下,不同屏幕空间的差异不会造成影响

使用渲染到纹理技术(RTT)的时候:把屏幕图像渲染到一张渲染纹理中(图中黄色的箭头),不处理的话就会导致纹理翻转过来。(但是好在Unity一般会帮助我们自动翻转(翻转的是采样纹理坐标UV)来避免这个问题)

因为抓取屏幕图像这一操作是由硬件指令发出的,不同硬件支持的图像API接口可能是不一样的(有的是OpenGL,有的是DirectX),而不同的图像API就可能会使得截取过程使用的坐标系不同,导致截取出来的图像数据的排列顺序是相反的(支持的是DirectX时),这时候再用这个数据去采用渲染到渲染纹理上的时候(按OpenGL标准),那纹理自然就会翻转了

Unity自动翻转失效

当我们开启了抗锯齿之后使用了渲染到纹理(RTT)技术

  • 开启MSAA后,抗锯齿处理是​​由显卡硬件完成​​的,Unity只能拿到硬件处理后的结果纹理
  • 硬件处理抗锯齿时跳过了Unity的坐标翻转层(ComputeGrabScreenPos失效),导致纹理保持DirectX原生格式(左上角原点)
  • 当使用Unity提供的Graphics.Blit()进行全屏绘制时,引擎会在内部自动处理坐标系转换
  • 当同时处理屏幕图像和法线纹理时,这些图像在竖直方向朝向可能不同
  • 对非Blit传递的纹理(如深度图/脚本传递的RT),需主动在顶点着色器翻转坐标

此时只有我们手动翻转这些没有自动翻转的纹理了

#if UNITY_UV_STARTS_AT_TOP    //判断当前平台是否为DirectX

if(_MainTex_TexelSize.y < 0)   //任意纹理都可以这样判断是否需要改变采样方式
        uv.y = 1-uv.y

#endif

2.Shader语法差异

2.1.变量赋值不全

Dierct9/11对Shader语义更加严格

float4 v = float4(0.0) 只在OpenGL中可以

float4 v = float4(0.0,0.0,0.0,0.0) 在DirectX和OpenGL都可以

2.2.初始化问题

如果顶点着色器中没有对所有要out修饰的参数进行初始化,那也是报错

void vert (inout appdata_full v, out Input o) {

    //使用Unity内置的UNITY_INITIALIZE_OUTPUT宏进行初始化
    UNITY_INITIALZE_OUTPUT(Input,o);

}

这样子之后才不会报错

3.Shader语义差异

  • 有的顶点着色器输出裁剪空间顶点坐标位置的变量语义修饰是POSITION,有的是SV_POSITION
  • 有的Shader的片元着色器的输出颜色的变量的语义是COLOR0,有的是SV_Target

总而言之,都用SV_POSITION和SV_Target就好了


七、编写Unity Shader的注意事项

①尽量用fixed,它和half以及float的区别只是精度更低而已,它们都是浮点值(对移动平台友好)

②更加严格的语法,向DirectX的要求看齐

③不要在Shader中进行大量的运算,这不是GPU该干的事

④慎用循环与分支(是要要用那就尽量条件变量是常数、分支中操作尽可能少、分支嵌套数尽可能少)

⑤不要除以0


八、总结

①顶点着色器中接受的数据是从应用中提供的(Mesh Render组件),这些数据通过语义来告诉它要存在哪个变量里。而从顶点着色器到片元着色器中提供的数据也是通过语义来指定的,一些内置的函数以及数据结构可以帮助我们完成很多基础的变量传递变换操作。

②顶点着色器与片元着色器是哪个函数是通过编译指令来指定的

③对Shader进行Debug的方法有颜色输出观察法、VS调式、和Frame Debugger三种

④平台不同可能会导致一些最终渲染结果的纹理图像有所差异,需要针对处理

⑤编写Shader的时候要尽可能严格语法,并且考虑不同平台的适配性,以及尽可能进行优化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萘柰奈

谢谢老板喵

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值