大家好,我是前端西瓜哥,今天我們來(lái)了解 WebGL 的紋理對(duì)象(Texture)
(資料圖片僅供參考)
紋理對(duì)象,是將像素(texels)以數(shù)組方式傳給 GPU 的對(duì)象,常見(jiàn)場(chǎng)景是貼圖,就是將圖片的數(shù)據(jù)應(yīng)用到 3D 物體上。
紋理對(duì)象創(chuàng)建和綁定先創(chuàng)建紋理對(duì)象:
const texture = gl.createTexture(); // 創(chuàng)建紋理對(duì)象
然后綁定到紋理單元:
gl.bindTexture(gl.TEXTURE_2D, texture); // 將紋理對(duì)象綁定上去
填充方式紋理是要貼到畫(huà)布的某個(gè)區(qū)域上的,并不一定剛好設(shè)置一下填充方式。
紋理比繪制區(qū)域大,就要做縮放;紋理比繪制區(qū)域小,就要做放大;紋理沒(méi)能完全填充繪制區(qū)域,就要在水平和垂直方向進(jìn)行填充。
這些場(chǎng)景都需要對(duì)應(yīng)設(shè)置不同的策略。
// 縮小和放大都都使用 “最近點(diǎn)采樣”gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
紋理單元WebGL 支持設(shè)置多個(gè)紋理單元(Texture Unit),即我們可以將多個(gè)圖片放到多個(gè)單元中,然后進(jìn)行切換。
就好像手里拿著不同的蓋章,想印哪種圖案就掏出哪個(gè)蓋上去。
紋理單元是有上限的,至少要支持 8 個(gè),主流瀏覽器一般支持 16 個(gè)。
具體支持幾個(gè),可通過(guò)下面代碼獲得。
gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) // 通常是 16
默認(rèn)使用 0 號(hào)紋理單元,可通過(guò)下面這一行代碼來(lái)切換紋理單元:
gl.activeTexture(gl.TEXTURE1); // 開(kāi)啟 1 號(hào)紋理單元
注意這個(gè)要在將紋理對(duì)象綁定紋理單元之前執(zhí)行。
最后我們需要設(shè)置一下我們的紋理采樣器選擇使用哪個(gè)紋理單元:
gl.uniform1i(u_Sampler, 0); // 開(kāi)啟 0 號(hào)紋理對(duì)象
不主動(dòng)調(diào)用這個(gè)方法,默認(rèn)會(huì)使用 0 號(hào)紋理單元。
切換紋理單元是有一定的性能代價(jià)的,不建議你在短時(shí)間內(nèi)不斷地切換紋理單元。簡(jiǎn)單的渲染場(chǎng)景可忽略不計(jì)。
純色紋理畫(huà)個(gè)純純的紅色紋理。
// 紅色const data = new Uint8Array([ 255, 0, 0]);gl.texImage2D( gl.TEXTURE_2D, // 紋理目標(biāo),這里是二維紋理 0, // 細(xì)節(jié)級(jí)別,0 表示最高級(jí)別 gl.RGB, // 紋理內(nèi)部格式,還支持其他的比如 gl.RGBA、LUMINANCE(流明) 1, // 寬(寬高的單位為像素,且為 2 的 n 次冪) 1, // 高 0, // 是否描邊。必須為 0(但 opengl 支持) gl.RGB, // 源圖像數(shù)據(jù)格式 gl.UNSIGNED_BYTE, // 紋素(單個(gè)像素)數(shù)據(jù)類(lèi)型 data // 數(shù)據(jù)數(shù)組,一個(gè)個(gè)像素點(diǎn));
主要注意的是,gl.texImage2D()方法支持函數(shù)重載,有多種傳入的參數(shù)的方式,注意分辨。具體看 官方文檔。
這里選擇使用 gl.RGB 格式,設(shè)置了一個(gè)(255, 0, 0)的紅色顏色值。
最后我們成功畫(huà)出一個(gè)純紅色塊。
完整代碼:
/** @type {HTMLCanvasElement} */const canvas = document.querySelector("canvas");const gl = canvas.getContext("webgl");const vertexShaderSrc = `attribute vec4 a_Position;attribute vec2 a_TexCoord;varying vec2 v_TexCoord;void main() { gl_Position = a_Position; v_TexCoord = a_TexCoord;}`;const fragmentShaderSrc = `precision highp float;uniform sampler2D u_Sampler;varying vec2 v_TexCoord;void main() { gl_FragColor = texture2D(u_Sampler, v_TexCoord);}`;// 創(chuàng)建程序?qū)ο骳reateProgram(gl);// 頂點(diǎn)坐標(biāo),紋理坐標(biāo)const verticesTexCoords = new Float32Array([ // 左上點(diǎn)。 // 左邊兩個(gè)是頂點(diǎn);右邊兩個(gè)是紋理 -0.5, 0.5, 0.0, 1, // 左下 -0.5, -0.5, 0.0, 0.0, // 右上 0.5, 0.5, 1, 1, // 右下 0.5, -0.5, 1, 0.0,]);const FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;// 創(chuàng)建緩存對(duì)象const verticesTexBuffer = gl.createBuffer();// 綁定緩存對(duì)象到上下文gl.bindBuffer(gl.ARRAY_BUFFER, verticesTexBuffer);// 向緩存區(qū)寫(xiě)入數(shù)據(jù)gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);// 獲取 a_Position 變量地址const a_Position = gl.getAttribLocation(gl.program, "a_Position");// 將緩沖區(qū)對(duì)象分配給 a_Position 變量gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);// 允許訪問(wèn)緩存區(qū)gl.enableVertexAttribArray(a_Position);// 傳入紋理坐標(biāo)位置信息const a_TexCoord = gl.getAttribLocation(gl.program, "a_TexCoord");gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);gl.enableVertexAttribArray(a_TexCoord);/***** 紋理對(duì)象 *****/const texture = gl.createTexture(); // 創(chuàng)建紋理對(duì)象const u_Sampler = gl.getUniformLocation(gl.program, "u_Sampler"); // 獲取 u_Sampler 地址// 記載圖片gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 翻轉(zhuǎn)紋路圖像的 y 軸gl.activeTexture(gl.TEXTURE0); // 開(kāi)啟 0 號(hào)紋理單元gl.bindTexture(gl.TEXTURE_2D, texture); // 將我們的紋理對(duì)象綁定上去// 配置紋理參數(shù)gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);// 【----關(guān)鍵代碼---】配置紋理圖像const data = new Uint8Array([255, 0, 0, 0, 255, 255, 0, 255, 0, 0, 255, 0]);gl.texImage2D( gl.TEXTURE_2D, // 紋理目標(biāo) 0, // 細(xì)節(jié)級(jí)別 gl.RGB, // 紋理內(nèi)部格式 1, 1, 0, gl.RGB, // 源圖像數(shù)據(jù)格式 gl.UNSIGNED_BYTE, // 紋素?cái)?shù)據(jù)類(lèi)型 data // 數(shù)據(jù));gl.uniform1i(u_Sampler, 0); // 開(kāi)啟 0 號(hào)紋理對(duì)象/****** 繪制 ******/// 清空畫(huà)布,并指定顏色gl.clearColor(0, 0, 0, 1);gl.clear(gl.COLOR_BUFFER_BIT);// 繪制矩形,這里提供了 4 個(gè)點(diǎn)gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);/**** 封裝的方法 ****/function createProgram(gl) { /**** 渲染器生成處理 ****/ // 創(chuàng)建頂點(diǎn)渲染器 const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexShaderSrc); gl.compileShader(vertexShader); // 創(chuàng)建片元渲染器 const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentShaderSrc); gl.compileShader(fragmentShader); // 程序?qū)ο? const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); gl.program = program;}
線上 demo:
https://codesandbox.io/s/1hvp4x?file=/index.js。
多個(gè)色塊紋理也可以同時(shí)設(shè)置多個(gè)色塊。
const data = new Uint8Array([ 255, 0, 0, 255, // 紅色 255, 255, 0, 255, // 黃色 0, 0, 255, 255, // 藍(lán)色 0, 255, 0, 255, // 綠色]);gl.texImage2D( gl.TEXTURE_2D, // 紋理目標(biāo) 0, // 細(xì)節(jié)級(jí)別 gl.RGBA, // 紋理內(nèi)部格式 2, 2, 0, gl.RGBA, // 源圖像數(shù)據(jù)格式 gl.UNSIGNED_BYTE, // 紋素?cái)?shù)據(jù)類(lèi)型 data // 數(shù)據(jù));
創(chuàng)建了 2x2 4個(gè)像素大小的紋理,并制定了這個(gè) 4 個(gè)像素點(diǎn)的顏色,然后被放大繪制到指定區(qū)域上。
線上演示 demo:
https://codesandbox.io/s/7436cs?file=/index.js。
圖片紋理圖片紋理,需要加載玩圖片,將圖片對(duì)象綁定到紋理對(duì)象上。
// 將紋理圖像分配給紋理對(duì)象gl.texImage2D( gl.TEXTURE_2D, 0, // 細(xì)節(jié)級(jí)別 gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img // Image 實(shí)例);
結(jié)尾紋理對(duì)象是很常用的一個(gè)對(duì)象,用于指定區(qū)域要填充的像素。
常見(jiàn)的是加載圖片,把圖片貼到三維的一個(gè)面上。也可以自己指定像素值。
標(biāo)簽: