Fork me on GitHub

地形 SplattingMap 采样 HLSL Demo

地形 SplattingMap 采样 HLSL Demo

  1. Fetching splatting index
  2. Translate it into a local cache index
  3. Sample all the pbr textures from the cache

使用 ChatGPT 写一个 hlsl 脚本来实现这三个步骤。这个示例假设已经有了 splatting index 纹理和一组 PBR (Physically-Based Rendering) 纹理加载在本地缓存中。

1. hlsl 代码示例

// `:register(t0)` 是 HLSL 中的寄存器绑定语法。它指示编译器将这个纹理变量绑定到特定的着色器资源寄存器上。
// 在这个案例中,使用的是 `t0` 寄存器
// `:` 的作用见下面的解释
Texture2D SplatMap : register(t0);	// Splatting index map
Texture2DArray PBRTextures : register(t1);	// PBR textures in a texture array
SamplerState samLinear : register(s0);

struct PixelInputType
{
    float2 uv : TEXCOORD0;
};
    
float4 MainPS(PixelInputType input) : SV_TARGET
{
    // 本行解释见小节 5
    float4 splattingIndices = SplatMap.Sample(samLinear, input.uv);
    float4 color = float4(0, 0, 0, 0);
    
    // Assuming we have a 4 PBR textures indexed by the splatting map
    for (int i = 0; i < 4; i++)
    {
        // The local cache index is directly the splatting index in this case
        float localCacheIndex = splattingIndices[i];
        // Sample the PBR texture based on the local cache index
        // 这里实际上是让 SplatMap 中采样后每个通道中的值作为权重
        color += PBRTextures.Sample(samLinear, float3(input.uv, localCacheIndex)) * localCacheIndex;		// Simple blend
    }
    
    return color;
}

首先解释一下各个变量:

SplatMap

SplatMap是一个Texture2D类型的纹理,通常用于存储splatting信息,即它包含了如何在场景中混合不同纹理的数据。在一个典型的splatting场景中,SplatMap的每个通道(比如RGBA)代表不同纹理的混合权重。因此,SplatMap自身并不直接参与最终的视觉呈现,而是作为一个数据源,告诉着色器如何结合其他纹理(在你的示例中是PBRTextures)来产生最终效果。

input.uv

input.uv是从PixelInputType结构体传入着色器的,代表了当前像素或片元的纹理坐标。在3D图形渲染中,纹理坐标用于指定从纹理图像中获取颜色值的位置,通常范围在[0, 1]之间。input.uv用于从SplatMapPBRTextures纹理中采样。

SplatMap.Sample(samLinear, input.uv)

这行代码执行的操作是使用input.uv坐标和samLinear采样器状态从SplatMap中采样。这意味着,根据当前片元的位置(由input.uv提供),从SplatMap中获取一个颜色值。这个颜色值的每个通道(RGBA)然后被用作权重,指示了如何从PBRTextures(一个纹理数组)中混合不同纹理。

实际的采样流程

  1. SplatMap中采样:首先,根据当前片元的纹理坐标input.uv,从SplatMap中采样获取权重。这个步骤决定了后续如何混合PBRTextures中的纹理。
  2. 使用权重混合PBRTextures:然后,根据从SplatMap中获取的权重(splattingIndices),从PBRTextures纹理数组中采样并混合不同的PBR纹理。每个通道的权重决定了相应纹理在最终颜色中的比重。

总的来说,SplatMap是用来存储混合信息的纹理,而input.uv是用于从SplatMapPBRTextures中采样的纹理坐标。SplatMap通过samLinearinput.uv被采样来获得不同纹理的混合权重,然后使用这些权重从PBRTextures中混合实际的纹理,创造出复杂的材质效果。

2. 寄存器(Register)详解

2.1 解释

  • 寄存器 Register

    在 GPU 中,寄存器是一种非常快速的内存形式,用于存储临时数据和着色器资源(如 纹理常量缓冲区 等)。通过显式地指定寄存器,开发者可以更精确地控制资源在着色器中的使用和 绑定,这对于 性能优化资源管理 非常重要。

    扩展资料 知乎 - DX12 的资源绑定

    image-20240319200816321 图片来源

  • **类型前缀 **t

    HLSL 中使用不同的字母前缀来区分不同类型的资源。在这个例子中,t 表示这是一个 纹理资源 寄存器 (texture)。除了 t ,还有

    • b
      • buffer 用于常量缓冲区
    • s
      • sampler state 用于采样器状态
  • 编号 0

    • 寄存器的 槽位号,可以理解为 索引。GPU 上的 register 是有限的,通过编号来指定使用哪一个。编号允许 shader 访问绑定到这个特定槽位的资源。

      这些槽位并不代表物理寄存器的大小或数量,而是逻辑上用于 资源绑定的标识符

2.2 Register 的数量和在 HLSL 代码中显示指定的编号

依赖于 GPU 的架构和使用的 DX 版本(或其它图形 API,如 Vulkan 和 OpenGL)。不同的 GPU 和不同的图形 API 版本有不同的资源限制。

  • 寄存器类型和限制
    • t
      • 用于纹理 Texture。DirectX 11中,像素着色器 PixelShader (PS)可以最多访问 128 个纹理资源(t0 到 t127,而顶点着色器 (VertexShader, VS) 的限制可能较小。
    • c
      • 用于常量缓冲区 (Constant Buffers)。DX11 中,每个 Shader 可以绑定最多 14 个常量缓冲区(c0 到 c13,每个最大支持 4096 个四元数向量。
    • s
      • 用于采样器(Samplers)。DX11 标准中,Shader 可以使用最多 16 个 Sampler (s0 到 s15
    • u
      • 用于 Unordered Acess Views, UAVs,无序访问视图,在 DX 11.1 中引入,用于计算 Shader 和其它可以写入的资源。可用数量依赖于特定的使用情况和 GPU 能力。
  • 架构和 API 版本差异
    • 不同的图形 API 版本和不同的 GPU 架构可能对用于寄存器数量有不同的规定。
    • 例如,DX 12 和 Vulkan 旨在提供更低层次的硬件访问,可能允许更灵活地使用资源,单页要求开发者更精确地管理资源和寄存器。
    • 此外,动态索引 (即使用变量而非常量来索引数组)的资源(如纹理数组)可能有更严格的限制。

3. HLSL 中 : 的作用

3.1 Register 和 Semantic

: 用于为变量或者参数指定额外的信息。如 Texture2D SplatMap : register(t0);float2 uv : TEXCOORD0; 中都使用 : 。这个符号的具体含义取决于它后面跟随的关键词。

  1. 寄存器指定 register

    Texture2D SplatMap : register(t0);
    

    register(t0) 是一个寄存器指定,明确指示编译器将 SplatMap 纹理绑定到特定的图形管线寄存器 t0 上。这是一种资源绑定机制,用于管理着色器程序和图形硬件间的资源分配。

  2. 语义注释 Semantic

    float2 uv : TEXCOORD0;
    

    TEXCOORD0 是一个 语义 Semantic ,用于高速图形管线这个变量怎样被用于 shader 程序中。语义主要用于 输入和输出数据,它为变量提供了一种用途的说明,比如这里的 uv 变量被用作第一组纹理坐标。

除了 registersemantic ,hlsl 中还有其他的信息类型,如:

3.2 系统值语义 System-Value Semantics

系统值语义是一类特殊的语义,用于标识那些具有特定意义的变量,这些变量对于图形管线的正常运行至关重要。例如:

  • SV_Position
    • 用于 VS 的 output 或者 PS 的 input,表示 vertex 的屏幕空间位置 screen-space position
  • SV_Target
    • 用于 PS 的 output,表示 渲染目标(通常是帧缓冲区) 中的颜色
  • SV_DispatchThreadID
    • 用于计算着色器,表示当前线程唯一 ID

3.3 插值修饰符 Interpolation Modifiers

Interpolation Modifiers 用于 PS 的 input variables,指定 变量的插值方式。这对于性能优化和达到特定的视觉效果很有帮助。例如:

  • nointerpolation

    • 表示不对变量进行插值。
    • 这通常用于那些你希望在各个像素之间保持 恒定值 的变量。
  • linear

    • 使用 线性插值
    • 默认的插值方式,适用于大多数情况。
  • centroid

    • 在 MSAA (Multi-Sample Anti-Aliasing) 多重采样抗锯齿 渲染中,使用样本的 中心点 进行插值,可以避免插值导致的一些边缘问题。
    • alias
      • n. 别名;化名
      • v. 错误识别(信号频率),使失真
    • aliasing
      • 混叠;走样;锯齿(引申到图形学)

3.4 存储类说明符 Storage-Class Specifiers

存储类说明符用于全局变量,只是变量的存储类别。虽然在 HLSL 中不常见,但在其他 Shader 语言如 GLSL 中比较常用。HLSL 的等效机制通常通过使用特定的资源类型或寄存器绑定来实现。

3.5 数据精度说明符 Data Precision Specifiers

在某些图形 API 和 Shader 语言中,可以指定变量的数据精度,以优化性能和内存使用。

例如,在 OpenGL ES 的 GLSL 中,可以使用 lowp , mediumphighp 来分别指定低、中、高精度。

HLSL 中默认使用高精度,且主要依赖硬件和编译器优化来管理精度和性能。

4. PS 输入结构体

4.1 说明

struct PixelInputType
{
    float2 uv : TEXCOORD0;
};

定义一个二维向量 uv ,这个向量通常用来存储纹理坐标。这个结构体用于从 VS 到 PS 传递纹理坐标信息。

  • u 水平方向

  • v 垂直方向

  • TEXCOORD0

    Semantic 注释,用于明确指定数据的用途。HLSL 中,语义用来告诉渲染管线如何解释结构体中的某个字段。

    • TEXCOORD
      • 预定义语义,指示变量包含纹理坐标数据
    • 0
      • 表示这是第一个纹理坐标。如果有多个纹理坐标,可以使用 TEXCOORD1, TEXCOORD2 等来标识。这个数字可以视作是一个索引,用于在顶点数据结构中区分不同的纹理坐标

4.2 用途

在图形管线中,Vertex Shader 通常会计算或接收顶点的 uv 坐标,并通过 语义注释 将这些坐标传递给 Pixel Shader。PS 随后使用这些坐标从纹理中采样颜色值,以确定每个像素的颜色

4.3 总结

TEXCOORD0 不是 HLSL 中的数据类型,而是一个语义注释。用于标记变量的含义,确保数据在 Shader 中正确传递。这种机制使得 GPU 的渲染管线能够正确理解和处理着色器程序中的数据。

5. 采样 Sample

5.1 综述

5.1.1 纹理采样做了什么

float4 splattingIndices = SplatMap.Sample(samLinear, input.uv);

它做的事情:

  1. 采样纹理

    SplatMap.Sample 是调用 Sample 方法从一个纹理 (在这个例子中是 SplatMap) 中采样颜色或数据。

    Sample 方法是用来根据提供的纹理坐标 (input.uv) 和 采样器状态 samLinear 从纹理中获取数据。

  2. 采样器状态 samLinear

    samLinear 是一个 SamplerState,定义了如何从纹理中取样。

    在这个上下文中,linear 可能表示使用线性滤波,这是一种平滑过渡的采样方式,对于纹理之间的过度区域特别有用,因为它可以减少像素化的视觉效果。

  3. **纹理坐标 ** input.uv

    input.uv 是传递给 shader 的二维纹理坐标,用于指定从 SplatMap 纹理中采样的具体位置。

    UV 坐标定义了纹理上的一个点,其中 uv 分别代表纹理宽度和高度的坐标轴,范围通常在 0 ~ 1 之间

  4. 返回值 float4 splattingIndices

    采样操作的结果是一个 float4 类型的值,这里被赋值给 float4 splattingIndices 变量。

    float4 是一个包含 4 个浮点数的向量,这在图形编程中通常用来表示颜色(RGBA) 或者其它四维数据。

    在这个例子中,splattingIndices 可能用于存储不同纹理的 混合权重,每个通道代表一个不同的纹理权重。

5.1.2 应用场景

float4 splattingIndices = SplatMap.Sample(samLinear, input.uv);

这行代码的应用场景通常是在地形渲染或其它需要纹理混合的场合。通过 splattingIndices ,你可以控制多个纹理在给定位置的混合方式.

例如,在一个地形渲染的 Shader 中,splattingIndices 的每个通道 (RGBA) 可能代表不同的地面纹理(如泥土、草地、岩石、雪)的混合权重。这样,可以根据 splattingIndices 中的值来混合多个纹理,实现更加复杂和逼真的地面效果。

5.2 核心处理代码

float4 splattingIndices = SplatMap.Sample(samLinear, input.uv);

float localCacheIndex = splattingIndices[i];

color += 
PBRTextures.Sample(samLinear, float3(input.uv, localCacheIndex)) * localCacheIndex;

SplatMap 中的 RGBA 中的每个通道作为一个权重用来给不同地形进行加权处理。

5.3 为什么让 SplatMap 中每个通道的值作为权重

使用SplatMap中的每个通道的值作为权重在纹理混合(splatting技术)中是一种常见的方法,这样做有几个原因和好处:

1. 灵活的纹理控制

SplatMap允许艺术家和开发者以非常直观和控制的方式定义不同纹理在地形或对象表面的分布。通过将每个通道(如RGBA)分配给不同的纹理,可以精确控制这些纹理在模型表面的混合方式和区域。这种方法提供了对纹理如何显示和混合的极大灵活性。

2. 高效的资源使用

将SplatMap的通道作为权重来混合纹理,意味着你可以在一个单一的纹理中存储多个纹理的混合信息。这比为每种纹理混合创建单独的纹理要高效得多,因为它减少了纹理的数量,从而节省了内存和提高了渲染性能。

3. 平滑的纹理过渡

使用权重(SplatMap中的值)可以创建不同纹理之间平滑的过渡效果。权重确定了每个纹理对最终颜色的贡献大小,允许纹理之间的柔和过渡,而不是硬边界。这对于创建更自然和逼真的场景非常重要,特别是在模拟自然环境如地形时。

4. 动态修改能力

通过动态修改SplatMap,可以实现在游戏或应用程序运行时改变纹理分布的效果。例如,可以根据玩家的行为或外部事件动态调整地面的外观(比如,添加路径、改变地形类型等)。这为动态环境和响应式世界提供了可能。

实现细节

在实现上,SplatMap的每个通道(比如R通道)存储了对应纹理(比如泥土纹理)在每个像素上的权重。当渲染时,着色器会根据这些权重从各个纹理采样并混合颜色。因此,SplatMap中通道的值越高,对应纹理在最终混合中的比重就越大。这种方法使得纹理混合不仅限于二元选择(即显示纹理A或纹理B),而是可以基于权重在多个纹理间平滑过渡。

综上所述,将SplatMap的通道值用作权重是一种有效的技术,可以实现复杂的纹理混合效果,提高视觉质量,同时保持资源的高效使用。

posted @ 2024-03-21 11:11  icewalnut  阅读(46)  评论(0编辑  收藏  举报