|
--------------------------------------------------更新中---------------------------------------------------
最近希望在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[4];
maskMatrix[0] = 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[1] = 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[2] = 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[3] = 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[4];
for (int j = 0; j < 4; j++) {
float2 vij = offset+float2(i,j)*OneOverSize;
zrowv[j] = tex2D(CoarseLevelElevationSampler, vij);
}
vector mask = mul(maskType.bgra, maskMatrix);
vector zrow = vector(zrowv[0], zrowv[1], zrowv[2], zrowv[3]);
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)
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|