Unity 渲染教程(三):使用多张纹理贴图优信彩

原标题:[内容分享] 基于高度的纹理混合shader

对多个纹理进行采样

纹理混合(Texture Blend)是非常常见的着色器需求,在很多实时游戏中都需要它来实现复杂的地面纹理,参考了Advanced Terrain Texture Splatting这篇文章写了一个基于高度进行混合的shader,这里分享一下自己的理解,效果如下:

应用一张细节贴图

优信彩票购彩大厅 1

在线性空间中处理颜色

说到贴图混合,也许你已经听说过Texture Splatting技术了,这个术语是Charles Bloom创造的,他在http://www.cbloom.com/3d/techdocs/splatting.txt里对这个技术进行了阐述;

使用一张splat纹理

混合的最简单做法就是,把贴图颜色的不透明度相乘,然后把结果相加 ,效果如下

这是关于渲染的教程系列的第三部分。 前面的部分介绍了着色器和纹理。 我们已经看到如何使用单个纹理来使平坦表面看起来更加复杂。 现在我们超越了它,同时使用多个纹理。

优信彩票购彩大厅 2

本教程是使用Unity 5.4.0进行的,目前该版本是开放测试版。 我使用build 5.4.0b15。

核心代码如下

优信彩票购彩大厅 3

优信彩票购彩大厅 4

混合的多张贴图

这端代码很好理解,通过splat_control这张贴图的四个通道控制_Splat0~_Splat3这四张贴图的混合,如果splat_control对应通道的值为1,那么这个通道对应的贴图就完全显示,为0则完全不显示,通过修改splat_control贴图就可以实现想要的混合效果了;

texel的解释

这种技术在的标准地形编辑器中有使用。

texel(纹理元素的简写)是纹理图形的基本单位,用于定义三维对象的曲面。3D 对象曲面的基本单位是纹理,而 2D 对象由像素组成。

如你所见,过渡很平滑,但不太自然。石头看起来就好像被沙子污染了,但在现实世界中这是不可能发生的情况。沙子不会粘着石头,相反地,沙子会落下来,填补到石头之间的缝隙里,而石头表面仍是干净的。

纹理,纹理元素或纹理像素是纹理图的基本单位,[1]用于计算机图形学。 纹理由表示纹理空间的纹素数组表示,就像其他图像由像素数组表示一样。

我们希望沙子会更多的在缝隙里面出现,而石头越高的地方沙子应该要越少,那么我们需要知道每一张贴图的深度信息,这里我把贴图对应的高度图保存在每张贴图的alpha通道。通过对比每张贴图的高度差,就可以知道应该显示哪张贴图了,为了简化,我们先计算两张贴图混合的情况,代码如下:

Texels也可以通过通过简单的过程获得的图像区域进行描述,例如阈值。 Voronoi细分可以用来定义它们的空间关系。 这意味着在每个纹理像素的质心和每个周围纹理的质心之间的中点进行整个纹理的划分。 结果是每个纹素重心都会围绕着一个Voronoi多边形。 这个多边形区域由所有比其他质心更贴近纹素质心的点组成

优信彩票购彩大厅 5

当纹理化3D表面或表面(称为纹理映射的过程)时,渲染器将纹素映射到输出图像中的适当像素。在现代计算机上,该操作在图形处理单元上完成。

`得到的是这么样的效果

纹理过程从空间中的位置开始。该位置可以在世界空间中,但通常它在模型空间中,使得纹理与模型一起移动。将投影机功能应用于位置,以将位置从三元素向量改变为值为零到一(uv)的两元素向量。[3]这些值乘以纹理的分辨率以获得纹素的位置。当请求的纹素不在整数位置时,应用纹理过滤。

优信彩票购彩大厅 6

当请求纹理外部的纹理,使用两种技术之一:夹紧或缠绕。夹紧将纹理限制到纹理大小,如果超过纹理大小,则将其移动到最接近的边。包装以纹理大小的增量移动纹理文件,以将其重新导入纹理。包装导致重复纹理;夹紧使其仅在一个位置。

其中用于混合的两张贴图和他们的透明通道分别是这样的:

纹理很好,但是有局限性。 它们具有固定数量的纹素,无论它们显示什么大小。 如果它们变小,我们可以使用mipmap来保持它们的良好状态。 但是当它们变大时,它们变得模糊。 我们不能发明任何细节,所以没有办法。 还是在那里?

优信彩票购彩大厅 7

当然,我们可以使用更大的纹理。 更多的纹素意味着更多的细节。 但纹理的大小有限制。 存储很多额外的数据是很浪费的,这些数据只是很接近。

我们加上splat_control 贴图的影响试试

增加纹理密度的另一种方法是平铺纹理。 那么你可以得到你想要的那么小,但你显然会得到一个重复的模式。 尽管如此,这可能并不明显。 毕竟,当你站在你的鼻子上碰壁,你只会看到整个墙壁的一小部分。

优信彩票购彩大厅 8

因此我们最好是能够通过合并未平铺的贴图和一张平铺的贴图来实现添加纹理。为了达到这个目的,让我们使用一张有明显样式的贴图。下面是一张方格网格。默认设置方式导入工程。我稍微扭曲了一下网格,使它更有趣,并使它可以感知到它的平铺。

得到这样的效果:

优信彩票购彩大厅 9

优信彩票购彩大厅 10

轻微扭曲的网格贴图

相比原来的线性混合,现在看起来已经自然很多了,沙子落在石砖路的缝隙里,并慢慢减少;但因为目前只是单纯的判断显示那个贴图,所以边缘看起来太硬了,人工痕迹比较明显,为了改进效果,我们给边缘增加一点过渡。

复制一份My First Shader并重新命名为Textured With Detail。我们从现在开始使用新的 shader。

优信彩票购彩大厅 11

Shader"Custom/Textured With Detail"{    Properties {        _Tint ("Tint",Color) = (1,1,1,1)        _MainTex ("Texture",2D) ="white"{}    }    SubShader {        …    }}

解释一下这段代码,先对比两张贴图的高度,高度差超过0.3的会被舍弃掉,为了防止在边缘以外的地方也被保留下来了,所以后面再乘一次splat_control,最后做一个标准化处理,把他们按比例缩放到0-1这个区间。

创建一个新的材质球,并把这张贴图赋值给它。

于是,我们就得到了下面的这个效果

优信彩票购彩大厅 12

看起来非常自然,沙子慢慢过渡到石砖路,砖面上的沙子比较少,缝隙里的沙子更多 。

优信彩票购彩大厅 13

我们把这个算法拓展到4张贴图,并通过一个值来控制混合的权重,完整代码如下:

带网格的细节材质

优信彩票购彩大厅 14

把材质球附加给四边形并查看一下。从远处看,它还挺好的。但是越靠近,它会变得越来越模糊。除了缺少细节外,由于压缩而导致的粗糙的艺术品也特别明显。

优信彩票购彩大厅 15

优信彩票购彩大厅 16

优信彩票购彩大厅 17

多次贴图采样

优信彩票购彩大厅 18

目前为止我们只是使用单张贴图来进行的采样,并使用采样的数据作为我们的片段程序的结果。接下来,我们将尝试去改变这种取样的方式,我们可以把采用的数据存储在一个临时的变量当中。

最终效果:

float4 MyFragmentProgram (Interpolators i) : SV_TARGET {                float4color= tex2D(_MainTex, i.uv) * _Tint;returncolor;}

左边混合权重为0.2,右边为1,混合权重为1的时候其实就是普通的线性混合了。

我们推断,我们可以通过引入一张平铺纹理来增加纹理密度。 我们简单地表现第二张纹理,这张纹理的采样是原始纹理采样的10倍的平铺,它是原始样本的十倍。 实际上替换原来的颜色,而不是叠加上去的。

优信彩票购彩大厅 19

float4color= tex2D(_MainTex, i.uv) * _Tint;color= tex2D(_MainTex, i.uv *10);returncolor;

加上法线和高光的效果

这样就产生了更细更小的网格。在网格看起来模糊之前,你现在要比前面能够靠得更近一些,也就是说没那么容易看得出来模糊了,也就是图像的分辨率变得更高了。

优信彩票购彩大厅 20

注意,到目前为止,我们表现了2张贴图,而最终只使用了他们中的一张。这看起来很浪费。是这样吗?让我们来看看编译后的代码,分别在 OpenGLCore 和Direct3D 11平台下。

知乎@喵喵Mya返回搜狐,查看更多

uniformsampler2D_MainTex;invec2vs_TEXCOORD0;layout(location =0)outvec4SV_TARGET0;vec2t0;voidmain(){    t0.xy = vs_TEXCOORD0.xy *vec2(10.0,10.0);    SV_TARGET0 =texture(_MainTex, t0.xy);return;}

责任编辑:

SetTexture0[_MainTex]2D0ps_4_0      dcl_sampler s0, mode_default      dcl_resource_texture2d (float,float,float,float) t0      dcl_input_ps linear v0.xydcl_output o0.xyzwdcl_temps10:mulr0.xy, v0.xyxx, l(10.000000,10.000000,0.000000,0.000000)1: sample o0.xyzw,r0.xyxx, t0.xyzw, s02:ret

优信彩票购彩大厅 21

硬编码的平铺

你发现没,在编译过后的代码中只有一张贴图在采样?对的。编译器替我们移除掉了不需要的代码。本质上来讲,编译器会以最后的结果为准,而舍弃掉任何对于结果无用的的事情。

当然话又说转来,我们并不是想替换掉原来的取样。我们想要的是合并两次取样。让我们把这两次取样的结果相乘起来,来实现我们想要的合并效果。

float4color= tex2D(_MainTex, i.uv) * _Tint;color*= tex2D(_MainTex, i.uv);returncolor;

那 shader 编译器是如何做的呢?

uniformsampler2D_MainTex;invec2vs_TEXCOORD0;layout(location =0)outvec4SV_TARGET0;mediumpvec4t16_0;lowpvec4t10_0;voidmain(){    t10_0 =texture(_MainTex, vs_TEXCOORD0.xy);    t16_0 = t10_0 * t10_0;    SV_TARGET0 = t16_0 * _Tint;return;}

SetTexture0[_MainTex]2D0ConstBuffer"$Globals"144Vector96[_Tint]BindCB"$Globals"0ps_4_0      dcl_constantbuffer cb0[7], immediateIndexed      dcl_sampler s0, mode_default      dcl_resource_texture2d (float,float,float,float) t0      dcl_input_ps linear v0.xydcl_output o0.xyzwdcl_temps10: sampler0.xyzw, v0.xyxx, t0.xyzw, s01:mulr0.xyzw,r0.xyzw,r0.xyzw2:mulo0.xyzw,r0.xyzw, cb0[6].xyzw3:ret

再一次,我们的结果里面只有单次纹理采样。编译器检测到了重复的代码然后做了优化,因此贴图只被采样了一次。采样的结果被存储到寄存器中被重复使用。编译器足够聪明,能够检测到这些代码是重复的,即使我们使用的是中间变量。它会追溯所有东西的原始输入,然后它尽可能高香的识别到任何东西。

现在我们把第二次取样中的UV 坐标与10的相乘结合在一块来写。我们最终看到了大小网格合并在一起了(既有大的网格,又有小的网格)

color *=tex2D(_MainTex, i.uv * 10);

优信彩票购彩大厅 22

两次不同的平铺相乘的结果

由于两次取样不再相同,编译器就不得不对这两次采样都视为有用而使用进去。(也就是可以总结说,前面我们讲到的采用一次的现象,是因为编译器对一模一样的采样会智能的进行优化舍弃,取其一来进行取样,而不会取样两次)

uniformsampler2D_MainTex;invec2vs_TEXCOORD0;layout(location =0)outvec4SV_TARGET0;vec4t0;lowpvec4t10_0;vec2t1;lowpvec4t10_1;voidmain(){    t10_0 =texture(_MainTex, vs_TEXCOORD0.xy);    t0 = t10_0 * _Tint;    t1.xy = vs_TEXCOORD0.xy *vec2(10.0,10.0);    t10_优信彩票购彩大厅,1 =texture(_MainTex, t1.xy);    SV_TARGET0 = t0 * t10_1;return;}

SetTexture0[_MainTex]2D0ConstBuffer"$Globals"144Vector96[_Tint]BindCB"$Globals"0ps_4_0      dcl_constantbuffer cb0[7], immediateIndexed      dcl_sampler s0, mode_default      dcl_resource_texture2d (float,float,float,float) t0      dcl_input_ps linear v0.xydcl_output o0.xyzwdcl_temps20: sampler0.xyzw, v0.xyxx, t0.xyzw, s01:mulr0.xyzw,r0.xyzw, cb0[6].xyzw2:mulr1.xy, v0.xyxx, l(10.000000,10.000000,0.000000,0.000000)3: sampler1.xyzw,r1.xyxx, t0.xyzw, s04:mulo0.xyzw,r0.xyzw,r1.xyzw5:ret

分离细节贴图

当你将两张贴图相乘,相乘后的渲染结果会是比较黑的。除非其中一张图是白色的。那是因为每个纹理元素的颜色通道的值都介于0到1之间。当添加了一张细节贴图到一张贴图上,你可能想通过变暗来实现,但是也可以通过变亮来实现。

color *=tex2D(_MainTex, i.uv * 10) * 2;

我的扩展理解

tex2D采样有两种模式,Clamp和Repeat。Clamp只会在UV指定的区域进行取样,而Repeat会重复取样。比如我们uv*10,那么UV坐标的区间就变成了(0,10),超过1后,tex2D函数又开始重复的取,比如UV为(1.5,1.5),那么取样点就同(1.5-1,1.5-1)=(0.5,0.5)这个点。

另外,我们取样后本来是颜色值,介于(0,1),如果乘以一个倍率,将不是一个线性的颜色增强,而是分区间段的。比如乘以2,那么0.5以上的UV区间比0.5以上的UV区间增强的效果要明显一些。

优信彩票购彩大厅 23

乘以2倍的细节

这种方法要求我们重新解释用于细节的纹理。 乘以1不改变任何东西。 但是,当我们加倍的细节样本,这是现在的½。 这意味着坚实的灰色 - 不是白色的纹理将不会产生变化。 低于1/2的所有值都会使结果变暗,而½以上的任何值会使其变亮。

所以我们需要一个特殊的细节纹理,它以灰色为中心。 这是网格的纹理。

优信彩票购彩大厅 24

图片的下方注解

细节贴图必须是灰度级的么?

它们不必是灰度级的,但它们通常是灰度级的。 灰度细节纹理将通过增亮和变暗来严格调整原色。 这是比较直接的工作。 与非灰色的乘法产生较不直观的结果。 但没有什么是阻止你做这件事。 彩色细节纹理用于产生微妙的色彩偏移。

要使用一张单独的细节贴图,我们就得添加第二个贴图属性到我们的shader中。默认使用它的灰度,这样就可以不改变我们主贴图的外观。

Properties {        _Tint ("Tint",Color) = (1,1,1,1)        _MainTex ("Texture",2D) ="white"{}        _DetailTex ("Detail Texture",2D) ="gray"{}    }

把这张贴图添加到材质球上并把tiling设置为10.

优信彩票购彩大厅 25

两张贴图

当然我们还需要在代码里面添加细节贴图和它的平铺和偏移数据的变量。

sampler2D_MainTex, _DetailTex;            float4 _MainTex_ST, _DetailTex_ST;

使用两对UV

取代硬编码的直接乘以10,我们现在应该使用细节贴图的平铺和偏移数据了。我们可以最终计算出细节贴图的UV,就像我们在顶点程序中计算主贴图那样,这意味着我们需要插入一对新的UV

struct Interpolators {                float4position: SV_POSITION;float2 uv: TEXCOORD0;**float2 uvDetail: TEXCOORD1;**            };

新的细节贴图的UV通过变换原始UV的平铺和偏移数据而得到。

Interpolators MyVertexProgram (VertexData v) {                Interpolators i;i.position=mul(UNITY_MATRIX_MVP, v.position);i.uv= TRANSFORM_TEX(v.uv, _MainTex);**i.uvDetail= TRANSFORM_TEX(v.uv, _DetailTex);**return i;}

uniformvec4_Tint;uniformvec4_MainTex_ST;uniformvec4_DetailTex_ST;invec4in_POSITION0;invec2in_TEXCOORD0;outvec2vs_TEXCOORD0;outvec2vs_TEXCOORD1;vec4t0;voidmain(){    t0 = in_POSITION0.yyyy * glstate_matrix_mvp[1];    t0 = glstate_matrix_mvp[0] * in_POSITION0.xxxx + t0;    t0 = glstate_matrix_mvp[2] * in_POSITION0.zzzz + t0;gl_Position= glstate_matrix_mvp[3] * in_POSITION0.wwww + t0;    vs_TEXCOORD0.xy = in_TEXCOORD0.xy * _MainTex_ST.xy + _MainTex_ST.zw;    vs_TEXCOORD1.xy = in_TEXCOORD0.xy * _DetailTex_ST.xy + _DetailTex_ST.zw;return;}

Vector112[_MainTex_ST]Vector128[_DetailTex_ST]ConstBuffer"UnityPerDraw"352Matrix0[glstate_matrix_mvp]BindCB"$Globals"0BindCB"UnityPerDraw"1vs_4_0      dcl_constantbuffer cb0[9], immediateIndexed      dcl_constantbuffer cb1[4], immediateIndexed      dcl_input v0.xyzwdcl_input v1.xydcl_output_siv o0.xyzw, position      dcl_output o1.xydcl_output o1.zwdcl_temps10:mulr0.xyzw, v0.yyyy, cb1[1].xyzw1: madr0.xyzw, cb1[0].xyzw, v0.xxxx,r0.xyzw2: madr0.xyzw, cb1[2].xyzw, v0.zzzz,r0.xyzw3: mad o0.xyzw, cb1[3].xyzw, v0.wwww,r0.xyzw4: mad o1.xy, v1.xyxx, cb0[7].xyxx, cb0[7].zwzz5: mad o1.zw, v1.xxxy, cb0[8].xxxy, cb0[8].zzzw6:ret

现在我们可以使用额外的UV对到我们的片元程序中了。

float4 MyFragmentProgram (Interpolators i) : SV_TARGET {                float4color= tex2D(_MainTex, i.uv) * _Tint;color*= tex2D(_DetailTex, i.uvDetail) *2;returncolor;            }

uniformvec4_Tint;uniformvec4_MainTex_ST;uniformvec4_DetailTex_ST;uniformsampler2D_MainTex;uniformsampler2D_DetailTex;invec2vs_TEXCOORD0;invec2vs_TEXCOORD1;layout(location =0)outvec4SV_TARGET0;vec4t0;lowpvec4t10_0;lowpvec4t10_1;voidmain(){    t10_0 =texture(_MainTex, vs_TEXCOORD0.xy);    t0 = t10_0 * _Tint;    t10_1 =texture(_DetailTex, vs_TEXCOORD1.xy);    t0 = t0 * t10_1;    SV_TARGET0 = t0 + t0;return;}

SetTexture0[_MainTex]2D0SetTexture1[_DetailTex]2D1ConstBuffer"$Globals"144Vector96[_Tint]BindCB"$Globals"0ps_4_0      dcl_constantbuffer cb0[7], immediateIndexed      dcl_sampler s0, mode_default      dcl_sampler s1, mode_default      dcl_resource_texture2d (float,float,float,float) t0      dcl_resource_texture2d (float,float,float,float) t1      dcl_input_ps linear v0.xydcl_input_ps linear v0.zwdcl_output o0.xyzwdcl_temps20: sampler0.xyzw, v0.xyxx, t0.xyzw, s01:mulr0.xyzw,r0.xyzw, cb0[6].xyzw2: sampler1.xyzw, v0.zwzz, t1.xyzw, s13:mulr0.xyzw,r0.xyzw,r1.xyzw4:addo0.xyzw,r0.xyzw,r0.xyzw5:ret

现在我们的shader见效了。主贴图变得既明亮又暗,基于细节贴图。

优信彩票购彩大厅 26

优信彩票购彩大厅 27

亮部和暗部

far away

close-up

让细节消隐

添加细节的想法是,它们可以使材料的外观更加贴近或缩小,不应该看得很远或者缩小,因为这样使得平铺很明显。 所以当纹理的显示尺寸减小时,我们需要一种淡化细节的方法。 我们可以通过将细节纹理淡化为灰色来实现,因为没有改变颜色。

我们已经做到了! 我们需要做的就是在详细纹理的导入设置中启用Fadeout Mip Maps。 请注意,这也自动将过滤器模式切换为三线性,以使渐变为灰色渐变。

优信彩票购彩大厅 28

优信彩票购彩大厅 29

图片的下方注解

很明显能看出来,网格从细节详细到几乎没什么细节,但通常我们不会察觉到。比如,这里有一张大理石材质的主贴图和一张细节贴图。拖拽到工程中,使用默认设置。

本文由优信彩票购彩大厅发布于技术支持,转载请注明出处:Unity 渲染教程(三):使用多张纹理贴图优信彩

您可能还会对下面的文章感兴趣: