赞
踩
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
, 那么这句话是什么意思呢? 今天我们就来详细研究.
这个接口在文件Editor/Data/CGIncludes/UnityCG.cginc
中, 是一个宏定义:
// Transforms 2D UV by scale/bias property
// 通过缩放(scale)和位移(bias)属性转换2D UV坐标
#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)
要使用这个接口, 我们需要几个变量:
Properties { _MainTex ("Texture", 2D) = "white" {} } struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f o; // 包含纹理坐标的输出结构体 sampler2D _MainTex; // 纹理采样器, 与材质属性名字一致 float4 _MainTex_ST; // 纹理ST属性向量(S: Scale, T: Transform), Unity会自动向这个变量填充值 o.uv = TRANSFORM_TEX(v.uv, _MainTex); // #define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)
可以看到, 接口的作用是使用_MainTex_ST
的xy
分别对纹理坐标的xy
坐标进行缩放, 使用_MainTex_ST
的zw
分别对纹理坐标的xy
坐标进行位移.
具体的效果我们在下面介绍. 这里只需要理解为使用这个接口对纹理坐标进行缩放和位移.
图中的"Tiling(瓦片)"对应_MainTex_ST
的xy
分量, "Offset(偏移)"对应_MainTex_ST
的zw
分量.
要理解这个调用, 我们需要一些前置知识, 当然, 这些知识网上已经有很多优秀的文章, 本文只是做通俗化理解, 各位有兴趣可以自行搜索:
我们知道, 日常所说的纹理只是一张普通的图片(这里只说2D纹理), 但是如果在图形引擎中使用, 只是一张图片的信息是不够的, 我们需要在图片上附加一些额外的信息, 也就是需要告诉引擎我们需要怎么使用这张图片, 比如使用什么颜色格式来加载这张图片到内存中使用, 不同的格式会有不同的效果, 再比如这张图片是什么用途(用作ui? 用作法线?)等等.
没有Unity之类的游戏引擎时, 我们直接使用图形API, 比如OpenGL, 这时我们需要显示调用API告诉引擎纹理的使用方式(如glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
这句调用告诉图形API, 在纹理坐标越界时, 我们需要重复采样纹理), 而使用游戏引擎时, 只需在Editor上设置纹理参数, 引擎会在底层自行通知图形API如何设置这些参数, 如图:
总之我们可以简单的理解, 纹理就是一张附加上了额外信息的图片, 这些额外信息是告诉引擎如何使用这张图片.
有了图片, 同时也知道使用这张图片的方式(各种参数), 我们就可以将纹理的数据加载到GPU内存了, 然后在绘制某个图形时, 激活要使用的纹理即可.
纹理坐标就是我们一般所说的UV坐标, 3D纹理为UVW坐标, 分别对应XYZ轴. 顺便说一下, 有些同学可能也见过用STR来表示纹理坐标的, 也是分别对应XYZ轴, 那么两者有什么区别呢? 我简单的研究了下, 可能STR坐标更多讲的是物体表面的纹理坐标, 而UVW更多的是说纹理本身的纹理坐标, 在大部分情况下两者基本上是一样的, 有些图形API习惯讲UVW(比如DX), 有些则习惯STR(比如OpenGL, 在上面的接口glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
中, GL_TEXTURE_WRAP_S
里面的S就是对应水平方向, 也就是X轴). 对于我们一般的使用者, 简单将两者看成一样的即可, 因为Unity里面使用的是UVW的概念, 所以本文使用UVW来介绍.
所谓纹理映射就是将图片"贴"到物体表面的过程.
简单想象一下, 我们需要将一张四边形的图片"贴"到四边形物体表面, 在图片的四个角上使用铆钉将图片分别钉在物体表面的四个角上, 所以平时也将纹理映射称为"纹理贴图". 这个"贴"字是很形象的.
要将图片"贴"到物体表面, 首先要做的就是将两者的大小做匹配, 如果两者大小一致, 那么直接"贴"上去就行, 如果图片比物体表面大, 那么就需要缩小图片, 反之则需要放大图片, 还如果我们只想将图片的一部分贴到表面, 那么就需要放大图片, 然后移动图片使需要的那一部分与表面匹配.
当然, 这里的所谓图片的缩放, 本质上只是因为采样坐标的变化, 最后的结果"看起来"像是缩放了图片, 并不是真的对图片进行了缩放.
图片的缩放, 我们一般通过向量的缩放(Scale)操作来完成, 而选取部分贴图, 则通过向量的位移(Transition)来完成.
图片和表面大小匹配好了之后, 我们就需要真正开始"贴"的操作, 这就是所谓纹理采样.
我们知道, 绘制一个表面时, 其实是在给表面的所有顶点着色, 也就是说给每个顶点设置颜色值, 而这个颜色值就是取自纹理(当然也可以取自其它地方).
在绘制每个顶点的时候, 可以将该顶点的坐标转换到纹理坐标, 也就是计算这个顶点的位置在纹理上对应位置的坐标, 然后用这个纹理坐标在纹理上取颜色值, 这就是纹理采样.
而在代码上的实现就更简单了, 纹理的数据会组装成一个二维数组, uv对应的就是数组索引, 纹理采样就是通过索引来取数组的值而已.
总结一下纹理映射:顶点的坐标转换到纹理坐标, 然后通过纹理坐标取颜色值来给顶点着色的过程.
也就是说纹理映射分为两个过程, 一个是坐标转换, 一个是纹理采样.
通常情况下, 坐标转换的过程是引擎自己完成的(直接使用图形API则需要自行转换), 这个过程也比较简单, 就是将物体表面与纹理进行1:1匹配, 就像上文所说, 将物体矩形表面的四个顶点与纹理的四个顶点锚定而已.
也就是说, 引擎传给顶点shader的纹理坐标, 已经是做过1:1匹配之后的结果:
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
那么如果我们需要利用纹理坐标做一些效果, 比如uv动画, 放大镜等, 那么就需要在引擎给的纹理坐标基础上做进一步的处理, 这也就是本文要介绍的内容由来.
当然, 这只是最基础自定义的处理, 如果需要更多的效果就需要自行编写算法了.
综上所述, 引擎给我们提供了基础的顶点纹理坐标, 我们如果不做任何处理的情况下, 纹理将充满整个表面, 而TRANSFORM_TEX
接口可以对纹理坐标做二次处理(缩放和位移)以达到一些更复杂的效果.
因为引擎提供给我们的顶点纹理坐标是1:1映射, 坐标范围基本上不会超过[0, 1], 但是通过TRANSFORM_TEX
之后的坐标很可能会处于[0, 1]之外. 比如Tiling.x = 2
会造成所有的uv坐标的x坐标放大两倍, 这样的坐标在纹理上是取不到值的.
这时, 我们就需要告知图形API如果处理这种情况. 这就是纹理环绕模式.
之所以被称为纹理环绕模式, 我猜想是为了表达纹理坐标"越界"这个概念.
在Unity中有以下几种方式可以设置:
重复模式, 简单讲就是超出[0, 1]的部分重复采样, 详细的算法为:使用取余操作(%1)进行采样, 比如1.2%1=0.2, 然后使用0.2去采样纹理. 得出的效果就是纹理重复显示.
算法解释起来可能不是那么直观, 直接给效果(分别Tiling=(2, 1)和Tiling=(1, 2)的结果).
约束模式: 简单讲就是讲所有坐标约束在边界, 类似函数Clamp
的效果, 即Clamp(0, 1)
, 大于1的使用1, 小于0的使用0. 放在这里也就是超过1的坐标就使用1来采样. 效果如下:
镜像模式: 和Repeat模式类似, 只是使用其镜像(在重复的轴上添加"负号", 即翻转另一个轴), 效果如下:
镜像一次模式: 先使用镜像, 然后约束到边界, 实质是镜像后将坐标约束到[-1, 1], 效果如下:
今天我们通过TRANSFORM_TEX
接口, 了解了纹理相关的基础知识, 主要是纹理的概念, 纹理映射和纹理采样, 纹理环绕模式等.
同时我们了解到了在Unity中如何应用这些概念, 对于经常听到了uv, uv动画等概念的由来有了基本的认识, 这将有助于日常的工作和接下来的研究.
如果对上面的概念还有不清晰的地方, 大家可以参考网上其他优秀的文章.
好了, 今天的内容就是这些, 希望对大家有所帮助.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。