FeastSC 发表于 2022-3-16 09:25

[unity] 实现大地形渲染之 geometry clipmap

--------------------------------------------------更新中---------------------------------------------------
最近希望在unity中实现一个简单的geometry clipmap,用于飞行模拟游戏中巨大地形的渲染。但是网上冲浪之后发现资料有点少,只好一点点从头做起。那么就用本文记录一些个人比较迷惑的地方,作为我的备忘录和经验分享吧。
参考资料:GPU Gems 2, geometry clipmap 原始论文
算法整体框架




Per-frame algorithm

从论文中可以清晰得知,算法需要三个步骤:计算active region(我们希望渲染的地形范围);更新geometry clipmap(多级地形纹理/贴图);裁剪渲染范围至贴图范围,并进行渲染。
代码解读

Upsample(上采样)

算法通过对较粗糙的高度图纹理进行插值来获取分辨率更高(一级)的高度图。
不过在gpu gems提供的示例代码中,为了实现不同类型坐标的插值计算,这个算法写的比较绕,它的pixel shader是这样的:
float4 UpsamplePS(float2 p_uv : TEXCOORD0) : COLOR
{
    float residual = tex2D(ResidualSampler, p_uv*OneOverSize);
   
    p_uv = floor(p_uv);
    float2 p_uv_div2 = p_uv/2;
    float2 lookup_tij = p_uv_div2+1;
    float4 maskType = tex2D(LookupSampler, lookup_tij);   
         
    matrix maskMatrix;
    maskMatrix = matrix(0, 0, 0, 0,
                           0, -1.0f/16.0f, 0, 0,
                           0, 0, 0, 0,
                           1.0f/256.0f, -9.0f/256.0f, -9.0f/256.0f, 1.0f/256.0f);
                           
    maskMatrix = matrix(0, 1, 0, 0,
                           0, 9.0f/16.0f, 0, 0,
                           -1.0f/16.0f, 9.0f/16.0f, 9.0f/16.0f, -1.0f/16.0f,
                           -9.0f/256.0f, 81.0f/256.0f, 81.0f/256.0f, -9.0f/256.0f);                        
                           
    maskMatrix = matrix(0, 0, 0, 0,
                           0, 9.0f/16.0f, 0, 0,
                           0, 0, 0, 0,
                           -9.0f/256.0f, 81.0f/256.0f, 81.0f/256.0f, -9.0f/256.0f);
                           
    maskMatrix = matrix(0, 0, 0, 0,
                           0, -1.0f/16.0f, 0, 0,
                           0, 0, 0, 0,
                           1.0f/256.0f, -9.0f/256.0f, -9.0f/256.0f, 1.0f/256.0f);

    float2 offset = float2(dot(maskType.bgra, float4(1, 1.5, 1, 1.5)), dot(maskType.bgra, float4(1, 1, 1.5, 1.5)));
   
    float z_predicted=0;
    offset = (p_uv_div2-offset+0.5)*OneOverSize+TextureOffset;
    for(int i = 0; i < 4; i++) {
      float zrowv;
      for (int j = 0; j < 4; j++) {
                float2 vij    = offset+float2(i,j)*OneOverSize;
                zrowv      = tex2D(CoarseLevelElevationSampler, vij);
      }
      
      vector mask = mul(maskType.bgra, maskMatrix);
      vector zrow = vector(zrowv, zrowv, zrowv, zrowv);
      zrow = floor(zrow);
      z_predicted = z_predicted+dot(zrow, mask);
    }

   
    z_predicted = floor(z_predicted);
   
    // add the residual to get the actual elevation
    float zf = z_predicted + residual;
   
    // zf should always be an integer, since it gets packed
    //into the integer component of the floating-point texture
    zf = floor(zf);
   
    float4 uvc = floor(float4((p_uv_div2+float2(0.5f,0)),
                              (p_uv_div2+float2(0,0.5f))))*OneOverSize+TextureOffset.xyxy;
            
    // look up the z_predicted value in the coarser levels
    float zc0 = floor(tex2D(CoarseLevelElevationSampler, float4(uvc.xy, 0, 1)));
    float zc1 = floor(tex2D(CoarseLevelElevationSampler, float4(uvc.zw, 0, 1)));      
   
    float zf_zd = zf + ((zc0+zc1)/2-zf+256)/512;

    return float4(zf_zd, 0, 0, 0);
}这个LookupSampler(texture)和maskMatrix比较魔法,在此分析一下。
我们在上采样时要从粗糙lv0的高度图(方框)插值计算出更精确一级lv1的高度图(圆圈),它们之间的关系如图所示。为了方便起见,假设我们的uv坐标是一个单位对应一像素(注意在lv0中坐标细分为0.5),那么lv1在lv0中的坐标有4种类型:

[*]和上一级重合:(0,0)
[*]在两个像素中央:(0,0.5),(0.5,0)
[*]在四个像素中央:(0.5,0.5)



两个等级的高度图

那么通过权重为(-1/16, 9/16, 9/16, -1/16)的四点法插值



A 4-point interpolatory subdivision scheme for curve design

这四种类型的坐标的计算方式分别需要1、4、4、16个点的数据,如下图不同颜色的方框所示。



采样数据范围

比较直观的插值公式如右侧的矩阵所示,直接对最大范围的16点数据与权重(方便起见省去了分母 /256)进行点乘即可。但是因为gpu不便处理分支语句,gpu gems里使用循环+额外的一张2*2控制纹理来实现算法,这些权重矩阵则被拆散进了maskMatrix中(如左侧所示)。比如右侧矩阵1的第一行在左侧矩阵4的第一行(这里为了和图片对应,行坐标颠倒了),而其第二行则在左侧矩阵3的第一行,第三行在左侧矩阵2的第一行......依次类推。控制纹理中则简单地记录了4个one-hot向量:

[*](1,0,0,0):对应坐标类型(0,0)
[*](0,1,0,0):对应坐标类型(0,0.5)
[*](0,0,1,0):对应坐标类型(0.5, 0)
[*](0,0,0,1):对应坐标类型(0.5,0.5)

Ylisar 发表于 2022-3-16 09:26

好奇为啥这么搞不会在每一级边缘处出现明显裂缝…无缝的搞法不是用细分那套算法吗

DomDomm 发表于 2022-3-16 09:32

会有t junction的,不过高度图有过渡区

super1 发表于 2022-3-16 09:36

这个用瞄准镜的时候怎么处理,不处理会有空气墙,处理了又怕性能爆炸
页: [1]
查看完整版本: [unity] 实现大地形渲染之 geometry clipmap