OpenGL核心之视差映射
笔者介绍:姜雪伟,IT公司技术合伙人。IT高级讲师,CSDN社区专家,特邀编辑。畅销书作者;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术具体解释》电子工业出版社等。
CSDN视频网址:http://edu.csdn.net/lecturer/144
另一些技巧让我们在陡峭的高度上能够获得差点儿完美的结果,即使当以一定角度观看的时候。比如。我们不再使用单一样本。取而代之使用多样本来找到近期点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核心之视差映射的更多相关文章
- 【OpenGL游戏开发之三】OpenGl核心函数库汇总
OpenGl核心函数库 glAccum 操作累加缓冲区 glAddSwapHintRectWIN 定义一组被SwapBuffers拷贝的三角形 glAlphaFunc允许设置alpha检测功能 glA ...
- 3D游戏图形技术解析(7)——视差映射贴图(Parallax Mapping)【转】
http://www.cnblogs.com/taotaobujue/articles/2781371.html 视差映射贴图(Parallax Mapping) ● 传统纹理贴图的弊端 纹理贴图大家 ...
- Hibernate的核心对象关系映射
Hibernate的核心就是对象关系映射: 加载映射文件的两种方式: 第一种:<mapping resource="com/bie/lesson02/crud/po/employee. ...
- 3hibernate核心对象关系映射 xxx.hbm.xml
Hibernate的核心就是对象关系映射: 加载映射文件的两种方式: 第一种:<mapping resource="com/bie/lesson02/crud/po/employee. ...
- 1-3 hibernate核心对象关系映射 xxx.hbm.xml
详见 http://www.cnblogs.com/biehongli/p/6532800.html 1 <?xml version="1.0" encoding='utf ...
- OpenGL OpenCV根据视差图重建三维信息
代码如下: // disparity_to_3d_reconstruction.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" //Huan ...
- OpenGL核心之SSAO技术解说(一)
笔者介绍:姜雪伟,IT公司技术合伙人.IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D ...
- 视差映射 parrallax mapping
算个新的uv在heightmap https://learnopengl.com/Advanced-Lighting/Parallax-Mapping https://blog.csdn.net/so ...
- OpenGL的API函数使用手册
(一)OpenGL函数库 格式: <库前缀><根命令><可选的参数个数><可选的参数类型> 库前缀有 gl.glu.aux.glut.wgl.glx.a ...
随机推荐
- Trie&可持久化Trie
WARNING:以下代码未经测试,若发现错误,欢迎指出qwq~ Trie树(字典树) 一种简单的数据结构,可存储大量字符串,可在$O(len)$的时间内完成插入,删除,查找等操作. 下面是一个简单的例 ...
- JAVA MessageDigest(MD5加密等)
转自http://blog.csdn.net/hudashi/article/details/8394158 一.概述 java.security.MessageDigest类用于为应用程序提供信息摘 ...
- Office2010激活工具
mini-KMS Activator是一款好用Office2010激活工具,“KMS”是微软对于“大客户”(VOL或VL)提供的Microsoft产品激活方式之一.在适用此方式的Microsoft产品 ...
- 【2017百度之星程序设计大赛 - 复赛】Valley Numer
[链接]http://acm.hdu.edu.cn/showproblem.php?pid=6148 [题意] 在这里写题意 [题解] 先把1..N里面的山峰数字个数算出来->x 然后用N减去这 ...
- C 字符/字符串经常使用函数
string.h中经常使用函数 char * strchr(char * str ,char ch); 从字符串str中查找首次出现字符ch的位置,若存在返回查找后的地址.若不存在则返回NULL vo ...
- poi完美word转html(表格、图片、样式)
直入正题,需求为页面预览word文档,用的是poi3.8,以下代码支持表格.图片,不支持分页,只支持doc,不支持docx: /** * */ import java.io.BufferedWrite ...
- Visual Studio Team Services持续集成到Github仓库
Devops如何用VSTS持续集成到Github仓库! 工欲善其事,必先利其器.在开始正式的教程之前我们先来聊聊准备工作. 管理工具会VSTS. 代码管理会用GITHUB. 服务器会用Azure. ...
- winform程序,备份数据库+并压缩+并删除以前的备份
说明:为了定时备份服务器上的数据库并压缩到指定目录,方便下载到本地而写本程序.配合windows的任务计划,可以达到定时备份数据库的目的. 程序需引用SQLDMO.DLL,如电脑上已安装sqlserv ...
- Linux体系结构
linux内核结构: system call interface (SCI层) 为用户空间提供了一套标准的系统调用函数来访问linux内核. process management (PM层) 进程管理 ...
- [Angular] Protect The Session Id with https and http only
For the whole signup process. we need to Hash the password to create a password digest Store the use ...