組綁定到 Shader Uniform:GLSL 采樣器數(shù)組與紋理單元的完整實踐)
圖形學【免費下載鏈接】webgl-fundamentalsWebGL lessons that start with the basics項目地址https://gitcode.com/gh_mirrors/we/webgl-fundamentals點擊查看免費下載本文基于 webgl-fundamentals 倉庫的問答教程《How to bind an array of textures to a WebGL shader uniform?》展開。當你需要讓大量幾何體共享少量紋理、希望用頂點屬性vertex attribute為每個頂點/片元動態(tài)挑選紋理而不是為每張紋理手寫一個 uniform sampler 時本指南將給出可直接落地的 GLSL 聲明方式、JavaScript 側(cè)的gl.uniform1iv綁定方法以及完整的可運行示例。讀完你不僅能正確使用采樣器數(shù)組還會徹底理解紋理單元texture units在“紋理 → 采樣器”鏈路中的角色以及它與紋理圖集texture atlas方案的取舍。問題背景當每個頂點選一張紋理的需求撞上 GLSL 限制在常見的 WebGL 開發(fā)場景中我們可能有一批對象它們共享同一批例如 4 張紋理。最直覺的做法是每個對象手寫一個紋理采樣流程例如在片段著色器里寫死uniform sampler2D u_texture0; uniform sampler2D u_texture1; ...然后寫一堆switch/if分支去選擇用哪一張。但這樣的代碼很快會失控紋理數(shù)量一變著色器就要重寫、重新編譯分支邏輯和紋理索引完全寫死在 shader 里無法由數(shù)據(jù)驅(qū)動WebGL 的 GLSL 中沒有switch (index) { case ... }這類運行時分支語句無法用傳入一個索引、按索引取紋理這種直接思路。于是自然的想法是把該用哪張紋理作為一個頂點屬性vertex attribute傳給頂點著色器再把它作為索引在片段著色器中從一個紋理數(shù)組里取值。但 OpenGL Wiki 關(guān)于 Sampler 的說明給出了一條看似勸退的規(guī)則A variable of sampler can only be defined in one of two ways. It can be defined as a function parameter or as a uniform variable.uniform sampler2D texture1;這聽起來像是采樣器不能組成數(shù)組。實際上采樣器可以聲明為數(shù)組uniform sampler2D u_textures[4];只是WebGL 1GLSL ES 1.00不允許用運行時非編譯期常量表達式去動態(tài)索引采樣器數(shù)組。這正是本文要解決的核心矛盾既要用數(shù)組 索引的思路又必須滿足采樣器只能被常量表達式索引的約束。方案總覽采樣器數(shù)組 常量索引循環(huán) 逐單元綁定官方教程給出的解法可以拆成三個層次GLSL 側(cè)聲明uniform sampler2D u_textures[N];數(shù)組用#define定義數(shù)組大小GLSL 側(cè)的關(guān)鍵技巧不能直接texture2D(u_textures[ndx], uv)ndx是運行時值而是用編譯期可解析的循環(huán)索引遍歷整個數(shù)組再與傳入的運行時索引比較命中才累加JavaScript 側(cè)通過gl.getUniformLocation拿到數(shù)組 uniform 的位置再用gl.uniform1iv(textureLoc, [0, 1, 2, 3])一次性告訴 shader數(shù)組第 i 個元素對應第 i 號紋理單元隨后用gl.activeTexture(gl.TEXTURE0 i)把實際紋理綁定到對應單元。下面逐層拆解。第一步在片段著色器中聲明并使用采樣器數(shù)組教程中的片段著色器核心代碼如下#define numTextures 4 precision mediump float; varying float v_textureIndex; uniform sampler2D u_textures[numTextures]; vec4 getSampleFromArray(sampler2D textures[4], int ndx, vec2 uv) { vec4 color vec4(0); for (int i 0; i numTextures; i) { vec4 c texture2D(u_textures[i], uv); if (i ndx) { color c; } } return color; } void main() { gl_FragColor getSampleFromArray(u_textures, int(v_textureIndex), vec2(0.5, 0.5)); }幾個值得注意的細節(jié)#define numTextures 4是預處理宏它讓數(shù)組長度和循環(huán)次數(shù)兩處保持一致后續(xù)要改成 8 張紋理只需改這一處varying float v_textureIndex是頂點著色器插值傳入的紋理索引在main()中用int(v_textureIndex)轉(zhuǎn)成整數(shù)函數(shù)參數(shù)sampler2D textures[4]印證了 OpenGL Wiki 的說法——采樣器可以作為函數(shù)參數(shù)傳入規(guī)避動態(tài)索引的關(guān)鍵texture2D(u_textures[i], uv)中的下標i是for循環(huán)的循環(huán)索引。在 GLSL ES 1.00 中循環(huán)索引屬于常量索引表達式constant-index-expression的合法成員因此這種寫法是合法的而運行時變量ndx只參與if (i ndx)的比較從不直接作為采樣器數(shù)組下標——這就繞開了采樣器數(shù)組只能被常量表達式索引的限制效率上該寫法每次采樣會遍歷并采樣全部 4 張紋理color c只把命中項加進去。對本教程的演示場景沒有問題但若要追求極致性能可考慮后面介紹的紋理圖集方案。頂點著色器一側(cè)同樣簡單把a_textureIndex屬性直接透傳給varyingattribute vec4 a_position; attribute float a_textureIndex; varying float v_textureIndex; void main() { gl_Position a_position; v_textureIndex a_textureIndex; }完整代碼可見倉庫中的可運行示例 webgl-qna-how-to-bind-an-array-of-textures-to-a-webgl-shader-uniform--example-1.html。注意該示例在調(diào)用函數(shù)時寫的是int(v_textureIndex 0.5)——因為int()是截斷取整加 0.5 是常用的四舍五入小技巧避免插值后的浮點誤差比如 2.9999截斷成錯誤索引。第二步用 gl.uniform1iv 把采樣器數(shù)組映射到紋理單元光在著色器里聲明數(shù)組還不夠。采樣器本身不保存紋理數(shù)據(jù)它保存的是一個紋理單元編號整數(shù)。因此 JavaScript 側(cè)要做的是為采樣器數(shù)組整體賦值一組紋理單元編號。var textureLoc gl.getUniformLocation(program, u_textures); // Tell the shader to use texture units 0 to 3 gl.uniform1iv(textureLoc, [0, 1, 2, 3]);說明gl.getUniformLocation(program, u_textures)直接以數(shù)組名查詢位置是可行的得到的 location 指向整個數(shù)組倉庫示例 webgl-qna-how-to-bind-an-array-of-textures-to-a-webgl-shader-uniform--example-1.html 中寫的是u_textures[0]這也是一種常見寫法——查詢數(shù)組首元素的位置效果等價gl.uniform1iv是設(shè)置整型數(shù)組 uniform的 API它正是 WebGL Shaders and GLSL 教程 里列出的gl.uniform1iv(sampler2DUniformLoc, [v])用法對采樣器數(shù)組設(shè)置紋理單元時傳的數(shù)值就是紋理單元編號[0, 1, 2, 3]意味著u_textures[0]對應紋理單元 0u_textures[1]對應紋理單元 1以此類推。這里的下標順序必須與后面gl.activeTexture的綁定順序嚴格一致。第三步理解紋理單元——采樣器與真實紋理之間的橋梁為什么上面要強調(diào)紋理單元編號這需要理解 WebGL 的紋理單元機制。相關(guān)教程 WebGL Texture Units 給出了非常直觀的模型紋理單元本質(zhì)上是WebGL 全局狀態(tài)中的一個紋理引用數(shù)組如果 WebGL 用 JavaScript 實現(xiàn)其內(nèi)部狀態(tài)大致長這樣const gl { activeTextureUnit: 0, textureUnits: [ { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, }, { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, }, // ... 每個單元都有 TEXTURE_2D 和 TEXTURE_CUBE_MAP 兩個綁定點 ]; }而gl.activeTexture、gl.bindTexture的作用相當于gl.activeTexture function(unit) { gl.activeTextureUnit unit - gl.TEXTURE0; // 轉(zhuǎn)換為從 0 開始的下標 }; gl.bindTexture function(target, texture) { const textureUnit gl.textureUnits[gl.activeTextureUnit]; textureUnit[target] texture; };因此綁定紋理分兩步先用gl.activeTexture(gl.TEXTURE0 n)選中第 n 號單元再用gl.bindTexture(gl.TEXTURE_2D, tex)把紋理掛到該單元的TEXTURE_2D綁定點上。之后 shader 里采樣器值紋理單元編號就是去第 n 號單元里取我綁定的那張紋理。這正是 WebGL Using 2 or More Textures 教程 反復強調(diào)的兩條配套動作gl.uniform1i(u_image0Location, 0); // texture unit 0 gl.uniform1i(u_image1Location, 1); // texture unit 1以及gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, textures[0]); gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, textures[1]);由于gl.TEXTURE0、gl.TEXTURE1……這些常量是連續(xù)的可以寫成循環(huán)for (var ii 0; ii 4; ii) { gl.activeTexture(gl.TEXTURE0 ii); gl.bindTexture(gl.TEXTURE_2D, textures[ii]); }完整可運行示例四個單像素紋理拼一個方塊倉庫中給出了可直接運行的完整示例 webgl-qna-how-to-bind-an-array-of-textures-to-a-webgl-shader-uniform--example-1.html。它在 300×300 的畫布上繪制一個矩形六個頂點分別攜帶紋理索引 0、1、2、3、0、1從而把四個 1×1 的純色紋理按三角形分布渲染出來。整體流程如下。1. 編譯著色器程序var program webglUtils.createProgramFromScripts( gl, [vshader, fshader], [a_position, a_textureIndex]); gl.useProgram(program);這里用到了倉庫自帶的工具函數(shù)createProgramFromScripts定義于 webgl/resources/webgl-utils.js。從源碼可見它會先通過createShaderFromScript按[VERTEX_SHADER, FRAGMENT_SHADER]的順序編譯兩個script標簽里的著色器再調(diào)用createProgram完成 attach、link 與useProgram傳入的第三個參數(shù)[a_position, a_textureIndex]會被逐個執(zhí)行g(shù)l.bindAttribLocation(program, ndx, attrib)即按數(shù)組下標 0、1 綁定屬性位置省去手動查詢getAttribLocation的樣板代碼。2. 設(shè)置采樣器數(shù)組對應的紋理單元var textureLoc gl.getUniformLocation(program, u_textures[0]); // Tell the shader to use texture units 0 to 3 gl.uniform1iv(textureLoc, [0, 1, 2, 3]);3. 準備頂點數(shù)據(jù)位置 紋理索引屬性var positions [ 1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, ]; var textureIndex [ 0, 1, 2, 3, 0, 1, ];紋理索引使用Uint8Array上傳既省內(nèi)存相比 Float32Array 每個值只占 1 字節(jié)又天然是整數(shù)配合片元著色器里的int(v_textureIndex 0.5)取整邏輯非常合適var vertBuffer gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(textureIndex), gl.STATIC_DRAW); gl.enableVertexAttribArray(1); gl.vertexAttribPointer(1, 1, gl.UNSIGNED_BYTE, false, 0, 0);注意gl.vertexAttribPointer的type參數(shù)為gl.UNSIGNED_BYTE對應attribute float a_textureIndex會自動把無符號字節(jié)規(guī)范化為 0.0~1.0 的浮點數(shù)之后由 varying 插值進入片段著色器。4. 創(chuàng)建四張紋理并綁定到單元 0~3var colors [ [0, 0, 255, 255], // 藍 [0, 255, 0, 255], // 綠 [255, 0, 0, 255], // 紅 [0, 255, 255, 255], // 青 ]; colors.forEach(function(color, ndx) { gl.activeTexture(gl.TEXTURE0 ndx); var tex gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(color)); });這里的關(guān)鍵點gl.activeTexture(gl.TEXTURE0 ndx)與前面gl.uniform1iv(textureLoc, [0, 1, 2, 3])形成一一對應——采樣器數(shù)組第 ndx 個元素使用的紋理單元編號是 ndx而紋理單元 ndx 里綁定的正是第 ndx 張純色紋理。若兩者順序錯位畫面顏色就會張冠李戴。5. 繪制gl.drawArrays(gl.TRIANGLES, 0, positions.length / 2);由于使用的是紋理單元編號整數(shù)而不是紋理對象本身同一張紋理完全可以同時掛到多個采樣器上這也是許多對象共享少量紋理場景的關(guān)鍵能力。為什么不能在 WebGL 1 里動態(tài)索引采樣器數(shù)組這是本方案最容易被誤解的技術(shù)點。GLSL ES 1.00WebGL 1 的著色器語言規(guī)定采樣器數(shù)組只能用常量索引表達式constant-index-expression訪問即編譯期就能確定的下標。像texture2D(u_textures[ndx], uv)ndx來自 varying 或 uniform 的運行時值這類寫法在多數(shù)實現(xiàn)上會直接導致編譯失敗或未定義行為。教程給出的遍歷 比較寫法正是為滿足這一約束而設(shè)計的for (int i 0; i numTextures; i) { vec4 c texture2D(u_textures[i], uv); // i 是循環(huán)索引屬于常量索引表達式 if (i ndx) { color c; // ndx 只參與比較不參與數(shù)組索引 } }代價是每片元會對全部 N 張紋理采樣。對于紋理數(shù)量少、片元數(shù)量大的場景這會顯著放大采樣開銷——這也是教程作者在文末主動拋出紋理圖集替代方案的原因。與紋理圖集texture atlas方案的取舍原問答在 PS 中明確詢問如果紋理圖集更高效或更簡單歡迎分享經(jīng)驗。倉庫教程 WebGL 3D Textures 恰好給出了完整對比其核心結(jié)論是對一個模型的不同面用不同紋理這類需求紋理圖集通常是更優(yōu)解。紋理圖集是把多張圖片拼進同一張大紋理通過給每張圖分配不同的紋理坐標UV區(qū)間來選中對應區(qū)域。比如把 6 張圖片放進一張紋理再用類似下面的坐標選取不同子圖// select the top left image 0 , 0 , 0 , 0.5, 0.25, 0 , 0 , 0.5, 0.25, 0.5, 0.25, 0 ,該教程明確指出它的三大優(yōu)勢只有 1 張紋理要加載shader 保持簡單只需引用 1 個采樣器只需要 1 次 draw call 就能繪制整個形狀而不是每個紋理一次這正是幾乎所有高性能應用尤其是游戲采用的做法。對應的示例可見 webgl-3d-textures-texture-atlas.html。兩種方案的選擇建議紋理圖集適合同一幾何體/模型的不同部位用不同貼圖一次 draw call、單采樣器性能與代碼復雜度都最優(yōu)采樣器數(shù)組適合大量對象共享一小撮紋理、每對象/每頂點動態(tài)指定紋理尤其是紋理來源是運行時動態(tài)創(chuàng)建例如程序化生成、離屏渲染結(jié)果、多張 canvas/視頻源而難以拼進圖集的情況。此時用gl.uniform1iv一次性綁定整個數(shù)組配合頂點屬性做索引代碼組織比寫 N 個獨立 sampler 干凈得多。數(shù)量上限與設(shè)備兼容性提醒紋理單元不是無限的。教程 WebGL Texture Units 給出了重要的容量檢查方法const maxTextureUnits gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS); const maxVertexShaderTextureUnits gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS); const maxFragmentShaderTextureUnits gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);要點WebGL 規(guī)范要求實現(xiàn)至少支持 8 個紋理單元MAX_COMBINED_TEXTURE_IMAGE_UNITS頂點著色器與片段著色器的單元上限可能不同且共用組合上限例如組合上限為 8、頂點著色器占用了 2 個單元時片段著色器只剩 6 個可用MAX_VERTEX_TEXTURE_IMAGE_UNITS甚至可能返回 0——即部分設(shè)備完全不支持在頂點著色器里采樣紋理該情況雖然罕見但存在。若計劃在頂點著色器中使用紋理務必先查詢該參數(shù)并做降級處理。對本文的片段著色器方案而言只要u_textures的數(shù)組長度不超過MAX_TEXTURE_IMAGE_UNITS即可。若紋理數(shù)量較大建議在初始化時讀取該參數(shù)并動態(tài)確定數(shù)組長度。小結(jié)三個環(huán)節(jié)的對應關(guān)系把本文的完整鏈路串起來核心是三層一一對應環(huán)節(jié)代碼作用GLSL 聲明uniform sampler2D u_textures[4];聲明采樣器數(shù)組長度用#define宏控制JS 綁定單元編號gl.uniform1iv(loc, [0, 1, 2, 3]);告訴 shader 數(shù)組第 i 個采樣器用第 i 號紋理單元JS 綁定真實紋理gl.activeTexture(gl.TEXTURE0 i); gl.bindTexture(...)把實際紋理掛到對應紋理單元三者任一環(huán)節(jié)的索引錯位都會導致取樣錯亂。此外用頂點屬性攜帶紋理索引、在片元著色器里用常量循環(huán)索引采樣 運行時索引比較繞過動態(tài)索引限制是本方案能在 WebGL 1 下成立的關(guān)鍵。更深入地了解紋理與紋理單元可繼續(xù)閱讀倉庫內(nèi)的 WebGL 3D Textures、WebGL Using 2 or More Textures、WebGL Texture Units 以及 WebGL Shaders and GLSL 系列教程。贊分享圖形學【免費下載鏈接】webgl-fundamentalsWebGL lessons that start with the basics項目地址https://gitcode.com/gh_mirrors/we/webgl-fundamentals點擊查看免費下載相關(guān)推薦WebGL 紋理單元Texture Units完全指南從全局數(shù)組到著色器采樣器綁定WebGL 紋理單元Texture Units完全指南從全局數(shù)組到著色器采樣器綁定 紋理單元Texture Units是 WebGL 中連接“紋理數(shù)據(jù)圖形學WebGL 多紋理2 個及以上紋理實戰(zhàn)紋理單元與單次繪制調(diào)用中的多紋理采樣WebGL 多紋理2 個及以上紋理實戰(zhàn)紋理單元與單次繪制調(diào)用中的多紋理采樣 本指南基于 webgl fundamentals 倉庫的 WebGL Usin圖形學WebGL 紋理貼圖實戰(zhàn)指南從著色器采樣到紋理圖集webgl-fundamentals 系列WebGL 紋理貼圖實戰(zhàn)指南從著色器采樣到紋理圖集webgl fundamentals 系列 本指南以 webgl fundamentals https:圖形學創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考