所有 demo 和代码可以在 https://www.shadertoy.com/view/X3sBR7 查看

在传统 3D里面,纹理都是离线制作好,结合物体的形状生成的 UV贴图。Shadertoy中我们一切都是实时生成的。也就是我们需要更具实时生成出来的形状 找到对应面的纹理坐标。

平面映射

图片

平面映射最简单不过了,在三维空间中,每一个顶点都有其三维坐标 (x, y, z)。平面映射通常围绕特定的平面,例如 XY、XZ 或 YZ 平面,选择一个平面作为纹理的展示区域。对于每个三维顶点,通过投影到选定的二维平面生成相应的纹理坐标 (u, v)。例如,如果选择 XY 平面,则可以通过下面的方式生成纹理坐标:

  • u = x
  • v = y
vec2 uv = p.xz;
color =  texture(iChannel2,uv ).xyz * dif;

立方体映射

图片

立方体映射是通过一组六个纹理(分别对应立方体的六个面)来表示一个环境。在渲染一个物体时,可以根据视线方向选择这六个面中的某一个进行采样。立方体纹理实际上是一个立方体的六个面,每个面都是一个二维纹理。通常,这六个面分别表示正X、负X、正Y、负Y、正Z和负Z方向。

立方体映射和平面映射几乎一样,只是需要乘上立方体表面的法线。

vec3 cXZ =  texture(iChannel2, p.xz ).xyz ;
vec3 cYZ =  texture(iChannel2, p.yz ).xyz ;
vec3 cXY =  texture(iChannel2, p.xy ).xyz ;
color = cXZ * abs(n.y)+ cXY * abs(n.z) + cYZ * abs(n.x);

Cylindrical Mapping

图片

圆柱坐标系的映射关系通常是这样的:

  • x = r * cos(0)
  • y = h
  • z = r * sin(0)

映射方法如下

float theta = atan(p.z, p.x); // angle around the Y-axis
float u = 0.5 + theta / (2.0 * 3.14159); // map theta to [0,1]
float v = (p.y + cylinderHeight / 2.0) / cylinderHeight; // map height to [0,1]

Triplanar Mapping

Triplanar Mapping 旨在减少纹理拉伸现象,特别是在复杂表面(如不规则地形或有些许凹凸的模型)上效果更佳。相较于传统的UV贴图,Triplanar Mapping 结合了三个方向的纹理,从而不依赖于UV坐标的定义。Triplanar Mapping 的核心思想是根据法线的方向将纹理应用于物体的三个主面(X、Y、Z 轴)。以下是其基本步骤和原理:

  1. 法线计算

    • 对于模型的每一个片段,计算其法线向量。法线为表面的每一点指向外侧的单位向量。
  2. 纹理坐标生成

    • X方向(并适用于法线的Y和Z分量)
    • Y方向(并适用于法线的X和Z分量)
    • Z方向(并适用于法线的X和Y分量)
    • 对于每个片段,使用法线向量生成三个纹理坐标:
  3. 纹理混合

    • 计算每个方向的影响权重。在最基本的情况下,权重可以通过法线的绝对值来获得。例如,法线的X分量越大,X方向的纹理权重也就越大。
    • 将三个方向的纹理进行混合:根据每个方向的位置和它的权重计算最终的颜色。
  4. 最终输出

    • 根据混合后的颜色输出最终的像素颜色。

图片

uniform sampler2D textureX;
uniform sampler2D textureY;
uniform sampler2D textureZ;
uniform vec3 normal; // 片段法线
uniform vec3 position; // 片段位置
uniform float blendFactor; // 用于调节混合程度

vec4 triplanarMap(vec3 normal, vec3 position) {
    vec3 absNormal = abs(normal);
    
    // 纹理坐标
    vec2 coordX = position.yz; // YZ平面
    vec2 coordY = position.zx; // ZX平面
    vec2 coordZ = position.xy; // XY平面

    // 获取纹理
    vec4 texX = texture(textureX, coordX);
    vec4 texY = texture(textureY, coordY);
    vec4 texZ = texture(textureZ, coordZ);

    // 计算权重
    float weightX = absNormal.x;
    float weightY = absNormal.y;
    float weightZ = absNormal.z;

    // 归一化权重
    float totalWeight = weightX + weightY + weightZ;
    weightX /= totalWeight;
    weightY /= totalWeight;
    weightZ /= totalWeight;

    // 混合纹理
    vec4 finalColor = (texX * weightX) + (texY * weightY) + (texZ * weightZ);
    
    return finalColor;
}