笔者介绍:姜雪伟,IT公司技术合伙人。IT高级讲师,CSDN社区专家,特邀编辑。畅销书作者;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术具体解释》电子工业出版社等。

CSDN视频网址:http://edu.csdn.net/lecturer/144

继续接着上文的问题。先给读者展示一副图效果例如以下所看到的:
问题的解决办法是这仅仅是一个大致近似的视差映射。

另一些技巧让我们在陡峭的高度上能够获得差点儿完美的结果,即使当以一定角度观看的时候。比如。我们不再使用单一样本。取而代之使用多样本来找到近期点B会得到如何的结果?

陡峭视差映射(Steep Parallax Mapping)是视差映射的扩展,原则是一样的,但不是使用一个样本而是多个样本来确定向量P¯到B。

它能得到更好的结果,它将总深度范围分布到同一个深度/高度的多个层中。

从每一个层中我们沿着P¯方向移动採样纹理坐标,直到我们找到了一个採样得到的低于当前层的深度值的深度值。

看看以下的图片:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvanh3MTY3/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

我们从上到下遍历深度层,我们把每一个深度层和储存在深度贴图中的它的深度值进行对照。

假设这个层的深度值小于深度贴图的值,就意味着这一层的P¯向量部分在表面之下。

我们继续这个处理过程直到有一层的深度高于储存在深度贴图中的值:这个点就在(经过位移的)表面下方。

这个样例中我们能够看到第二层(D(2) = 0.73)的深度贴图的值仍低于第二层的深度值0.4。所以我们继续。下一次迭代。这一层的深度值0.6大于深度贴图中採样的深度值(D(3) = 0.37)。我们便能够假设第三层向量P¯是可用的位移几何位置。

我们能够用从向量P3¯的纹理坐标偏移T3来对fragment的纹理坐标进行位移。你能够看到随着深度曾的添加准确度也在提高。

为实现这个技术。我们仅仅须要改变ParallaxMapping函数,由于全部须要的变量都有了:

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
// number of depth layers
const float numLayers = 10;
// calculate the size of each layer
float layerDepth = 1.0 / numLayers;
// depth of current layer
float currentLayerDepth = 0.0;
// the amount to shift the texture coordinates per layer (from vector P)
vec2 P = viewDir.xy * height_scale;
float deltaTexCoords = P / numLayers; [...]
}

我们先定义层的数量,计算每一层的深度。最后计算纹理坐标偏移。每一层我们必须沿着P¯的方向进行移动。

然后我们遍历全部层,从上開始,知道找到小于这一层的深度值的深度贴图值:

// get initial values
vec2 currentTexCoords = texCoords;
float currentDepthMapValue = texture(depthMap, currentTexCoords).r; while(currentLayerDepth < currentDepthMapValue)
{
// shift texture coordinates along direction of P
currentTexCoords -= deltaTexCoords;
// get depthmap value at current texture coordinates
currentDepthMapValue = texture(depthMap, currentTexCoords).r;
// get depth of next layer
currentLayerDepth += layerDepth;
} return texCoords - currentTexCoords;

这里我们循环每一层深度,直到沿着P¯向量找到第一个返回低于(位移)表面的深度的纹理坐标偏移量。从fragment的纹理坐标减去最后的偏移量,来得到终于的经过位移的纹理坐标向量,这次就比传统的视差映射更精确了。

有10个样本砖墙从一个角度看上去就已经非常好了,可是当有一个强前面展示的木制表面一样陡峭的表面时,陡峭的视差映射的威力就显示出来了:

我们能够通过对视差贴图的一个属性的利用。对算法进行一点提升。当垂直看一个表面的时候纹理时位移比以一定角度看时的小。

我们能够在垂直看时使用更少的样本。以一定角度看时添加样本数量:

const float minLayers = 8;
const float maxLayers = 32;
float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));

这里我们得到viewDir和正z方向的点乘,使用它的结果依据我们看向表面的角度调整样本数量(注意正z方向等于切线空间中的表面的法线)。假设我们所看的方向平行于表面,我们就是用32层。

你能够在这里找到最新的像素着色器代码。

这里也提供木制玩具箱的表面贴图:diffuse、法线、深度。

陡峭视差贴图相同有自己的问题。

由于这个技术是基于有限的样本数量的。我们会遇到锯齿效果以及图层之间有明显的断层:

我们能够通过添加样本的方式降低这个问题,可是非常快就会花费非常多性能。有些旨在修复这个问题的方法:不适用低于表面的第一个位置。而是在两个接近的深度层进行插值找出更匹配B的。

两种最流行的解决方法叫做Relief Parallax Mapping和Parallax Occlusion Mapping,Relief Parallax Mapping更精确一些,可是比Parallax Occlusion Mapping性能开销很多其它。由于Parallax Occlusion Mapping的效果和前者几乎相同可是效率更高。因此这样的方式更常常使用。所以我们将在以下讨论一下。

视差遮蔽映射(Parallax Occlusion Mapping)和陡峭视差映射的原则相同,但不是用触碰的第一个深度层的纹理坐标。而是在触碰之前和之后,在深度层之间进行线性插值。

我们依据表面的高度距离啷个深度层的深度层值的距离来确定线性插值的大小。

看看以下的图片就能了解它是如何工作的:

你能够看到大部分和陡峭视差映射一样,不一样的地方是有个额外的步骤,两个深度层的纹理坐标环绕着交叉点的线性插值。这也是近似的。可是比陡峭视差映射更精确。

视差遮蔽映射的代码基于陡峭视差映射。所以并不难:

[...] // steep parallax mapping code here

// get texture coordinates before collision (reverse operations)
vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get depth after and before collision for linear interpolation
float afterDepth = currentDepthMapValue - currentLayerDepth;
float beforeDepth = texture(depthMap, prevTexCoords).r - currentLayerDepth + layerDepth; // interpolation of texture coordinates
float weight = afterDepth / (afterDepth - beforeDepth);
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight); return finalTexCoords;

在对(位移的)表面几何进行交叉,找到深度层之后。我们获取交叉前的纹理坐标。然后我们计算来自对应深度层的几何之间的深度之间的距离。并在两个值之间进行插值。线性插值的方式是在两个层的纹理坐标之间进行的基础插值。函数最后返回终于的经过插值的纹理坐标。

视差遮蔽映射的效果非常好。虽然有一些能够看到的轻微的不真实和锯齿的问题,这仍是一个好交易。由于除非是放得非常大或者观察角度特别陡,否则也看不到。

最后把视线该效果的源码给读者展示一下,首先展示的顶点着色器代码:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;
layout (location = 3) in vec3 tangent;
layout (location = 4) in vec3 bitangent; out VS_OUT {
vec3 FragPos;
vec2 TexCoords;
vec3 TangentLightPos;
vec3 TangentViewPos;
vec3 TangentFragPos;
} vs_out; uniform mat4 projection;
uniform mat4 view;
uniform mat4 model; uniform vec3 lightPos;
uniform vec3 viewPos; void main()
{
gl_Position = projection * view * model * vec4(position, 1.0f);
vs_out.FragPos = vec3(model * vec4(position, 1.0));
vs_out.TexCoords = texCoords; vec3 T = normalize(mat3(model) * tangent);
vec3 B = normalize(mat3(model) * bitangent);
vec3 N = normalize(mat3(model) * normal);
mat3 TBN = transpose(mat3(T, B, N)); vs_out.TangentLightPos = TBN * lightPos;
vs_out.TangentViewPos = TBN * viewPos;
vs_out.TangentFragPos = TBN * vs_out.FragPos;
}

片段着色器代码例如以下所看到的:

#version 330 core
out vec4 FragColor; in VS_OUT {
vec3 FragPos;
vec2 TexCoords;
vec3 TangentLightPos;
vec3 TangentViewPos;
vec3 TangentFragPos;
} fs_in; uniform sampler2D diffuseMap;
uniform sampler2D normalMap;
uniform sampler2D depthMap; uniform bool parallax;
uniform float height_scale; vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
// number of depth layers
const float minLayers = 10;
const float maxLayers = 20;
float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));
// calculate the size of each layer
float layerDepth = 1.0 / numLayers;
// depth of current layer
float currentLayerDepth = 0.0;
// the amount to shift the texture coordinates per layer (from vector P)
vec2 P = viewDir.xy / viewDir.z * height_scale;
vec2 deltaTexCoords = P / numLayers; // get initial values
vec2 currentTexCoords = texCoords;
float currentDepthMapValue = texture(depthMap, currentTexCoords).r; while(currentLayerDepth < currentDepthMapValue)
{
// shift texture coordinates along direction of P
currentTexCoords -= deltaTexCoords;
// get depthmap value at current texture coordinates
currentDepthMapValue = texture(depthMap, currentTexCoords).r;
// get depth of next layer
currentLayerDepth += layerDepth;
} // -- parallax occlusion mapping interpolation from here on
// get texture coordinates before collision (reverse operations)
vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get depth after and before collision for linear interpolation
float afterDepth = currentDepthMapValue - currentLayerDepth;
float beforeDepth = texture(depthMap, prevTexCoords).r - currentLayerDepth + layerDepth; // interpolation of texture coordinates
float weight = afterDepth / (afterDepth - beforeDepth);
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight); return finalTexCoords;
} void main()
{
// Offset texture coordinates with Parallax Mapping
vec3 viewDir = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos);
vec2 texCoords = fs_in.TexCoords;
if(parallax)
texCoords = ParallaxMapping(fs_in.TexCoords, viewDir); // discards a fragment when sampling outside default texture region (fixes border artifacts)
if(texCoords.x > 1.0 || texCoords.y > 1.0 || texCoords.x < 0.0 || texCoords.y < 0.0)
discard; // Obtain normal from normal map
vec3 normal = texture(normalMap, texCoords).rgb;
normal = normalize(normal * 2.0 - 1.0); // Get diffuse color
vec3 color = texture(diffuseMap, texCoords).rgb;
// Ambient
vec3 ambient = 0.1 * color;
// Diffuse
vec3 lightDir = normalize(fs_in.TangentLightPos - fs_in.TangentFragPos);
float diff = max(dot(lightDir, normal), 0.0);
vec3 diffuse = diff * color;
// Specular
vec3 reflectDir = reflect(-lightDir, normal);
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), 32.0); vec3 specular = vec3(0.2) * spec;
FragColor = vec4(ambient + diffuse + specular, 1.0f);
}

视差贴图是提升场景细节非常好的技术,可是使用的时候还是要考虑到它会带来一点不自然。大多数时候视差贴图用在地面和墙壁表面,这样的情况下查明表面的轮廓并不easy。同一时候观察角度往往趋向于垂直于表面。

这样视差贴图的不自然也就非常难能被注意到了,对于提升物体的细节能够祈祷难以置信的效果。

OpenGL核心之视差映射的更多相关文章

  1. 【OpenGL游戏开发之三】OpenGl核心函数库汇总

    OpenGl核心函数库 glAccum 操作累加缓冲区 glAddSwapHintRectWIN 定义一组被SwapBuffers拷贝的三角形 glAlphaFunc允许设置alpha检测功能 glA ...

  2. 3D游戏图形技术解析(7)——视差映射贴图(Parallax Mapping)【转】

    http://www.cnblogs.com/taotaobujue/articles/2781371.html 视差映射贴图(Parallax Mapping) ● 传统纹理贴图的弊端 纹理贴图大家 ...

  3. Hibernate的核心对象关系映射

    Hibernate的核心就是对象关系映射: 加载映射文件的两种方式: 第一种:<mapping resource="com/bie/lesson02/crud/po/employee. ...

  4. 3hibernate核心对象关系映射 xxx.hbm.xml

    Hibernate的核心就是对象关系映射: 加载映射文件的两种方式: 第一种:<mapping resource="com/bie/lesson02/crud/po/employee. ...

  5. 1-3 hibernate核心对象关系映射 xxx.hbm.xml

    详见  http://www.cnblogs.com/biehongli/p/6532800.html 1 <?xml version="1.0" encoding='utf ...

  6. OpenGL OpenCV根据视差图重建三维信息

    代码如下: // disparity_to_3d_reconstruction.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" //Huan ...

  7. OpenGL核心之SSAO技术解说(一)

    笔者介绍:姜雪伟,IT公司技术合伙人.IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D ...

  8. 视差映射 parrallax mapping

    算个新的uv在heightmap https://learnopengl.com/Advanced-Lighting/Parallax-Mapping https://blog.csdn.net/so ...

  9. OpenGL的API函数使用手册

    (一)OpenGL函数库 格式: <库前缀><根命令><可选的参数个数><可选的参数类型> 库前缀有 gl.glu.aux.glut.wgl.glx.a ...

随机推荐

  1. HDU 1007 Quoit Design 平面内最近点对

    http://acm.hdu.edu.cn/showproblem.php?pid=1007 上半年在人人上看到过这个题,当时就知道用分治但是没有仔细想... 今年多校又出了这个...于是学习了一下平 ...

  2. 非常不错的canvas效果,线随心动

    非常不错的canvas效果,下面是html代码. <!DOCTYPE html> <html> <head> <meta charset="utf- ...

  3. BZOJ2002: [Hnoi2010]Bounce 弹飞绵羊(LCT)

    Description 某天,Lostmonkey发明了一种超级弹力装置,为了在 他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏.游戏一开始,Lostmonkey在地上沿着一条直线摆上n个装置,每个装 ...

  4. Docker 搭建java+tomcat

    1. 准备java和tomcat的软件包 jdk-7u79-linux-x64.tar.gz apache-tomcat-7.0.57.tar.gz 2. 编辑Dockerfile 文件 vim Do ...

  5. IOS-Run loop学习总结

    不知道大家有没有想过这个问题,一个应用開始执行以后放在那里,假设不正确它进行不论什么操作.这个应用就像精巧了一样,不会自发的有不论什么动作发生.可是假设我们点击界面上的一个button.这个时候就会有 ...

  6. Android中实现整个视图切换的左右滑动效果

    Android中提供了一个Gallary,可以实现图片或者文本的左右滑动效果. 如何让整个视图都能实现左右滑动,达到类似于Gallary的效果呢?可以直接用一个开源的ViewFlow来实现.   项目 ...

  7. Shell中反引号(`)与$()用法的区别

    今天有人提问: echo `echo \\\\\\\w` echo $(echo \\\\\\\w) 为什么输出的不一样? 这就引申出了另一个问题:反引号与$()有没有区别? 这是一个非常有意思的问题 ...

  8. Linux 解压缩命令整理

    一.tar命令 参数 参数 详解 参数 详解 -c 可以使用绝对路径来压缩 -x 解开一个压缩文件的参数指令 -t 查看内容 -r 向压缩归档文件末尾追加文件 -u 更新原压缩包中的文件 -z 有gz ...

  9. vue中监听路由参数变化

    今天遇到一个这样的业务场景:在同一个路由下,只改变路由后面的参数值, 比如在这个页面  /aaa?id=1 ,在这个页面中点击一个按钮后 跳转到 /aaa?id=2 , 但从“/aaa?id=1”到“ ...

  10. Django中pycharm中 报错 ---ValueError: The field admin.LogEntry.user was declared with a lazy reference to 'system.sysuser', bu

    问题是:已经在settings.py文件中注册过app仍旧提示没有安装,并且使用makegirations命令时会抛出如下异常 解决方法: 找到自己的python3.x,进入site-packages ...