在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ WebGL 文本 使用字符紋理
WebGL 文本 HTML
WebGL 文本 Canvas 2D
WebGL 2D 圖像旋轉(zhuǎn)
WebGL 圖像處理(續(xù))
WebGL 2D 矩陣
WebGL 繪制多個(gè)東西
WebGL 圖像處理
WebGL 2D 圖像轉(zhuǎn)換
WebGL 3D 透視
WebGL 是如何工作的
WebGL 文本 紋理
WebGL 2D 圖像伸縮
WebGL 場(chǎng)景圖
WebGL 3D 攝像機(jī)
WebGL 文本 使用字符紋理
WebGL 正交 3D
WebGL 基本原理
WebGL - 更少的代碼,更多的樂(lè)趣
WebGL 著色器和 GLSL

WebGL 文本 使用字符紋理

在上一篇文章中我們復(fù)習(xí)了在 WebGL 場(chǎng)景中如何使用紋理繪制文本。技術(shù)是很常見(jiàn)的,對(duì)一些事物也是極重要的,例如在多人游戲中你想在一個(gè)頭像上放置一個(gè)名字。同時(shí)這個(gè)名字也不能影響它的完美性。

比方說(shuō)你想呈現(xiàn)大量的文本,這需要經(jīng)常改變 UI 之類的事物。前一篇文章給出的最后一個(gè)例子中,一個(gè)明顯的解決方案是給每個(gè)字母加紋理。我們來(lái)嘗試一下改變上一個(gè)例子。

var names = [
  "anna",   // 0
  "colin",  // 1
  "james",  // 2
  "danny",  // 3
  "kalin",  // 4
  "hiro",   // 5
  "eddie",  // 6
  "shu",// 7
  "brian",  // 8
  "tami",   // 9
  "rick",   // 10
  "gene",   // 11
  "natalie",// 12,
  "evan",   // 13,
  "sakura", // 14,
  "kai",// 15,
];

// create text textures, one for each letter
var textTextures = [
  "a",// 0
  "b",// 1
  "c",// 2
  "d",// 3
  "e",// 4
  "f",// 5
  "g",// 6
  "h",// 7
  "i",// 8
  "j",// 9
  "k",// 10
  "l",// 11
  "m",// 12,
  "n",// 13,
  "o",// 14,
  "p",// 14,
  "q",// 14,
  "r",// 14,
  "s",// 14,
  "t",// 14,
  "u",// 14,
  "v",// 14,
  "w",// 14,
  "x",// 14,
  "y",// 14,
  "z",// 14,
].map(function(name) {
  var textCanvas = makeTextCanvas(name, 10, 26);

相對(duì)于為每個(gè)名字呈現(xiàn)一個(gè)四元組,我們將為每個(gè)名字的每個(gè)字母呈現(xiàn)一個(gè)四元組。

// setup to draw the text.
// Because every letter uses the same attributes and the same progarm
// we only need to do this once.
gl.useProgram(textProgramInfo.program);
setBuffersAndAttributes(gl, textProgramInfo.attribSetters, textBufferInfo);

textPositions.forEach(function(pos, ndx) {
  var name = names[ndx];
  // for each leter
  for (var ii = 0; ii < name.length; ++ii) {
var letter = name.charCodeAt(ii);
var letterNdx = letter - "a".charCodeAt(0);
// select a letter texture
var tex = textTextures[letterNdx];

// use just the position of the 'F' for the text

// because pos is in view space that means it's a vector from the eye to
// some position. So translate along that vector back toward the eye some distance
var fromEye = normalize(pos);
var amountToMoveTowardEye = 150;  // because the F is 150 units long
var viewX = pos[0] - fromEye[0] * amountToMoveTowardEye;
var viewY = pos[1] - fromEye[1] * amountToMoveTowardEye;
var viewZ = pos[2] - fromEye[2] * amountToMoveTowardEye;
var desiredTextScale = -1 / gl.canvas.height;  // 1x1 pixels
var scale = viewZ * desiredTextScale;

var textMatrix = makeIdentity();
textMatrix = matrixMultiply(textMatrix, makeTranslation(ii, 0, 0));
textMatrix = matrixMultiply(textMatrix, makeScale(tex.width * scale, tex.height * scale, 1));
textMatrix = matrixMultiply(textMatrix, makeTranslation(viewX, viewY, viewZ));
textMatrix = matrixMultiply(textMatrix, projectionMatrix);

// set texture uniform
textUniforms.u_texture = tex.texture;
copyMatrix(textMatrix, textUniforms.u_matrix);
setUniforms(textProgramInfo.uniformSetters, textUniforms);

// Draw the text.
gl.drawElements(gl.TRIANGLES, textBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
  }
});

你可以看到它是如何工作的:

不幸的是它很慢。下面的例子:?jiǎn)为?dú)繪制 73 個(gè)四元組,還看不出來(lái)差別。我們計(jì)算 73 個(gè)矩陣和 292 個(gè)矩陣倍數(shù)。一個(gè)典型的 UI 可能有 1000 個(gè)字母要顯示。這是眾多工作可以得到一個(gè)合理的幀速率的方式。

解決這個(gè)問(wèn)題通常的方法是構(gòu)造一個(gè)紋理圖譜,其中包含所有的字母。我們討論給立方體的 6 面加紋理時(shí),復(fù)習(xí)了紋理圖譜。

下面的代碼構(gòu)造了字符的紋理圖譜。

function makeGlyphCanvas(ctx, maxWidthOfTexture, heightOfLetters, baseLine, padding, letters) {
  var rows = 1;  // number of rows of glyphs
  var x = 0; // x position in texture to draw next glyph
  var y = 0; // y position in texture to draw next glyph
  var glyphInfos = { // info for each glyph
  };

  // Go through each letter, measure it, remember its width and position
  for (var ii = 0; ii < letters.length; ++ii) {
var letter = letters[ii];
var t = ctx.measureText(letter);
// Will this letter fit on this row?
if (x + t.width + padding > maxWidthOfTexture) {
   // so move to the start of the next row
   x = 0;
   y += heightOfLetters;
   ++rows;
}
// Remember the data for this letter
glyphInfos[letter] = {
  x: x,
  y: y,
  width: t.width,
};
// advance to space for next letter.
x += t.width + padding;
  }

  // Now that we know the size we need set the size of the canvas
  // We have to save the canvas settings because changing the size
  // of a canvas resets all the settings
  var settings = saveProperties(ctx);
  ctx.canvas.width = (rows == 1) ? x : maxWidthOfTexture;
  ctx.canvas.height = rows * heightOfLetters;
  restoreProperties(settings, ctx);

  // Draw the letters into the canvas
  for (var ii = 0; ii < letters.length; ++ii) {
var letter = letters[ii];
var glyphInfo = glyphInfos[letter];
var t = ctx.fillText(letter, glyphInfo.x, glyphInfo.y + baseLine);
  }

  return glyphInfos;
}

現(xiàn)在我們?cè)囋嚳矗?/p>

var ctx = document.createElement("canvas").getContext("2d");
ctx.font = "20px sans-serif";
ctx.fillStyle = "white";
var maxTextureWidth = 256;
var letterHeight = 22;
var baseline = 16;
var padding = 1;
var letters = "0123456789.abcdefghijklmnopqrstuvwxyz";
var glyphInfos = makeGlyphCanvas(
ctx,
maxTextureWidth,
letterHeight,
baseline,
padding,
letters);

結(jié)果如下

現(xiàn)在,我們已經(jīng)創(chuàng)建了一個(gè)我們需要使用的字符紋理。看看效果怎樣,我們?yōu)槊總€(gè)字符建四個(gè)頂點(diǎn)。這些頂點(diǎn)將使用紋理坐標(biāo)來(lái)選擇特殊的字符。

給定一個(gè)字符串,來(lái)建立頂點(diǎn):

function makeVerticesForString(fontInfo, s) {
  var len = s.length;
  var numVertices = len * 6;
  var positions = new Float32Array(numVertices * 2);
  var texcoords = new Float32Array(numVertices * 2);
  var offset = 0;
  var x = 0;
  for (var ii = 0; ii < len; ++ii) {
var letter = s[ii];
var glyphInfo = fontInfo.glyphInfos[letter];
if (glyphInfo) {
  var x2 = x + glyphInfo.width;
  var u1 = glyphInfo.x / fontInfo.textureWidth;
  var v1 = (glyphInfo.y + fontInfo.letterHeight) / fontInfo.textureHeight;
  var u2 = (glyphInfo.x + glyphInfo.width) / fontInfo.textureWidth;
  var v2 = glyphInfo.y / fontInfo.textureHeight;

  // 6 vertices per letter
  positions[offset + 0] = x;
  positions[offset + 1] = 0;
  texcoords[offset + 0] = u1;
  texcoords[offset + 1] = v1;

  positions[offset + 2] = x2;
  positions[offset + 3] = 0;
  texcoords[offset + 2] = u2;
  texcoords[offset + 3] = v1;

  positions[offset + 4] = x;
  positions[offset + 5] = fontInfo.letterHeight;
  texcoords[offset + 4] = u1;
  texcoords[offset + 5] = v2;

  positions[offset + 6] = x;
  positions[offset + 7] = fontInfo.letterHeight;
  texcoords[offset + 6] = u1;
  texcoords[offset + 7] = v2;

  positions[offset + 8] = x2;
  positions[offset + 9] = 0;
  texcoords[offset + 8] = u2;
  texcoords[offset + 9] = v1;

  positions[offset + 10] = x2;
  positions[offset + 11] = fontInfo.letterHeight;
  texcoords[offset + 10] = u2;
  texcoords[offset + 11] = v2;

  x += glyphInfo.width;
  offset += 12;
} else {
  // we don't have this character so just advance
  x += fontInfo.spaceWidth;
}
  }

  // return ArrayBufferViews for the portion of the TypedArrays
  // that were actually used.
  return {
arrays: {
  position: new Float32Array(positions.buffer, 0, offset),
  texcoord: new Float32Array(texcoords.buffer, 0, offset),
},
numVertices: offset / 2,
  };
}

為了使用它,我們手動(dòng)創(chuàng)建一個(gè) bufferInfo。(如果你已經(jīng)不記得了,可以查看前面的文章:bufferInfo 是什么)。

// Maunally create a bufferInfo
var textBufferInfo = {
  attribs: {
a_position: { buffer: gl.createBuffer(), numComponents: 2, },
a_texcoord: { buffer: gl.createBuffer(), numComponents: 2, },
  },
  numElements: 0,
};

使用 bufferInfo 中的字符創(chuàng)建畫(huà)布的 fontInfo 和紋理:

var ctx = document.createElement("canvas").getContext("2d");
ctx.font = "20px sans-serif";
ctx.fillStyle = "white";
var maxTextureWidth = 256;
var letterHeight = 22;
var baseline = 16;
var padding = 1;
var letters = "0123456789.,abcdefghijklmnopqrstuvwxyz";
var glyphInfos = makeGlyphCanvas(
ctx,
maxTextureWidth,
letterHeight,
baseline,
padding,
letters);
var fontInfo = {
  glyphInfos: glyphInfos,
  letterHeight: letterHeight,
  baseline: baseline,
  spaceWidth: 5,
  textureWidth: ctx.canvas.width,
  textureHeight: ctx.canvas.height,
};

然后渲染我們將更新緩沖的文本。我們也可以構(gòu)成動(dòng)態(tài)的文本:

textPositions.forEach(function(pos, ndx) {

  var name = names[ndx];
  var s = name + ":" + pos[0].toFixed(0) + "," + pos[1].toFixed(0) + "," + pos[2].toFixed(0);
  var vertices = makeVerticesForString(fontInfo, s);

  // update the buffers
  textBufferInfo.attribs.a_position.numComponents = 2;
  gl.bindBuffer(gl.ARRAY_BUFFER, textBufferInfo.attribs.a_position.buffer);
  gl.bufferData(gl.ARRAY_BUFFER, vertices.arrays.position, gl.DYNAMIC_DRAW);
  gl.bindBuffer(gl.ARRAY_BUFFER, textBufferInfo.attribs.a_texcoord.buffer);
  gl.bufferData(gl.ARRAY_BUFFER, vertices.arrays.texcoord, gl.DYNAMIC_DRAW);

  setBuffersAndAttributes(gl, textProgramInfo.attribSetters, textBufferInfo);

  // use just the position of the 'F' for the text
  var textMatrix = makeIdentity();
  // because pos is in view space that means it's a vector from the eye to
  // some position. So translate along that vector back toward the eye some distance
  var fromEye = normalize(pos);
  var amountToMoveTowardEye = 150;  // because the F is 150 units long
  textMatrix = matrixMultiply(textMatrix, makeTranslation(
  pos[0] - fromEye[0] * amountToMoveTowardEye,
  pos[1] - fromEye[1] * amountToMoveTowardEye,
  pos[2] - fromEye[2] * amountToMoveTowardEye));
  textMatrix = matrixMultiply(textMatrix, projectionMatrix);

  // set texture uniform
  copyMatrix(textMatrix, textUniforms.u_matrix);
  setUniforms(textProgramInfo.uniformSetters, textUniforms);

  // Draw the text.
  gl.drawArrays(gl.TRIANGLES, 0, vertices.numVertices);
});

即:

這是使用字符紋理集的基本技術(shù)??梢蕴砑右恍┟黠@的東西或方式來(lái)改進(jìn)它。

  • 重用相同的數(shù)組。
    目前,每次被調(diào)用時(shí),makeVerticesForString 就會(huì)分配新的 32 位浮點(diǎn)型數(shù)組。這最終可能會(huì)導(dǎo)致垃圾收集出現(xiàn)問(wèn)題。重用相同的數(shù)組可能會(huì)更好。如果不是足夠大,你也放大數(shù)組,但是保留原來(lái)的大小。
  • 添加支持回車
    當(dāng)生成頂點(diǎn)時(shí),檢查 \n 是否存在從而實(shí)現(xiàn)換行。這將使文本分隔段落更容易。
  • 添加對(duì)各種格式的支持。
    如果你想文本居中,或調(diào)整你添加的一切文本的格式。
  • 添加對(duì)頂點(diǎn)顏色的支持。
    你可以為文本的每個(gè)字母添加不同的顏色。當(dāng)然你必須決定如何指定何時(shí)改變顏色。

這里不打算涉及的另一個(gè)大問(wèn)題是:紋理大小有限,但字體實(shí)際上是無(wú)限的。如果你想支持所有的 unicode,你就必須處理漢語(yǔ)、日語(yǔ)和阿拉伯語(yǔ)等其他所有語(yǔ)言,2015 年在 unicode 有超過(guò) 110000 個(gè)符號(hào)!你不可能在紋理中適配所有這些,也沒(méi)有足夠的空間供你這樣做。

操作系統(tǒng)和瀏覽器 GPU 加速處理這個(gè)問(wèn)題的方式是:通過(guò)使用一個(gè)字符紋理緩存實(shí)現(xiàn)。上面的實(shí)現(xiàn)他們是把紋理處理成紋理集,但他們?yōu)槊總€(gè) glpyh 布置一個(gè)固定大小的區(qū)域,保留紋理集中最近使用的符號(hào)。如果需要繪制一個(gè)字符,而這個(gè)字符不在紋理集中,他們就用他們需要的這個(gè)新的字符取代最近最少使用的一個(gè)。當(dāng)然如果他們即將取代的字符仍被有待繪制的四元組引用,他們需要繪制他們之前所取代的字符。

雖然我不推薦它,但是還有另一件事你可以做,將這項(xiàng)技術(shù)和以前的技術(shù)結(jié)合在一起。你可以直接渲染另一種紋理的符號(hào)。當(dāng)然 GPU 加速畫(huà)布已經(jīng)這樣做了,你可能沒(méi)有自己動(dòng)手的理由。

另一種在 WebGL 中繪制文本的方法實(shí)際上是使用了 3D 文本。在上面所有的例子中 “F” 是一個(gè) 3D 的字母。你已經(jīng)為每個(gè)字母都構(gòu)成了一個(gè)相應(yīng)的 3D 字符。3D 字母常見(jiàn)于標(biāo)題和電影標(biāo)志,此外的用處就少了。

我希望在 WebGL 這可以覆蓋文本。