【问题标题】:GLSL Vertex shader bilinear sampling heightmapGLSL 顶点着色器双线性采样高度图
【发布时间】:2014-09-30 21:26:07
【问题描述】:

我正在创建一个 geomip 映射的地形。到目前为止,我的工作还不错。相机附近的地形镶嵌非常高,并且越小,几何形状越远。地形的几何形状基本上跟随相机并根据顶点的位置对高度图纹理进行采样。因为几何细分非常高,所以您有时可以看到纹理中的每个像素在采样时。它会产生明显的像素凸起。我想我可以通过平滑高度图的采样来解决这个问题。但是,我似乎有一个与一些双线性采样代码相关的奇怪问题。我通过根据高度图纹理置换每个顶点来渲染地形。要获取给定 UV 坐标处顶点的高度,我可以使用:

vec2 worldToMapSpace( vec2 worldPosition ) {
   return ( worldPosition / worldScale + 0.5 );
}

float getHeight( vec3 worldPosition )
{
        #ifdef USE_HEIGHTFIELD
        vec2 heightUv = worldToMapSpace(worldPosition.xz);
        vec2 tHeightSize = vec2( HEIGHTFIELD_SIZE_WIDTH, HEIGHTFIELD_SIZE_HEIGHT ); //both 512
        vec2 texel = vec2( 1.0 / tHeightSize );
        //float coarseHeight = texture2DBilinear( heightfield, heightUv,  texel, tHeightSize ).r;
        float coarseHeight = texture2D( heightfield, vUv ).r;
        return altitude * coarseHeight + heightOffset;
    #else
        return 0.0;
    #endif
}

产生这个(注意你是如何看到每个像素的):

这是一个线框图:

我想让地形采样更平滑。所以我想我可以使用一些双线性采样而不是标准的 texture2D 函数。所以这是我的双线性采样函数:

vec4 texture2DBilinear( sampler2D textureSampler, vec2 uv, vec2 texelSize, vec2 textureSize )
{
    vec4 tl = texture2D(textureSampler, uv);
    vec4 tr = texture2D(textureSampler, uv + vec2( texelSize.x, 0.0 ));
    vec4 bl = texture2D(textureSampler, uv + vec2( 0.0, texelSize.y ));
    vec4 br = texture2D(textureSampler, uv + vec2( texelSize.x, texelSize.y ));
    vec2 f = fract( uv.xy * textureSize ); // get the decimal part
    vec4 tA = mix( tl, tr, f.x );
    vec4 tB = mix( bl, br, f.x );
    return mix( tA, tB, f.y ); 
}

texelSize 计算为 1 / heightmap size:

vec2 texel = vec2( 1.0 / tHeightSize );

textureSize 是高度图的宽度和高度。但是,当我使用这个函数时,我得到了这个结果:

float coarseHeight = texture2DBilinear( heightfield, heightUv,  texel, tHeightSize ).r;

现在看起来更糟了 :( 知道我可能做错了什么吗?或者如何获得更平滑的地形采样?

编辑

这是一张俯视地形的垂直屏幕截图。您可以看到图层工作正常。但是请注意,具有较少三角剖分且看起来更平滑的外层,而具有较高曲面细分的外层会显示每个像素。我试图找到一种方法来平滑纹理采样。

【问题讨论】:

  • 你为什么首先使用自定义双线性插值?如果每个顶点在高度图中都有一个像素,则应在纹理上使用高斯模糊以使其“平滑”。如果您的顶点多于像素,则内置纹理插值可以完成工作。
  • 嗨,达里,我将不得不编辑我的问题以澄清。原因是因为我使用的是 geoclipmapping 技术。相机附近的地形是一个非常高的细分。因为曲面细分非常高,所以三角形的数量比像素的数量多。所以它不是1比1的比例。即采样需要更精细,或者更确切地说,它需要在像素值之间进行插值。
  • 你为什么不使用内置插值? opengl.org/wiki/Sampler_Object#Sampling_parameters
  • 是的,它们已经设置为线性。我读到您必须在顶点着色器上创建自己的双线性采样:gamerendering.com/2008/10/05/bilinear-interpolation“请注意,在顶点着色器中,您必须在纹理样本之间进行手动双线性插值。”
  • 好的,首先你的链接状态,代码和内插的构建完全相同。所以我仍然认为使用它没有任何意义。在您可以看到的线框图片中,插值工作正常。问题是,您将多个线性批次彼此相邻放置。虽然两个批次的高度值在边界处相同,但导数/梯度不同。对于“光滑”的表面,您需要双三次插值或贝塞尔表面。

标签: opengl glsl webgl


【解决方案1】:

我能够找到并实现一种使用 catmulrom 插值的技术。代码如下。

// catmull works by specifying 4 control points p0, p1, p2, p3 and a weight. The function is used to calculate a point n between p1 and p2 based
// on the weight. The weight is normalized, so if it's a value of 0 then the return value will be p1 and if its 1 it will return p2. 
float catmullRom( float p0, float p1, float p2, float p3, float weight ) {
    float weight2 = weight * weight;
    return 0.5 * (
        p0 * weight * ( ( 2.0 - weight ) * weight - 1.0 ) +
        p1 * ( weight2 * ( 3.0 * weight - 5.0 ) + 2.0 ) +
        p2 * weight * ( ( 4.0 - 3.0 * weight ) * weight + 1.0 ) +
        p3 * ( weight - 1.0 ) * weight2 );
}

// Performs a horizontal catmulrom operation at a given V value.
float textureCubicU( sampler2D samp, vec2 uv00, float texel, float offsetV, float frac ) {
    return catmullRom(
        texture2DLod( samp, uv00 + vec2( -texel, offsetV ), 0.0 ).r,
        texture2DLod( samp, uv00 + vec2( 0.0, offsetV ), 0.0 ).r,
        texture2DLod( samp, uv00 + vec2( texel, offsetV ), 0.0 ).r,
        texture2DLod( samp, uv00 + vec2( texel * 2.0, offsetV ), 0.0 ).r,
    frac );
}

// Samples a texture using a bicubic sampling algorithm. This essentially queries neighbouring
// pixels to get an average value.
float textureBicubic( sampler2D samp, vec2 uv00, vec2 texel, vec2 frac ) {
    return catmullRom(
        textureCubicU( samp, uv00, texel.x, -texel.y, frac.x ),
        textureCubicU( samp, uv00, texel.x, 0.0, frac.x ),
        textureCubicU( samp, uv00, texel.x, texel.y, frac.x ),
        textureCubicU( samp, uv00, texel.x, texel.y * 2.0, frac.x ),
    frac.y );
}

    // Gets the  UV coordinates based on the world X Z position
    vec2 worldToMapSpace( vec2 worldPosition ) {
        return ( worldPosition / worldScale + 0.5 );
    }


// Gets the height at a location p (world space)
float getHeight( vec3 worldPosition )
{
    #ifdef USE_HEIGHTFIELD

        vec2 heightUv = worldToMapSpace(worldPosition.xz);
        vec2 tHeightSize = vec2( HEIGHTFIELD_WIDTH, HEIGHTFIELD_HEIGHT );

        // If we increase the smoothness factor, the terrain becomes a lot smoother.
        // This is because it has the effect of shrinking the texture size and increaing
        // the texel size. Which means when we do sampling the samples are from farther away - making
        // it smoother. However this means the terrain looks less like the original heightmap and so
        // terrain picking goes a bit off. 
        float smoothness = 1.1;
        tHeightSize /= smoothness;

        // The size of each texel
        vec2 texel = vec2( 1.0 / tHeightSize );

        // Find the top-left texel we need to sample.
        vec2 heightUv00 = ( floor( heightUv * tHeightSize ) ) / tHeightSize;

        // Determine the fraction across the 4-texel quad we need to compute.
        vec2 frac = vec2( heightUv - heightUv00 ) * tHeightSize;

        float coarseHeight = textureBicubic( heightfield, heightUv00, texel, frac );
        return altitude * coarseHeight + heightOffset;
    #else
        return 0.0;
    #endif
}

【讨论】:

  • 有用!谢谢。能否分享一下结果(图片)?
猜你喜欢
  • 2011-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-11
  • 1970-01-01
  • 1970-01-01
  • 2022-01-04
  • 1970-01-01
相关资源
最近更新 更多