Three.js and Twgl.js

I have an application that requires me to write a vertex and fragment shader for Twgl.js (for text rendering). I’m not interested in creating a shader material. I want to know if I create shaders for Twgl.js will still be able to use Three.js native materials and geometries?

No, you won’t be able to. TWGL.js uses different attribute names than Three.JS, and a different structure altogether.

const positionLoc = gl.getAttribLocation(program, "a_position");
const normalLoc = gl.getAttribLocation(program, "a_normal");
const texcoordLoc = gl.getAttribLocation(program, "a_texcoord");

There are too many differences in naming conventions, and you’d have to do a lot of manual work to “hook up” the Three.js objects to update TWGL.js objects. You should just stick with one library, they weren’t made to be used in unison.

But if you want to write your own shader code, you can still do so with Three.js!

1 Like

I really appreciate your quick and insightful response. I need to display text (numeric identifiers, hundred of thousands of them) and using TextGeometry is quite resource expensive. I was hoping to find a means to have the two libraries coexist, with Three.js managing the graphical objects and Twgl.js handling text. Does WebGL or WebGL2 support vertex subroutines? If so, can Three.js be modified to use them so that Twgl.js functions can be written?

@3DGraphics
There are many ways to achieve what you want.
Can you provide an explanatory pic with more detailed description of the desired result?

With THREE.RawShaderMaterial and a custom THREE.BufferGeometry, no one decides your attribute names.

What alternatives are you exploring? Hundreds of thousands of text markers sounds visually bloated and computationally expensive regardless of how they are made. Any chance of displaying the markers only on hover/click, perhaps just using carefully configured and positioned HTML elements?

@3DGraphics There are several methods to write text with Three.js. Here’s a small list in the docs of some WebGL options, but you can also use the CSS3DRenderer approach. Not sure which one would work best with your project.

@EliasHasle He asked if he’d still be able to use “Three.js native materials and geometries” and the answer to that question is no; he’d have to write custom ones.

That depends on what you mean with “native”. One possible interpretation would be that he writes the shader for Twgl.js, and uses it in Three.js. I outlined how that would be possible.

bonds

segments

@prisoner849, The above are examples of what I’m trying to do.

@marquizzo, I will review the list that you have provided.

@EliasHasle, you are correct about it being visually bloated but that is the worse case. The user has the ability to graphically cut away sections and isolate particular regions of interest. We’ve had discussions about using hovering but that was voted down.

I really appreciate you guys help. :slight_smile:

Reminded me about this: https://threejs.org/examples/?q=loader#webgl_loader_pdb

@prisoner849, I appreciate the help. I am studying the example. I would like to know if it is possible to switch shaders (vertex/fragment) between ones written by me and the ones provided by three.js? If this is possible, what mechanism does three.js provide to support shader switching.

It’s hard to say what’s possible or not possible without at least an example of shaders you have (jsfiddle, codepen, link to github). I’m not familiar with twgl.js, but maybe there are users on the forum who work(ed) with it, so they can provide better help.
So far it’s very unclear what to what you want to convert.

I appreciate the prompt response. I wasn’t thinking about using twgl but rather pure webgl. As stated earlier, I am studying the example you provided and so far it appears to provide a good starting point. I’m just seeing what options I may have. I have to present a solution strategy.

Hi 3DGraphics.

Given what you are trying to do, they are ways of optimizing all this.

1/ If the letters are always facing the camera, you don’t need 3D models. Sprites would be more than enough.
The thing is they have to be transparent and can raise issues with depth if you mix them with opaque meshes, but you can put all the objects in your scene as transparent (even when they don’t need to be) or render the numbers always on top of the rest 'playing with renderorder, or doing multiple renders using, for example, layers as a mean of selection).

2/ It seems a lot of the letters a repeated, be it for sprites or for 3D volumes, you can reuse the materials and the geometry objects. Don’t initiate a new one every time you need a letter.
And if you need a sentence (ABCD) don’t make it one object but 4 objects side by side. That’s what makes this point valide ( you gonna have 40 As part of words in you scene, but only 2 ABCD as a whole for example).

3/ Level Of Details. If you want 3D model letters that look cool up close to the camera, I can understand. But if you you really are gonna hundreds of thousands, some of them ought to be in the back.
They don’t need, when far away, to be fully detailed 3D models.
That’s what LOD is for. Have a crisp 3D letter up close to the camera if you want, but make a sprite for when it is far from the camera.
https://threejs.org/docs/#api/en/objects/LOD

Not always, actually. It’s enough to use .alphaTest with value of 0.5 (for example), without setting .transparent to true on a material.

I do appreciate the examples provided and I’m examining them but the issue regarding vertex switching still has been unanswered. @prisoner849 you asked for an example. I have provided an example which is very generic(see below).

I want to implement vertex shading by creating multiple program objects. How can this be done with three.js? I’m not attempting to create a material.

var SOLID_VSHADER_SOURCE =
‘attribute vec4 a_Position;\n’ +
‘attribute vec4 a_Normal;\n’ +
‘uniform mat4 u_MvpMatrix;\n’ +
‘uniform mat4 u_NormalMatrix;\n’ +
‘varying vec4 v_Color;\n’ +
‘void main() {\n’ +
’ vec3 lightDirection = vec3(0.0, 0.0, 1.0);\n’ + // Light direction(World coordinate)
’ vec4 color = vec4(0.0, 1.0, 1.0, 1.0);\n’ + // Face color
’ gl_Position = u_MvpMatrix * a_Position;\n’ +
’ vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\n’ +
’ float nDotL = max(dot(normal, lightDirection), 0.0);\n’ +
’ v_Color = vec4(color.rgb * nDotL, color.a);\n’ +
‘}\n’;

// Fragment shader for single color drawing
var SOLID_FSHADER_SOURCE =
#ifdef GL_ES\n’ +
‘precision mediump float;\n’ +
#endif\n’ +
‘varying vec4 v_Color;\n’ +
‘void main() {\n’ +
’ gl_FragColor = v_Color;\n’ +
‘}\n’;

// Vertex shader for texture drawing
var TEXTURE_VSHADER_SOURCE =
‘attribute vec4 a_Position;\n’ +
‘attribute vec4 a_Normal;\n’ +
‘attribute vec2 a_TexCoord;\n’ +
‘uniform mat4 u_MvpMatrix;\n’ +
‘uniform mat4 u_NormalMatrix;\n’ +
‘varying float v_NdotL;\n’ +
‘varying vec2 v_TexCoord;\n’ +
‘void main() {\n’ +
’ vec3 lightDirection = vec3(0.0, 0.0, 1.0);\n’ + // Light direction(World coordinate)
’ gl_Position = u_MvpMatrix * a_Position;\n’ +
’ vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\n’ +
’ v_NdotL = max(dot(normal, lightDirection), 0.0);\n’ +
’ v_TexCoord = a_TexCoord;\n’ +
‘}\n’;

// Fragment shader for texture drawing
var TEXTURE_FSHADER_SOURCE =
#ifdef GL_ES\n’ +
‘precision mediump float;\n’ +
#endif\n’ +
‘uniform sampler2D u_Sampler;\n’ +
‘varying vec2 v_TexCoord;\n’ +
‘varying float v_NdotL;\n’ +
‘void main() {\n’ +
’ vec4 color = texture2D(u_Sampler, v_TexCoord);\n’ +
’ gl_FragColor = vec4(color.rgb * v_NdotL, color.a);\n’ +
‘}\n’;

function main() {
// Retrieve element
var canvas = document.getElementById(‘webgl’);

// Get the rendering context for WebGL
var gl = getWebGLContext(canvas);
if (!gl) {
console.log(‘Failed to get the rendering context for WebGL’);
return;
}

// Initialize shaders
var solidProgram = createProgram(gl, SOLID_VSHADER_SOURCE, SOLID_FSHADER_SOURCE);
var texProgram = createProgram(gl, TEXTURE_VSHADER_SOURCE, TEXTURE_FSHADER_SOURCE);
if (!solidProgram || !texProgram) {
console.log(‘Failed to intialize shaders.’);
return;
}

twgl.js is for working more closely with webgl, whereas three.js is a whole layer of abstraction.
If you want to work with three and write shaders yourself, use shadermaterial or rawshadermaterial.
I’m doing that myself - using a class called shaderlayer, that includes a screen size quad that allows me to write a custom shader for a background (or foreground) to go behind or in front of my three.js content.
Three.js does modify the custom shaders you provide before compiling them, iirc.
Ther’e a good example of how to get started with three.js and custom shaders here:

1 Like

Sorry, I just noticed you specified that you want to create many objects, and not use a material.
I recommend you consider using a single quad with a custom shader to display anything higher than 5000 objects, and that includes text, it really is the fastest solution, though tricky, to implement. Tracking hundreds of thousand of objects in any flavour of Javascript is going to be troublesome, with the possible exception of webassembly.
There’s some amazing examples of massive text rendering using a shader here:
https://wdobbie.com/post/gpu-text-rendering-with-vector-textures/
Though I had trouble understanding that one myself.
You want to think in terms of writing per pixel, identifying which letter that pixel is in, and rendering that part of the letter, and having a shader cover the entire area with letters, so the number of letters shouldn’t slow things down. That’s the theory anyway. For rendering vectors at all sizes with a shader you need to use a technique called signed distance fields, which essentially tells you if a point is within a shape or outside it.

1 Like