日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時(shí)間:2025/4/9 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

在 OpenGL ES 2.0 上實(shí)現(xiàn)視差貼圖(Parallax Mapping)

視差貼圖

最近一直在研究如何在我的 iPad 2(只支持 OpenGL ES 2.0, 不支持 3.0) 上實(shí)現(xiàn) 視差貼圖(Parallax Mapping) 和 位移貼圖(Displacement Mapping).

經(jīng)過一番研究, 搜索閱讀了不少文章, 終于確定, OpenGL ES 2.0 可以支持 視差貼圖, 不過暫時(shí)還沒什么好辦法支持 位移貼圖.

因?yàn)榫臀夷壳八私獾奈灰瀑N圖, 有這么兩種方法來實(shí)現(xiàn), 一種是用 Tessellation 來提供多面數(shù)網(wǎng)格, 另一種是在頂點(diǎn)著色器中對(duì)高度圖進(jìn)行紋理采樣來計(jì)算對(duì)應(yīng)的頂點(diǎn)偏移量.

第一種方法就不必想了, 因?yàn)槟壳耙苿?dòng)設(shè)備的 OpenGL ES 2.0/3.0 都不支持(貌似 DX11 和 OpenGL 4.0 才支持), 而 OpenGL ES 3.0 衍生自 OpenGL 3.3.

第二種方法目前看起來只能在 OpenGL ES 3.0 上使用, 請(qǐng)參考這篇文檔Jim's GameDev Blog: 置換貼圖 Displacement Mapping. 不過沒辦法在 OpenGL ES 2.0 上使用, 因?yàn)樗笤陧旤c(diǎn)著色器中進(jìn)行紋理采樣, 而這個(gè)特性恰恰是 2.0 不支持, 3.0 支持的.

我們可以看看 Jim 在 3.0 設(shè)備上實(shí)現(xiàn)位移貼圖的效果:

原始圖:

使用位移貼圖后的效果:

好了, 現(xiàn)在在我們的 2.0 設(shè)備上實(shí)現(xiàn)我們的 視差貼圖 吧, 先看看效果:

使用不同參數(shù)的效果:

  • height_scale = -0.015

  • height_scale = -0.055

  • height_scale = -0.095

你可以靈活調(diào)整這些參數(shù):

  • lightPos: 光源位置
  • viewPos: 眼睛位置
  • height_scale: 高度圖取樣值縮放比例

看看這個(gè)視頻 video:

實(shí)現(xiàn)細(xì)節(jié)

關(guān)鍵技術(shù)點(diǎn)就這么幾個(gè):

手動(dòng)構(gòu)造正切空間 TBN 變換矩陣

如果你使用比較大的引擎, 比如 Unity, 它會(huì)幫你計(jì)算好法線,切線和次法線, 如果自己開發(fā), 沒有使用這些引擎, 那么很可能就需要自己手動(dòng)構(gòu)造了.

目前我發(fā)現(xiàn)有 3 種根據(jù)法線手動(dòng)計(jì)算 TBN 的近似算法, 其中一種既能在 OpenGL ES 2.0 的頂點(diǎn)著色器內(nèi)使用, 也能在片段著色器內(nèi)使用, 就是我們下面要提到的這種, 主要原理就是已知了法線 Normal, 要據(jù)此求出對(duì)應(yīng)的切線 Tangent 和 次法線 Binormal, 因?yàn)樗鼈儍蓛纱怪? 而且 TB 跟 UV 對(duì)齊, 因此很容易求得 T, 再根據(jù) T 和 N 求得 B, 算法代碼如下:

// 根據(jù)法線 N 計(jì)算 T,Bvec3 tangent; vec3 binormal; // 使用頂點(diǎn)屬性法線,并歸一化vec3 Normal = normalize(normal*2.0-1.0);// 通過叉積來計(jì)算夾角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);// 構(gòu)造出 TBN 矩陣mat3 TBN = mat3(T, B, N);

得到 TBN 矩陣后, 既可以把其他向量從其他空間變換進(jìn)正切空間來, 也可以把正切空間的向量變換到其他空間去. 通常意義的做法是:

  • TBN 用于正向變換
  • TBN 的逆陣用于反向變換

不過在 OpenGL 中, 你把矩陣放在向量左邊乘, 就是正向變換, 它會(huì)按列矩陣處理; 你把矩陣放在向量右邊乘就是反向變換, 它會(huì)按行矩陣處理. 這樣就不需要再進(jìn)行矩陣求逆的操作了.

視差映射函數(shù)

視差貼圖 的本質(zhì)就是根據(jù)高度紋理圖的不同高度以及視線向量的坐標(biāo), 來實(shí)時(shí)計(jì)算紋理坐標(biāo)在視線下的偏移, 并以此作為新的紋理坐標(biāo)來從紋理貼圖中進(jìn)行取樣.

代碼如下:

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

在此基礎(chǔ)上提出的 視差遮掩 可以提供更好的視覺效果, 具體原理在代碼注釋中.

光照模型

最后就是一個(gè)非常簡(jiǎn)單的光照模型, 先從原始紋理中取樣, 以此為基礎(chǔ), 縮小10倍作為環(huán)境光, 根據(jù)前面計(jì)算得到的切線來計(jì)算光線攝入方向, 再結(jié)合法線可以計(jì)算出漫射光和反射光, 最后把這些光線混合就得到最終的光照顏色值了.

這個(gè)光照模型的好處是簡(jiǎn)單易用, 不需要另外設(shè)置過多參數(shù), 壞處就是不太靈活, 實(shí)際使用時(shí)可以把參數(shù)設(shè)置為可變, 或者直接換成其他光照模型也可以(具體的參數(shù)值就需要自己調(diào)整了).

因?yàn)榇a自己計(jì)算構(gòu)造了 TBN 變換矩陣, 所以這段 shader 代碼具有很好的移植性, 可以輕松地把它用在其他地方.

完整代碼

代碼如下:

function setup()displayMode(OVERLAY)print("試驗(yàn) 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-- 試驗(yàn) 視差貼圖 中的例子 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;// 根據(jù)法線 N 計(jì)算 T,Bvec3 tangent; vec3 binormal; // 使用頂點(diǎn)屬性法線,并歸一化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 根據(jù)原始紋理(img1)生成的.

你也可以下載它們直接使用:

img1:

img2:

img3:

參考

38 視差貼圖
視差貼圖(Parallax Mapping)與陡峭視差貼圖(Steep Palallax Mapping)
Parallax Occlusion Mapping in GLSL
Jim's GameDev Blog: 置換貼圖 Displacement Mapping

轉(zhuǎn)載于:https://www.cnblogs.com/freeblues/p/5748935.html

總結(jié)

以上是生活随笔為你收集整理的在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。