纹理基础

纹理坐标

纹理基础_第1张图片

图一:direct3d API定义纹理坐标,起始点左上角

纹理基础_第2张图片

图二:其他API定义的纹理坐标,起始点左下角

Unity的默认网格有适合纹理映射的UV坐标,可以使用TEXCOORD(X)访问:最后一位数字代表插值寄存器

float2 UV 	: TEXCOORD0;
float4 normal 	: TEXCOORD1;
View Code

采样函数:

tex2D(_MainTex, i.uv);
View Code

Tiling and Offset 平铺和偏移:

[NoScaleOffset]属性前缀,隐藏Tilling和Offset
//为了获取纹理的属性,unity语义提供了:纹理名_ST
float4 _MainTex_ST;

//S : scale; T: translation
//_MainTex_ST.xy : 存储纹理缩放值
//_MainTex_ST.zw : 存储纹理偏移值

//对纹理缩放偏移计算
v.uv = a.uv * _MainTex_ST.xy + _MainTex_ST.zw;

//unity内置宏计算纹理平移缩放
TRANSFORM_TEX(v.uv, _MainTex);
View Code

纹理基础_第3张图片

图三:纹理的平铺模式,超过(0,1)之后如何平铺

  • Repeat:到达边缘后,重复采样平铺。例如1.01、2.07、3.05,舍弃整数部分只对小数采样,无线重复。
  • Clamp:到达边缘后,只对边缘最后一像素位置采样,被拉伸的感觉。
  • Mirror:镜像翻转。
  • Per_axis:单独给x和y轴设定平铺模式。

image

图四:纹理的过滤模式

Fileter Mode是对Wrap Mode的一次修正,计算量由低到高如下排列:

  • Point:使用最近的纹素采样,如果纹素没有精确映射到像素,呈现质量就是像素风、块状感。
  • Bilinear:以自身为中心找到临近四个纹素做双线性插值,以得到更好的平滑。256纹素密度与256像素密度,如果放大到512像素密度会模糊,缩小到128像素密度会被锐化(丢弃了一些纹素)。 与之对应的解决办法就是使用MipMap。
  • Trilinear:一般不用,手机上太费了。

image

图五:各向异性level

image

图六:开启关闭控制

在透视投影下,一个纹理的投射角度差异,前后维度上会出现的视角上的扭曲,开启各向异性可以降低一定程度上影响。

  • Disabled:关闭所有纹理Aniso Level,不论如何设置都不采样。
  • Per Texture: 单独为某个纹理开启,支持该纹理Ansio采样。
  • Forced On:为所有纹理强制开启, 也可单独关闭某个纹理(值为0)。

MipMap and Fade out MipMap

image

图七:淡出距离越远(与摄像机的距离越远),生成的MipMap就越模糊。


将两张和合并,一张主要纹理一张细节纹理

v2f vert (appdata v)
 {
     v2f o;
     o.vertex = UnityObjectToClipPos(v.vertex);
     o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);//v.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw;
     o.uv.zw = TRANSFORM_TEX(v.uv, _DetailTex);// v.uv.xy * _DetailTex_ST.xy + _DetailTex_ST.zw;
     return o;
 }

fixed4 frag (v2f i) : SV_Target
 {
     //fixed4 col = (tex2D(_MainTex, i.uv*10) * _Tint * 10) * tex2D(_MainTex, i.uv) ;
     float4 col = tex2D(_MainTex, i.uv.xy ) * _Tint;
     col *= tex2D(_DetailTex, i.uv.zw * 10)*2;
     return col;
 }
View Code
Unity自带贴图合并宏公式:
combine src1 * src2 例如将模型顶点颜色与纹理采样后相乘,或者多个纹理采样后相乘。结果颜色更深
combine src1 + src2 ---。结果色更明亮

combine src1src2

                                           

(1,0,0,1) - (1, 1, 0, 1) = (0,-1,0, 0)负数强制取正。为黑色

(1,1,0,1) - (1, 0, 0, 1) = (0,1,0, 0)为绿色

combine src1 lerp (src2) src3

使用src2的alpha值在src3和src1之间插值。注意,当为1时使用src1,当为0时使用src3。

Color.lerp(c_from , c_to , t); t为0返回from,t为1返回to

combine src1 * src2 + src3 src1与src2的alpha相乘,结果加到src3。
SetTexture [TextureName] {Texture Block}
//SetTexture [_MainTex] { combine previous * texture, previous + texture }
  • Previous 上一次SetTexture结果.
  • Primary 来自光照计算结果或者顶点颜色. //diffuse, ambient and specular 三种颜色的集合
  • Texture 指定TextureName的纹理.
  • constantColor 指定常量的颜色. //在Combie中定义一个常量颜色

Bump Map(凹凸贴图):

NormalMap 法线纹理:比较常用

HeightMap 高度纹理(视差映射):手机平台不常用,使用法线纹理替代。

Occlusion Map:细节纹理

Secondary Maps (Detail Maps) & Detail Mask:细节纹理

Bump Map Type Describe
NormalMap                 法线图,映射公式:normal=pixel*2-1,反映射:pixel=(normal+1)/2. 法线存储既可以在模型空间,也可以在切线空间。//unity顶点输入结构带切线变量,一般存在切线空间更佳。
HeightMap 灰度图(黑白纹理-强度值),颜色越浅该表面越向外凸起,颜色深越凹。视差映射技术,与Occlusion Map搭配使用体验更佳,计算昂贵
Occlusion Map 灰度图,表面细节更丰富。颜色值白色表示应该接收完全间接照明的区域,黑色表示没有间接照明。如裂缝或褶皱,实际上不会接收到太多的间接光,可与高度图一起使用。
Detail Maps 参考StandardShader:第二细节纹理,应用第二反照率图和第二法线图,在近距离观察时有清晰的细节,比如毛孔、细小的裂缝等。计算昂贵


HeightMap:

高度图为了模拟平面的凹凸程度,将高度(黑白色)数据存储在纹理中,由于纹理数据是二维的,即u轴和v轴,那为了得到这些数据为每个片段生成法向量,可分别在u轴和v轴上采样。

纹理基础_第4张图片

图八:斜率采样示意图,从一个点到下一个点

先从U轴计算:f(u)=h ,如果知道了斜率就能求得u轴上所有点的法向量,但斜率由h的变化程度高低决定。为了近似得到从一个点到下一个点的高度差,f(0+1)−f(0)->2(f(0+1/2) – f(0))->4(f(0+1/4) – f(0))…..

image=>image

图九:有限差分

那么切向量就是[1, f’(u), 0]T,从切向量计算法向量[f’(u),1,0]T:

sampler2D _HeightMap;
float4 _HeightMap_TexelSize;//xy是纹素坐标(uv),zw是整张纹理宽高
float2 delta = float2(_HeightMap_T exelSize.x, 0);//u轴
float h1 = tex2D(_HeightMap, i.uv);//模型uv在高度图采样
float h2 = tex2D(_HeightMap, i.uv + delta);//二次采样

//第一步套用公式
//i.normal = float3(1, (h2 - h1) / delta.x, 0); 

//第二步优化,缩放向量并不改变方向,消除了除法操作
//i.normal = float3( delta.x, (h2 - h1), 0);

//第三步改变垂直方向,需要得到法向量正向垂直于表面,那么逆时针旋转90度以翻转x分量符号.//Y是扰动法向量的高低变化因子
i.normal = float3( h1 - h2, 1 , 0);

i.normal = normalize(i.normal);

有限差分只在一个方向近似求值,为了更好近似可以在两个方向线性逼近:

image

图十:中心差分

float2 delta = float2(_HeightMap_TexelSize.x * 0.5, 0);
float h1 = tex2D(_HeightMap, i.uv - delta);
float h2 = tex2D(_HeightMap, i.uv + delta);
i.normal = float3(h1 - h2, 1, 0);

那么f’(u,v)计算f’(v)同理,切向量[0,f’(v),1]T,法向量是[0,1,-f’(v)]:

float2 du = float2(_HeightMap_TexelSize.x * 0.5, 0);
float u1 = tex2D(_HeightMap, i.uv - du);
float u2 = tex2D(_HeightMap, i.uv + du);
//float3 tu = float3(1, u2 - u1, 0);
float2 dv = float2(0, _HeightMap_TexelSize.y * 0.5);
float v1 = tex2D(_HeightMap, i.uv - dv);
float v2 = tex2D(_HeightMap, i.uv + dv);
//float3 tv = float3(0, v2 - v1, 1);
//i.normal = cross(tv, tu);//直接使用叉积求出垂直于u和v轴的法向量=>(0*(v2-v1)-(u2-u1)*1, 1*1-0*0, (u2-u1)*0-1*(v2-v1))=(u1-u2, 1, v1-v2)
i.normal = float3(u1 - u2, 1, v1 - v2);
i.normal = normalize(i.normal);

 image

Normal Map:

高度图是每帧采样实时计算法线,为了避免计算,采用预制法线纹理代替。

image

图十一:Unity中使用高度图

导入高度图预先计算法线纹理必须勾选Create from Grayscale,白色表示相对更高,黑色表示相对更低。

像素分量范围是[0,1],而法线分量范围[-1,1]。相互映射转换公式为:

pixel = (normal+1)/2;

normal = pixel · 2 – 1;

法线纹理呈现淡蓝色,这是因为法向映射最常见的约定是将向上的方向存储在Z分量中(垂直于表面外侧),又由于DXT5nm纹理压缩格式的原因,只存储了X与Y分量舍弃了Z分量(Y分量存储在G通道,X分量存储在A通道,RB通道被舍弃)。通过推导法向量的单位向量可得Z分量:

|N| = |N|2 = Nx2 + Ny2 + Nz2 = 1;

Nz = 根号(1 -Nx2 - Ny2);


//第一种方法
// Unpack normal as DXT5nm (1, y, 1, x) or BC5 (x, y, 0, 1)
//dxt5压缩对应的位置取wy
i.normal.xy = tex2D(_NormalMap, i.uv).wy * 2 - 1;
i.normal.xy *= _BumpScale;//计算Z之前缩放才有效,平坦凹凸程度
i.normal.z = sqrt(1 - saturate(dot(i.normal.xy, i.normal.xy)));//dot模拟平方计算-((x,y)*(x,y))=-x方-y方
i.normal = i.normal.xzy;
i.normal = normalize(i.normal);

//UnityStandardUtils.cginc包含了解码法线函数,替代上面的方法
i.normal = UnpackScaleNormal(tex2D(_NormalMap, i.uv), _BumpScale);
i.normal = i.normal.xzy;
i.normal = normalize(i.normal);


Detail Maps(Second Texture) 与 Detail Normals

第二细节纹理与MainTexture合并,简要代码如下:

//顶点uv坐标映射到纹理uv
i.main_uv  = TRANSFORM_TEX(v.uv, _MainTex);
i.detail_uv = TRANSFORM_TEX(v.uv, _DetailTex);
//计算第二纹理的影响
float3 albedo = tex2D(_MainTex, i.main_uv).rgb * _Tint.rgb;
albedo *= tex2D(_DetailTex, i.detail_uv) * unity_ColorSpaceDouble;//颜色空间转换

第二细节纹理的法线映射

i.normal = UnpackScaleNormal(tex2D(_NormalMap, i.main_uv), _BumpScale);
i.normal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.detail_uv), _DetailBumpScale);
i.normal = i.normal.xzy;
i.normal = normalize(i.normal);


Blending Normals

方式一:(main.normal + details.normal) * 0.5; 简单容易,但结果不是很好。主纹理和细节纹理都变得平坦。理想情况下,当其中一个是平的,期望它不会影响到另一个。

float3 mainNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
i.normal = (mainNormal + detailNormal) * 0.5;
i.normal = i.normal.xzy;
i.normal = normalize(i.normal);
View Code

方式二:用z分量做缩放因子求偏导函数,然后相加。[Mx, My, Mz]T = [Mx/Mz, My/Mz, 1]T 同理求得detail偏导函数,然后相加:[Mx/Mz + Dx/Dz, My/Mz + Dy/Dz, 1]T .效果很好,但是在合并陡峭时仍将失去细节。

float3 mainNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
i.normal = float3(mainNormal.xy / mainNormal.z + detailNormal.xy / detailNormal.z, 1);
i.normal = i.normal.xzy;
i.normal = normalize(i.normal);
View Code

方式三:白色调和,对上一步合并法线分别乘以MzDz,然后再去掉x和y的缩放因子夸大缩放,使陡峭更加明显,同时平坦的法线,它不会影响到另一个了。

float3 mainNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
i.normal = float3(mainNormal.xy + detailNormal.xy, mainNormal.z * detailNormal.z);
//UnityStandardUtils包含了混合函数
//i.normal = BlendNormals(mainNormal, detailNormal);
i.normal = i.normal.xzy;
i.normal = normalize(i.normal);
View Code


Tangent Space

切线空间的法线纹理:顶点为原点,z轴为法线方向,x轴为切线方向,y轴为垂直于xz的副切线方向。Unity导入模型计算切线默认使用了mikktspace(在顶点着色器计算),也可以在片元着色器计算cross得到副切线向量。

在世界空间下计算

//在顶点或片元计算副切线后,求出世界空间下的法线向量
struct{
	//...
	#if defined(BINORMAL_PER_FRAGMENT)
	    float4 tangent : TEXCOORD2;
	#else
	    float3 tangent : TEXCOORD2;
	    float3 binormal : TEXCOORD3;
	#endif
	//...
};

v2f MyVertex(a2v v){
	//...
	#if defined(BINORMAL_PER_FRAGMENT)
	    i.tangent = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
	#else
	    i.tangent = UnityObjectToWorldDir(v.tangent.xyz);
	    i.binormal = cross(i.normal, i.tangent) * v.tangent.w;
	#endif
	//...
	return i;
}

fixed4 MyFrag(v2f v) : SV_TARGET{
	//...
	float3 tangentSpaceNormal= UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
	#if defined(BINORMAL_PER_FRAGMENT)
	    float3 binormal = cross(v.normal, v.tangent.xyz) * v.tangent.w;
	#else
	    float3 binormal = v.binormal;
	#endif
	//把切线空间转到世界空间
	//tangentSpaceNormal * [v.tangent,binromal, v.normal]转置
	v.normal = normalize(
	    tangentSpaceNormal.x * v.tangent +
	    tangentSpaceNormal.y * binormal +
	    tangentSpaceNormal.z * v.normal
	);
	//...
}
View Code

在切线空间计算

//计算副切线
float3 binormal = cross(normalize(i.normal), normalize(i.tangent.xyz)) * i.tangent.w;
//切线空间矩阵
float3x3 t_matrix = float3x3(i.tangent.xyz, binormal, i.normal);
//把各种信息转到切线空间下参与计算
View Code


完整代码:

1

你可能感兴趣的:(纹理基础)