draw text in 3d space gmod

This post is a continuation of many manufactures about WebGL. The last one was well-nigh using Canvas 2d for rendering text over a WebGL canvas. If you lot haven't read information technology you might want to check that out earlier continuing.

In the last article we went over how to apply a 2d canvas to depict text over your WebGL scene. That technique works and is easy to do but it has a limitation that the text tin not exist obscured by other 3d objects. To practise that we actually need to draw the text in WebGL.

The simplest way to practise that is to make textures with text in them. You lot could for instance go into Photoshop or some other paint plan and depict an epitome with some text in it.

Then make some aeroplane geometry and display information technology. This is actually how some games I've worked on did all their text. For example Locoroco simply had about 270 strings. Information technology was localized into 17 languages. We had an Excel canvas with all the languages and a script that would launch Photoshop and generate a texture, ane for each message in each language.

Of course you can likewise generate the textures at runtime. Since WebGL is in the browser again we tin can rely on the Canvas 2D API to assistance generate our textures.

Starting with the examples from the previous commodity let's add a role to fill up a 2nd canvass with some text

          var textCtx = document.createElement("sail").getContext("2d");  // Puts text in center of canvas. function makeTextCanvas(text, width, superlative) {   textCtx.canvas.width  = width;   textCtx.canvas.height = acme;   textCtx.font = "20px monospace";   textCtx.textAlign = "middle";   textCtx.textBaseline = "eye";   textCtx.fillStyle = "black";   textCtx.clearRect(0, 0, textCtx.canvas.width, textCtx.canvas.meridian);   textCtx.fillText(text, width / 2, summit / two);   render textCtx.canvas; }                  

Now that we need to draw ii different things in WebGL, the 'F' and our text, I'm going to switch over to using some helper functions as described in a previous commodity. If information technology's non clear what programInfo, bufferInfo, etc are see that article.

And then, let's create the 'F' and a unit quad.

          // Create data for 'F' var fBufferInfo = primitives.create3DFBufferInfo(gl); // Create a unit quad for the 'text' var textBufferInfo = primitives.createPlaneBufferInfo(gl, 1, 1, 1, 1, m4.xRotation(Math.PI / ii));                  

A unit quad is a quad (square) that's i unit big. This one is centered over the origin. createPlaneBufferInfo creates a plane in the xz plane. We pass in a matrix to rotate it and give united states an xy plane unit quad.

Next create two shaders

          // setup GLSL programs var fProgramInfo = createProgramInfo(gl, ["vertex-shader-3d", "fragment-shader-3d"]); var textProgramInfo = createProgramInfo(gl, ["text-vertex-shader", "text-fragment-shader"]);                  

And create our text texture

          // create text texture. var textCanvas = makeTextCanvas("Hullo!", 100, 26); var textWidth  = textCanvas.width; var textHeight = textCanvas.height; var textTex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, textTex); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textCanvas); // make sure we tin can render it even if it's not a power of 2 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);                  

Setup uniforms for both the 'F' and text

          var fUniforms = {   u_matrix: m4.identity(), };  var textUniforms = {   u_matrix: m4.identity(),   u_texture: textTex, };                  

Now when we compute the matrices for the F we save off the F's view matrix

          var fViewMatrix = m4.interpret(viewMatrix,     translation[0] + 20 * spread, translation[1] + yy * spread, translation[2]); fViewMatrix = m4.xRotate(fViewMatrix, rotation[0]); fViewMatrix = m4.yRotate(fViewMatrix, rotation[1] + yy * xx * 0.2); fViewMatrix = m4.zRotate(fViewMatrix, rotation[2] + now + (yy * three + xx) * 0.1); fViewMatrix = m4.calibration(fViewMatrix, scale[0], scale[1], calibration[2]); fViewMatrix = m4.translate(fViewMatrix, -50, -75, 0);                  

Drawing the F looks like this

          gl.useProgram(fProgramInfo.program);  webglUtils.setBuffersAndAttributes(gl, fProgramInfo, fBufferInfo);  fUniforms.u_matrix = m4.multiply(projectionMatrix, fViewMatrix);  webglUtils.setUniforms(fProgramInfo, fUniforms);  // Draw the geometry. gl.drawElements(gl.TRIANGLES, fBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);                  

For the text nosotros just demand the position of the origin of the F. We also need to scale our unit quad to friction match the dimensions of the texture. Finally we need to multiply by the projection matrix.

          // employ just the view position of the 'F' for the text var textMatrix = m4.interpret(projectionMatrix,     fViewMatrix[12], fViewMatrix[thirteen], fViewMatrix[14]); // calibration the quad to the size we need it. textMatrix = m4.scale(textMatrix, textWidth, textHeight, 1);                  

And then render the text

          // setup to draw the text. gl.useProgram(textProgramInfo.program);  webglUtils.setBuffersAndAttributes(gl, textProgramInfo, textBufferInfo);  m4.copy(textMatrix, textUniforms.u_matrix); webglUtils.setUniforms(textProgramInfo, textUniforms);  // Draw the text. gl.drawElements(gl.TRIANGLES, textBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);                  

So here it is

You'll notice that sometimes parts of our text cover up parts of our Fs. That'south because we're cartoon a quad. The default color of the canvas is transparent black (0,0,0,0) and we're drawing that color in the quad. We could instead blend our pixels.

          gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);                  

This makes it take the source pixel (the color from our fragment shader) and combine it with the dest pixel (the color in the canvas) according to the alloy function. Nosotros've fix the blend function to SRC_ALPHA for source and ONE_MINUS_SRC_ALPHA for dest.

          effect = dest * (1 - src_alpha) + src * src_alpha                  

so for example if the dest is green 0,i,0,1 and the source is blood-red 1,0,0,1 nosotros'd have

          src = [1, 0, 0, 1] dst = [0, 1, 0, 1] src_alpha = src[3]  // this is ane result = dst * (i - src_alpha) + src * src_alpha  // which is the same as result = dst * 0 + src * 1  // which is the aforementioned as result = src                  

For the parts of the texture with transparent black 0,0,0,0

          src = [0, 0, 0, 0] dst = [0, 1, 0, 1] src_alpha = src[3]  // this is 0 result = dst * (1 - src_alpha) + src * src_alpha  // which is the aforementioned every bit issue = dst * 1 + src * 0  // which is the same as result = dst                  

Here'southward the result with blending enabled.

Yous can encounter information technology'south better but it's nevertheless non perfect. If you wait close you lot'll sometimes see this issue

What's happening? We're currently cartoon an F then its text, then the side by side F then its text repeated. We all the same have a depth buffer then when we draw the text for an F, even though blending made some pixels stay the groundwork color the depth buffer was notwithstanding updated. When nosotros describe the next F if parts of that F are behind those pixels from some previously drawn text they won't exist drawn.

We've just run into one of the most difficult problems of rendering 3D on a GPU. Transparency has problems.

The most common solution for pretty much all transparent rendering is to draw all the opaque stuff first, so after, draw all the transparent stuff sorted by z altitude with the depth buffer testing on merely depth buffer updating off.

Permit's beginning separate drawing of the opaque stuff (the Fs) from the transparent stuff (the text). First we'll declare something to remember the text positions.

          var textPositions = [];                  

And in the loop for rendering the Fs we'll recall those positions

          var fViewMatrix = m4.interpret(viewMatrix,     translation[0] + 20 * spread, translation[1] + yy * spread, translation[ii]); fViewMatrix = m4.xRotate(fViewMatrix, rotation[0]); fViewMatrix = m4.yRotate(fViewMatrix, rotation[1] + yy * 20 * 0.2); fViewMatrix = m4.zRotate(fViewMatrix, rotation[2] + now + (yy * 3 + xx) * 0.ane); fViewMatrix = m4.scale(fViewMatrix, calibration[0], scale[1], scale[ii]); fViewMatrix = m4.interpret(fViewMatrix, -l, -75, 0); +// Save the f's view position +textPositions.push([fViewMatrix[12], fViewMatrix[thirteen], fViewMatrix[14]]);                  

Before we draw the 'F's we'll disable blending and turn on writing to the depth buffer

          gl.disable(gl.BLEND); gl.depthMask(true);                  

For cartoon the text we'll turn on blending and plow off writing to the depth buffer.

          gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); gl.depthMask(imitation);                  

And then describe text at all the positions we saved

          +// setup to draw the text. +gl.useProgram(textProgramInfo.program); + +webglUtils.setBuffersAndAttributes(gl, textProgramInfo, textBufferInfo);  +textPositions.forEach(function(pos) {   // draw the text    // utilize just the view position of the 'F' for the text *  var textMatrix = m4.translate(projectionMatrix, pos[0], pos[1], pos[ii]);   // calibration the quad to the size we need it.   textMatrix = m4.calibration(textMatrix, textWidth, textHeight, one);    m4.copy(textMatrix, textUniforms.u_matrix);   webglUtils.setUniforms(textProgramInfo, textUniforms);    // Depict the text.   gl.drawElements(gl.TRIANGLES, textBufferInfo.numElements, gl.UNSIGNED_SHORT, 0); +});                  

Note nosotros moved setting the current program and the attributes outside the loop since we're drawing the aforementioned matter multiple times at that place's no reason to set those for each iteration.

And now information technology more often than not works

Detect we didn't sort like I mentioned above. In this case since we're cartoon mostly opaque text there'due south probably going to exist no noticeable divergence if we sort so I'll save that for some other commodity.

Another issue is the text is intersecting its own 'F'. At that place really isn't a specific solution for that. If you were making an MMO and wanted the text of each player to ever announced you lot might attempt to make the text appear in a higher place the caput. Just translate it +Y some number of units, enough to make certain it was always above the actor.

You lot can also move it forward toward the camera. Let's do that here just for the hell of it. Because 'pos' is in view space that ways it'south relative to the eye (which is at 0,0,0 in view space). So if nosotros normalize it we go a unit vector pointing from the eye to that point which we can and then multiply by some amount to motility the text a specific number of units toward or away from the eye.

          +// because pos is in view space that means it's a vector from the eye to +// some position. So translate forth that vector back toward the centre some altitude +var fromEye = m4.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 textMatrix = m4.translate(projectionMatrix, viewX, viewY, viewZ);  *var textMatrix = m4.translate(projectionMatrix, viewX, viewY, viewZ); // scale the quad to the size we need information technology. textMatrix = m4.scale(textMatrix, textWidth, textHeight, one);                  

Here'south that.

You still might notice an issue with the edges of the messages.

The event hither is the Canvas second API produces only premultiplied alpha values. When nosotros upload the contents of the canvass to a texture WebGL tries to un-premultiply the values but it can't do this perfectly considering premultiplied blastoff is lossy.

To fix that let's tell WebGL not to un-premultiply

          gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);                  

This tells WebGL to supply premultiplied blastoff values to gl.texImage2D and gl.texSubImage2D. If the data passed to gl.texImage2D is already premultiplied as it is for Canvas 2D information then WebGL can simply laissez passer it through.

Nosotros also need to modify the blending function

          -gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); +gl.blendFunc(gl.1, gl.ONE_MINUS_SRC_ALPHA);                  

The old one multiplied the src color by its blastoff. That'southward what SRC_ALPHA means. Merely at present our texture'southward information has already been multiplied by its alpha. That'due south what premultiplied means. And so we don't demand the GPU to do the multiplication. Setting it to 1 means multiply by 1.

The edges are gone now.

What if y'all want to keep the text a fixed size simply nevertheless sort correctly? Well, if you remember from the perspective commodity our perspective matrix is going to scale our object by 1 / -Z to make it get smaller in the distance. So, nosotros tin just scale by -Z times some desired-scale to compensate.

          ... // considering pos is in view infinite that means information technology'south a vector from the eye to // some position. And then translate forth that vector back toward the centre 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[one] * amountToMoveTowardEye; var viewZ = pos[ii] - fromEye[2] * amountToMoveTowardEye; +var desiredTextScale = -i / gl.canvas.elevation;  // 1x1 pixels +var scale = viewZ * desiredTextScale;  var textMatrix = m4.translate(projectionMatrix, viewX, viewY, viewZ); // calibration the quad to the size we demand it. *textMatrix = m4.scale(textMatrix, textWidth * calibration, textHeight * scale, i); ...                  

If you want to depict different text at each F you should brand a new texture for each F and just update the text uniforms for that F.

          // create text textures, i for each F var textTextures = [   "anna",   // 0   "colin",  // one   "james",  // ii   "danny",  // 3   "kalin",  // 4   "hiro",   // 5   "eddie",  // 6   "shu",    // 7   "brian",  // 8   "tami",   // nine   "rick",   // ten   "gene",   // 11   "natalie",// 12,   "evan",   // 13,   "sakura", // 14,   "kai",    // 15, ].map(function(name) {   var textCanvas = makeTextCanvas(name, 100, 26);   var textWidth  = textCanvas.width;   var textHeight = textCanvas.pinnacle;   var textTex = gl.createTexture();   gl.bindTexture(gl.TEXTURE_2D, textTex);   gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textCanvas);   // make sure we can render it fifty-fifty if it's not a power of 2   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);   render {     texture: textTex,     width: textWidth,     height: textHeight,   }; });                  

Then at render time select a texture

          *textPositions.forEach(function(pos, ndx) {    +// select a texture   +var tex = textTextures[ndx];    // scale the F to the size nosotros need it.   var textMatrix = m4.translate(projectionMatrix, viewX, viewY, viewZ);   // scale the quad to the size we need it.   *textMatrix = m4.scale(textMatrix, tex.width * calibration, tex.pinnacle * scale, one);                  

and set the uniform for the texture before cartoon

                      *textUniforms.u_texture = tex.texture;                  

We've been using black to draw the text into the canvas. Information technology would be more useful if we rendered the text in white. Then we could multiply the text by a color and get in any color we want.

First we'll change the text shader to multiply by a color

          varying vec2 v_texcoord;  uniform sampler2D u_texture; +uniform vec4 u_color;  void main() { *   gl_FragColor = texture2D(u_texture, v_texcoord) * u_color; }                  

And when we depict the text into the canvass use white

          textCtx.fillStyle = "white";                  

And then we'll make some colors

          // colors, one for each F var colors = [   [0.0, 0.0, 0.0, i], // 0   [1.0, 0.0, 0.0, 1], // 1   [0.0, 1.0, 0.0, i], // two   [ane.0, one.0, 0.0, i], // 3   [0.0, 0.0, 1.0, ane], // 4   [1.0, 0.0, 1.0, 1], // 5   [0.0, 1.0, 1.0, one], // 6   [0.5, 0.v, 0.5, 1], // vii   [0.5, 0.0, 0.0, i], // 8   [0.0, 0.0, 0.0, 1], // 9   [0.5, five.0, 0.0, 1], // 10   [0.0, 5.0, 0.0, 1], // 11   [0.5, 0.0, five.0, 1], // 12,   [0.0, 0.0, 5.0, 1], // 13,   [0.v, 5.0, v.0, 1], // 14,   [0.0, 5.0, 5.0, 1], // 15, ];                  

At draw time we select a colour

          // set colour uniform textUniforms.u_color = colors[ndx];                  

Colors

This technique is actually the technique most browsers use when they are GPU accelerated. They generate textures with your HTML content and all the various styles you've applied and as long as that content doesn't alter they can just render the texture again when you scroll etc.. Of course if you're updating things all the time then this technique might get a little bit dull because re-generating the textures and re-uploading them to the GPU is a relatively dull operation.

In the next article we'll become over a technique that is probably better for cases where things update often.

Scaling Text without pixelation

You might observe in the examples earlier we started using a consistent size the text gets very pixelated as it gets close to the camera. How exercise we fix that?

Well, honestly it'south non very common to scale 2D text in 3D. Expect at most games or 3D editors and y'all'll encounter the text is almost always ane consistent size regardless of how far or close to the camera it is. In fact often that text might be drawn in 2nd instead of 3D so that fifty-fifty if someone or something is behind something else similar a teammate backside a wall y'all tin can however read the text.

If y'all do happen to want to scale second text in 3D I don't know of whatsoever easy options. A few off the superlative of my head

  • Make different sizes of textures with fonts at different resolutions. Yous and then use the college resolution textures as the text gets larger. This is chosen LODing (using different Levels of Detail).
  • Another would be to render the textures with the exact correct size of text every frame. That would probable be actually slow.
  • Yet another would be to brand second text out of geometry. In other words instead of drawing text into a texture make text from lots and lots of triangles. That works only it has other issues in that small-scale text will non render well and big text you'll start to see the triangles.
  • One more is to utilise very special shaders that render curves. That's very cool just way beyond what I tin can explain here.

norrisusts1952.blogspot.com

Source: https://webglfundamentals.org/webgl/lessons/webgl-text-texture.html

0 Response to "draw text in 3d space gmod"

Enregistrer un commentaire

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel