python 深度 视差 计算_1,Learn about Parallax(视差贴图)

本文详细介绍了如何利用GLSL和Python进行视差计算,探讨了视差贴图的基本原理和不同技术,包括基础视差、偏移限制视差、陡峭视差贴图等。通过比较法线贴图,阐述了视差贴图如何在2D表面上创造出3D细节的错觉。同时,文章提供了基础视差贴图和陡峭视差贴图的GLSL shader实现,帮助理解其工作原理。
摘要由CSDN通过智能技术生成

纯手码字翻译大佬教程. 因为parallax算法和RayMarching算法相似(都为步进采样),就放在一个专栏里了,自己理解的地方有注明.

原文链接 :https://github.com/UPBGE/upbge/issues/1009

这个教程介绍了怎么用GLSL使用不同的视差贴图技术(也可以在DX中运用)。下面要介绍的技术有:视差贴图,偏移限制视差贴图,陡峭视差贴图,浮雕视差贴图以及视差映射贴图(POM),同样这篇文章介绍了怎么在视差中加自投影。下面的图片描绘了在简单灯光下视差贴图和法线贴图的差别:

c691a77836ae5252de37433fa0cb8d61.png

基础视差贴图

在计算机图形学中视差贴图是法线贴图的进阶版,也就是不仅仅改变光的行为还在平面中制造了3D细节的假象.并没有额外生成模型.在之前展示的视差和法线的对比图中.您可能会认为视差移动了原来的模型,实际上是移动了UV后采样了diffuse贴图和normal贴图.

使用视差贴图需要一张高度图,高度图储存了每个像素表面的高度信息。可以描述成距离表面的深度,在这种情况里必须反转高度图. 这个教程里把高度图的信息视作深度信息.黑色(0)代表了没有孔,白色(1)代表了孔的最大深度.

在下面的视差贴图范例中我们使用三张贴图:heightMap,diffuseMap还有normalMap,通常normalMap是由heightMap生成的.在我们的范例中高度图作为深度图使用.所以在生成normalMap之前.您必须反转高度图.您可以把normalMap和heightMap合并成一张图(heightMap作为Alpha通道),但是本教程为了方便一点使用了三张不同的贴图.下面是视差的贴图范例:

657f9f6cc42a71e7758902a1b7eab1d4.png

视差贴图技术的主要任务是通过移动UV修改贴图来让平面看着像3D. 这个效果会在片段着色器中显示的所有像素进行计算. 如下图.Level0代表没有坑洞.Level1代表最深的孔. 模型实际上没有变而且一直在Level0处.曲线代表的是储存在高度图中的值.当下的像素坐落在黄色方形处.这个像素对应的UV(TextureCoordinates)是T0.V是从摄像机到该像素的向量.如果按照T0采样高度图的话能得到值H(T0) = 0.55.值不等于0。所以该像素并不在表面. 在像素之下有坑. 所以你必须延申V到由高度图定义曲面的交点.这个交点正在深度为H,UV位T1的位置,然后用T1去采样diffuseMap和normalMap.

因此所有视差算法的目标都是去计算摄像机向量V和高度图所定义表面的交点。

2d8007990652f60f6cca91beea58f405.png

视差贴图基础shader

视差贴图的计算必须在切线空间里(和法线一样).所以灯光(L)和摄像机(V)的向量必须转换到切线空间.然后用视差技术计算新的UV坐标,然后您可以用这个新的UV坐标计算子投影以及采样这个像素的diffuseMap和normalMap.在这个例子中执行视差算法的函数是parallaxMapping() ,计算自投影的函数是parallaxSoftShadowMultiplier().光照模型用的是Blinn_Phong光照模型.法线方程是normalMappingLighting().下面的顶点和片段着色器可以当作是视差贴图算法的基本架构.顶点着色器变换了L,V到切线空间.片段着色器调用了视差算法函数,然后计算自投影最后计算光照:

// Basic vertex shader for parallax mapping
#version 330


// attributes
layout(location = 0) in vec3 i_position; // xyz - position
layout(location = 1) in vec3 i_normal; // xyz - normal
layout(location = 2) in vec2 i_texcoord0; // xy - texture coords
layout(location = 3) in vec4 i_tangent; // xyz - tangent, w - handedness


// uniforms
uniform mat4 u_model_mat;
uniform mat4 u_view_mat;
uniform mat4 u_proj_mat;
uniform mat3 u_normal_mat;
uniform vec3 u_light_position;
uniform vec3 u_camera_position;


// data for fragment shader
out vec2 o_texcoords;
out vec3 o_toLightInTangentSpace;
out vec3 o_toCameraInTangentSpace;


///


void main(void)
{
   // transform to world space
   vec4 worldPosition = u_model_mat * vec4(i_position, 1);
   vec3 worldNormal = normalize(u_normal_mat * i_normal);
   vec3 worldTangent = normalize(u_normal_mat * i_tangent.xyz);


   // calculate vectors to the camera and to the light
   vec3 worldDirectionToLight = normalize(u_light_position - worldPosition.xyz);
   vec3 worldDirectionToCamera = normalize(u_camera_position - worldPosition.xyz);


   // calculate bitangent from normal and tangent
   vec3 worldBitangnent = cross(worldNormal, worldTangent) * i_tangent.w;


   // transform direction to the light to tangent space
   o_toLightInTangentSpace = vec3(
         dot(worldDirectionToLight, worldTangent),
         dot(worldDirectionToLight, worldBitangnent),
         dot(worldDirectionToLight, worldNormal)
      );


   // transform direction to the camera to tangent space
   o_toCameraInTangentSpace= vec3(
         dot(worldDirectionToCamera, worldTangent),
         dot(worldDirectionToCamera, worldBitangnent),
         dot(worldDirectionToCamera, worldNormal)
      );


   // pass texture coordinates to fragment shader
   o_texcoords = i_texcoord0;


   // calculate screen space position of the vertex
   gl_Position = u_proj_mat * u_view_mat * worldPosition;
}
// basic fragment shader for Parallax Mapping
#version 330


// data from vertex shader
in vec2 o_texcoords;
in vec3 o_toLightInTangentSpace;
in vec3 o_toCameraInTangentSpace;


// textures
layout(location = 0) uniform sampler2D u_diffuseTexture;
layout(location = 1) uniform sampler2D u_heightTexture;
layout(location = 2) uniform sampler2D u_normalTexture;


// color output to the framebuffer
out vec4 resultingColor;





// scale for size of Parallax Mapping effect
uniform float parallaxScale; // ~0.1


//
// Implements Parallax Mapping technique
// Returns modified texture coordinates, and last used depth
vec2 parallaxMapping(in vec3 V, in vec2 T, out float parallaxHeight)
{
   // ...
}


//
// Implements self-shadowing technique - hard or soft shadows
// Returns shadow factor
float parallaxSoftShadowMultiplier(in vec3 L, in vec2 initialTexCoord,
                                       in float initialHeight)
{
   // ...
}


//
// Calculates lighting by Blinn-Phong model and Normal Mapping
// Returns color of the fragment
vec4 normalMappingLighting(in vec2 T, in vec3 L, in vec3 V, float shadowMultiplier)
{

// restore normal from normal map

   vec3 N = normalize(texture(u_normalTexture, T).xyz * 2 - 1);
   vec3 D = texture(u_diffuseTexture, T).rgb;


   // ambient lighting
   float iamb = 0.2;
   // diffuse lighting
   float idiff = clamp(dot(N, L), 0, 1);
   // specular lighting
   float ispec = 0;
   if(dot(N, L) > 0.2)
   {
      vec3 R = reflect(-L, N);
      ispec = pow(dot(R, V), 32) / 1.5;
   }


   vec4 resColor;
   resColor.rgb = D * (ambientLighting + (idiff + ispec) * pow(shadowMultiplier, 4));
   resColor.a = 1;


   return resColor;
}


/
// Entry point for Parallax Mapping shader
void main(void)
{
   // normalize vectors after vertex shader
   vec3 V = normalize(o_toCameraInTangentSpace);
   vec3 L = normalize(o_toLightInTangentSpace);


   // get new texture coordinates from Parallax Mapping
   float parallaxHeight;
   vec2 T = parallaxMapping(V, o_texcoords, parallaxHeight);


   // get self-shadowing factor for elements of parallax
   float shadowMultiplier = parallaxSoftShadowMultiplier(L, T, parallaxHeight - 0.05);


   // calculate lighting
   resultingColor = normalMappingLighting(T, L, V, shadowMultiplier);
}

视差和视差偏移限制

最简单版本的视差技术是由原来的UV通过一步计算得到接近交点的UV,简单的称之为视差贴图.只有当高度图平滑而且不包含很多小细节时,才能给出或多或少的有效结果.另一种情况,向量V和N之间的夹角过大时,视差的影响将无效,近似视差的计算过程如下:

  • 从高度图中获得H(T0),初始UV 坐标T0.
  • 根据摄像机向量V和初始高度H(T0)来偏移初始UV坐标.

偏移UV是通过以下方法完成的.由于V向量在切线空间中,并且切线空间是沿着纹理坐标的梯度建立的.所以V.xy可以不做任何变换就用作沿着V偏移UV坐标的方向.而V.z是法线分量.它垂直于表面.您可以用V.xy除以V.z.这就是视差贴图中UV的原始计算. 或者您可以去掉V.z,这样的实现被称为带偏移限制的视差贴图. 这可以减少在V和N之间的角度较高的怪异结果.那么您可以把V.xy添加到原始的UV上,这样就得到了沿着V方向偏移的新UV.

您可以用一个变量来控制视差的效果.同样的.必须乘上V.xy.最有效的数值范围是0到0.5。更高的数值在多数情况下都是错误的(如下图).您也可以让值为负.这种情况必须翻转normalMap的b通道.那么偏移UV计算的最后公式是:

0a2c3db0fcc70437b3768df416758ef9.png

38186fd6efe8fcf7c5194f8f8fe49a8f.png

下面的图片描述了高度图的深度H(T0)是如何确定UV沿着V方向的偏移量的.在这里TP是错误的结果,因为视差贴图仅是求近似值,而无意寻找V和表面交点的确切位置.

8885aa61200ad811dd792b7e8ae27484.png

此算法的主要优势就是仅多了一次对高度图的采样而提高GLSLshader的性能.下面是简单视差贴图shader的实现函数:

vec2 parallaxMapping(in vec3 V, in vec2 T, out float parallaxHeight)
{
   // get depth for this fragment
   float initialHeight = texture(u_heightTexture, o_texcoords).r;


   // calculate amount of offset for Parallax Mapping
   vec2 texCoordOffset = parallaxScale * V.xy / V.z * initialHeight;


   // calculate amount of offset for Parallax Mapping With Offset Limiting
   texCoordOffset = parallaxScale * V.xy * initialHeight;


   // return modified texture coordinates
   return o_texcoords - texCoordOffset;
}

陡峭视差贴图

陡峭视差贴图不同于简单近似视差贴图,不需要检查有效性和相关性.但是检查结果是否更接近有效值.这个方法的主要思路是将表面的深度分为等高的数层.然后从最上面的层数开始采样高度图.每次沿着V方向移动UV.如果采样点低于表面(当下层的深度大于采样深度),然后停止采样并使用最后使用的UV作为陡峭视差贴图的结果.

下图是陡峭视差贴图的实例.深度分为8层.每层高度为0.125.每层的UV偏移量为V.xy/V.z*Scale/numLayers.从最上面的层开始检查.也就是像素所在的层(黄色方块),以下为计算流程:

  • 高度图中的深度大于层深度(点在表面之上),因此继续迭代采样.
  • 沿着V方向偏移UV.选择深度等于0.125的下一个层.深度H(T1) = 0.625,高度图中的深度大于层深度,因此继续迭代采样.
  • 沿着V方向偏移UV.选择深度等于0.25的下一个层.深度H(T2) = 0.4,高度图中的深度大于层深度,因此继续迭代采样.
  • 沿着V方向偏移UV.选择深度等于0.375的下一个层.深度H(T3) = 0.2,高度图中的深度小于层深度,因此当下沿着V的采样点在表面之下,我们找到了最接近交点的采样点.Tp= T3.

b6b76a1bb6f2e272385cc0f002894d25.png

从上图可以看到纹理坐标T3距离V与表面的交点距离还是很远.但是与基础视差相比更加接近有效结果.如果需要更精确的结果.可以增加采样层数.

陡峭视差的最大缺点在于它将深度分成若干层.如果层数比较大.那么性能会下降.如果层数太小.您会注意右图所示的锯齿.您可以根据V和N的角度来差值最小,最大采样来确定层数.性能和锯齿的问题可以在浮雕视差或者映射视差贴图(POM)中得到解决.这些内容会在后面的教程中介绍.

b2f16842e3cfd841416b75325e30d05c.png

下面是陡峭视差的shader实现:

vec2 parallaxMapping(in vec3 V, in vec2 T, out float parallaxHeight)
{
   // determine number of layers from angle between V and N
   const float minLayers = 5;
   const float maxLayers = 15;
   float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0, 0, 1), V)));


   // height of each layer
   float layerHeight = 1.0 / numLayers;
   // depth of current layer
   float currentLayerHeight = 0;
   // shift of texture coordinates for each iteration
   vec2 dtex = parallaxScale * V.xy / V.z / numLayers;


   // current texture coordinates
   vec2 currentTextureCoords = T;


   // get first depth from heightmap
   float heightFromTexture = texture(u_heightTexture, currentTextureCoords).r;


   // while point is above surface
   while(heightFromTexture > currentLayerHeight) 
   {
      // to the next layer
      currentLayerHeight += layerHeight;
      // shift texture coordinates along vector V
      currentTextureCoords -= dtex;
      // get new depth from heightmap
      heightFromTexture = texture(u_heightTexture, currentTextureCoords).r;
   }


   // return results
   parallaxHeight = currentLayerHeight;
   return currentTextureCoords;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值