一、纹理
纹理用来给着色器采样使用,一般为外部导入,但是同时纹理还可以从帧缓存中截取,例如之前学习到的屏幕热扭曲效果中在帧缓存中抓取当前屏幕图像
二、纹理的作用
在lambert光照模型中贴图纹理采样后的结果可以作为其中的漫反射系数Kd使用
同时法线贴图纹理采样后的结果还可代替法线向量N使用
三、纹理的多级渐远MipMap
如何在unity中开启纹理的多级渐远模式?
选中贴图,在左侧的Inspector界面中勾选 Generate Mipmaps
为什么要引入纹理的多级渐远贴图呢?
如何在Shader中应用纹理的多级渐远贴图?
通过增加一个滑杆属性来调节纹理的不同分辨率级别LOD
首先在Properties中添加一个滑杆属性
// [IntRange]只能用于Range()滑杆中,在滑杆调节值时只能生成整数[IntRange]_Mipmap("Mipmap",Range)=0
之后在Pass中声明_Mipmap
half _Mipmap;
在片元着色器中利用tex2Dlod采样不同级别的纹理
tex2Dlod(samper 2D,float4 s)
函数代表采样不同级别的纹理 其中有两个参数,第二个参数是float4类型 xy代表uv s.z=0 s.w代表采样纹理的级别(_Mipmap)
fixed4 c= tex2Dlod(_MainTex,float4(i.uv,0,_Mipmap));
效果如下:
四、纹理的过滤
纹理过滤是在图形渲染过程中,用于确定如何从纹理中获取像素颜色值来映射到屏幕上的 3D 模型表面的一种技术。在 3D 图形中,纹理通常是二维图像,当这些纹理被映射到几何形状(如三角形)上时,由于几何形状在屏幕空间中的投影可能会导致纹理像素与屏幕像素的映射不是一一对应的,这就需要纹理过滤来处理这种情况。
纹理过滤的方式有最邻近过滤、双线性过滤、三线性过滤、各项异性过滤
最近邻过滤(Nearest - Neighbor Filtering)
- 这是最简单的纹理过滤方式。在采样纹理时,它选择距离采样点最近的纹理像素的值作为采样结果。
- 例如,当将一个纹理映射到一个三角形上,在屏幕空间中某个像素需要获取纹理颜色时,如果使用最近邻过滤,就会找到纹理空间中离这个采样位置最近的纹理像素,将其颜色赋值给屏幕像素。这种方法计算速度快,但可能会导致视觉效果上的锯齿(aliasing),特别是当纹理被拉伸或缩小的时候。比如,一个低分辨率纹理映射到一个较大的几何表面上,会出现明显的块状纹理,就像马赛克一样。
线性过滤
- 线性过滤会考虑采样点周围的多个纹理像素。它在纹理空间中对采样点周围的纹理像素进行加权平均来得到采样结果。
- 比如,对于一个二维纹理,在采样时会考虑采样点周围的 4 个(对于双线性过滤)或更多个(对于三线性过滤)纹理像素。以双线性过滤为例,当需要采样纹理时,它会根据采样点在 4 个相邻纹理像素之间的位置,通过线性插值计算出一个颜色值。这样可以得到更平滑的纹理效果,相比于最近邻过滤,在纹理拉伸或缩小的情况下,视觉效果更好,减少了锯齿感。但是,线性过滤的计算成本相对较高,因为它需要进行插值计算。
- 双线性过滤的缺点是当映射区域的分辨率大幅度小于纹理图案的分辨率时就会产生闪烁的现象,纹理的细节也会消失
双线性过滤可以配合Mipmap(多级渐远贴图使用)
- Mipmap和双线性过滤组合后,可以解决当映射区域的分辨率大幅度小于纹理图案的分辨率时就会产生闪烁的现象的问题。
- 在当映射区域的分辨率缩小时,可以切换到更小的Mipmap。但是当从一个级别切换到另一个级别时,会产生闪烁的现象
三线性过滤
- 三线性过滤会在两个相邻的Mipmap上进行差值计算
- 三线性过滤几乎消除了所有在当纹理映射区域缩小时所产生的闪烁现象
(point)最邻近过滤效果:(看橙黑色的棋盘格,有锯齿效果)
双线性过滤+Mipmap效果:(当从一个级别切换到另一个级别时,会产生闪烁的现象)
三线性过滤效果:
五、如何在Shader中实现纹理的不同的平铺方式
例如 Repeat(重复平铺)、Clamp(单次平铺)
首先在Properties中添加枚举属性 [KeywordEnum(n,m)](自定义枚举变量)
[KeywordEnum(Repeat,Clamp)]_WrapMode("WrapMode",int)=0
在Pass中添加与UV平铺方式相关的变体
//定义UV平铺方式的相关变体
#pragma shader_feature _WRAPMODE_REPEAT _WRAPMODE_CLAMP
在片元着色器中使用变体
#if _WRAPMODE_REPEAT //重复平铺有效时
i.uv=frac(i.uv);
#elif _WRAPMODE_CLAMP //单次平铺有效时
//i.uv=clamp(i.uv,0,1);
i.uv=saturate(i.uv);
#endif
使单次平铺有效的方法:
//方法一:clamp(i.uv,0,1) 若i.uv小于0则返回0,若i.uv大于1则返回1(指将i.uv限制在0-1的范围内)
//方法二:saturate(i.uv) 函数的作用为将值限制在0-1的范围内
使重复平铺有效的函数方法: frac(x)
//frac(x)函数的作用是当x大于1时,取其小数部分,这样就可以实现当UV的Tilling值大于1时UV值仍然在0-1之间,从而产生平铺的功能
//可以解决当默认的纹理的平铺方式为Clamp时,Repeat(重复平铺)无效的现象
六、立方体纹理的实现(CubeMap)
1.什么是立方体纹理?
2.立方体纹理的两种生成方式:
3.在unity中立方体纹理的模式如何打开:
选中一张纹理,在右侧的Inspector面板中选择 Texture Shape中的 Cube 模式即可
4.立方体纹理的坐标原点位置
二维纹理采样时(0,0)点在左下角,而立方体纹理采样时(0,0,0)点在立方体的的中心
5.如何在Shader中使用CubeMap来实现反射效果
首先在Properties中定义一个_CubeMap属性来存放CubeMap贴图
_CubeMap("CubeMap",Cube)="white"{}
在Shader中声明立方体纹理
//声明立方体纹理
samplerCUBE _CubeMap;
在片元着色器中对立方体纹理进行采样
//采样立方体纹理 利用模型的本地空间坐标采样立方体纹理
//fixed4 Cubemap=texCUBE(_CubeMap,i.localPos);
但是此时在立方体纹理采样后并不会实现反射的效果,而只是一张普通的纹理贴图而已
因此就需要使用到环境映射
思路:利用视角的入射方向向量和法向量来求反射向量,再利用计算得到的反射向量来对立方体纹理进行采样
先求出指向视角方向的向量
//向量V代表指向摄像机方向的向量
fixed3 V=normalize(_WorldSpaceCameraPos-i.worldPos.xyz);
求法线向量
fixed3 N=normalize(i.worldNormal);
利用reflect函数,(包括视角的入射方向向量和法线向量参数)求反射方向向量
fixed3 R=reflect(-V,N);
最后用计算得到的反射向量来对立方体纹理进行采样
//由反射向量R采样_CubeMap
fixed4 Cubemap=texCUBE(_CubeMap,R);
最终效果:
Shader "unity/Texture_Mipmap"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
//设置滑杆值来调节Mipmap [IntRange]只能用于Range()滑杆中,在滑杆调节值时只能生成整数
[IntRange]_Mipmap("Mipmap",Range(0,12))=0
//定义关键字枚举
[KeywordEnum(Repeat,Clamp)]_WrapMode("WrapMode",int)=0
//定义立方体纹理
_CubeMap("CubeMap",Cube)="white"{}
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//定义UV平铺方式的相关变体
#pragma shader_feature _WRAPMODE_REPEAT _WRAPMODE_CLAMP
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
half3 normal:NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
fixed3 localPos: TEXCOORD1;
float4 worldPos:TEXCOORD2;
half3 worldNormal:TEXCOORD3;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half _Mipmap;
//声明立方体纹理
samplerCUBE _CubeMap;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
//模型的本地空间坐标
o.localPos=v.vertex.xyz;
o.worldPos=mul(unity_ObjectToWorld,v.vertex);
o.worldNormal=UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
#if _WRAPMODE_REPEAT
//frac(x)函数的作用是当x大于1时,取其小数部分,这样就可以实现当UV的Tilling值大于1时UV产生平铺的功能
//可以解决当默认的纹理的平铺方式为Clamp时,Repeat(重复平铺)无效的现象
i.uv=frac(i.uv);
#elif _WRAPMODE_CLAMP
//方法一:clamp(x,a,b) 若x小于a则返回a,若x大于b则返回b(指将x限制在a-b的范围内)
//方法二:saturate(i.uv) 函数的作用为将值限制在0-1的范围内
//i.uv=clamp(i.uv,0,1);
i.uv=saturate(i.uv);
#endif
// fixed4 c = tex2D(_MainTex, i.uv);
//tex2Dlod(samper 2D,float4 s)函数代表采样不同级别的纹理 其中有两个参数,第二个参数是float4类型 s.z=0 s.w代表采样纹理的级别(_Mipmap)
fixed4 c= tex2Dlod(_MainTex,float4(i.uv,0,_Mipmap));
//采样立方体纹理 利用模型的本地空间坐标采样立方体纹理
//fixed4 Cubemap=texCUBE(_CubeMap,i.localPos);
//向量V代表指向摄像机方向的向量
fixed3 V=_WorldSpaceCameraPos-i.worldPos.xyz;
fixed3 N=normalize(i.worldNormal);
//反射向量R 其中-V代表由摄像机指向模型方向的向量,相当于入射方向的向量
fixed3 R=reflect(-V,N);
//由反射向量R采样_CubeMap
fixed4 Cubemap=texCUBE(_CubeMap,R);
return Cubemap;
}
ENDCG
}
}
}