本文是视频课程《Unity技术美术TA:Shader篇》,算是对自己学习的总结,也希望分享下所学知识~~
渲染管线什么时候用到纹理?
1.定点/片段着色器(读取)
2.帧缓存(写入)
纹理有哪些?
- 颜色纹理
- 一维纹理
- 二维纹理
- 三维纹理
- 立方体纹理
- 几何纹理
- 凹凸纹理
- 视差纹理
- 置换纹理
- 法线纹理
Mipmap:纹理的LOD
近处采用大图,远处采用小图
生成规则:
依次生成分辨率xy分别/2的一级一级的贴图
优点:
1.角色在远处,采样抖动
2.分级加载,减少不同配置下的内存
坏处:+30%显存
Shader LOD 采样代码:
Shader "Custom/TA/Tex2"
{
Properties
{
...
[IntRange] _Mipmap("Mipmap", Range(0,10))=0
}
SubShader
{
Pass
{
...
half _Mipmap;
fixed4 frag(v2f i) : SV_Target
{
//tex2Dlod
//二维纹理采样,w表示在mipmap的第几级
fixed4 col = tex2Dlod(_MainTex, float4(i.uv, 0, _Mipmap));
return col;
}
ENDCG
}
}
}
纹理过滤:
一个像素可能对应多个纹素,需要处理一下到底取哪个颜色
1.Point(no filter)
不过滤,只采样采样点最近的纹素
2.Bilinear
双线性过滤,采样点周围采样4个纹素,加权平均(离得近的权重高)
3.Trilinear
三线性过滤,双线性过滤基础上再加上对Mipmap远近级别过滤,采样8个纹素插值
纹理环绕方式:
1.Repeat:重复
2.Clamp:最后像素填充
3.其他
设置是在图片的 Inspector面板,一个图需要两种模式如果用两张图浪费内存
需要在Shader内实现
代码如下:
Shader "Custom/TA/Tex2"
{
Properties
{
...
//关键字枚举定义方式,同时需要定义宏
[KeywordEnum(Repeat,Clamp)]_WrapMode("WrapMode",int)=0
}
SubShader
{
Pass
{
...
//这个不需要运行时候改,用shader_feature就行
#pragma shader_feature _WRAPMODE_REPEAT _WRAPMODE_CLAMP
fixed4 frag(v2f i) : SV_Target
{
fixed2 uv = i.uv;
#if _WRAPMODE_REPEAT
//只取小数部分
uv = frac(uv);
#elif _WRAPMODE_CLAMP
//限制在0-1之间
// uv = clamp(uv, 0,1);
uv = saturate(uv);
#endif
,,,
}
ENDCG
}
}
}
立方体纹理
Cubemap,通常用作反射效果
采样效果
如何采样呢?
Shader "Custom/TA/TexTest"
{
Properties
{
...
//定义 Cubemap
_CubeMap("CubeMap", cube) = "white" {}
}
SubShader
{
Pass
{
...
samplerCUBE _CubeMap;
fixed4 frag(v2f i) : SV_Target
{
...
// v n r
// 反射:视角方向延法线的反射坐标
fixed3 v = normalize(UnityWorldSpaceViewDir(i.worldPos)); //内置函数求视角方向
fixed3 n = normalize(i.worldNormal);
fixed3 r = reflect(-v, n);
fixed4 cubeMapCol = texCUBE(_CubeMap, r);
return cubeMapCol;
}
ENDCG
}
}
}
法线贴图
给几何物体添加细节
发现纹理偏蓝色为主
因为rgb存储的是xyz轴上的偏移数据,z轴代表高度,z轴多一些,就是蓝色
xyz范围是[-1,1],需要*0.5+0.5,
例子:(0, 0,1)=>(0.5,0.5,1),就是蓝色
世界空间下采样如图:
(表现不正确,有点凹进去有的凸出来)
代码如下:
Shader "Custom/TA/TexTest"
{
Properties
{
...
//法线贴图格式 [Normal]
//法线的默认值 bump
[NoScaleOffset] [Normal]_NormalMap("NormalMap", 2D) = "bump"{}
}
SubShader
{
Pass
{
...
sampler2D _NormalMap;
fixed4 frag(v2f i) : SV_Target
{
// 错误效果 [0,1] -> [-1,1]
// fixed3 nMap = tex2D(_NormalMap, i.uv).xyz * 2 - 1;
// UnpackNormal
// 发现采样不能这么用,在不同平台法线贴图不一样,需要解码
fixed3 nMap = UnpackNormal(tex2D(_NormalMap, i.uv));
fixed3 n1 = normalize(nMap); //此处需要到切线空间下采样,否则表现不正确
fixed3 l = UnityWorldSpaceLightDir(i.worldPos);
fixed3 diffuse = max(0, dot(n1, l));
return fixed4(diffuse, 1);
}
ENDCG
}
}
}
为什么要有法线空间?
可以在模型空间中做偏移,但是是强关联的,如果换模型 就不通用了
但是法线空间存的是绝对值,不随模型改变而变化
x:切线(T)
y:副切线(B)
z:法线(N)
俗称TBN矩阵
计算方式:
1.统一到切线空间(不常用)
2.统一到世界空间
Shader "Custom/TA/TexTest"
{
SubShader
{
Pass
{
struct appdata
{
...
//切线
float4 tangent : TANGENT;
};
struct v2f
{
...
//表示切线空间下的旋转矩阵
float3 tSpace0 : TEXCOORD4;
float3 tSpace1 : TEXCOORD5;
float3 tSpace2 : TEXCOORD6;
};
v2f vert(appdata v)
{
...
//切线相关计算
//切线从本地空间转世界空间
float3 worldTangent = UnityWorldSpaceLightDir(v.tangent);
//副切线计算,v.tangent.w 表示副切线的方向
//unity_WorldTransformParams.w 缩放的值 1或者 -1
fixed3 worldBinormal = cross(o.worldNormal, worldTangent) * v.tangent.w * unity_WorldTransformParams.w;
//切线变换矩阵 * 世界空间下法线 = 切线空间下法线
// 世界空间下法线 = 切线空间下法线 * 切线变换的逆矩阵
// 切线变换矩阵 是单位矩阵,逆矩阵就等于转置矩阵
//求从切线空间到世界空间的变换矩阵
o.tSpace0 = float3(worldTangent.x, worldBinormal.x, o.worldNormal.x);
o.tSpace1 = float3(worldTangent.y, worldBinormal.y, o.worldNormal.y);
o.tSpace2 = float3(worldTangent.z, worldBinormal.z, o.worldNormal.z);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
...
// 错误效果 [0,1] -> [-1,1]
// fixed3 nMap = tex2D(_NormalMap, i.uv).xyz * 2 - 1;
// 法线采样不能这么用 (tex2D),在不同平台法线贴图不一样,需要解码
// UnpackNormal
fixed3 nMap = UnpackNormal(tex2D(_NormalMap, uv));
//切线空间转换为世界空间
half3 worldNormal = half3(dot(i.tSpace0, nMap), dot(i.tSpace1, nMap), dot(i.tSpace2, nMap));
}
ENDCG
}
}
}
反射探针
烘焙会根据所有静态物体环境生成一个Cubemap
可以用来做反射
1.添加反射探针
在里面加静态物体
采样代码:
//当前激活的CubeMap存储在unity_SpecCube0当中
// 必须要用UNITY_SAMPLE_TEXCUBE进行采样,然后需要对其进行解码
fixed4 unity_CubeColData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, r);
half3 unity_CubeCol = DecodeHDR(unity_CubeColData, unity_SpecCube0_HDR);
效果图: