Unity URP Shader 实现几种视差映射

代码

陡峭视差映射 Steep Parallax Mapping

float2 SteepParallaxMapping(TEXTURE2D_PARAM(heightMap, sampler_heightMap), half3 viewDirTS, half scale, float2 uv)

{

  //determine number of layers from angle between V and N

  const float minLayers = 5;

  const float maxLayers = 15;

  float numLayers = lerp(minLayers, maxLayers, abs(dot(half3(0, 0, 1), viewDirTS)));


  //height of each layer

  float layerHeight = 1.0 / numLayers;

  //depth of current layer

  float currentLayerHeight = 0;

  //shift of texture coordinates for each iteration

  half2 dtex = scale * viewDirTS.xy / viewDirTS.z / numLayers;

  //current texture coordinates

  float2 currentTextureCoords = uv;

  //get first depth from heightmap

  float heightFromTexture = 1 - SAMPLE_TEXTURE2D(heightMap, sampler_heightMap, currentTextureCoords).g;

  for(int i = 0; i < 15; i++)

  {

      if(heightFromTexture <= currentLayerHeight)

        break;

      //to the next layer

      currentLayerHeight += layerHeight;

      //shift texture coordinates along vector viewDirTS

      currentTextureCoords -= dtex;

      //get new depth from heightmap

      heightFromTexture = 1 - SAMPLE_TEXTURE2D(heightMap, sampler_heightMap, currentTextureCoords).g;

  }


  return currentTextureCoords - uv;

}

浮雕视差映射 Relief Parallax Mapping

float2 ReliefParallaxMapping(TEXTURE2D_PARAM(heightMap, sampler_heightMap), half3 viewDirTS, half scale, float2 uv)

{

  // determine required number of layers

  const float minLayers = 10;

  const float maxLayers = 15;

  float numLayers = lerp(minLayers, maxLayers, abs(dot(half3(0, 0, 1), viewDirTS)));

  // height of each layer

  float layerHeight = 1.0 / numLayers;

  // depth of current layer

  float currentLayerHeight = 0;

  // shift of texture coordinates for each iteration

  half2 dtex = scale * viewDirTS.xy / viewDirTS.z / numLayers;

  // current texture coordinates

  float2 currentTextureCoords = uv;

  // depth from heightmap

  float heightFromTexture = 1 - SAMPLE_TEXTURE2D(heightMap, sampler_heightMap, currentTextureCoords).g;

  for(int i = 0; i < 15; i++)

  {

      if(heightFromTexture <= currentLayerHeight)

        break;

      // to the next layer

      currentLayerHeight += layerHeight;

      // shift texture coordinates along vector viewDirTS

      currentTextureCoords -= dtex;

      // get new depth from heightmap

      heightFromTexture = 1 - SAMPLE_TEXTURE2D(heightMap, sampler_heightMap, currentTextureCoords).g;

  }

  ///////////////////////////////////////////////////////////

  // Start of Relief Parallax Mapping

  // decrease shift and height of layer by half

  half2 deltaTexCoord = dtex / 2;

  float deltaHeight = layerHeight / 2;

  // return to the mid point of previous layer

  currentTextureCoords += deltaTexCoord;

  currentLayerHeight -= deltaHeight;

  // binary search to increase precision of Steep Paralax Mapping

  const int numSearches = 5;

  for(int j = 0; j < numSearches; j++)

  {

      // decrease shift and height of layer by half

      deltaTexCoord /= 2;

      deltaHeight /= 2;

      // new depth from heightmap

      heightFromTexture = 1 - SAMPLE_TEXTURE2D(heightMap, sampler_heightMap, currentTextureCoords).g;

      // shift along or agains vector V

      if(heightFromTexture > currentLayerHeight) // below the surface

      {

        currentTextureCoords -= deltaTexCoord;

        currentLayerHeight += deltaHeight;

      }

      else // above the surface

      {

        currentTextureCoords += deltaTexCoord;

        currentLayerHeight -= deltaHeight;

      }

  }

  // return results

  //parallaxHeight = currentLayerHeight;   

  return currentTextureCoords - uv;

}

视差遮蔽映射 Parallax Occlusion Mapping

float2 ParallaxOcclusionMapping(TEXTURE2D_PARAM(heightMap, sampler_heightMap), half3 viewDirTS, half scale, float2 uv)

{

  // determine optimal number of layers

  const float minLayers = 10;

  const float maxLayers = 15;

  float numLayers = lerp(minLayers, maxLayers, abs(dot(half3(0, 0, 1), viewDirTS)));

  // height of each layer

  float layerHeight = 1.0 / numLayers;

  // current depth of the layer

  float currentLayerHeight = 0;

  // shift of texture coordinates for each layer

  half2 dtex = scale * viewDirTS.xy / viewDirTS.z / numLayers;

  // current texture coordinates

  float2 currentTextureCoords = uv;

  // depth from heightmap

  float heightFromTexture = 1 - SAMPLE_TEXTURE2D(heightMap, sampler_heightMap, currentTextureCoords).g;

  for(int i = 0; i < 15; i++)

  {

      if(heightFromTexture <= currentLayerHeight)

        break;

      // to the next layer

      currentLayerHeight += layerHeight;

      // shift texture coordinates along vector viewDirTS

      currentTextureCoords -= dtex;

      // get new depth from heightmap

      heightFromTexture = 1 - SAMPLE_TEXTURE2D(heightMap, sampler_heightMap, currentTextureCoords).g;

  }

  /////////////////////////////////////////////////////////

  // previous texture coordinates

  half2 prevTCoords = currentTextureCoords + dtex;

  // heights for linear interpolation

  float nextH    = heightFromTexture - currentLayerHeight;

  float prevH    = 1 - SAMPLE_TEXTURE2D(heightMap, sampler_heightMap, currentTextureCoords).g

                          - currentLayerHeight + layerHeight;

  // proportions for linear interpolation

  float weight = nextH / (nextH - prevH);

  // interpolation of texture coordinates

  float2 finalTexCoords = prevTCoords * weight + currentTextureCoords * (1.0-weight);

  return finalTexCoords - uv;

  //return currentTextureCoords - uv;

}

优缺点总结

陡峭视差映射 Steep Parallax Mapping

如果层数很多,那性能就会低。但如果层数少,就会有明显的锯齿现象产生,可以根据摄像机向量V和多边形法向N之间的夹角来动态的决定层的数量。

浮雕视差映射 Relief Parallax Mapping

可以使用较少的层数进行一阶段陡峭视差检索

二阶段的二分查找算法仍然消耗性能,但相较陡峭视差映射可以得到更精确的结果

视差遮蔽映射 Parallax Occlusion Mapping

视差遮蔽映射可以使用相对较少的采样次数产生很好的结果。但视差遮蔽映射比浮雕视差映射更容易跳过高度图中的小细节,也更容易在高度图数据产生大幅度的变化时得到错误的结果。

原理

视差映射技术的主要任务是修改纹理坐标,让平面看起来像是立体的。主要计算都是在Fragment Shader中进行。看看下面的图片。水平线0.0表示完全没有凹陷的深度,水平线1.0表示凹陷的最大深度。实际的几何体并没改变,其实一直都在0.0水平线上。图中的曲线代表了高度图中存储的高度数据。

设当前点(译者:原文中用的是Fragment,片元。)是图片中用黄色方块高亮出来的那个点,这个点的纹理坐标是T0。向量V是从摄像机到点的方向向量。用坐标T0在高度图上采样,你能得到这个点的高度值H(T0)=0.55。这个值不是0,所以点并不是在表面上,而是凹陷下去的。所以你得把向量V继续延长直到与高度图定义出来的表面最近的一个交点。这个交点我们说它的深度就是H(T1),它的纹理坐标就是T1。所以我们就应该用T1的纹理坐标去对颜色和法线贴图进行采样。

所以说,所有视差映射技术的主要目的,就是要精确的计算摄像机的向量V和高度图定义出来的表面的交点。

视差原理

陡峭视差映射 Steep Parallax Mapping

如图所示把表面的深度切分成等距的若干层。然后从最顶端的一层开始采样高度图,每一次会沿着V的方向偏移纹理坐标。如果点已经低于了表面(当前的层的深度大于采样出的深度),停止检查并且使用最后一次采样的纹理坐标作为结果。

陡峭视差映射的工作方式在下面的图片上举例。深度被分割成8个层,每层的高度值是0.125。每层的纹理坐标偏移是V.xy/V.z * scale/numLayers。从顶层黄色方块的位置开始检查。

以下参照下图给出算法的执行步骤:

层的深度为0,高度图深度H(T0)大约为0.75。采样到的深度大于层的深度,所以开始下一次迭代。

沿着V方向偏移纹理坐标,选定下一层。层深度为0.125,高度图深度H(T1)大约为0.625。采样到的深度大于层的深度,所以开始下一次迭代。

沿着V方向偏移纹理坐标,选定下一层。层深度为0.25,高度图深度H(T2)大约为0.4。采样到的深度大于层的深度,所以开始下一次迭代。

沿着V方向偏移纹理坐标,选定下一层。层深度为0.375,高度图深度H(T3)大约为0.2。采样到的深度小于层的深度,所以向量V上的当前点在表面之下。我们找到了纹理坐标Tp=T3是实际交点的近似点。

陡峭视差映射原理

浮雕视差映射 Relief Parallax Mapping

浮雕视差映射升级了陡峭视差映射。

算法一阶段采用陡峭视差映射得到交点前后的两个层,和对应的深度值。在下面的原理图中这两个层分别对应纹理坐标T2和T3。

算法在二阶段采用二分法来进一步改进你的结果,每一次搜索迭代可以使精确度提升一倍。

以下参照下图给出了算法的执行步骤:

在陡峭视差映射之后,我们知道交点肯定在T2和T3之间。真实的交点在图上用绿点标出来了。

设每次迭代时的纹理坐标变化量ST,它的初始值等于向量V在穿过一个层的深度时的XY分量。

设每次迭代时的深度值变化量SH,它的初始值等于一个层的深度。

把ST和SH都除以2。

把纹理坐标T3沿着反方向偏移ST,把层深度沿反方向偏移SH,得到此次迭代的纹理坐标T4和层深度H(T4)。

(*)采样高度图,把ST和SH都除以2。

如果高度图中的深度值大于当前迭代层的深度H(T4),则将当前迭代层的深度增加SH,迭代的纹理坐标沿着V的方向增加ST。

如果高度图中的深度值小于当前迭代层的深度H(T4),则将当前迭代层的深度减少SH,迭代的纹理坐标沿着V的相反方向增加ST。

从(*)处循环,继续二分搜索,直到规定的次数。

最后一步得到的纹理坐标就是浮雕视差映射取到的近似结果。

浮雕视差映射原理

视差遮蔽映射 Parallax Occlusion Mapping

视差遮蔽映射(POM)是陡峭视差映射的另一个改进版本。

算法一阶段采用陡峭视差映射得到交点前后的两个层,和对应的深度值。在下面的原理图中这两个层分别对应纹理坐标T2和T3。

算法二阶段对一阶段获得的两个纹理偏移值进行插值。如原理图所示,POM使用相交之后的层深度(0.375,陡峭视差映射停止迭代的层),上一个采样深度H(T2)和下一个采样深度H(T3)。从图片中你能看到,视差遮蔽映射的插值结果是在视向量V和H(T2)和H(T3)高度的连线的交点上。这个交点已经足够接近实际交点(标记为绿色的点)了。

视差遮蔽映射原理

参考资料

[译] GLSL 中的视差遮蔽映射(Parallax Occlusion Mapping in GLSL)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容