文档章节

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

FreeBlues
 FreeBlues
发布于 2016/08/08 13:28
字数 1275
阅读 108
收藏 0

在 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:

<embed src="http://player.youku.com/player.php/sid/XMTY3NTQ1MDc0OA==/v.swf" allowFullScreen="true" quality="high" width="480" height="400" align="middle" allowScriptAccess="always" type="application/x-shockwave-flash"></embed>

代码如下:

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,HEIGHT
    local c = color(223, 218, 218, 255)
    m3 = mesh()
    m3i = m3:addRect(w/2,h/2,w/1,h/1)
    m3:setColors(c)
    m3.texture = img1
    m3:setRectTex(m3i,0,0,1,1)
    m3.shader = shader(shaders.vs,shaders.fs)
    m3.shader.diffuseMap = img1
    m3.shader.normalMap = img2
    m3.shader.depthMap =  img3

    -- local tb = m3:buffer("tangent")
    -- tb:resize(6)
    tchx,tchy = 0,0
end

function 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()   
end

function touched(touch)
    if touch.state == BEGAN or touch.state == MOVING then
        tchx=touch.x+10
        tchy=touch.y+10
    end
end

function 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 = true
    m.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,B
    vec3 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 layers
    const 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 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 = texture2D(depthMap, currentTexCoords).r;
      
    while(currentLayerDepth < currentDepthMapValue)
    {
        // shift texture coordinates along direction of P
        currentTexCoords -= deltaTexCoords;
        // get depthmap value at current texture coordinates
        currentDepthMapValue = texture2D(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 = texture2D(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(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 map
    vec3 normal = texture2D(normalMap, texCoords).rgb;
    normal = normalize(normal * 2.0 - 1.0);   
   
    // Get diffuse color
    vec3 color = texture2D(diffuseMap, texCoords).rgb;
    // Ambient
    vec3 ambient = 0.1 * color;
    // Diffuse
    vec3 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

© 著作权归作者所有

共有 人打赏支持
FreeBlues
粉丝 98
博文 280
码字总数 493678
作品 0
其它
程序员
私信 提问
OpenGL ARB 推出 OpenGL 4.1 规范

OpenGL ARB 推出了 OpenGL 4.1 规格。OpenGL 4.1 核心规格的主要新功能包括: 强化和 OpenGL ES 2.0 的相容性,将之前 OpenGL ES 2.0 中包含,但 OpenGL 4.0 中没有的功能加入 OpenGL 4.1 中(GL...

鉴客
2010/07/27
409
0
Android OpenGL es 纹理坐标设定与贴图规则

当opengl对一个四方形进行贴图时,会定义纹理贴图坐标,一串数组,相信初学openggl es者看到后会很头疼,不知道写得是什么东西。现在就将我的研究成果与大家分享下! 当纹理映射启动后绘图时...

长平狐
2012/08/29
2.5K
0
Android图形---OpenGL(二)

本文译自:http://developer.android.com/guide/topics/graphics/opengl.html OpenGL 包 一旦使用GLSurfaceView和GLSurfaceView.Renderer类给OpenGL建立了一个View容器,那么就可以开始使用以...

长平狐
2012/10/16
81
0
多平台游戏渲染开发

多平台游戏渲染开发 跨平台移动应用开发的渲染解决方案综述 在移动设备上做应用/游戏开发,实现跨平台是主流。对于应用/游戏的逻辑功能和核心算法部分,如果是用C++进行开发的,因为iOS/And...

失足处男的倒霉孩子
2013/12/25
1K
0
与OpenGL ES的第一次约会

最近公司的项目中需要实现一个实时视频绘制的功能,在相机中根据识别到的人脸点位信息,对指定的点之间绘制出图案来引导用户。出于性能的考量,决定采用OpenGL ES来进行图案的绘制。最终效果...

金小俊
2018/09/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Java单例模式学习记录

在项目开发中经常能遇见的设计模式就是单例模式了,而实现的方式最常见的有两种:饿汉和饱汉(懒汉)。由于日常接触较多而研究的不够深入,导致面试的时候被询问到后有点没底,这里记录一下学习...

JerryLin123
昨天
2
0
VSCODE 无法调试

VSCODE 无法调试 可以运行 可能的原因: GCC 的参数忘了加 -g

shzwork
昨天
3
0
理解去中心化 稳定币 DAI

随着摩根大通推出JPM Coin 稳定币,可以预见稳定币将成为区块链落地的一大助推器。 坦白来讲,对于一个程序员的我来讲(不懂一点专业经济和金融),理解DAI的机制,真的有一点复杂。耐心看完...

Tiny熊
昨天
4
0
5.线程实现

用于线程实现的Python模块 Python线程有时称为轻量级进程,因为线程比进程占用的内存少得多。 线程允许一次执行多个任务。 在Python中,以下两个模块在一个程序中实现线程 - _thread 模块 th...

Eappo_Geng
昨天
4
0
ServiceLoader

创建一个接口文件在resources资源目录下创建META-INF/services文件夹在services文件夹中创建文件,以接口全名命名创建接口实现类 内容me.zzp.ar.d.PostgreSQLDialectme.zzp.ar.d.Hype...

Cobbage
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部