PA2 Combining Textures
Shader 与渲染算法 - 基础 - 纹理混合
combining texture 所解决的问题:纹理过小。大纹理会产生存储浪费,使用 tile 的方法又很容易看到重复的模式,所以使用纹理混合方案:将一个 untiled texture 和一个 tiled texture 进行混合,得到比较经济实用的结果。
Combining: Use multiple textures at the same time.

1 A × A':自己叠自己

1.1 粗糙的想法 Rough Idea
定义 A 为原图(Untiled Texture),A' 为 A 图的 Tile 版本(Tiled Texture),n 为 Tiled 的密度;
fragment shader:纹理采样赋值给变量 A,A 乘以 A'( ×)
1.2 CODING
Shader "Code/PA2_Texture/A&A'"
{
Properties {
_MainTex("Texture", 2D) = "white"{}
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex Vert
#pragma fragment Frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
struct VertexData {
float4 position: POSITION;
float2 uv: TEXCOORD0;
};
struct Interpolators {
float4 position: SV_POSITION;
float2 uv: TEXCOORD0;
};
Interpolators Vert(VertexData v) {
Interpolators i;
i.uv = TRANSFORM_TEX(v.uv, _MainTex);
i.position = mul(unity_MatrixMVP, v.position);
return i;
}
float4 Frag(Interpolators i): SV_Target {
float4 color = tex2D(_MainTex, i.uv);
color *= tex2D(_MainTex, i.uv * 10) * 2;
return color;
}
ENDCG
}
}
}
2 A × B:叠加灰度图

2.1 粗糙的想法 Rough Idea
A × A' (A' = nA × 2) 会使 A' 过曝。解决这个问题,考虑另外使用一张灰度图 B 与 A 图进行叠加。
为新增的灰度图 B 添加一个 Property 及其控制柄来使用它。分配材质,并为设置其 Tilling;
在结构体 Interpolators 中增加一个 UV(此时结构体中包含主纹理 uv 、细节纹理 uvDetail);
在其后的 fragment shader 中,对主纹理坐标和细节纹理坐标分别进行转换(使用两次
TRANSFORM_TEX
宏),连同转换为屏幕空间后的顶点坐标一起打包到 Interpolators 结构体的实例中返回。


2.2 CODING
Shader "Code/PA2_Texture/A&B"
{
// 增加新的 Property
Properties {
_MainTex("Texture", 2D) = "white"{}
_DetailTex("Detail Texture", 2D) = "gray"{}
// gray:表示如果在材质中没有为 Detail Texture 手动分配纹理, Unity 将使用内置的灰色纹理作为默认值。
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex Vert
#pragma fragment Frag
#include "UnityCG.cginc"
sampler2D _MainTex, _DetailTex;
float4 _MainTex_ST, _DetailTex_ST;
struct VertexData {
float4 position: POSITION;
float2 uv: TEXCOORD0;
};
struct Interpolators {
float4 position: SV_POSITION;
float2 uv: TEXCOORD0;
float2 uvDetail: TEXCOORD1; // 增加 detail uv
};
Interpolators Vert(VertexData v) {
Interpolators i;
i.uv = TRANSFORM_TEX(v.uv, _MainTex);
i.uvDetail = TRANSFORM_TEX(v.uv, _DetailTex); // 分别对顶点纹理坐标进行转换
i.position = mul(unity_MatrixMVP, v.position);
return i;
}
// 最后,在 fragment shader 里将两个纹理采样并混合
float4 Frag(Interpolators i): SV_Target {
float4 color = tex2D(_MainTex, i.uv);
color *= tex2D(_DetailTex, i.uvDetail) * unity_ColorSpaceDouble;
return color;
}
ENDCG
}
}
}
2.3 渐隐模式:Fading Details
当纹理缩小或放在远处时,过于清晰的纹理会使得 tiling 的模式看起来非常明显。因此需要在纹理中进行 Mipmap 的设置。
细节纹理中设置 Fadeout to Gray,同时可以将 Filter Mode 设置为 Trilinear Mode(默认为 Bilinear)。这样一来,细节纹理将在画面放远时逐渐置灰,且不改变结果颜色。

3 喷射战士:Splatting Map
细节纹理被重复使用在了全图范围。如果面对更加复杂的场景,想要对不同的表面使用不同的纹理,就需要新的、非全局的混合办法。

3.1 天才的想法 Genius Idea
需要一个布尔值来判定每个片元使用纹理 A or 纹理 B,Splat map 就是实现这个布尔判定的纹理图;
添加 Properties 和与之对应的变量。因为一般情况下,对于一个场景内所有的纹理,其 Tilling 和 Offset 设置都相同,而 splat map 不做任何 Tile。因此我们可以只在 splat map 上设置 tile,统一应用于所有纹理,如此可以减少信息传输与计算量;
在结构体 Interpolators 中加入新的 UV(uvSplat),并在顶点着色器中为它绑定顶点;
fragment shader: 用 splat 的 RGB 通道分量来进行取值并输出。

3.2 准备工作
开启 Splat map 的 Bypass sRGB Sampling。 因为这张 map 是用来进行操作的,而非使用其中的颜色或亮度信息,因此 splat map 中的颜色值需要直接用于计算,而不经过 sRGB 到线性空间的转换。否则会产生不准确的混合效果
设置它的 Wrap Mode 为 clamp。即不会进行 tile 效果。
为纹理 A 和纹理 B 设置一些 Tilling,比如 4 × 4。

3.3 CODING
// key:理解 tex2D 函数。即接受一个纹理采样器和一个 UV 坐标,输出该位置的颜色值。
Shader "Code/PA2_Texture/Splat"
{
Properties {
// _MainTex 为 splat map;_Texture1 和 _Texture2 分别为待混合的两个纹理
_MainTex("Splat Map", 2D) = "white"{}
[NoScaleOffset] _Texture1("Texture 1", 2D) = "white" {}
[NoScaleOffset] _Texture2("Texture 2", 2D) = "white" {}
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex Vert
#pragma fragment Frag
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _Texture1, _Texture2;
float4 _MainTex_ST;
struct VertexData {
float4 position: POSITION;
float2 uv: TEXCOORD0;
};
struct Interpolators {
float4 position: SV_POSITION;
float2 uv: TEXCOORD0;
float2 uvSplat: TEXCOORD1;
};
Interpolators Vert(VertexData v) {
Interpolators i;
i.position = mul(unity_MatrixMVP, v.position);
i.uv = TRANSFORM_TEX(v.uv, _MainTex);
i.uvSplat = v.uv;
return i;
}
// 最后在片元着色器中完成最终的采样
float4 Frag(Interpolators i): SV_Target {
float4 splat = tex2D(_MainTex, i.uvSplat);
return tex2D(_Texture1, i.uv) * splat.r + tex2D(_Texture2, i.uv) * (1-splat.r);
}
ENDCG
}
}
}
3.4 RGB 喷射战士

原理同上。只需要增加 Properties 与其对应属性,然后在片元着色器中返回多色通道即可。

// RGB 通道的片元着色器返回值
return
(_Texture1, i.uv) * splat.r +
(_Texture2, i.uv) * splat.g +
(_Texture3, i.uv) * splat.b +
(_Texture4, i.uv) * (1 - splat.r - splat.g - splat.b);
}
APPENDIX
Anki
Reference
GAMES101 - Lecture 9 - Shading3 (Texture Mapping cont.)
Last updated