Terrain Cliff Problem | 地形峭壁问题

传统高度图地形的制作方式不可避免会遇到峭壁拉伸严重的问题,另一方面峭壁要有分层的感觉。常见的解决方法主要有两种:一种是贴面,用石头的模型摆成悬崖,但是人力成本太高;另一种是用triplanar的uv,但是贴图采样次数过多。

一些新的程序化的方式包括:stochastic triplanar(Far Cry5),拆峭壁减少triplanar的贴图采样次数;启发式贴面Heruistic Quilting(Dauntless),程序化摆贴面模型,Ghost Recon目测大概也是这么做的。

不过triplanar仍然采样次数过多;而贴面意味着drawcall过多的问题。而要达到峭壁也有层积的效果,这里提出对峭壁mesh自动展UV的方法。

1. 建模

没什么好说的,houdini terrain程序化生成一下就好了,这里选择了一种大峡谷的地貌。方法就是经典的多层terrace然后erode,不是重点此处略过

2. 拆峭壁分块

转mesh然后remesh,transfer一下slope参数挑出来,然后用GameDevtool的AutoUV的Cluster直接分组先展UV

3. 分拆孤岛

我们只希望mesh是单片的而不是多块连通,要分拆孤岛,最简单的收缩扩张就好了,给一个参数向内收缩到孤岛再扩张

收缩用了一个loop


float step = 0.1;
if(@weight <= step){
@weight = 0;
return;
}
int neibs[] = neighbours(0, @ptnum);
int neighborAllZero = 0;
int neighborAllPosi = 1;
int neighborHasZero = 0;
float min_neib_weight = 10000;
foreach(int neib ; neibs){
vector position = point(0, "P", neib);
float weight = point(0, "weight", neib);
if(weight < step){ neighborAllPosi = 0; } if(weight > 0){
neighborAllZero = 1;
}
if(weight == 0){
neighborHasZero = 1;
}
float distance = distance(position, @P);
if(weight + distance < min_neib_weight){
min_neib_weight = weight+distance;
}
}
if(neighborAllZero == 0){
@weight -= step;
}
else if(neighborAllPosi == 1){
return;
}
else if( neighborHasZero == 1){
f@weight -= step;
}
else{
f@weight = min_neib_weight - step;
}

扩张用了一个loop


if(@class > -0.5){
return;
}
int neibs[] = neighbours(0, @ptnum);
int minclass = -1;
foreach(int neib; neibs){
float thisclass= point(0, "class", neib);
if(thisclass > minclass){
minclass = thisclass;
}
}
if(minclass > -0.5){
@class = minclass;
}

这样UV就能拆成小片了

4 矫正UV

基本思路:我们假设uv已经展好,且应该接近平面形状。这是一组点在二维平面内的投影。之后我们将顶点投影到法平面上,这是同样一组点在二维平面内的另一个投影。我们希望找出一个旋转量,使得这两组投影尽可能接近。解析方法应该是用这个角度建立方程,求个导求值为0的解;采用的近似做法是,计算每个点的旋转角度求平均。


vector pos0 = point(0, "P", 0);
vector uv0= point(0, "uv_transfered", 0);
vector avgNormal = normalize(v@avgN);
float anglesum = 0;
vector center = getbbox_center(0) ;
vector uvcenter = (0,0,0);
for(int i = 1; i < @numpt; i++){
uvcenter += point(0, "uv_transfered", i);
}
uvcenter /= (@numpt - 1);
vector up = (0,1,0);
up.y = 1;
vector avgPlaneX = cross(up, avgNormal);
vector avgPlaneY = cross(avgNormal, avgPlaneX);
v@avgX = avgPlaneX;
v@avgY = avgPlaneY;
for(int i = 1; i < @numpt; i++)
{ vector thispos = point(0, "P", i);
vector thisuv = point(0, "uv_transfered", i);
vector position_offset = (thispos - center);
vector uv_offset = normalize(thisuv - uvcenter);
float position_offset_project_on_angN = dot(avgNormal, position_offset);
vector position_offset_project_on_angNPlane = position_offset - position_offset_project_on_angN * avgNormal;
float onX = dot(avgPlaneX, position_offset_project_on_angNPlane);
float onY = dot(avgPlaneY, position_offset_project_on_angNPlane);
vector onAvgPlane = (0,0,0);
onAvgPlane.x = onX;
onAvgPlane.y = onY;
float dot = dot(normalize(onAvgPlane), uv_offset);
float angle = acos(dot);
vector crossangle = cross(normalize(onAvgPlane), uv_offset);
if(crossangle.z>0){
if(dot > 0){
angle = -angle;
}
else{
angle = 2*3.1416 - angle;
}
}
anglesum += angle;
setpointattrib(0, "errorness", i, dot);
setpointattrib(0, "pos_offset", i, normalize(onAvgPlane));
setpointattrib(0, "uv_offset", i, uv_offset);
}
anglesum /= (@numpt - 1);
@anglesum = anglesum * 360 / (2 * 3.14159);

 

这样就基本矫正了UV.

以下是在UE4中创建的材质截图的效果

 

 

 

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注