自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一起學(xué) WebGL:繪制圖片

開發(fā) 前端
紋理映射會(huì)根據(jù)紋理圖像,將光柵化后的每個(gè)片元(像素點(diǎn))設(shè)置對(duì)應(yīng)顏色值。這些像素也稱為 紋素(Texels, Texture Elements)。

大家好,我是前端西瓜哥。之前講解了如何用 WebGL 繪制紅色三角形,今天西瓜哥帶大家來學(xué)習(xí)如何將圖片繪制到畫布上的技術(shù):紋理映射(texture mapping)。

紋理映射會(huì)根據(jù)紋理圖像,將光柵化后的每個(gè)片元(像素點(diǎn))設(shè)置對(duì)應(yīng)顏色值。這些像素也稱為 紋素(texels, texture elements)。

紋理坐標(biāo)

紋理圖像的坐標(biāo)系統(tǒng)是二維的,為和世界坐標(biāo)的 x、y 區(qū)分,WebGL 對(duì)應(yīng)使用 s、t 來表示。

目前紋理坐標(biāo)更常用的命名是 uv。因?yàn)闅v史原因,WebGL 還是用的 st。

圖片

和世界坐標(biāo)系類似,寬高使用的是一個(gè)比例值,即真實(shí)像素位置除以寬高后得到的比例。

著色器

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);
}
`;

相比繪制單色三角形,我們?cè)陧旤c(diǎn)著色器加了 a_TexCoord,記錄頂點(diǎn)對(duì)應(yīng)的紋理坐標(biāo),因?yàn)榧y理坐標(biāo)只有兩個(gè)維度,所以用的 vec2 屬性。

并命名一個(gè) v_TexCoord 的 varying 變量,用于將 a_TexCoord 的值傳遞給片元著色器。

片元著色器,聲明了一個(gè)接收同名 v_TexCoord 變量 接收傳過來的紋理坐標(biāo)。

u_Sampler 是 sampler2D 類型,是一個(gè)二維紋理采樣器,指定著色器提取顏色的紋理對(duì)象。texture2D(u_Sampler, v_TexCoord) 表示從 u_Sampler 紋理采樣器中的某個(gè)位置中取出顏色。

傳入頂點(diǎn)數(shù)據(jù)

將頂點(diǎn)位置和紋理位置對(duì)應(yīng)好,放在一個(gè)緩沖區(qū)中,并設(shè)置讀取規(guī)則。

先讀第一個(gè)點(diǎn)的位置,然后是第一個(gè)點(diǎn)對(duì)應(yīng)的紋理坐標(biāo)。然后第二個(gè)點(diǎn)...

// 頂點(diǎn)坐標(biāo),紋理坐標(biāo)
const verticesTexCoords = new Float32Array([
  // 左上點(diǎn)。左邊兩個(gè)是頂點(diǎn),右邊兩個(gè)是紋理
  -0.5, 0.5, 0.0, 1.0,
  // 左下
  -0.5, -0.5, 0.0, 0.0,
  // 右上
  0.5, 0.5, 1.0, 1.0,
  // 右下
  0.5, -0.5, 1.0, 0.0,
]);
const FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;

// 創(chuàng)建緩存對(duì)象
const verticesTexBuffer = gl.createBuffer();
// 綁定緩存對(duì)象到上下文
gl.bindBuffer(gl.ARRAY_BUFFER, verticesTexBuffer);
// 向緩存區(qū)寫入數(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);
// 允許訪問緩存區(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ì)象與圖片綁定

/***** 紋理對(duì)象 *****/
const texture = gl.createTexture(); // 創(chuàng)建紋理對(duì)象
const u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler'); // 獲取 u_Sampler 地址

const img = new Image();
img.onload = () => {
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 翻轉(zhuǎn)紋理圖像的 y 軸
  gl.activeTexture(gl.TEXTURE0); // 開啟 0 號(hào)紋理單元
  gl.bindTexture(gl.TEXTURE_2D, texture); // 將我們的紋理對(duì)象綁定到 gl.TEXTURE_2D,類似綁定緩沖區(qū)對(duì)象

  // 配置紋理參數(shù)
  // 這里表示在 “繪制范圍小于紋理尺寸” 時(shí),使用 “加權(quán)平均” 算法縮小
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  
  // 將紋理圖像分配給紋理對(duì)象
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);

  // 使用 0 號(hào)紋理單元
  gl.uniform1i(u_Sampler, 0);

  /****** 繪制 ******/
  // 清空畫布,并指定顏色
  gl.clearColor(0, 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  // 繪制矩形,這里提供了 4 個(gè)點(diǎn)
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
};

img.src = './fe_watermelon.jpg';

創(chuàng)建紋理對(duì)象,然后在圖片加載完之后配置到紋理對(duì)象上。

WebGL 下有多個(gè)紋理紋理單元(比如 gl.TEXTURE0、gl.TEXTURE5 之類),至少有 8 個(gè)。這些單元可以保存多個(gè)我們創(chuàng)建好的紋理圖片,在需要的時(shí)候進(jìn)行切換。

激活一個(gè)紋理單元:

gl.activeTexture(gl.TEXTURE0);

激活后,我們用 gl.TEXTURE_2D 來訪問這個(gè)紋理圖像,進(jìn)行紋理綁定和參數(shù)配置。

這里我們需要反轉(zhuǎn)紋理圖像的 y 軸線,因?yàn)閳D片和紋理坐標(biāo)系不一樣。我實(shí)在是蚌不住了。

gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 翻轉(zhuǎn)紋理圖像的 y 軸

圖片

gl.texImage2D 用于將紋理圖像分配給紋理對(duì)象。

gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);

gl.RGB 表示紋素的格式為 RGB,此外還有 gl.RGBA、gl.LUMINANCE(流明) 等。gl.UNSIGNED_BYTE 表示紋理的數(shù)據(jù)類型。

將紋理單元綁定到 u_Sampler 變量上。

gl.uniform1i(u_Sampler, 0);

最后就是調(diào)用 el.drawArrays 方法進(jìn)行繪制。

圖片的注意事項(xiàng)

關(guān)于圖片,有幾點(diǎn)需要注意。

首先是 圖片不要跨域,因?yàn)榘踩拗?,Canvas 是不能將跨域的圖片繪制上去的,會(huì)報(bào)錯(cuò)。

然后是 圖片的尺寸需要是 2 的冪次方,比如 16、32、64、128、256、512。

尺寸不對(duì)的圖片需要留白補(bǔ)全到 2 的冪次方,然后在設(shè)置紋理坐標(biāo)時(shí)指定對(duì)應(yīng)真正寬高比例。

完整源碼

/** @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)建頂點(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;

// 頂點(diǎn)坐標(biāo),紋理坐標(biāo)
const verticesTexCoords = new Float32Array([
  // 左上點(diǎn)。左邊兩個(gè)是頂點(diǎn),右邊兩個(gè)是紋理
  -0.5, 0.5, 0.0, 1.0,
  // 左下
  -0.5, -0.5, 0.0, 0.0,
  // 右上
  0.5, 0.5, 1.0, 1.0,
  // 右下
  0.5, -0.5, 1.0, 0.0,
]);
const FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;

// 創(chuàng)建緩存對(duì)象
const verticesTexBuffer = gl.createBuffer();
// 綁定緩存對(duì)象到上下文
gl.bindBuffer(gl.ARRAY_BUFFER, verticesTexBuffer);
// 向緩存區(qū)寫入數(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);
// 允許訪問緩存區(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 地址

// 記載圖片
const img = new Image();
img.onload = () => {
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 翻轉(zhuǎn)紋理圖像的 y 軸
  gl.activeTexture(gl.TEXTURE0); // 開啟 0 號(hào)紋理單元
  gl.bindTexture(gl.TEXTURE_2D, texture); // 將我們的材質(zhì)對(duì)象綁定上去

  // 配置紋理參數(shù)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  // 配置紋理圖像
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);
 // 綁定 0 號(hào)紋理單元
  gl.uniform1i(u_Sampler, 0);

  /****** 繪制 ******/
  // 清空畫布,并指定顏色
  gl.clearColor(0, 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  // 繪制矩形,這里提供了 4 個(gè)點(diǎn)
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
};

img.src = './fe_watermelon.jpg';

繪制結(jié)果:

圖片

填充方式

補(bǔ)充一下設(shè)置圖片的幾種方式。

gl.texParameteri(target, pname, param);

上面表示,在 pname 場景下,使用 param 策略。比如

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

表示在 “繪制范圍小于紋理尺寸”(gl.TEXTURE_MIN_FILTER) 場景下,使用 “加權(quán)平均”(gl.LINEAR) 算法進(jìn)行縮小。

參數(shù) pname 有下面幾個(gè)幾個(gè)值可選擇:

  • gl.TEXTURE_MAG_FILTER:紋理放大。對(duì)應(yīng)場景為紋理尺寸小于要繪制區(qū)域,需要將紋理放大。默認(rèn)值為 gl.LINEAR
  • gl.TEXTURE_MIN_FILTER:紋理縮小。默認(rèn)值為 gl.NEAREST_MIPMAP_LINEAR。
  • gl.TEXTURE_WRAP_S:紋理水平填充。默認(rèn)為 gl.REPEAT。
  • gl.TEXTURE_WRAP_T:紋理垂直填充。默認(rèn)為 gl.REPEAT。

參數(shù) param 的一些可選值:

  • gl.LINEAR:使用 “加權(quán)平均” 縮放;
  • gl.NEAREST:使用 “曼哈頓距離” 縮放;
  • gl.REPEAT:重復(fù)平鋪;
  • gl.MIRRORED_REPEAT:鏡像重復(fù)平鋪:
  • gl.CLAMP_TO_EDGE:使用紋理圖像的邊緣進(jìn)行延伸填充;

看個(gè)實(shí)例,將繪制區(qū)域設(shè)置為圖片的 2.5 倍,并設(shè)置填充方式。

// 頂點(diǎn)坐標(biāo),紋理坐標(biāo)
const verticesTexCoords = new Float32Array([
  // 左上點(diǎn)。左邊兩個(gè)是頂點(diǎn),右邊兩個(gè)是紋理
  -0.5, 0.5, 0.0, 2.5,
  // 左下
  -0.5, -0.5, 0.0, 0.0,
  // 右上
  0.5, 0.5, 2.5, 2.5,
  // 右下
  0.5, -0.5, 2.5, 0.0,
]);

// ...
// 配置紋理參數(shù)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// 邊緣像素平鋪
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

得到了一個(gè)很有意思的結(jié)果:

圖片

責(zé)任編輯:姜華 來源: 前端西瓜哥
相關(guān)推薦

2023-04-12 07:46:24

JavaScriptWebGL

2023-05-31 20:10:03

WebGL繪制立方體

2023-04-11 07:48:32

WebGLCanvas

2023-04-17 09:01:01

WebGL繪制三角形

2023-04-26 07:42:16

WebGL圖元的類型

2023-05-04 08:48:42

WebGL復(fù)合矩陣

2023-06-26 15:14:19

WebGL紋理對(duì)象學(xué)習(xí)

2023-03-29 07:31:09

WebGL坐標(biāo)系

2023-04-13 07:45:15

WebGL片元著色器

2023-05-17 08:28:55

2023-04-27 08:27:29

WebGL變形矩陣

2023-03-02 07:44:39

pixijsWebGL

2023-02-22 09:27:31

CanvasWebGL

2023-05-08 07:29:48

WebGL視圖矩陣

2022-11-29 16:35:02

Tetris鴻蒙

2022-12-02 14:20:09

Tetris鴻蒙

2022-11-14 17:01:34

游戲開發(fā)畫布功能

2023-03-30 09:32:27

2009-10-23 16:43:01

VB.NET繪制圖形

2011-06-30 15:09:37

QT 繪制 圖形
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)