zoukankan      html  css  js  c++  java
  • 置换贴图 Displacement Mapping

    视差贴图和法线贴图都是使用特定的手段来达到欺骗视觉的目的,让人以为物体的表面是凹凸起伏的。而置换贴图却是真的将模型的顶点进行偏移,在原本的平面上创造出凹凸的效果。既然是对顶点进行偏移,那么就需要模型有足够多的顶点数量,否则达不到比较好的效果。为了达到足以置换的顶点数量,一般会使用 Tessellation 技术来增加低模的面数。查阅了相关资料,Tessellation 是 DirectX11 才有的技术,OpenGL 要到 4.0 才能使用。对于 Tessellation 知道的并不多,再加上移动平台根本无法使用,就没有去看这部分的内容,这里也不会涉及到。其实目前来看 Tessellation 技术并不是非用不可,很多情况下还是光照和贴图起到更大的作用。

    看下要实现的效果,是一个转动的地球。地球的表面并不是平坦的,每一块陆地都是有凸起的,并且可以看到凸起的陆地上山脉和沟壑。球模型本身是 1200 面。

    置换前效果 置换后效果 置换后效果

    首先是在顶点阶段采样高度纹理,将采样到的颜色作为高度数据偏移顶点坐标(在顶点阶段采样纹理是sm3.0才支持的,我记得看到过什么资料上说 sm3.0 还是 2.0 是 DirectX 的叫法,和 OpenGL 没关系,不知道对不对。编译选项需要加上 #pragma glsl,让编译器把 cg 代码直接编译成 glsl,而不是中间代码,否则会报错)。可以看到如下效果。

    // vertex shader
    o.wnormal = mul (i.normal, (float3x3)_World2Object);
    float dispValue = tex2D (_DisplacementMap, i.texcoord.xy).r * _DisplacementAmount;
    o.wpos = mul(_Object2World, i.vertex);
    o.wpos.xyz += o.wnormal * dispValue;
    o.pos = mul(UNITY_MATRIX_VP, o.wpos);
    

    地形表面已经有了凸起效果了。由于这里没有任何的光照计算,所以整体效果特别的平,没有体积感。到此为止还是很简单的,下面就是要加入光照计算。光照计算最重要的就是法线了,使用法线和光线进行计算来实现明暗效果,表现出物体的体积感。但是现在的问题是,由于模型表面已经不是平坦的了,而是凹凸不平的,如果还使用原来的法线信息肯定会得到错误的效果。

    模型顶点的法线 置换顶点后错误的法线 置换顶点后正确的法线

    只要能够计算出如右图所示的法线,再计算光照就不是什么难事了。我特意查看了下 Unity 文档中关于 Displacement Mapping 的代码,并在 Unity 编辑器中测试了下,光照效果是不正确的,因为当偏移顶点坐标的时候,并没有对法线进行调整。

    从图中可以看到,左边的凸起是模型做出来的(可以看到网格),右边凸起是使用置换贴图实现的(看不到网格,使用的 Unity 文档中的代码实现)。问题就在于右边的光照是错误的,凸起的背光面并没有变暗,这正是因为偏移了顶点坐标后没有重新计算法线造成的。至于如何能够比较精确的计算出新的法线可以看这篇文章,作者对置换贴图进行了多次采样,通过计算高度差的方式实现的,感觉类似于 Unity 编辑器将灰度图转换成法线图的功能。然而我们这里使用另一种近似计算的方式,这种方式得到的结果并不是很精确,在模型边缘可能会出现瑕疵,但是计算量要小得多。我在设备上测试过,是可以得到比较好的效果。如果使用置换贴图后,光照效果的错误是可以忽略的,就不用调整法线了(和 Unity 一样,并且我看到很多类似的资料里也都没有讲到调整法线)。

    下面就来说说如何来近似模拟计算调整后的法线。这里使用到的两个 cg 函数是 ddxddy。首先要说明的是 ddx 和 ddy 是在屏幕空间中的两个函数。如果写了这样的代码 ddx(x),得到的结果就是下一个像素(一般当前像素的右边一个像素)的 ddx(x) 括号中的 x 的值,减去当前像素 ddx(x) 括号中的 x 的值。也就是两个像素的 x 值相差多少。ddy同理,只是描述的是纵向,而不是横向。在旋转的地球这个例子中,如果下一个像素并不是地球模型,而是其他模型,那就可能出现上文所说的边缘瑕疵,如果你不是太在意,一般不会注意到。之所以这两个函数能够做到这点,是因为 GPU 中 fragment 是并行的。原理说明白了就看下代码。

    float4 frag(v2f i)
    {
    	// 水平方向uv的增量
    	float2 uv_dx = ddx(i.uv.xy);
    	// 垂直方向uv的增量
    	float2 uv_dy = ddy(i.uv.xy);
    	// 当前置换距离
    	float height = tex2D(_DisplacementMap, i.uv.xy).r;
    	// 水平方向下一个像素点的置换距离
    	float height_h = tex2D(_DisplacementMap, i.uv.xy + uv_dx).r;
    	// 垂直方向下一个像素点的置换距离
    	float height_v = tex2D(_DisplacementMap, i.uv.xy + uv_dy).r;
    	// 水平方向置换增量
    	float t_h = (height_h - height) * _DisplacementScale;
    	// 垂直方向置换增量
    	float t_v = (height_v - height) * _DisplacementScale;
    	
    	// 水平方向顶点坐标增量,作为一个假的tangent来使用
    	float3 fake_tangent = ddx(i.wpos);
    	// 水平方向顶点坐标增量,作为一个假的bintangent来使用
    	float3 fake_bintangent = ddx(i.wpos);
    	
    	// 到这里为止,其实已经可以用 fake_tangent 差乘 fake_bintangent 来得到 fake_normal 了
    	// 但是会发现 fake_normal 并不平滑,用来计算光照会出现硬边
    	// 解决办法是使用从 vertex shader 传过来的 normal 来进行纠正,顶点上的 normal 是平滑的
    	
    	// corss 部分就是用平滑的 normal 来重新计算新的 fake_tangent
    	// 后面加法是使用置换增量来对其进行扰动
    	float3 fake_tangent_new = cross(fake_bintangent, i.wnormal) + i.wnormal * t_h;
    	// 同理
    	float3 fake_bintangent_new = cross(i.wnormal, fake_tangent) + i.wnormal * t_v;
    	// 最终得到平滑的 fake_normal
    	float3 fake_normal = cross(fake_bintangent_new, fake_tangent_new);
    	fake_normal = normalize(fake_normal);
    	
    	// 这里就可以使用这个 fake_normal 参与光照计算了
    }
    

    至此,所有的要点都说明了。最后我又在设备上进行了测试,发现当图形接口是 OpenglES 3.0 的时候显示效果是正确的,但是 Metal 就不正确了。最终发现 Metal 中 ddy 和 OpenglES 3.0 中的 ddy 返回值不同,第一反应是 Metal 在实现上应该不会出问题,因为两个图形接口在 ddx 的返回结果是一致的。我把最后一步求平滑的 fake_normal 的差乘两边交换了一下,变成 float3 fake_normal = cross(fake_tangent_new, fake_bintangent_new),于是效果就正确了。这说明两个图形接口在处理 ddy 时,虽然都是纵向,但是方向相反。

    goto blog

  • 相关阅读:
    Codeforces 834D The Bakery
    hdu 1394 Minimum Inversion Number
    Codeforces 837E Vasya's Function
    Codeforces 837D Round Subset
    Codeforces 825E Minimal Labels
    Codeforces 437D The Child and Zoo
    Codeforces 822D My pretty girl Noora
    Codeforces 799D Field expansion
    Codeforces 438D The Child and Sequence
    Codeforces Round #427 (Div. 2) Problem D Palindromic characteristics (Codeforces 835D)
  • 原文地址:https://www.cnblogs.com/jim-game-dev/p/5476336.html
Copyright © 2011-2022 走看看