解决纹理重复问题

解决纹理重复问题

首页休闲益智相同图块连接更新时间:2024-10-04

背景:

大面积的纹理映射最典型的问题之一是纹理的重复问题。虽然像 GL_ARB_texture_mirrored_repeat 这样的东西可以通过将重复周期增大两倍来帮助缓解问题,但硬件无法自行解决问题。然而,如果我们接受为每个样本进行一次以上的纹理采样,那么有相当不错的方法来防止纹理重复。我将介绍 3 种不同的方案。


需要平铺的纹理


使用GL_REPEAT进行常规纹理平铺


使用方案1的效果

1

方案1

防止纹理重复的一种方法是为重复的每个图块分配随机偏移和方向。我们可以通过确定我们位于哪个图块中,为该图块创建一系列四个伪随机值,然后使用这些值来偏移和重新定向纹理来实现这一点。重新定向可以像在 x 或 y 或两者上进行镜像一样简单。这会在整个表面上产生不重复的图案。

刚刚描述的方案存在一些需要解决的警告:首先,图案将显示跨越图块边界的接缝,因为不同偏移的纹理图块在图块边界处不匹配。其次,由于最终纹理获取坐标本身引入了不连续性,导数将在图块边界处产生巨大的跳跃,并且 mipmap 将破裂,从而产生线条伪影。

解决这两个问题的一种解决方案是在四个纹理图块处使用上述偏移和方向对纹理进行采样,并在足够接近当前图块的边界时(例如在正 U 和 V 方向上)在它们之间进行混合。虽然这会在图块的某些区域引入一定程度的模糊,但在大多数情况下这是可以接受的,如本文开头的图像所示。

当然,为此我们必须使用自定义纹理渐变,它必须来自原始的重复 UV 映射。

代码非常简单,可以参考Shadertoy :https://www.shadertoy.com/view/lt2GDd

vec4 textureNoTile( sampler2D samp, in vec2 uv ) { ivec2 iuv = ivec2( floor( uv ) ); vec2 fuv = fract( uv ); // generate per-tile transform vec4 ofa = hash4( iuv ivec2(0,0) ); vec4 ofb = hash4( iuv ivec2(1,0) ); vec4 ofc = hash4( iuv ivec2(0,1) ); vec4 ofd = hash4( iuv ivec2(1,1) ); vec2 ddx = dFdx( uv ); vec2 ddy = dFdy( uv ); // transform per-tile uvs ofa.zw = sign( ofa.zw-0.5 ); ofb.zw = sign( ofb.zw-0.5 ); ofc.zw = sign( ofc.zw-0.5 ); ofd.zw = sign( ofd.zw-0.5 ); // uv's, and derivatives (for correct mipmapping) vec2 uva = uv*ofa.zw ofa.xy, ddxa = ddx*ofa.zw, ddya = ddy*ofa.zw; vec2 uvb = uv*ofb.zw ofb.xy, ddxb = ddx*ofb.zw, ddyb = ddy*ofb.zw; vec2 uvc = uv*ofc.zw ofc.xy, ddxc = ddx*ofc.zw, ddyc = ddy*ofc.zw; vec2 uvd = uv*ofd.zw ofd.xy, ddxd = ddx*ofd.zw, ddyd = ddy*ofd.zw; // fetch and blend vec2 b = smoothstep( 0.25,0.75, fuv ); return mix( mix( textureGrad( samp, uva, ddxa, ddya ), textureGrad( samp, uvb, ddxb, ddyb ), b.x ), mix( textureGrad( samp, uvc, ddxc, ddyc ), textureGrad( samp, uvd, ddxd, ddyd ), b.x), b.y ); }

注意代码将方向镜变换传播到导数。由于底层硬件可能会取这些值的绝对值,因此还可以优化,只需将ddx和ddy传递给textureGrad()函数即可。

该技术唯一需要注意的是,每个图块的哈希函数可能会在高缩小因子下出现别名。例如,如果使用此技术对巨大地形进行纹理处理,则根据此纹理处理方法的使用方式,地平线或地形的远处部分可能会出现锯齿。

这种技术不仅可以用于正方形,还可以用于任何平铺空间的图案,例如三角形或六边形。

2

方案2

第二种方法是用原始纹理的随机缩放、偏移和旋转副本覆盖整个表面,这些副本混合在一起,并依赖于混合权重因子到每个副本中心的距离。这可以通过平滑的 voronoi模式来完成。与 voronoi 模式中每个特征点的高斯衰减成比例的混合权重效果很好。只需记住将最终颜色重新归一化为每个特征点的总贡献,否则纹理亮度范围将会丢失。

Shadertoy的参考代码:https://www.shadertoy.com/view/4tsGzf

vec4 textureNoTile( sampler2D samp, in vec2 uv ) { vec2 p = floor( uv ); vec2 f = fract( uv ); // derivatives (for correct mipmapping) vec2 ddx = dFdx( uv ); vec2 ddy = dFdy( uv ); // voronoi contribution vec4 va = vec4( 0.0 ); float wt = 0.0; for( int j=-1; j<=1; j ) for( int i=-1; i<=1; i ) { vec2 g = vec2( float(i), float(j) ); vec4 o = hash4( p g ); vec2 r = g - f o.xy; float d = dot(r,r); float w = exp(-5.0*d ); vec4 c = textureGrad( samp, uv o.zw, ddx, ddy ); va = w*c; wt = w; } // normalization return va/wt; }

当然,缺点是该算法有9次纹理采样,这可能会对内存造成很大的压力,但是它确实有助于高质量的图像。


使用GL_REPEAT进行常规纹理平铺


基于Voroni的平滑平铺

3

方案3

还有一种非常简单的方法来实现。这个想法不用方法 1 中那样的图块,而是具有类似于方法 2 中的区域,但定义不同。首先,让纹理像往常一样在平面上重复。然后,假设我们通过简单地将常量偏移应用于纹理查找来拥有此平铺图案的多个虚拟版本,例如 8 个。通过允许这 8 个虚拟版本的旋转、对称和缩放,可以使该方案效果更好,但对于我们的目的来说,大多数情况下偏移就足够了。

现在,最终的不重复模式在纹理域的每一点进行评估,首先在0到7之间选择一个数字,我们可以称之为索引,然后根据它从这些版本中选择一个进行采样。通过在区域内选择相同的索引值,我们可以创建使用相同虚拟图案的平面区域。当然,会看到接缝,为了改善这一点,我们实际上将索引设置为浮点值,而不是整数。这样,索引可以在平面上缓慢、平滑地变化。然后,我们可以使用它在最接近的两个虚拟模式之间进行插值,而不是只选择一个。这种低频率的索引变化模式可以是程序噪声,或者来自查找表或纹理的随机值,这样更容易进行滤波,从而得到一个完全可滤波的结果模式(不像基于程序函数的索引值通常更难滤波)。

该方案的代码如下,具体可以参考Shadertoy :https://www.shadertoy.com/view/Xtl3zf

vec4 textureNoTile( sampler2D samp, in vec2 uv ) { // sample variation pattern float k = texture( iChannel1, 0.005*x ).x; // cheap (cache friendly) lookup // compute index float index = k*8.0; float i = floor( index ); float f = fract( index ); // offsets for the different virtual patterns vec2 offa = sin(vec2(3.0,7.0)*(i 0.0)); // can replace with any other hash vec2 offb = sin(vec2(3.0,7.0)*(i 1.0)); // can replace with any other hash // compute derivatives for mip-mapping vec2 dx = dFdx(x), dy = dFdy(x); // sample the two closest virtual patterns vec3 cola = textureGrad( iChannel0, x offa, dx, dy ).xxx; vec3 colb = textureGrad( iChannel0, x offb, dx, dy ).xxx; float sum( vec3 v ) { return v.x v.y v.z; } // interpolate between the two virtual patterns return mix( cola, colb, smoothstep(0.2,0.8,f-0.1*sum(cola-colb)) ); }

这里有几点需要注意:首先,只要变化模式是低频率的,采样它就非常便宜,因为几乎所有时候数据都会在纹理缓存中。其次,用于创建虚拟图案偏移量的哈希函数可以根据需要变得复杂。第三,由于我们引入了不连续性,需要计算纹理坐标的导数以进行适当的滤波。第四,插值函数可以是像上面例子中那样的立方或线性的,也可以使用任何其他使图像看起来好看的花哨技术——在这个例子中,我使用了两个虚拟图案之间的差异来增强混合区域的对比度。

下面是常规平铺图案和方案3的比较,方案3只需两次纹理获取,以及不同虚拟纹理采样区域的分解:


使用GL_REPEAT进行常规纹理平铺


方案3的效果


用于变化的索引,编码为颜色


变体2,索引mask


变体4,索引mask


变体6,索引mask

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved