第五章-开始Unity Shader学习之旅

一个最简单的着色器

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

Shader "Unity Shader Book/Chapter 5/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.0, 1.0, 1.0, 1.0);
            }

            ENDCG
        }
    }
}
  • Pass
    • 编译指令#pragma告诉Unity哪个函数包含顶点着色器的代码,哪个函数包含了片段着色器的代码,一般函数名会分别使用vertfrag,可读性更高
      • #pragma vertext func1
      • #pragma fragment func2
  • 语义:告知系统用户需要哪些输入值,以及用户的输出值,不可省略
    • POSITON:将告诉Unity要求把模型的顶点坐标填充到输入参数v中
    • SV_POSITION:将告知Unity顶点着色器的输出是裁剪空间中的顶点坐标
    • SV_Target:告知Unity将用户输出的颜色村粗到一个渲染目标(RT)中,这里是将输出到默认的帧缓冲中
  • 数据类型
    • Unity官方文档介绍:https://docs.unity.cn/cn/2020.3/Manual/SL-DataTypesAndPrecision.html
    • float4和fixed4的区别:高精度和低精度(还有一个half的中等精度)

调整顶点着色器输入参数

  • 背景:Vertext Shader期望能获取到更多的模型数据,例如纹理坐标、法线方向
  • 方案:结构体处理
  • 结构体代码模版
struct StructName {
	Type Name : Semantic;
	...
}
  • 完整代码示例
    • 注意:结构体声明后要加;,否则会存在Unity编译报错问题;
    • 数据来源:Unity会通过材质关联的Mesh Render组件中提供
    • 更多数据:可以参考官网文档在这里插入图片描述
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shader Book/Chapter 5/Simple Shader" {
    SubShader {
        Pass {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD; // 为啥是float4,而不是float2就好
            };

            float4 vert(a2v v) : SV_POSITION {
                return UnityObjectToClipPos(v.vertex);
            }

            fixed4 frag() : SV_Target {
                return fixed4(1.0, 1.0, 1.0, 1.0);
            }

            ENDCG
        }
    }
}

顶点着色器和片段着色器的通信处理

  • 背景:需要将顶点着色器中的模型的法线、纹理坐标等信息传入到片段着色器中进行运算
  • 方案:声明结构体 -> 顶点着色器输出改为结构体 -> 片段着色器输入改为结构体
  • 代码示例
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shader Book/Chapter 5/Simple Shader" {
    SubShader {
        Pass {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            
            // application to vertext shader
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD; // 为啥是float4,而不是float2就好
            };
            
            // vertext shader to fragment shader
            struct v2f {
                float4 pos : SV_POSITION;
                fixed3 color : COLOR0;
            };

            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                return fixed4(i.color, 1.0);
            }

            ENDCG
        }
    }
}
  • 效果展示:
    在这里插入图片描述

如何使用属性(Properties)

  • 坑点
    • 每个属性定义后添加会提示报错在这里插入图片描述
    • Pass中需要针对用到的属性需要重新定义,否则用到时会提示异常在这里插入图片描述
  • 效果展示:在这里插入图片描述

强大的援手:Unity的内置文件和变量

包含文件(include file)

  • 文件后缀:.cginc(盲猜:cg语言的include file)
  • 使用语法:#include "UnityCG.cginc"
  • 官方文档在这里插入图片描述
  • 本地查看:在这里插入图片描述

Unity提供的Cg/HLSL语义

  • 系统数值语义:SV(system-value)开头,一般这类数据在Unity的渲染管线中是关键的数据,一般不以SV开头也是可以使用,但一般为了跨平台可用(例如DX10中用SV_POSITION)会更推荐SV开头的语义;

程序员的烦恼:Debug

  • 背景:shader的debug难度较大,连基本的输出打印都不具备,而且调试方法也相对有限
假彩色图像(false-color image)
  • 方案:使用假彩色图像(false-color image):通过将调试数据转换为[0,1]之间并输出到屏幕上用以观察变量值是否符合预期
  • 评价:原始社会Debug手段
  • 过程编码错误
    • vertex写错成vertext
    • tangent.xyz * 0.5 + fixed3(0.5, 0.5, 0.5)+写成,
    • vert函数遗漏返回值o
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 5/False Color"
{
	SubShader
	{
		Pass
		{
			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag

			#include"UnityCG.cginc"

			struct v2f {
				float4 pos : SV_POSITION;
				fixed4 color : COLOR;
			};

			v2f vert(appdata_full v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				//o.color = fixed4(v.normal * 0.5 + fixed3(0.5, 0.5, 0.5), 1.0);


				// o.color = fixed4(v.tangent.xyz * 0.5 + fixed3(0.5, 0.5, 0.5), 1.0);


				//o.color = fixed4(v.texcoord.xy, 0.0, 1.0);
				
				o.color = fixed4(v.texcoord1.xy, 0.0, 1.0);

				// o.color = frac(v.texcoord);
				// if(any(saturate(v.texcoord) - v.texcoord)) {
				// 	o.color.b = 0.5;
				// }
				// o.color.a = 1.0;

				// o.color = v.color;
				return o;
			}

			fixed4 frag(v2f i): SV_Target
			{
				return i.color;
			}

			ENDCG
		}
	}
}

在这里插入图片描述

利用神器:Visual Studio
  • Grahics Debugger
最新利器:帧调试器(Frame Debugger)
  • 个人观点:类似RenderDoc,但由于是Unity自己定制的,所以类似用了哪个shader文件、VP矩阵等信息都可以显示出来
    在这里插入图片描述

渲染平台差异

  • DX和OpenGL的渲染纹理结果的差异(屏幕空间坐标系不一致)
  • Shader的语法差异
  • Shader的语义差异

Shader整洁之道

  • 精度选择float、half还是fixed
    • 桌面GPU:大多数目前都是默认按最高的浮点精度计算(即这三者等价),因此不一定能够看出来差异
    • 移动端GPU:有不同的精度范围,可以看出差异
    • fixed精度一般是老旧的移动平台使用,可以理解为现在大部分都是将half和fixed看作等价
    • 基本建议:尽可能使用较低的精度来优化shader的性能(尤其是移动端)
  • 规范语法:DX对Shader的语义有更严格的要求
  • 避免不必要的计算:通过预计算(离线计算)是更好的解决方案
  • 慎用分支和循环语句:降低GPU并行能力,性能成倍下降
  • 不要除0:出现未定义行为,例如在不同平台(软硬件环境)效果可能不一样(可能出现崩溃的情况)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值