在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping)

视差贴图

最近一直在研究如何在我的 iPad 2(只支持 OpenGL ES 2.0, 不支持 3.0) 上实现 视差贴图(Parallax Mapping) 和 位移贴图(Displacement Mapping).

经过一番研究, 搜索阅读了不少文章, 终于确定, OpenGL ES 2.0 可以支持 视差贴图, 不过暂时还没什么好办法支持 位移贴图.

因为就我目前所了解的位移贴图, 有这么两种方法来实现, 一种是用 Tessellation 来提供多面数网格, 另一种是在顶点着色器中对高度图进行纹理采样来计算对应的顶点偏移量.

第一种方法就不必想了, 因为目前移动设备的 OpenGL ES 2.0/3.0 都不支持(貌似 DX11OpenGL 4.0 才支持), 而 OpenGL ES 3.0 衍生自 OpenGL 3.3.

第二种方法目前看起来只能在 OpenGL ES 3.0 上使用, 请参考这篇文档Jim's GameDev Blog: 置换贴图 Displacement Mapping. 不过没办法在 OpenGL ES 2.0 上使用, 因为它要求在顶点着色器中进行纹理采样, 而这个特性恰恰是 2.0 不支持, 3.0 支持的.

我们可以看看 Jim3.0 设备上实现位移贴图的效果:

原始图:

使用位移贴图后的效果:

好了, 现在在我们的 2.0 设备上实现我们的 视差贴图 吧, 先看看效果:

使用不同参数的效果:

  • height_scale = -0.015

  • height_scale = -0.055

  • height_scale = -0.095

你可以灵活调整这些参数:

  • lightPos: 光源位置
  • viewPos: 眼睛位置
  • height_scale: 高度图取样值缩放比例

看看这个视频 video:

实现细节

关键技术点就这么几个:

手动构造正切空间 TBN 变换矩阵

如果你使用比较大的引擎, 比如 Unity, 它会帮你计算好法线,切线次法线, 如果自己开发, 没有使用这些引擎, 那么很可能就需要自己手动构造了.

目前我发现有 3 种根据法线手动计算 TBN 的近似算法, 其中一种既能在 OpenGL ES 2.0 的顶点着色器内使用, 也能在片段着色器内使用, 就是我们下面要提到的这种, 主要原理就是已知了法线 Normal, 要据此求出对应的切线 Tangent 和 次法线 Binormal, 因为它们两两垂直, 而且 TBUV 对齐, 因此很容易求得 T, 再根据 TN 求得 B, 算法代码如下:

    // 根据法线 N 计算 T,Bvec3 tangent; vec3 binormal; // 使用顶点属性法线,并归一化vec3 Normal = normalize(normal*2.0-1.0);// 通过叉积来计算夹角vec3 c1 = cross(Normal, vec3(0.0, 0.0, 1.0)); vec3 c2 = cross(Normal, vec3(0.0, 1.0, 0.0)); // 方向朝外的是我们要的if ( length(c1) > length(c2) ) { tangent = c1;  } else { tangent = c2;}// 归一化切线和次法线tangent = normalize(tangent);binormal = normalize(cross(normal, tangent)); vec3 T = normalize(mat3(model) * tangent);vec3 B = normalize(mat3(model) * binormal);vec3 N = normalize(mat3(model) * normal);// 构造出 TBN 矩阵mat3 TBN = mat3(T, B, N);

得到 TBN 矩阵后, 既可以把其他向量从其他空间变换进正切空间来, 也可以把正切空间的向量变换到其他空间去. 通常意义的做法是:

  • TBN 用于正向变换
  • TBN 的逆阵用于反向变换

不过在 OpenGL 中, 你把矩阵放在向量左边乘, 就是正向变换, 它会按列矩阵处理; 你把矩阵放在向量右边乘就是反向变换, 它会按行矩阵处理. 这样就不需要再进行矩阵求逆的操作了.

视差映射函数

视差贴图 的本质就是根据高度纹理图的不同高度以及视线向量的坐标, 来实时计算纹理坐标在视线下的偏移, 并以此作为新的纹理坐标来从纹理贴图中进行取样.

代码如下:

// The Parallax Mapping
vec2 ParallaxMapping1(vec2 texCoords, vec3 viewDir)
{ float height =  texture2D(depthMap, texCoords).r;     return texCoords - viewDir.xy / viewDir.z * (height * height_scale);
}

在此基础上提出的 视差遮掩 可以提供更好的视觉效果, 具体原理在代码注释中.

光照模型

最后就是一个非常简单的光照模型, 先从原始纹理中取样, 以此为基础, 缩小10倍作为环境光, 根据前面计算得到的切线来计算光线摄入方向, 再结合法线可以计算出漫射光和反射光, 最后把这些光线混合就得到最终的光照颜色值了.

这个光照模型的好处是简单易用, 不需要另外设置过多参数, 坏处就是不太灵活, 实际使用时可以把参数设置为可变, 或者直接换成其他光照模型也可以(具体的参数值就需要自己调整了).

因为代码自己计算构造了 TBN 变换矩阵, 所以这段 shader 代码具有很好的移植性, 可以轻松地把它用在其他地方.

完整代码

代码如下:

function setup()displayMode(OVERLAY)print("试验 OpenGL ES 2.0 中位移贴图的例子")print("Test the Parallax Mapping in OpenGL ES 2.0")img1 = readImage("Dropbox:dm")img2 = readImage("Dropbox:dnm1")img3 = readImage("Dropbox:dm1")local w,h = WIDTH,HEIGHTlocal c = color(223, 218, 218, 255)m3 = mesh()m3i = m3:addRect(w/2,h/2,w/1,h/1)m3:setColors(c)m3.texture = img1m3:setRectTex(m3i,0,0,1,1)m3.shader = shader(shaders.vs,shaders.fs)m3.shader.diffuseMap = img1m3.shader.normalMap = img2m3.shader.depthMap =  img3-- local tb = m3:buffer("tangent")-- tb:resize(6)tchx,tchy = 0,0
endfunction draw()background(40, 40, 50)perspective()-- camera(e.x, e.y, e.z, p.x, p.y, p.z)-- 用于立方体-- camera(300,300,600, 0,500,0, 0,0,1)-- 用于平面位移贴图camera(WIDTH/2,HEIGHT/2,1000, WIDTH/2,HEIGHT/2,-200, 0,1,0)-- mySp:Sphere(100,100,100,0,0,0,10)light = vec3(tchx, tchy, 100.75)view = vec3(tchx, tchy, 300.75)-- light = vec3(300, 300, 500)-- rotate(ElapsedTime*5,0,1,0)-- m3:setRect(m2i,tchx,tchy,WIDTH/100,HEIGHT/100)setShaderParam(m3)m3:draw()
endfunction touched(touch)if touch.state == BEGAN or touch.state == MOVING thentchx=touch.x+10tchy=touch.y+10end
endfunction setShaderParam(m)m.shader.model = modelMatrix()m.shader.lightPos = light -- m.shader.lightPos = vec3(0.5, 1.0, 900.3)m.shader.viewPos = vec3(WIDTH/2,HEIGHT/2,5000) m.shader.viewPos = view-- m.shader.viewPos = vec3(0.0, 0.0, 90.0)m.shader.parallax = truem.shader.height_scale = -0.015
end-- 试验 视差贴图 中的例子
shaders = {
vs = [[
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
//attribute vec3 tangent;
//attribute vec3 bitangent;varying vec3 vFragPos;
varying vec2 vTexCoords;
varying vec3 vTangentLightPos;
varying vec3 vTangentViewPos;
varying vec3 vTangentFragPos;uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
uniform mat4 modelViewProjection;uniform vec3 lightPos;
uniform vec3 viewPos;void main()
{//gl_Position = projection * view * model * position;gl_Position = modelViewProjection * position;vFragPos = vec3(model * position);   vTexCoords = texCoord;// 根据法线 N 计算 T,Bvec3 tangent; vec3 binormal; // 使用顶点属性法线,并归一化vec3 Normal = normalize(normal*2.0-1.0);vec3 c1 = cross(Normal, vec3(0.0, 0.0, 1.0)); vec3 c2 = cross(Normal, vec3(0.0, 1.0, 0.0)); if ( length(c1) > length(c2) ) { tangent = c1;  } else { tangent = c2;}// 归一化切线和次法线tangent = normalize(tangent);binormal = normalize(cross(normal, tangent)); vec3 T = normalize(mat3(model) * tangent);vec3 B = normalize(mat3(model) * binormal);vec3 N = normalize(mat3(model) * normal);mat3 TBN = mat3(T, B, N);vTangentLightPos = lightPos*TBN;vTangentViewPos  = viewPos*TBN;vTangentFragPos  = vFragPos*TBN;
}
]],fs = [[
precision highp float;varying vec3 vFragPos;
varying vec2 vTexCoords;
varying vec3 vTangentLightPos;
varying vec3 vTangentViewPos;
varying vec3 vTangentFragPos;uniform sampler2D diffuseMap;
uniform sampler2D normalMap;
uniform sampler2D depthMap;uniform bool parallax;
uniform float height_scale;vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir);
vec2 ParallaxMapping1(vec2 texCoords, vec3 viewDir);// The Parallax Mapping
vec2 ParallaxMapping1(vec2 texCoords, vec3 viewDir)
{ float height =  texture2D(depthMap, texCoords).r;     return texCoords - viewDir.xy / viewDir.z * (height * height_scale);
}// Parallax Occlusion Mapping
vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{ // number of depth layersconst float minLayers = 10.0;const float maxLayers = 50.0;float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));  // calculate the size of each layerfloat layerDepth = 1.0 / numLayers;// depth of current layerfloat 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 valuesvec2  currentTexCoords     = texCoords;float currentDepthMapValue = texture2D(depthMap, currentTexCoords).r;while(currentLayerDepth < currentDepthMapValue){// shift texture coordinates along direction of PcurrentTexCoords -= deltaTexCoords;// get depthmap value at current texture coordinatescurrentDepthMapValue = texture2D(depthMap, currentTexCoords).r;  // get depth of next layercurrentLayerDepth += 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 interpolationfloat afterDepth  = currentDepthMapValue - currentLayerDepth;float beforeDepth = texture2D(depthMap, prevTexCoords).r - currentLayerDepth + layerDepth;// interpolation of texture coordinatesfloat weight = afterDepth / (afterDepth - beforeDepth);vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);return finalTexCoords;
}void main()
{           // Offset texture coordinates with Parallax Mappingvec3 viewDir = normalize(vTangentViewPos - vTangentFragPos);vec2 texCoords = vTexCoords;if(parallax)texCoords = ParallaxMapping(vTexCoords,  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 mapvec3 normal = texture2D(normalMap, texCoords).rgb;normal = normalize(normal * 2.0 - 1.0);   // Get diffuse colorvec3 color = texture2D(diffuseMap, texCoords).rgb;// Ambientvec3 ambient = 0.1 * color;// Diffusevec3 lightDir = normalize(vTangentLightPos - vTangentFragPos);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;gl_FragColor = vec4(ambient + diffuse + specular, 1.0);
}
]]
}

其中法线图(img2),高度图(img3) 都是通过软件 CrazyBump 根据原始纹理(img1)生成的.

你也可以下载它们直接使用:

img1:

img2:

img3:

参考

38 视差贴图
视差贴图(Parallax Mapping)与陡峭视差贴图(Steep Palallax Mapping)
Parallax Occlusion Mapping in GLSL
Jim's GameDev Blog: 置换贴图 Displacement Mapping

转载于:https://www.cnblogs.com/freeblues/p/5748935.html

在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping)相关推荐

  1. OpenGL 视差贴图 Parallax Mapping

    OpenGL 视差贴图 Parallax Mapping 视差贴图 Parallax Mapping简介 视差贴图 陡峭视差映射 视差遮蔽映射 视差贴图 Parallax Mapping简介 视差贴图 ...

  2. android平台下OpenGL ES 3.0给图片添加黑白滤镜

    OpenGL ES 3.0学习实践 android平台下OpenGL ES 3.0从零开始 android平台下OpenGL ES 3.0绘制纯色背景 android平台下OpenGL ES 3.0绘 ...

  3. android平台下OpenGL ES 3.0绘制圆点、直线和三角形

    OpenGL ES 3.0学习实践 android平台下OpenGL ES 3.0从零开始 android平台下OpenGL ES 3.0绘制纯色背景 android平台下OpenGL ES 3.0绘 ...

  4. android平台下OpenGL ES 3.0绘制纯色背景

    OpenGL ES 3.0学习实践 android平台下OpenGL ES 3.0从零开始 android平台下OpenGL ES 3.0绘制纯色背景 android平台下OpenGL ES 3.0绘 ...

  5. android平台下OpenGL ES 3.0从零开始

    OpenGL ES 3.0学习实践 android平台下OpenGL ES 3.0从零开始 android平台下OpenGL ES 3.0绘制纯色背景 android平台下OpenGL ES 3.0绘 ...

  6. android平台下OpenGL ES 3.0使用GLSurfaceView对相机Camera预览实时处理

    OpenGL ES 3.0学习实践 android平台下OpenGL ES 3.0从零开始 android平台下OpenGL ES 3.0绘制纯色背景 android平台下OpenGL ES 3.0绘 ...

  7. 《Android 3D游戏开发技术宝典——OpenGL ES 2.0》——2.3节手机自带数据库——SQLite...

    本节书摘来自异步社区<Android 3D游戏开发技术宝典--OpenGL ES 2.0>一书中的第2章,第2.3节手机自带数据库--SQLite,作者 吴亚峰,更多章节内容可以访问云栖社 ...

  8. android opengl es 纹理 不同设备 白色,android – OpenGL ES 2.0纹理没有在某些设备上显示...

    早上好,这是2个纹理非幂的典型例子. 由于多种原因,纹理在分辨率上需要2的幂,这是一个非常常见的错误,每个人都碰巧陷入这个陷阱:)我也是. 2个纹理的非功率在某些设备/ GPU上运行平稳的事实,仅仅取 ...

  9. linux opengl es,在Linux上,如何开发OpenGL ES(GLES)2.0应用程序?

    问题描述 我想在我的Ubuntu计算机上开发OpenGL ES 2.0应用程序.我找不到支持GLES 2.0的任何库/仿真器.有什么建议么? 最佳回答 您可以使用POWERVR SDK在PC上模拟Op ...

最新文章

  1. ubuntu16.06+vsftpd+nginx搭建图片服务器
  2. java 数据聚合_Java数据聚合问题请教?
  3. 入职五年回顾(一) 2012年8月
  4. ArcGIS.Server.9.3和ArcGIS API for Flex的GeometryService和buffer分析(十)
  5. 聊聊JVM(二)说说GC的一些常见概念
  6. oracle 存储过程获取当前日期
  7. 作者:周晓津(1971-),男,博士,广州市社会科学院研究员。
  8. 准确率(Accuracy) | 查准率(Precision) | 查全率(Recall)
  9. Java设计模式之十 ---- 访问者模式和中介者模式
  10. Node.js使用jszip实现文件夹操作
  11. APR学习-消息池的设计与使用
  12. 0017 求正方体的体积与表面积
  13. 新手好例子图书馆管理系统Python+MySQL+tkinter图形化界面+源码(注释详细)
  14. 牛牛的跳跳棋(贪心)
  15. 前端三刺客----HTML
  16. canvas图形放大缩小鼠标拖拽
  17. 在线ssd测试软件,AS SSD Benchmark测试
  18. 从FTP模块学习先进的诊断技术(Erlang Trace机制)
  19. 支付业务稳健,到店电商快速放量,移卡迎来价值评估新锚点
  20. 使用C#语言编写记事本程序

热门文章

  1. CTFshow 命令执行 web41
  2. [YTU]_2638(编程题:多态--动物叫)
  3. MATLAB双纵坐标绘图(重要)
  4. Python基础10 反过头来看看
  5. BP神经网络预测实例
  6. 遗传算法(Genetic Algorithm )+C++实现解决TSP问题
  7. 创建文件夹(如果已经存在就清空)python脚本
  8. 求连通块个数(使用并查集)
  9. 在linux系统中 用于配置和显示,在Linux系统中使用sway设置多个显示器/监视器
  10. [Java并发编程(二)] 线程池 FixedThreadPool、CachedThreadPool、ForkJoinPool?为后台任务选择合适的 Java executors...