基本结构
Shader "MyShaderName" {
Properties {
// 属性
}
SubShader {
// 针对显卡A的SubShader
Pass {
// 设置渲染状态和标签
Tags { "LightMode"="ForwardBase" }
// 开始Cg代码片段
CGPROGRAM
// 该代码片段的编译指令,例如:
#pragma vertex vert
#pragma fragment frag
// Cg代码写在这里
ENDCG
// 其他设置
}
// 其他需要的Pass
}
SubShader {
// 针对显卡B的SubShader
}
// 上述SubShader都失败后用于回调的Unity Shader
Fallback "VertexLit"
}
CGINCLUDE
SubShader {
CGINCLUDE
...
ENDCG
...
这些代码不需要包含在任何Pass语义块中,在使用时,我们只需要在Pass中直接指定需要使用的顶点着色器和片元着色器函数名即可。CGINCLUDE类似于C++中头文件的功能。由于高斯模糊需要定义两个Pass,但它们使用的片元着色器代码是完全相同的,使用CGINCLUDE可以避免我们编写两个完全一样的frag函数。
矩阵
UNITY_MATRIX_MVP
UNITY_MATRIX_MVP 是 Unity 引擎中的一个内置矩阵,表示 Model-View-Projection(模型-视图-投影)变换的组合矩阵。
Model-View-Projection(MVP)变换是一种常用的计算机图形学技术,用于将三维模型从模型空间(Model Space)转换到屏幕空间(Screen Space)。它由以下三个矩阵的乘积组成:
-
Model Matrix(模型矩阵):将三维模型从其局部坐标系变换到世界坐标系中的位置、旋转和缩放。
-
View Matrix(视图矩阵):定义观察者或相机的位置和朝向,将世界坐标系中的物体变换到观察者的视角。
-
Projection Matrix(投影矩阵):定义透视或正交投影的方式,将观察空间中的物体映射到裁剪空间。
UNITY_MATRIX_MVP 矩阵是以上三个矩阵的乘积。它将三维模型从模型空间经过模型矩阵变换到世界坐标系,再经过视图矩阵变换到观察空间,最后经过投影矩阵映射到裁剪空间。该矩阵的结果可以用于将顶点坐标从三维空间投影到屏幕上的二维坐标。
在 Unity 的着色器编程中,通过使用 UNITY_MATRIX_MVP 矩阵,可以将顶点的位置和法线等数据进行变换,并进行下一步的光照、纹理映射等操作,以生成最终的渲染结果。
Unity_WorldToObject
在Unity中,Unity_WorldToObject
是一个内置的着色器变换矩阵(Shader Transform Matrix)。它用于将世界坐标系下的位置转换为物体本地坐标系下的位置。
在片段着色器中,使用 Unity_WorldToObject
可以方便地进行从世界空间到物体空间的转换,以便在物体空间下执行一些特定计算或操作。例如,你可以使用这个变换矩阵来计算从相机到像素的距离,或者对位置进行局部偏移。
以下是一个示例,展示了如何在片段着色器中使用 Unity_WorldToObject
进行位置转换:
float4 fragShader(float4 vertex : SV_POSITION) : SV_Target
{
// 将顶点位置从裁剪空间转换为世界空间
float4 worldPosition = mul(unity_ObjectToWorld, vertex);
// 将世界空间位置转换为物体空间
float4 objectPosition = mul(unity_WorldToObject, worldPosition);
// 在物体空间下执行一些计算或操作
// ...
return float4(1.0, 1.0, 1.0, 1.0); // 返回结果颜色
}
在上述示例中,我们首先将顶点位置从裁剪空间转换为世界空间,然后使用 Unity_WorldToObject
矩阵将世界空间位置转换为物体空间。之后,你可以在物体空间下对位置进行计算或操作。
需要注意的是,在使用 Unity_WorldToObject
时,你需要确保该着色器变量是正确设置的,并且在合适的上下文环境中使用它。通常情况下,Unity会自动为内置的着色器提供这些变量,但如果你使用自定义着色器,可能需要手动传递这些变量。
总而言之,Unity_WorldToObject
可以帮助你在片段着色器中将世界坐标转换为物体本地坐标,为你提供了更多的灵活性和控制权来执行特定的计算或操作。
unity_ObjectToWorld
在 Unity Shader 中,unity_ObjectToWorld
是一个变换矩阵,它用于将顶点从对象空间转换到世界空间。它是一个 4x4 的矩阵,表示了物体从本地坐标系到世界坐标系的变换。
通过使用 unity_ObjectToWorld
,我们可以将对象空间的顶点坐标转换为世界空间的顶点坐标。例如,如果我们需要在顶点着色器中计算每个顶点到摄像机的距离,可以使用以下代码:
float3 worldPosition = mul(unity_ObjectToWorld, v.vertex).xyz;
float distanceToCamera = length(_WorldSpaceCameraPos.xyz - worldPosition);
在上述示例中,v.vertex
是当前顶点的对象空间坐标。我们使用 mul()
函数将 v.vertex
乘以 unity_ObjectToWorld
矩阵,得到当前顶点在世界空间的坐标 worldPosition
。然后,我们计算当前顶点与摄像机之间的距离,将结果保存在 distanceToCamera
变量中。
除了顶点位置的转换,unity_ObjectToWorld
还可以用于转换法线向量、切线向量等其他属性。例如,如果我们需要在片段着色器中使用世界空间的法线向量进行光照计算,可以使用以下代码:
float3 worldNormal = normalize(mul((float3x3)unity_ObjectToWorld, v.normal));
在上述示例中,v.normal
是当前顶点的对象空间法线向量。我们通过将 unity_ObjectToWorld
转换为一个 3x3 的矩阵,并将其与 v.normal
相乘,得到当前顶点在世界空间的法线向量 worldNormal
。最后,我们使用 normalize()
函数将其归一化,以便在光照计算中使用。
总之,unity_ObjectToWorld
在 Unity Shader 中是一个非常有用的变换矩阵,用于将顶点从对象空间转换到世界空间,以及转换其他属性如法线向量、切线向量等。
函数
saturate
函数 :saturate(x )
参数 :x :为用于操作的标量或矢量,可以是float、float2、float3等类型。
描述 :把x 截取在[0, 1]范围内,如果x 是一个矢量,那么会对它的每一个分量进行这样的操作。
mul
在着色器编程中,“mul” 运算符的乘法操作是将左侧的矩阵或向量与右侧的矩阵或向量相乘。也就是说,mul(a, b) 表示将矩阵或向量 b 左乘矩阵或向量 a。
例如,如果我们有两个矩阵 A 和 B,想要将它们相乘并得到结果矩阵 C,那么我们可以使用 “mul” 运算符进行左乘操作,如下所示:
float4x4 A;
float4x4 B;
float4x4 C = mul(A, B);
在上述代码中,矩阵 B 左乘矩阵 A,并将结果存储在矩阵 C 中。
同样地,如果我们有一个向量 v 和一个矩阵 M,想要将它们相乘并得到新的变换后的向量 w,我们可以使用 “mul” 运算符进行左乘操作,如下所示:
float3 v;
float4x4 M;
float4 w = mul(float4(v, 1), M);
在上述代码中,向量 v 左乘矩阵 M,并将结果存储在向量 w 中。在这种情况下,需要注意的是,为了进行矩阵乘法运算,向量 v 被转换为齐次坐标向量(即添加了一个 w 分量为 1)。
老版本unity常用于将模型空间顶点坐标转换为
mul(UNITY_MATRIX_MVP, v.vertex)
新版本unity用
UnityObjectToClipPos(v.vertex)
因为顶点是float4 类型,1x4矩阵
tex2d
在计算机图形学中,Shader 中的 tex2D 函数用于对纹理进行采样,并返回采样到的纹素值。纹素值通常是一个颜色向量,表示在纹理坐标位置上获取到的颜色值。
纹素(texel)是纹理中最小的单位,类似于像素。每个纹素可以包含一个或多个通道,例如 RGB、RGBA 或其他颜色空间等。当使用 tex2D 函数时,它会将纹理坐标作为输入,并从纹理中获取相应的纹素值。这个纹素值通常存储在一个变量中,例如 float4 类型的变量。
在大多数情况下,我们使用的纹理都是二维的,因此 tex2D 函数通常返回一个 float4 类型的向量,其中包含了纹理采样得到的四个分量:R、G、B 和 A。这个向量可以用来进行颜色混合、光照计算、阴影处理等操作。
需要注意的是,在一些特殊情况下,我们可能需要对三维纹理、立方体贴图等进行采样。这些情况下,tex2D 函数返回的变量类型可能会有所不同,例如 float3 类型的向量。在这种情况下,我们可以通过查看着色器程序代码中变量的定义来确定纹素值的类型和通道数。
一般使用
1.顶点着色器中,先使用TRANSFORM_TEX 转换成UV
2.片段着色器中,对纹理图,uv进行采样,使用tex2D
v2f vert (a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//把两个uv放入到一个float4中,减少占用的插值寄存器空间
//TRANSFORM_TEX得到纹理uv坐标
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex) + frac(float2(_ScrollX, 0.0) * _Time.y);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _DetailTex) + frac(float2(_Scroll2X, 0.0) * _Time.y);
return o;
}
fixed4 frag (v2f i) : SV_Target {
fixed4 firstLayer = tex2D(_MainTex, i.uv.xy);
fixed4 secondLayer = tex2D(_DetailTex, i.uv.zw);
fixed4 c = lerp(firstLayer, secondLayer, secondLayer.a);
c.rgb *= _Multiplier;
return c;
}
在 Unity 的着色器语言中(通常是使用的是 ShaderLab),tex2D
是一个用于从纹理中采样颜色的函数。它的用法如下:
fixed4 color = tex2D(sampler, uv);
其中:
sampler
是一个采样器(sampler),用于指定要采样的纹理。uv
是一个二维向量,表示纹理坐标,用于确定在纹理中的采样位置。
tex2D
函数的返回值是一个 fixed4
类型的颜色,表示在指定纹理坐标处采样得到的颜色值。这个颜色值通常是 RGBA 格式的,分别表示红、绿、蓝和 Alpha 通道的值。
在使用 tex2D
函数时,通常需要将纹理绑定到一个采样器上,然后通过采样器来进行采样。例如,在 Unity 的 ShaderLab 中,可以通过以下方式定义一个采样器并绑定纹理:
sampler2D _MainTex;
然后在着色器代码中使用 tex2D
函数来进行采样:
fixed4 color = tex2D(_MainTex, uv);
这样就可以在指定的纹理坐标 uv
处采样纹理 _MainTex
,并得到采样得到的颜色值 color
。
UnpackNormal
在Unity中,如果你使用了法线贴图(Normal Map),通常需要将其标记为法线贴图。这样Unity的渲染管线会使用正确的解压缩方法进行法线重构。
要标记法线贴图,你可以在材质的属性面板中找到纹理属性并选择合适的纹理类型。对于法线贴图,你可以选择"Normal Map"或者"Normal Map (from Heightmap)"。
当你选择了合适的纹理类型后,Unity会自动应用正确的纹理采样和解压缩方法,以保证法线贴图的正确显示和渲染。
如果你没有将法线贴图标记为法线贴图,你可以使用"UnpackNormal"函数来手动解压缩法线贴图。通过使用"UnpackNormal"函数,你可以在着色器中将纹理采样的结果转换为法线向量,然后再进行后续的计算和操作。
总结起来,在Unity中,如果你使用了法线贴图,通常需要将其标记为法线贴图以便Unity能够正确处理。你可以在材质的属性面板中选择合适的纹理类型来标记法线贴图。如果没有标记法线贴图,你可以使用"UnpackNormal"函数来手动解压缩法线贴图。
法线纹理中存储的就是表面的法线方向。由于法线方向的分量范围在[−1, 1],而像素的分量范围为[0, 1],因此我们需要做一个映射,通常使用的映射就是:
Shader中对法线纹理进行纹理采样后,还需要对结果进行一次反映射的过程,以得到原先的法线方向。反映射的过程实际就是使用上面映射函数的逆函数:
normal =pixel ×2−1
clip
变量
WorldSpaceLightPos0
在 Unity Shader 中,_WorldSpaceLightPos0
代表的是灯光在世界空间中的位置或方向。对于点光源和聚光灯,它代表的是灯光的位置;而对于方向光,它代表的是灯光的方向。
因此,如果我们使用 _WorldSpaceLightPos0.xyz
来获取方向光的方向,那么它就是从世界空间原点指向方向光的方向。如果我们需要将这个方向转化为从模型指向方向光的方向,我们需要将其乘以模型矩阵的逆转置矩阵,即:
float3 directionToSun = mul(float4(_WorldSpaceLightPos0.xyz, 0), unity_WorldToObject).xyz;
在上述代码中,unity_WorldToObject
表示当前物体的世界空间到对象空间的变换矩阵,它是由 Unity 自动计算的。我们将 _WorldSpaceLightPos0.xyz
转化为一个四维向量,并乘以 unity_WorldToObject
的逆转置矩阵,然后取结果的前三个分量,即可得到从模型指向方向光的方向。
需要注意的是,在使用 _WorldSpaceLightPos0
计算方向时,需要根据实际情况选择正确的方式。对于点光源和聚光灯,使用 _WorldSpaceLightPos0.xyz
即可;对于方向光,则需要使用其方向,并根据需要将其转化为从模型指向光源的方向。
_WorldSpaceCameraPos
在 Unity Shader 中,_WorldSpaceCameraPos
是一个四维向量,它代表了摄像机在世界空间中的位置。它的前三个分量表示摄像机的位置(x、y、z),第四个分量为1。
使用 _WorldSpaceCameraPos
可以方便地计算摄像机与当前像素之间的距离、方向等信息。例如,如果我们需要计算从当前像素到摄像机的方向向量,则可以使用以下代码:
float3 directionToCamera = normalize(_WorldSpaceCameraPos.xyz - worldPosition.xyz);
在上述示例中,worldPosition
表示当前像素的世界空间位置。我们通过 _WorldSpaceCameraPos.xyz
获取摄像机的位置,并将其减去当前像素的世界空间位置,得到从当前像素到摄像机的方向向量。最后,我们使用 normalize()
函数将其归一化,以便在之后的计算中使用。
除了计算方向向量之外,我们还可以使用 _WorldSpaceCameraPos
计算摄像机与当前像素之间的距离。例如,如果我们想要将当前像素的颜色随着距离摄像机的远近而发生变化,可以使用以下代码:
float distanceToCamera = length(_WorldSpaceCameraPos.xyz - worldPosition.xyz);
在上述示例中,我们通过 length()
函数计算当前像素与摄像机之间的距离,并将其保存在 distanceToCamera
变量中。
总之, _WorldSpaceCameraPos
在 Unity Shader 中是一个非常有用的变量,可以方便地计算当前像素与摄像机之间的距离、方向等信息。
v2f
在 shader 中,插值寄存器(interpolated register)是一种特殊的寄存器类型,用于在顶点着色器和片元着色器之间传递数据。
在图形渲染中,通常需要在顶点着色器中计算出每个顶点的属性(例如颜色、法线、纹理坐标等),然后将这些属性插值到三角形的其它位置上,以便在片元着色器中进行处理。这个过程就需要用到插值寄存器了。
具体来说,在顶点着色器中计算出来的每个属性都会被存储到插值寄存器中,然后通过光栅化阶段将三角形分解成像素,并在片元着色器中对每个像素进行处理。在片元着色器中,可以通过读取插值寄存器中的数据来获取每个像素的属性值,然后进行相应的计算和处理。
需要注意的是,插值寄存器中的数据是根据三角形的位置和纹理坐标等信息进行插值计算得到的,并不是从顶点着色器中直接传递过来的原始数据。因此,在片元着色器中读取插值寄存器中的数据时,可能会存在一定的误差和变形,需要在着色器程序中进行适当的处理。
总的来说,插值寄存器是 shader 中一种用于在顶点着色器和片元着色器之间传递数据的特殊寄存器类型,常用于存储顶点属性数据并进行插值计算,以便在片元着色器中进行处理。
使用上注意
1.尽量少变量,如果两个float2可以用1个float4.。4个float可以用1个float4
2.不能传递矩阵,使用3组float4模拟传递矩阵。每个float4存储矩阵的每一行
3.可以尽量减少寄存器数量。例如3*3用3个float4,然后每个float4.w 又可以组成一个新的float3
最常见的v2f
struct a2v {
float4 vertex : POSITION;//模型空间顶点坐标
float3 normal : NORMAL;//模型空间法线方向
float4 texcoord : TEXCOORD0;//用模型的第一套纹理坐标填充texcoord变量 };
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
return fixed4(ambient + diffuse, 1.0);
}
uv
uv坐标是从左下角开始0-1
在顶点着色器中完成,纹理属性变为UV
//纹理属性转uv坐标
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
// Or just call the built-in function
//内置函数在UnityCG.cginc
//o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
然后把uv传递到片段着色器,进行颜色采样
fixed4 c = tex2D(_MainTex, uv);
如果要对采样值进行*运算
fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
如果是内置appdata_img 结构体,包含了uv坐标
是的,appdata_img
结构体中的 half2 texcoord : TEXCOORD0;
可以代表 UV(纹理坐标)。在着色器编程中,TEXCOORD0
通常用于表示第一个纹理坐标(UV 坐标),用于从纹理中采样颜色或其他数据。
UV 坐标是一个二维向量,通常取值范围在 [0, 1] 区间内,用来确定在纹理上采样的位置。UV 坐标中的 (0, 0)
表示纹理的左下角,(1, 1)
表示纹理的右上角。
在 appdata_img
结构体中,half2 texcoord
就是用来存储顶点的 UV 坐标的,它表示了顶点在纹理上的位置。当使用这个结构体表示顶点数据时,texcoord
字段就可以用来在着色器中进行纹理采样,从而在渲染过程中将纹理映射到模型表面上。
Name_ST纹理属性变量
在着色器编程中,_ST 纹理属性变量通常用于获取纹理的平铺和偏移信息。它是一个二维向量,包含了纹理的水平平铺(Scale)和偏移(Offset)值。
_ST 变量通常与 tex2D 函数一起使用,以根据纹理的平铺和偏移来计算最终的纹理坐标。通过修改 _ST 变量的值,可以实现对纹理的缩放、旋转、平铺和偏移等操作。
以下是一个使用 _ST 变量的示例:
// 定义一个 2D 纹理采样器
sampler2D sampler;
// 定义一个输入纹理坐标
float2 uv;
// 定义 _ST 纹理属性变量
float4 _ST;
// 应用纹理平铺和偏移
uv = uv * _ST.xy + _ST.zw;
// 从纹理中采样颜色值
float4 color = tex2D(sampler, uv);
在上面的示例中,我们首先定义了一个 2D 纹理采样器 sampler
和一个输入纹理坐标 uv
,然后定义了一个 _ST 纹理属性变量 _ST
,它是一个包含四个分量的向量。
接下来,我们将输入纹理坐标 uv
乘以 _ST 的水平平铺和垂直平铺分量(_ST.xy),并加上 _ST 的水平偏移和垂直偏移分量(_ST.zw)。这样可以实现对纹理的平铺和偏移操作。
最后,我们使用 tex2D 函数从纹理中采样颜色值,并将其存储在 color
变量中。
通过修改 _ST 变量的值,可以调整纹理的平铺和偏移效果。例如,将 _ST.xy 设置为 (2, 2) 可以使纹理水平和垂直方向上都放大两倍,而将 _ST.zw 设置为 (0.5, 0.5) 可以使纹理水平和垂直方向上都偏移 0.5 个单位。
需要注意的是,_ST 变量的具体含义和使用方式可能因不同的图形 API 或着色器语言而有所差异。在实际使用中,应根据具体的 API 和语言文档进行参考和调整。
Name_TexelSize
在Shader中,_TexelSize 属性通常用于存储纹理的每个像素在世界坐标系下的大小。这个属性可以帮助在Shader中进行像素级别的计算,比如实现一些基于像素的效果或纹理操作。以下是一个简单的示例,展示了如何在Shader中使用 _TexelSize 属性:
Shader "Custom/ExampleShader"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma surface surf Lambert
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex;
float4 _MainTex_TexelSize; // 纹理像素大小
void surf(Input IN, inout SurfaceOutput o)
{
// 获取纹理像素大小
float2 texelSize = _MainTex_TexelSize.xy;
// 在Shader中使用纹理像素大小进行计算
// 例如,将纹理UV坐标缩放到像素级别
float2 scaledUV = IN.uv_MainTex * texelSize;
// 其他的Shader逻辑...
}
ENDCG
}
FallBack "Diffuse"
}
在上面的示例中:
- 我们定义了一个名为 _MainTex_TexelSize 的 float4 类型的属性,用于存储纹理 _MainTex 的像素大小。
- 在 surf 函数中,通过访问 _MainTex_TexelSize.xy 可以获取到纹理的每个像素在uv坐标下的大小。
- 然后我们可以利用这个像素大小进行一些像素级别的计算,比如将纹理UV坐标缩放到像素级别。
总之,_TexelSize 属性在Shader中用于存储纹理的每个像素在世界坐标系下的大小,可以帮助实现一些基于像素级别的效果或纹理操作。
TANGENT_SPACE_ROTATION
TANGENT_SPACE_ROTATION 是一个纹理属性变量,用于在法线贴图中转换切线空间(Tangent Space)的旋转矩阵。在着色器中,我们可以使用这个旋转矩阵来将法线从切线空间转换到世界空间或屏幕空间,以便进行光照计算和渲染。
切线空间是一种以顶点切线、法线和切线叉积向量为基础的局部坐标系,常用于处理带有法线贴图的模型。在切线空间中,每个顶点都有一个与之相关联的切线空间坐标系,可以用来描述该顶点上纹理的方向和曲率。通过将法线转换到切线空间,可以更加精确地计算光照和阴影效果,并提高模型的细节表现。
在使用法线贴图时,通常需要先将法线从切线空间转换到世界空间或屏幕空间,以便进行光照计算和渲染。这就需要使用 TANGENT_SPACE_ROTATION 来转换切线空间的旋转矩阵。
具体来说,我们可以通过如下公式将法线从切线空间转换到世界空间或屏幕空间:
N_world = normalize(TANGENT_SPACE_ROTATION * N_tangent)
其中,N_tangent 是法线在切线空间中的表示方式,TANGENT_SPACE_ROTATION 是切线空间的旋转矩阵,N_world 是经过转换后的法线在世界空间或屏幕空间中的表示方式。
需要注意的是,TANGENT_SPACE_ROTATION 的具体计算方式会根据不同的纹理坐标系和顶点切线计算方法而有所不同。在使用时,应根据具体的情况进行参考和调整。
TANGENT_SPACE_ROTATION 宏 相当于嵌入如下两行代码:
即
TANGENT_SPACE_ROTATION;
等于下面2行代码
float3 binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w;
float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal );
取值范围
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
//控制透明度测试时使用的阈值
//_Cutoff参数用于决定我们调用clip进行透明度测试时使用的判断条件。
//它的范围是[0,1],这是因为纹理像素的透明度就是在此范围内。
_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Cutoff;
对于位置、法线、视线方向这类敏感数据,使用float,根据需要光线方向可以用float或half。
各类中间计算结果和输入参数可以使用half。
颜色可以使用fixed。
frac
Shader中frac函数是一个取小数部分的函数,可以用来截取浮点数的小数部分。
frac函数通常用于计算纹理坐标在某个纹理单元中的位置,或者计算某个属性在0到1之间的相对位置。通过将值传递给frac函数,可以获得该值的小数部分,即该值减去其整数部分后剩余的部分。
在Shader中,frac函数通常采用以下形式:
float frac(float x);
其中,x表示待截取小数部分的浮点数,返回值为x的小数部分。
例如,如果有一个纹理坐标u,需要将其转换为在第二个纹理单元中的位置v,可以使用以下代码:
float v = frac(u * 2.0);
在这个例子中,将纹理坐标u与2相乘,然后将结果传递给frac函数,得到的值即为v在第二个纹理单元中的位置。
总之,Shader中frac函数的作用是取一个浮点数的小数部分,通常用于计算纹理坐标在某个纹理单元中的位置或者计算某个属性在0到1之间的相对位置。
使用示例,uv偏移
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex) + frac(float2(_ScrollX, 0.0) * _Time.y);
坐标转换
模型空间下顶点坐标转投影空间
这个是必须的
o.pos = UnityObjectToClipPos(v.vertex);
// Tranforms position from object to homogenous space
inline float4 UnityObjectToClipPos(in float3 pos)
{
#if defined(STEREO_CUBEMAP_RENDER_ON)
return UnityObjectToClipPosODS(pos);
#else
// More efficient than computing M*VP matrix product
return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));
#endif
}
也可直接使用MVP矩阵
mul(UNITY_MATRIX_MVP, float4(pos,1.0))
M代表模型矩阵。用于坐标从模型空间转世界空间
V代表观察矩阵。用于世界空间转观察空间
P代表投影矩阵。用于观察空间转裁剪空间
模型空间下顶点转世界空间
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
模型空间下法线转世界空间
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
// Transforms normal from object to world space
inline float3 UnityObjectToWorldNormal( in float3 norm )
{
#ifdef UNITY_ASSUME_UNIFORM_SCALING
return UnityObjectToWorldDir(norm);
#else
// mul(IT_M, norm) => mul(norm, I_M) => {dot(norm, I_M.col0), dot(norm, I_M.col1), dot(norm, I_M.col2)}
return normalize(mul(norm, (float3x3)unity_WorldToObject));
#endif
}
模型空间下切线转世界空间
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
// Transforms direction from object to world space
inline float3 UnityObjectToWorldDir( in float3 dir )
{
return normalize(mul((float3x3)unity_ObjectToWorld, dir));
}
世界空间下副法线
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
切线空间下光照方向,视角方向
TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
优化
v2f中变量多,导致使用寄存器数量多
在Unity的着色器编程中,V2F(Vertex to Fragment)结构体是用于传递顶点数据到片段着色器的一种方式。V2F结构体内部可以包含多个变量,这些变量用于在顶点着色器和片段着色器之间传递数据。
当在V2F结构体中定义大量变量时,会占用更多的寄存器空间。每个变量都需要占用一个或多个寄存器来存储其数据。因此,如果V2F结构体中的变量过多,可能会导致寄存器数量不足的问题。
在OpenGL ES 2.0和OpenGL Core Profile中,片段着色器的最大寄存器数量是有限制的。具体的限制取决于硬件和驱动程序的支持,通常在16到32之间。如果V2F结构体中的变量超过了可用的寄存器数量,编译和运行着色器时可能会出现错误。
为了减少V2F结构体中变量对寄存器数量的影响,我们可以优化着色器的代码和数据布局。以下是一些优化建议:
-
只定义需要的变量:避免定义不必要的变量,只保留需要在顶点和片段着色器之间传递的数据。
-
使用紧凑的数据类型:选择适当的数据类型来存储数据,避免使用过大的数据类型。例如,使用float代替float4来存储一个3D向量。
-
考虑使用顶点着色器中的纹理坐标:如果某些数据只在顶点着色器中使用,并且可以通过纹理坐标传递到片段着色器,可以考虑将其存储为纹理坐标而不是额外的变量。
-
合并变量:如果可能,将多个相关的数据合并到一个变量中,以减少寄存器的使用量。
总之,V2F结构体中的变量数量会影响到寄存器的使用量。在编写着色器时,我们应该合理设计数据布局,避免定义过多不必要的变量,以确保着色器能够正确编译和运行。
优化实例,可以将2个float2变量,塞入到一个float4变量中
a = c.xy
b = c.zw
共用主纹理属性
我们为主纹理_MainTex、法线纹理_BumpMap和遮罩纹理_SpecularMask定义了它们共同使用的纹理属性变量_MainTex_ST。这意味着,在材质面板中修改主纹理的平铺系数和偏移系数会同时影响3个纹理的采样。使用这种方式可以让我们节省需要存储的纹理坐标数目,如果我们为每一个纹理都使用一个单独的属性变量TextureName_ST,那么随着使用的纹理数目的增加,我们会迅速占满顶点着色器中可以使用的插值寄存器。而很多时候,我们不需要对纹理进行平铺和位移操作,或者很多纹理可以使用同一种平铺和位移操作,此时我们就可以对这些纹理使用同一个变换后的纹理坐标进行采样。
遮罩纹理rgba都利用起来
在真实的游戏制作过程中,遮罩纹理已经不止限于保护某些区域使它们免于某些修改,而是可以存储任何我们希望逐像素控制的表面属性。通常,我们会充分利用一张纹理的RGBA四个通道,用于存储不同的属性。例如,我们可以把高光反射的强度存储在R通道,把边缘光照的强度存储在G通道,把高光反射的指数部分存储在B通道,最后把自发光强度存储在A通道。
在游戏《DOTA 2》的开发中,开发人员为每个模型使用了4张纹理:一张用于定义模型颜色,一张用于定义表面法线,另外两张则都是遮罩纹理。这样,两张遮罩纹理提供了共8种额外的表面属性,这使得游戏中的人物材质自由度很强,可以支持很多高级的模型属性。读者可以在他们的官网上找到关于《DOTA 2》的更加详细的制作资料,包括游戏中的人物模型、纹理以及制作手册等。这是非常好的学习资料。
计算放入到顶点着色器
在通常情况下,片段着色器执行的次数要比顶点着色器多。
顶点着色器(Vertex Shader)在每个顶点上执行一次,并计算出每个顶点的位置、法线、纹理坐标等信息。顶点着色器负责将顶点从模型空间转换到屏幕空间,并进行一些基本的顶点变换和处理。因此,顶点着色器的执行次数与模型的顶点数成正比。
片段着色器(Fragment Shader)在像素级别上执行,对每个屏幕上的像素进行颜色计算。片段着色器通常用于计算光照、纹理采样、阴影等,并输出最终的像素颜色。由于屏幕上的像素数量通常比模型的顶点数量多得多,因此片段着色器的执行次数要比顶点着色器多。
需要注意的是,虽然片段着色器的执行次数通常比顶点着色器多,但这也取决于具体的渲染场景和效果。有些情况下,例如在使用简化的渲染技术(如LOD)或剔除不可见物体时,可以减少片段着色器的执行次数来提高性能。优化渲染流程可以根据具体情况对顶点和片段着色器的执行进行优化。
uv相关计算可放入到顶点着色器
通过把计算采样纹理坐标的代码从片元着色器中转移到顶点着色器中,可以减少运算,提高性能。由于从顶点着色器到片元着色器的插值是线性的,因此这样的转移并不会影响纹理坐标的计算结果。
实际应用
边缘检测,卷积
可配置
Cull
如果设置为Back,那么那些背对着摄像机的渲染图元就不会被渲染,这也是默认情况下的剔除状态;如果设置为Front,那么那些朝向摄像机的渲染图元就不会被渲染;如果设置为Off ,就会关闭剔除功能,那么所有的渲染图元都会被渲染,但由于这时需要渲染的图元数目会成倍增加,因此除非是用于特殊效果,例如这里的双面渲染的透明效果,通常情况是不会关闭剔除功能的。
Cull Back | Front | Off
Tags
RenderType
在 Unity 的 Shader 中,RenderType 是用于指定渲染对象类型的关键字。通过设置 RenderType,可以指示 Unity 如何处理该材质的渲染顺序和渲染方式。
以下是常见的几种 RenderType 种类:
-
Opaque(不透明):用于不透明的材质,如不需要透明度的表面。这是最常见的 RenderType,会按照默认的渲染顺序进行渲染。
-
Transparent(透明):用于具有透明度的材质,如玻璃、水、烟雾等。这些材质需要按照透明度从前到后进行绘制,以实现正确的混合效果。
-
TransparentCutout(透明切割):用于具有透明度且需要进行 alpha 测试的材质,如树叶、草等。这些材质根据 alpha 值来裁剪片段的绘制,只绘制 alpha 值大于阈值的部分。
-
Background(背景):用于表示背景物体,如天空盒。这种 RenderType 会在其他物体之前绘制,作为场景的背景。
-
Overlay(覆盖):用于表示覆盖在其他物体上的特殊效果,如屏幕后处理效果或 UI 元素。
除了以上几种常见的 RenderType 种类外,还可以自定义和扩展 RenderType。可以通过在 Shader 中使用 “Tags” 命令来指定渲染类型,例如:
Tags { "RenderType" = "Transparent" }
然后可以在材质或渲染队列中使用指定的 RenderType 进行自定义渲染。
需要注意的是,RenderType 的使用需要与渲染队列(Render Queue)一起考虑,以确保正确的渲染顺序。默认情况下,不同 RenderType 拥有不同的默认渲染队列值,可以使用 “Queue” 命令来手动设置渲染队列。
总之,RenderType 是指定 Shader 渲染对象类型的关键字,根据不同的 RenderType 和渲染队列,可以实现透明度、裁剪、背景等不同的渲染效果。
水使用GrabPass,“RenderType”=“Opaque”
而设置RenderType则是为了在使用着色器替换(Shader Replacement)时,该物体可以在需要时被正确渲染。这通常发生在我们需要得到摄像机的深度和法线纹理时,这在第13章中介绍过。随后,我们通过关键词GrabPass定义了一个抓取屏幕图像的Pass。在这个Pass中我们定义了一个字符串,该字符串内部的名称决定了抓取得到的屏幕图像将会被存入哪个纹理中
LightMode
Queue
在Shader中,Queue是用来控制渲染顺序和顺序绘制的一个关键字。通过设置不同的Queue值,可以控制Shader在渲染管线中的执行顺序和优先级。以下是几个常用的Queue值:
-
Background(背景):1000
适用于渲染背景、天空盒等需要在其他对象之前绘制的物体。 -
Geometry(几何体):2000
默认值,适用于大多数物体的渲染。 -
AlphaTest(透明测试):2450
适用于使用透明测试(alpha test)的物体,例如通过剔除片段来实现简单的透明效果。 -
Transparent(透明):3000
适用于透明物体的渲染,如玻璃、水、烟雾等。这个Queue值会根据物体的透明度和排序模式决定绘制顺序。 -
Overlay(覆盖):4000
适用于需要在其他物体之上绘制的物体,如UI元素、血条等。
除了上述常用的Queue值,还有一些其他的Queue值可供选择,具体取决于你的需求和场景。需要注意的是,Queue值越小的Shader会越早被执行,因此,如果你希望某个物体渲染在其他物体之前,可以选择一个较小的Queue值。
在Shader的SubShader中,可以通过设置Tags来指定Queue的值。例如:
Tags { "Queue" = "Transparent" }
这将把Shader的渲染顺序设置为Transparent,使其在透明物体的渲染队列中。
总结一下,Shader中的Queue关键字用于控制渲染顺序和绘制顺序,常用的Queue值包括Background、Geometry、AlphaTest、Transparent和Overlay等。根据实际需求,选择合适的Queue值可以确保物体以正确的顺序绘制,并获得预期的渲染效果。
半透明设置
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Pass {
Tags { "LightMode"="ForwardBase" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
半透明需要关闭深度写入
为什么需要ZWrite Off,不关闭会怎样
在渲染透明物体时,透明物体后面的物体不会被遮挡,从而达到正确的透明效果。同时,由于深度写入被关闭,透明物体的深度信息将不会被存储到深度缓冲区中,避免了后绘制的透明物体遮挡住前面已绘制的物体的问题。
为什么不关闭ZTest
要注意的是,透明度混合只关闭了深度写入,但没有关闭深度测试。这意味着,当使用透明度混合渲染一个片元时,还是会比较它的深度值与当前深度缓冲中的深度值,如果它的深度值距离摄像机更远,那么就不会再进行混合操作。这一点决定了,当一个不透明物体出现在一个透明物体的前面,而我们先渲染了不透明物体,它仍然可以正常地遮挡住透明物体。也就是说,对于透明度混合来说,深度缓冲是只读的。
透明度混合
混合因子
参 数 描 述
One 因子为1
Zero 因子为0
SrcColor 因子为源颜色值。当用于混合RGB的混合等式时,使用SrcColor的RGB分量作为混合因子;当用于混合A的混合等式时,使用SrcColor的A分量作为混合因子
SrcAlpha 因子为源颜色的透明度值(A通道)
DstColor 因子为目标颜色值。当用于混合RGB通道的混合等式时,使用DstColor的RGB分量作为混合因子;当用于混合A通道的混合等式时,使用DstColor的A分量作为混合因子。
DstAlpha 因子为目标颜色的透明度值(A通道)
OneMinusSrcColor 因子为(1-源颜色)。当用于混合RGB的混合等式时,使用结果的RGB分量作为混合因子;当用于混合A的混合等式时,使用结果的A分量作为混合因子
OneMinusSrcAlpha 因子为(1-源颜色的透明度值)
OneMinusDstColor 因子为(1-目标颜色)。当用于混合RGB的混合等式时,使用结果的RGB分量作为混合因子;当用于混合A的混合等式时,使用结果的A分量作为混合因子
OneMinusDstAlpha 因子为(1-目标颜色的透明度值)
常见的混合
透明度混合
Blend SrcAlpha OneMinusSrcAlpha, One Zero
Blend SrcAlpha OneMinusSrcAlpha 是一种混合模式,用于混合源颜色和目标颜色。其中,SrcAlpha 表示源颜色的 Alpha 值,OneMinusSrcAlpha 表示 1 减去源颜色的 Alpha 值。
具体地,当使用 Blend SrcAlpha OneMinusSrcAlpha 模式混合源颜色和目标颜色时,如下公式会被应用于混合过程中:
输出颜色 = 源颜色 x 源颜色的 Alpha 值 + 目标颜色 x (1 - 源颜色的 Alpha 值)
这个公式表示的是,混合后的颜色会同时考虑源颜色和目标颜色,源颜色的 Alpha 值决定了源颜色在混合后的颜色中所占的比例,而 OneMinusSrcAlpha 则表示目标颜色在混合后颜色中所占的比例。
具体来说,如果源颜色的 Alpha 值为 0,那么混合后的颜色就只会取目标颜色,即输出颜色为目标颜色。如果源颜色的 Alpha 值为 1,那么混合后的颜色就只会取源颜色,即输出颜色为源颜色。如果源颜色的 Alpha 值在 0 和 1 之间,则混合后的颜色会同时考虑源颜色和目标颜色,且源颜色所占的比例和 Alpha 值成正比。
Blend SrcAlpha OneMinusSrcAlpha 模式通常用于实现半透明效果,例如透明背景、半透明物体等。
总之,Blend SrcAlpha OneMinusSrcAlpha 是一种混合模式,用于混合源颜色和目标颜色。它的混合公式将同时考虑源颜色和目标颜色,且源颜色的 Alpha 值决定了在混合后颜色中的所占比例。
Blend One One
Blend One One 是一种混合模式,用于混合源颜色和目标颜色。其中,One 表示将源颜色和目标颜色的各个分量(红、绿、蓝和 Alpha)都乘以 1。
具体地,当使用 Blend One One 模式混合源颜色和目标颜色时,如下公式会被应用于混合过程中:
输出颜色 = 源颜色 x 1 + 目标颜色 x 1
这个公式表示的是,混合后的颜色是源颜色和目标颜色之间的简单相加。
换句话说,Blend One One 混合模式会将源颜色和目标颜色的各个分量进行相加,得到混合后的颜色。这种混合模式常用于实现加法混合效果,例如光照增强、发光物体等。
需要注意的是,Blend One One 混合模式适用于不透明的情况,因为它没有考虑到源颜色和目标颜色的 Alpha 值,因此无法实现半透明效果。
总之,Blend One One 是一种混合模式,通过简单相加的方式混合源颜色和目标颜色,适用于不透明的情况,常用于实现加法混合效果。
Fallback
除了"Diffuse"之外,Unity还提供了其他一些内置的传统着色器作为Fallback选项。以下是一些常用的内置传统着色器:
-
“Specular”:具有镜面反射效果的传统着色器。
-
“Bumped Diffuse”:带有法线贴图的传统着色器,可以模拟物体表面的凹凸纹理。
-
“Bumped Specular”:带有镜面反射和法线贴图效果的传统着色器。
-
“VertexLit”:使用顶点光照计算的传统着色器,适用于不需要像素光照计算的情况。
-
“Self-Illumin/Diffuse”:自发光的传统着色器,使物体表面看起来像是发光的材质。
-
“Particles/Alpha Blended”:用于渲染粒子系统的传统着色器,支持透明度混合。
这些内置的传统着色器提供了不同的渲染效果和功能,你可以根据项目需求选择适当的传统着色器作为Fallback。另外,你也可以通过自定义着色器来实现更复杂的效果,并将其作为Fallback选项。
阴影
使用的宏文件
#include "AutoLight.cginc"
SHADOW_COORDS 、TRANSFER_SHADOW 和SHADOW_ATTENUATION 是计算阴影时的“三剑客”。
深度
ZTest
ZTest是一种在Unity中用于深度测试的渲染状态,表示在渲染时使用哪种比较函数来进行深度测试。
深度测试是指在3D渲染中,为了正确地处理物体之间的遮挡关系,需要根据渲染顺序和深度值信息来判断哪些像素应该被绘制出来,哪些应该被覆盖或裁剪掉。而深度值是指物体到相机视点的距离,深度测试就是根据深度值来进行像素的比较和排序。
在Unity中,ZTest参数可以用于设置深度测试的比较函数,常用的比较函数包括:
- Less:当新像素深度小于缓存中像素的深度时通过测试,否则丢弃。
- Greater:当新像素深度大于缓存中像素的深度时通过测试,否则丢弃。
- LEqual:当新像素深度小于或等于缓存中像素的深度时通过测试,否则丢弃。
- GEqual:当新像素深度大于或等于缓存中像素的深度时通过测试,否则丢弃。
- Equal:当新像素深度等于缓存中像素的深度时通过测试,否则丢弃。
- NotEqual:当新像素深度不等于缓存中像素的深度时通过测试,否则丢弃。
- Always:总是通过深度测试,不管新像素的深度与缓存中像素的深度如何。
例如,如果将ZTest设置为Less,则表示只有当新像素的深度小于缓存中像素的深度时才会通过测试。这样可以保证物体之间的遮挡关系正确地呈现,避免出现穿模等问题。
在Shader中,可以使用ZTest语句来设置深度测试的比较函数:
// 在SubShader中的Pass中设置ZTest为Less
Pass {
ZTest Less
// ...
}
在上述代码中,将ZTest设置为Less表示使用Less函数来进行深度测试。这样,在渲染时就会根据新像素的深度值和缓存中像素的深度值来进行比较,从而判断是否通过测试。
默认的ZTest设置是LQueue。小于等于深度值,通过测试,说明是可以覆盖z轴更深的,保证物体之间的遮挡关系正确地呈现
Cull
在Shader中,Cull(剔除)语义用于指定哪些面片应该被剔除(不进行渲染),以优化性能和避免不必要的渲染。
Cull语义可以与渲染状态的Cull模式一起使用,常见的取值包括:
Cull Off
:关闭剔除,即不进行面剔除,所有面都会进行渲染。Cull Back
:剔除背面,只渲染面片的正面,背面将被剔除。Cull Front
:剔除正面,只渲染面片的背面,正面将被剔除。
使用Cull语义可以根据具体情况选择合适的面剔除模式,以提高渲染性能。例如,在绘制封闭物体时,可以使用Cull Back
来剔除背面,因为背面通常是不可见的,这样可以减少不必要的渲染。另外,当需要绘制单面物体(例如叶子、纸片等)时,可以使用Cull Front
来剔除正面,只渲染背面。
需要注意的是,剔除只会影响面的渲染,而不会影响顶点的位置计算。另外,Cull语义默认为Cull Back
,当没有明确指定时,会应用默认的剔除模式。
ZWrite
ZWrite(深度写入)是在渲染中控制深度缓冲区是否被写入的选项。深度缓冲区是一种用于存储场景中每个像素的深度信息的缓冲区。通过比较新像素的深度值与深度缓冲区中对应位置的深度值,可以确定新像素是否可见。
ZWrite的作用是控制渲染过程中是否将新像素的深度值写入深度缓冲区。它具有以下两个选项:
-
ZWrite On
:允许新像素的深度值写入深度缓冲区。这是默认选项,新像素的深度值将与深度缓冲区中的深度值进行比较,并根据深度测试的结果来确定像素的可见性。如果新像素通过了深度测试,则其深度值将被写入深度缓冲区,更新对应像素位置的深度信息。 -
ZWrite Off
:禁止新像素的深度值写入深度缓冲区。无论新像素是否通过了深度测试,其深度值都不会被写入深度缓冲区。这意味着在后续的渲染过程中,其他像素无法通过深度测试与该像素进行比较。这种情况下,被禁用的深度写入可以用于实现一些特殊效果,如描边、轮廓渲染等。
需要注意的是,关闭深度写入(ZWrite Off)可能会导致可见性错误,因为后续像素无法通过深度测试与已渲染的像素进行比较。在使用ZWrite Off时,通常需要采用其他方法来确保正确的像素遮挡关系,例如手动排序、使用深度预渲染等技术。
示例代码:
// 开启深度写入
#pragma surface surf Lambert vertex:vert
ZWrite On
// 关闭深度写入
#pragma surface surf Lambert vertex:vert
ZWrite Off
根据具体的渲染需求,可以选择合适的深度写入选项。默认情况下,保持ZWrite On即可满足大多数情况下的渲染需求。
SAMPLE_DEPTH_TEXTURE
在Unity中,SAMPLE_DEPTH_TEXTURE是一个渲染着色器中的预定义函数,用于从深度纹理中采样深度信息。
在一些场景中,我们可能需要在渲染过程中访问场景的深度信息,比如进行阴影计算、屏幕空间特效、体积光照等。而深度纹理就是存储了场景中每个像素的深度信息的纹理。
使用SAMPLE_DEPTH_TEXTURE函数可以从深度纹理中采样深度信息,然后在渲染过程中使用。这样我们就可以在渲染的过程中根据深度信息进行一些处理,比如实现基于深度的阴影、体积光照等效果。
具体而言,SAMPLE_DEPTH_TEXTURE函数允许我们在渲染过程中访问深度缓冲区中的深度值。这些深度值通常用于计算屏幕空间效果,例如体积光、景深、阴影等。
使用SAMPLE_DEPTH_TEXTURE函数时,需要提供一个采样器(sampler),以及要采样的深度纹理(depth texture)。函数将返回在指定纹理坐标处的深度值。这个深度值通常会经过一些额外的处理,比如从[0,1]范围映射到实际深度值的范围。
总之,SAMPLE_DEPTH_TEXTURE函数为我们提供了一种在渲染过程中访问深度信息的方法,为实现各种基于深度的效果提供了便利。
如何使用
在使用SAMPLE_DEPTH_TEXTURE
函数时,需要提供以下参数:
-
采样器(Sampler):指定要从中采样深度信息的深度纹理的采样器。通常,这是一个深度纹理的采样器,你需要在渲染器中声明和绑定这个采样器。
-
纹理坐标(Texture Coordinates):指定要在深度纹理中采样的纹理坐标。这通常是从屏幕空间转换得到的,以便在渲染过程中获取正确的深度信息。
-
偏移量(Offset):用于在采样过程中应用的偏移量。这个偏移量通常用于处理采样点周围的深度信息,以获取更加平滑的效果。
示例代码如下所示:
float SampleDepthTexture(sampler depthSampler, float2 texCoord, float offset)
{
float depth = tex2D(depthSampler, texCoord).r;
depth += offset;
return depth;
}
在这个示例中,SampleDepthTexture
函数接受一个深度纹理采样器(depthSampler)、一个纹理坐标(texCoord)以及一个偏移量(offset)作为参数。函数首先使用tex2D
函数从深度纹理中采样深度值,并将其与偏移量相加后返回。请注意,这只是一个简单的示例,实际使用中可能需要根据具体需求进行更复杂的处理。
后处理深度设置
//ZTest Always 是一种 Z 测试模式,其含义是无论 Z 缓冲中的值如何,总是进行绘制。
//换句话说,不管其他像素的深度值如何,当前像素都会被绘制在屏幕上。
//这通常用于实现一些特殊的效果,比如全屏后处理效果或者在绘制 GUI 元素时。
ZTest Always
//关闭了背面剔除,即不管三角形面是正面还是背面,都会被绘制。这意味着场景中的所有三角形面都会被渲染,不再考虑其朝向。
//通常情况下,开启背面剔除可以有效地减少不可见的三角形面的绘制,提高渲染效率。
//但在某些情况下,比如需要渲染双面材质或者特定的后处理效果时,可能需要关闭背面剔除,这时就可以使用 Cull Off。
Cull Off
//开启深度写入是必要的,因为它可以确保后绘制的像素不会被之前绘制的像素所遮挡。
//但在某些情况下,比如绘制透明物体或实现特定的后处理效果时,可能需要关闭深度写入,这时就可以使用 ZWrite Off。
ZWrite Off
亮度
在 Unity 中,Shader 中的 “luminance”(亮度)通常用于计算颜色的亮度值。亮度是通过将 RGB 色彩通道进行加权平均得到的,其中不同通道的权重可以根据需求进行调整。
使用亮度可以实现各种效果,例如:
-
灰度化:通过将颜色的 R、G、B 通道都设置为相同的亮度值,可以将图像转换为灰度图像。
-
创建黑白效果:通过根据亮度值将颜色转换为黑色或白色,可以创建黑白滤镜效果。
-
高光、反射等特效:通过提取亮度值,可以对场景中的高光部分进行特殊处理,例如增强或减弱高光区域的亮度。
以下是一个使用亮度计算灰度化的 Unity Shader 示例:
Shader "Custom/Grayscale" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o) {
fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
float luminance = dot(c.rgb, float3(0.2126, 0.7152, 0.0722));
o.Albedo = float3(luminance, luminance, luminance);
}
ENDCG
}
FallBack "Diffuse"
}
在这个示例中,我们使用 dot
函数计算 c.rgb
和权重 (0.2126, 0.7152, 0.0722) 的点乘结果作为亮度值,然后将亮度值应用到 Albedo
中,从而实现了灰度化的效果。
通过调整权重或使用其他的计算方式,你可以根据具体需求来改变亮度计算的方式,以达到不同的效果。
也可以使用,根据阈值,得到亮度区域
fixed luminance(fixed4 color) {
return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
fixed4 fragExtractBright(v2f i) : SV_Target {
fixed4 c = tex2D(_MainTex, i.uv);
//采样得到的亮度值减去阈值_LuminanceThreshold,并把结果截取到0~1范围内
fixed val = clamp(luminance(c) - _LuminanceThreshold, 0.0, 1.0);
//和原像素值相乘,得到提取后的亮部区域
return c * val;
}
平台宏定义
UNITY_UV_STARTS_AT_TOP
UNITY_UV_STARTS_AT_TOP 是 Unity 中的一个平台定义符号(platform define symbol),用于指示纹理坐标系的原点位置。当定义了这个符号时,Unity 将会使用纹理坐标系的原点在顶部左侧的方式来处理纹理。这意味着纹理坐标 (0, 0) 将位于纹理的左上角,而不是传统的左下角。
这个定义符号通常用于适配一些特定的渲染平台,例如在某些移动平台或者 WebGL 上,纹理坐标系的原点可能与传统的 OpenGL 或 Direct3D 不同。通过使用 UNITY_UV_STARTS_AT_TOP,开发者可以在不同平台上保持一致的纹理坐标系原点位置,从而简化纹理坐标的处理和适配工作。
//DirectX这样的平台上,我们需要处理平台差异导致的图像翻转问题
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv_depth.y = 1 - o.uv_depth.y;
#endif
后处理
ImageEffectOpaque
ImageEffectOpaque
是 Unity 引擎中的一个类,用于处理相机渲染时的图像效果。它的作用是指定相机渲染目标的 Alpha 通道是否为不透明(opaque)。
在 Unity 中,当相机渲染到目标纹理时,会根据相机的设置和渲染目标的属性来确定像素的透明度。而 ImageEffectOpaque
这个属性则可以影响这个过程。具体来说,它有以下几种作用:
-
Opaque(不透明): 当相机的
ImageEffectOpaque
属性设置为 true 时,表示相机渲染的结果是不透明的。这意味着渲染的图像不包含半透明像素,所有像素都是完全不透明的,Alpha 通道值为 1。这对于一些特定的渲染效果或优化渲染性能时非常有用,因为不需要处理透明度相关的计算和混合操作。 -
Non-Opaque(非不透明): 相反,当
ImageEffectOpaque
属性设置为 false 时,表示相机渲染的结果可能包含半透明像素,即 Alpha 通道值可以小于 1。这种情况下,渲染的结果可能包含半透明的效果,需要进行后续的透明度混合操作。
在实际开发中,根据项目需求和渲染效果的要求来设置相机的 ImageEffectOpaque
属性,可以有效地控制渲染结果的透明度处理,从而达到更好的视觉效果和性能表现。
如果使用描边,透明物体不显示描边可开启
//指定相机渲染目标的 Alpha 通道是否为不透明
[ImageEffectOpaque]
void OnRenderImage (RenderTexture src, RenderTexture dest) {