簡體   English   中英

WebGL/OpenGL 文本標簽動畫實例化形狀

[英]WebGL/OpenGL text labeling animated instanced shapes

我正在使用實例化在平面中渲染可變數量的圓圈,這些圓圈具有可變的大小、顏色和位置。 我希望達到 10k-100k 圈/標簽的數量級。

    in float instanceSize;
    in vec3 instanceColor;
    in vec2 instanceCenter;

支持instanceCenter屬性的緩沖區每幀都會更改,為圓圈設置動畫,但其余部分大部分是靜態的。

我每個圓有一個四邊形,我正在片段着色器中創建圓。

現在,我正在研究用字體大小與圓圈大小成正比的標簽來標記形狀,以圓圈為中心,隨圓圈移動。 從我讀到的最有效的方法是使用位圖紋理圖集或帶符號距離場紋理圖集為每個字母使用帶有四邊形的字形紋理。 我看到的示例似乎在 Javascript 方面做了很多工作,然后對每個字符串使用繪制調用,例如: https : //webgl2fundamentals.org/webgl/lessons/webgl-text-glyphs.html

有沒有一種方法可以通過一次繪制調用(使用實例化或其他方式?)渲染文本,同時每幀重用Float32Array支持instanceCenter 似乎需要在着色器中完成更多工作,但我不確定如何做。 因為每個標簽都有可變數量的字形,所以我不確定如何將單個instanceCenter與單個標簽相關聯。

拋開這些不談,更基本上,我想知道如何在某一點上居中文本?

任何幫助表示贊賞

在我的腦海中,您可以將消息存儲在紋理中,並為每個實例添加消息 texcoord 和長度。 然后,您可以計算在頂點着色器中繪制消息所需的矩形的大小,並將其用於居中。

attribute float msgLength;
attribute vec2 msgTexCoord;
...

widthOfQuad = max(minSizeForCircle, msgLength * glphyWidth)

在片段着色器中從紋理讀取消息並使用它查找字形(基於圖像或基於 SDF)。

varying vec2 v_msgTexCoord;  // passed in from vertex shader
varying float v_msgLength;   // passed in from vertex shader
varying vec2 uv;             // uv that goes 0 to 1 across quad

float glyphIndex = texture2D(
     messageTexture,
     v_msgTexCoord + vec2(uv.x * v_msgLength / widthOfMessageTexture)).r;

// now convert glyphIndex to tex coords to look up glyph in glyph texture

glyphUV = (up to you)

textColor = texture2D(glyphTexture, 
   glyphUV + glyphSize * vec2(fract(uv.x * v_msgLength), uv.v) / glyphTextureSize);

或類似的東西。 我不知道它會有多慢

 async function main() { const gl = document.querySelector('canvas').getContext('webgl'); twgl.addExtensionsToContext(gl); function convertToGlyphIndex(c) { c = c.toUpperCase(); if (c >= 'A' && c <= 'Z') { return c.charCodeAt(0) - 0x41; } else if (c >= '0' && c <= '9') { return c.charCodeAt(0) - 0x30 + 26; } else { return 255; } } const messages = [ 'pinapple', 'grape', 'banana', 'strawberry', ]; const glyphImg = await loadImage("https://webglfundamentals.org/webgl/resources/8x8-font.png"); const glyphTex = twgl.createTexture(gl, { src: glyphImg, minMag: gl.NEAREST, }); // being lazy about size, making them all the same. const glyphsAcross = 8; // too lazy to pack these in a texture in a more compact way // so just put one message per row const longestMsg = Math.max(...messages.map(m => m.length)); const messageData = new Uint8Array(longestMsg * messages.length * 4); messages.forEach((message, row) => { for (let i = 0; i < message.length; ++i) { const c = convertToGlyphIndex(message[i]); const offset = (row * longestMsg + i) * 4; const u = c % glyphsAcross; const v = c / glyphsAcross | 0; messageData[offset + 0] = u; messageData[offset + 1] = v; } }); const messageTex = twgl.createTexture(gl, { src: messageData, width: longestMsg, height: messages.length, minMag: gl.NEAREST, }); const vs = ` attribute vec4 position; // a centered quad (-1 + 1) attribute vec2 texcoord; attribute float messageLength; // instanced attribute vec4 center; // instanced attribute vec2 messageUV; // instanced uniform vec2 glyphDrawSize; varying vec2 v_texcoord; varying vec2 v_messageUV; varying float v_messageLength; void main() { vec2 size = vec2(messageLength * glyphDrawSize.x, glyphDrawSize.y); gl_Position = position * vec4(size, 1, 0) + center; v_texcoord = texcoord; v_messageUV = messageUV; v_messageLength = messageLength; } `; const fs = ` precision highp float; varying vec2 v_texcoord; varying vec2 v_messageUV; varying float v_messageLength; uniform sampler2D messageTex; uniform vec2 messageTexSize; uniform sampler2D glyphTex; uniform vec2 glyphTexSize; uniform vec2 glyphSize; void main() { vec2 msgUV = v_messageUV + vec2(v_texcoord.x * v_messageLength / messageTexSize.x, 0); vec2 glyphOffset = texture2D(messageTex, msgUV).xy * 255.0; vec2 glyphsAcrossDown = glyphTexSize / glyphSize; vec2 glyphUVOffset = glyphOffset / glyphsAcrossDown; vec2 glyphUV = fract(v_texcoord * vec2(v_messageLength, 1)) * glyphSize / glyphTexSize; vec4 glyphColor = texture2D(glyphTex, glyphUVOffset + glyphUV); // do some math here for a circle // TBD if (glyphColor.a < 0.1) discard; gl_FragColor = glyphColor; } `; const prgInfo = twgl.createProgramInfo(gl, [vs, fs]); const bufferInfo = twgl.createBufferInfoFromArrays(gl, { position: { numComponents: 2, data: [ -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, ], }, texcoord: [ 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, ], center: { numComponents: 2, divisor: 1, data: [ -0.4, 0.1, -0.3, -0.5, 0.6, 0, 0.1, 0.5, ], }, messageLength: { numComponents: 1, divisor: 1, data: messages.map(m => m.length), }, messageUV: { numComponents: 2, divisor: 1, data: messages.map((m, i) => [0, i / messages.length]).flat(), }, }); gl.clearColor(0, 0, 1, 1); gl.clear(gl.COLOR_BUFFER_BIT); gl.useProgram(prgInfo.program); twgl.setBuffersAndAttributes(gl, prgInfo, bufferInfo); twgl.setUniformsAndBindTextures(prgInfo, { glyphDrawSize: [16 / gl.canvas.width, 16 / gl.canvas.height], messageTex, messageTexSize: [longestMsg, messages.length], glyphTex, glyphTexSize: [glyphImg.width, glyphImg.height], glyphSize: [8, 8], }); // ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, messages.length); gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, messages.length); } function loadImage(url) { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = "anonymous"; img.onerror = reject; img.onload = () => resolve(img); img.src = url; }); } main();
 <canvas></canvas> <script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

請注意,如果字形大小不同,它似乎會變得非常慢,至少在我的頭頂上,在繪制四邊形時找到每個字形的唯一方法是循環遍歷消息中的所有字形每個像素。

另一方面,您可以構建一個類似於文章的字形網格,對於每條消息,對於該消息中的每個字形,添加用於從紋理查找偏移量或矩陣的每個頂點消息 ID 或消息 uv。 通過這種方式,您可以獨立移動每條消息,但在一次繪制調用中全部發生。 這將允許非等寬字形。 作為在紋理中存儲位置或矩陣的示例,請參閱有關蒙皮的這篇文章 它將骨骼矩陣存儲在紋理中。

 async function main() { const gl = document.querySelector('canvas').getContext('webgl'); const ext = gl.getExtension('OES_texture_float'); if (!ext) { alert('need OES_texture_float'); return; } twgl.addExtensionsToContext(gl); function convertToGlyphIndex(c) { c = c.toUpperCase(); if (c >= 'A' && c <= 'Z') { return c.charCodeAt(0) - 0x41; } else if (c >= '0' && c <= '9') { return c.charCodeAt(0) - 0x30 + 26; } else { return 255; } } const messages = [ 'pinapple', 'grape', 'banana', 'strawberry', ]; const glyphImg = await loadImage("https://webglfundamentals.org/webgl/resources/8x8-font.png"); const glyphTex = twgl.createTexture(gl, { src: glyphImg, minMag: gl.NEAREST, }); // being lazy about size, making them all the same. const glyphsAcross = 8; const glyphsDown = 5; const glyphWidth = glyphImg.width / glyphsAcross; const glyphHeight = glyphImg.height / glyphsDown; const glyphUWidth = glyphWidth / glyphImg.width; const glyphVHeight = glyphHeight / glyphImg.height; // too lazy to pack these in a texture in a more compact way // so just put one message per row const positions = []; const texcoords = []; const messageIds = []; const matrixData = new Float32Array(messages.length * 16); const msgMatrices = []; const quadPositions = [ -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, ]; const quadTexcoords = [ 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, ]; messages.forEach((message, id) => { msgMatrices.push(matrixData.subarray(id * 16, (id + 1) * 16)); for (let i = 0; i < message.length; ++i) { const c = convertToGlyphIndex(message[i]); const u = (c % glyphsAcross) * glyphUWidth; const v = (c / glyphsAcross | 0) * glyphVHeight; for (let j = 0; j < 6; ++j) { const offset = j * 2; positions.push( quadPositions[offset ] * 0.5 + i - message.length / 2, quadPositions[offset + 1] * 0.5, ); texcoords.push( u + quadTexcoords[offset ] * glyphUWidth, v + quadTexcoords[offset + 1] * glyphVHeight, ); messageIds.push(id); } } }); const matrixTex = twgl.createTexture(gl, { src: matrixData, type: gl.FLOAT, width: 4, height: messages.length, minMag: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, }); const vs = ` attribute vec4 position; attribute vec2 texcoord; attribute float messageId; uniform sampler2D matrixTex; uniform vec2 matrixTexSize; uniform mat4 viewProjection; varying vec2 v_texcoord; void main() { vec2 uv = (vec2(0, messageId) + 0.5) / matrixTexSize; mat4 model = mat4( texture2D(matrixTex, uv), texture2D(matrixTex, uv + vec2(1.0 / matrixTexSize.x, 0)), texture2D(matrixTex, uv + vec2(2.0 / matrixTexSize.x, 0)), texture2D(matrixTex, uv + vec2(3.0 / matrixTexSize.x, 0))); gl_Position = viewProjection * model * position; v_texcoord = texcoord; } `; const fs = ` precision highp float; varying vec2 v_texcoord; uniform sampler2D glyphTex; void main() { vec4 glyphColor = texture2D(glyphTex, v_texcoord); // do some math here for a circle // TBD if (glyphColor.a < 0.1) discard; gl_FragColor = glyphColor; } `; const prgInfo = twgl.createProgramInfo(gl, [vs, fs]); const bufferInfo = twgl.createBufferInfoFromArrays(gl, { position: { numComponents: 2, data: positions, }, texcoord: texcoords, messageId: { numComponents: 1, data: messageIds }, }); gl.clearColor(0, 0, 1, 1); gl.clear(gl.COLOR_BUFFER_BIT); gl.useProgram(prgInfo.program); const m4 = twgl.m4; const viewProjection = m4.ortho(0, gl.canvas.width, 0, gl.canvas.height, -1, 1); msgMatrices.forEach((mat, i) => { m4.translation([80 + i * 30, 30 + i * 25, 0], mat); m4.scale(mat, [16, 16, 1], mat) }); // update the matrices gl.bindTexture(gl.TEXTURE_2D, matrixTex); gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 4, messages.length, gl.RGBA, gl.FLOAT, matrixData); twgl.setBuffersAndAttributes(gl, prgInfo, bufferInfo); twgl.setUniformsAndBindTextures(prgInfo, { viewProjection, matrixTex, matrixTexSize: [4, messages.length], glyphTex, }); gl.drawArrays(gl.TRIANGLES, 0, positions.length / 2); } function loadImage(url) { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = "anonymous"; img.onerror = reject; img.onload = () => resolve(img); img.src = url; }); } main();
 <canvas></canvas> <script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>

另見https://stackoverflow.com/a/54720138/128511

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM