Mapping-fidelity in quadrilateral/triangle rendering

If you want to have more or less correct texturing for 5-segment LatheGeoemtry, then double the segments (10) and interpolate every 2nd set of points in between of 1st and 3rd, over 2 sets: Edit fiddle - JSFiddle - Code Playground

function beautifyLathe(g){
  let pos = g.attributes.position;
  let pCount = g.parameters.points.length;
  let segs = g.parameters.segments;
  let pStart = new THREE.Vector3();
  let pEnd = new THREE.Vector3();
  let pMid = new THREE.Vector3();
  for(let i = 0; i < segs; i += 2){
  	for(let p = 0; p < pCount; p++){
  	  pStart.fromBufferAttribute(pos, pCount * i + p);
      pEnd.fromBufferAttribute(pos, pCount * (i + 2) + p);
      pMid.addVectors(pStart, pEnd).multiplyScalar(0.5);
      pos.setXYZ(pCount * (i + 1) + p, pMid.x, pMid.y, pMid.z);

That is a neat idea for a very specific use case - thank you.

To be honest, I was fitting the # of lathe segments in my example to the # of vertical lines of that specific sample texture, to show the effect in maximum clarity. Actually, the kink in mapping a straight vertical line to a quadrilateral will be seen for any number of lathe segments which is different from an exact integer multiple of the number of visible texture map subdivisions, i.e. vertical white lines.

Ultimately I’m aiming at getting more realistic reflective mapping of natural textures, which typically lack any regular vertical traits. So the nature and cause of the problem would have been much more difficult to illustrate.

I found a solution! I asked at GameDev.StackExchange, which led me to a really good answer.

You know how you can fix perspective texturing by adding a 4th component w to the xyz position calculation? Turns out you can do the same to your uvs and turn them into uvws, where the w component contains the scaling factor of how stretched the texture is. It would require custom GLSL shader code injection to be used with Three.js materials, but turns out it is possible:


That sounds very promising indeed! :drooling_face:

Thanks for the follow-up. I’ll take a deeper look into this tomorrow …

OK, so here’s my findings:

The proposed solution works for trapezoidal quadrilaterals, in that it eliminates the “kink” along any of the diagonals.

The BAD:
The appearance of the trapezoid resembles perspective correct texturing, in that the rows of the regular checkerboard become narrower, going from the bottom edge towards the top. Although all vertices are in the xy-plane having z= 0.

This solution does not work for arbitrary quadrilaterals, without any parallel edges. I may have lacked the patience to experiment with different scaling factors which might have to be applied along two axes, as opposed to one “scale” factor to be applied to the vertices of one of the parallel edges of a trapezoid.

A complete Jsfiddle is provided, which I forked from a working example of a spinning, perspective correctly textured cube on dot html

, which I stripped down to one still quadrilateral. After which I applied the proposed changes regarding three-component uvws.

1 Like

But that’s exactly what we were trying to achieve! In the context of the rest of the LatheGeometry it’ll make sense to our eyes. It’ll end up looking exactly like @prisoner849’s image:

Not quite, I’m afraid. In the context of my previous Jsfiddle ( all four vertices having their z-component = 0.0), I’d expect equal spacing in y-direction, while proportional (to the taper) spacing in x-direction. Without any kinks in any of the diagonals, of course.

Any perspective foreshortening would have to come on top of that, if applicable due to perspective, but not right from the start when viewing straight-on.

Maybe bilinear interpolation will help, I’m not sure though.
I tried it here: Trilinear interpolation of vertices, see the codepen in the PS. But it was made in vertex shader with a segmented plane.

Well, a good night’s of sleep does make a difference :sunglasses:

Linear spacing along v, while proportional spacing along u.

The decisive change:

In the fragment shader, apply the division by scale only to the u-component, while leaving the v-component unchanged:

void main() {
   gl_FragColor = texture2D(u_texture, vec2( v_texcoord.x / v_texcoord.z, v_texcoord.y ) );

Plus, when setting up the array with uvws, don’t scale the vs in the first place.

I’ve updated the Jsfiddle:

P.S.: now that I know how to separate the effects of proportional scale of each axis from that of the other axis, I will experiment with bilinear scaling for arbitrarily shaped quadrilaterals. But don’t hold your breath …


Thank you for the link. That helped me!

Here’s the result from my

It’s not really an “irregular” quadrilateral yet, but has no parallel edges nevertheless. So I’m confident it could be done for a truly irregular quadrilateral as well. A little bit of context:

I started out with a unit square, centred about the coordinate origin, then moved vertex #4 out by +0.5 units in each direction:

               |      |      |
               |      |      |
               |       -----------> X
               |     /       |
               |    /        |
function setGeometry(gl) {
  var positions = new Float32Array(
    // front face, lower-left triangle
    -0.5, -0.5,  0.0,  // #1
     0.5, -0.5,  0.0,  // #3
    -0.5,  0.5,  0.0,  // #2
     // front face, upper right triangle
    -0.5,  0.5,  0.0,  // #2
     0.5, -0.5,  0.0,  // #3
     1.0,  1.0,  0.0,  // #4 make the the square irregular (no parallel edges), by moving this vertex out
  gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);

Moving vertex #4 out yields two vanishing points at a distance of 3.0 units from the lower-left vertex of the quadrilateral:

The maximum inclination (red) of the left fan-lines is 1/3, while the minimum inclination (blue) of the bottom fan-lines is 3/1, with “inclination” being the value “m” in the straight line equation:

y = m * x + b

I was unable to realise this with the 3-component uvw-approach and the “scale” - “scale back” trick as before. So I reverted this back to the familiar 2-component uv-approach. The whole magic is in the fragment shader, which now looks like this:

void main() {
   gl_FragColor = texture2D( u_texture, vec2( 
   	  3.0 * v_texcoord.x / (v_texcoord.y + 3.0),
      3.0 * v_texcoord.y / (v_texcoord.x + 3.0) ) );

A new Jsfiddle is provided:

Looking back, this was an interesting, yet imo futile exercise as far as advancing the fidelity of texturing in Three.js goes. Because there are no procedurally generated geometries in Three.js which result in irregular quadrilaterals that I’m aware of.

Thinking of it, the rectangle/trapezoid case has the most potential in that regard. Because

  • Cone
  • Cylinder
  • Extrude
  • Lathe
  • Plane
  • Ring
  • Sphere
  • Torus
  • TorusKnot
  • Tube

all produce quadrilaterals, which are either rectangular or trapezoidal, safe for the bands bordering poles.

I think I’m going to try my hand at the Lathe geometry and see how far I’ll get …


I have a working copy on my local computer, a Proof of Concept at this stage and for a LatheGeometry only.
I’d like to publish how it’s done, in a Jsfiddle. Unfortunately I’m running into unexpected Problems and am asking for help on this.

So, currently I have a teaser video only in place of a genuine proof:

Thanks to @repalash :1st_place_medal:, here we go:

See the effect of non-affine, perspective correct texturing (no more zig-zag lines in the diagonals of quadrilaterals) integrated into Three.js:

Disclaimer: This is currently a Proof of Concept only, and has been implemented for a LatheGeometry ONLY. Expect side effects for different geometries and/or non-MeshPhongMaterials.

I changed about six LOC in the shaders, and around the same number in the LatheGeometry function.

All changed lines of code have been marked with a ‘////’ signature at their ends.
View the changes in a hacked copy of three.module.js which is imported in the Jsfiddle.

Compare to the current behaviour(same link as initial post):

Are you sure you fixed the CORS issue? I’m getting this error:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

1 Like

Thanks for your feedback - I’ll look into this.

@marquizzo confirmed that the Jsfiddle now works :+1:

Play with the “latheSegments” slider and prepare to be stunned!


Friends of high-fidelity mappings :v:,

I left off with plain old texture mapping now working in a non-affine way, that is without any kinks in straight lines/features along the diagonals of trapezoidal quads. My last JSFiddle had temporarily stopped working because it referenced dat.gui.module.js which in the meantime has been replaced by the three.js project team with lil-gui.module.min.js . I updated the previous JSFiddle and it’s now working again. On that occasion I’m also hosting the texture maps I’m using in this JSFiddle on my own webspace, because I have better control over any CORS issues should they again arise.

Before I go on, I’d like to introduce my test 2k by 1k equirectangular map which I stitched together using PTGui Pro from single shots I took on a bike trip through the Swiss Alps (Klausenpass). For better reference, I added one-pixel wide vertical lines at the 0, 511, 1023 and 1535 pixel positions each:

View full size.

Those of you who have played (or are going to play) with the previos JSFiddle and select „reflective“ material and a lathe count of 4 segments will see (or have seen) something like this:

The above is the view straight down the lathing axis (y), overlayed (Gimp) with a wireframe rendering of the same scene.

I was expecting the general reflection to be „broken“ along the diagonals of the trapezoidal quads, but I was not expecting the straight red lines to display a behaviour as erratic as the above.

Which led me to look at the way vertex normals are computed in a LatheGeometry, because reflections are based on the evaluation of normals. I did find room for improvement, filed a PR which got accepted and which is going to be part of r136. For the „what“, „why“ and „how“ please refer to the description of that PR (#22927). The change introduced with that PR is a prerequisite for the following.

This is what the above rendering looks like with that PR applied:

Please note, that I’ve dedicated an evolved version of the previous JSFiddle for the following, as I didn’t want to loose the history by overwriting the previous one. Also please note, that this first fix only affects the “meridian” lines of the trapezoidal quads, not the content of the triangles yet. That said: Here we go!

And again overlayed with the corresponding wireframe rendering:

This is still not pretty, but the perfectly straight rendering of the vertical red lines of the equirect map indicates that now I have a sound basis for further work. Which I have already done and which I will introduce to you tomorrow.

Thanks for your attention.

P.S.: The bleeding red-ish triangles seem to be the result of some filtering applied during texturing.

prematurely released - sorry for the confusion. I’m still redacting this post.

Stay tuned …

Since I’m almost illiterate with respect to GLSL, I implemented a function to interpolate the vertex normals in JS, instead of the shaders. To find out early on if it’s worth the effort at all.
Spoiler alert: it definitely is!

This is my interpretation of the current (legacy) way of how the shaders interpolate triangle vertex normals, applied to the lower right wedge of the pentagon of my test geometry:

This is what the reflection, using the above interpolation of vertex coordinates and their respective normals, looks like:

The following is the proposed new way of bilinear interpolation of vertex normals for trapezoidal quads as generated by the LatheGeometry() function:

These are the resulting reflections:

Viewed straight-on:

Let the continuous flow of reflections between adjacent faces of the same wedge of the test geometry mesmerize you:

I seem to have failed so far to demonstrate what the benefit of all this might be, in the context of reflection mapping. Using an obscure test geometry certainly has not helped either, I guess. So with the New Year comes a new trial! :slight_smile:

As a real-life example I’m now using an intake valve from the ICE of my Ducati motorcycle:

which I model as a Three.js LatheGeometry() using the following path:

Although I shortened the valve stem to bring the aspect ratio of this geometry for demo purposes closer to 1.0, this sample LatheGeometry features most of the situations which one can encounter in a real life lathed part:

  • cylindrical and flat circular/ring shaped sections (black)
  • concave rounded sections (green)
  • convex rounded sections (blue)
  • conical sections (red)
  • smooth transitions between different sections
  • sharp-edged transitions.

So it’s a pretty versatile test geometry after all.

When it comes to reflections, even tiny deviations from the (physically) true orientations of normals have a huge impact on reflection fidelity. This impact is most pronounced in very low segment-count LatheGeometries:

See the straight red lines of the equirectangular map being rendered as zig-zag, with the “zigs” following the diagonals of the quads that are the result of LatheGeometry().

I have put together a demonstrator which allows you to

  • see if there is a problem at all, and if so: how big it is.
  • crank up the lathe segment count, until you reach a satisfactory reflection quality
  • choose from two different maps, one photographic, one geometric
  • overlay/hide a wireframe display of the geometry, to correlate display faults with quad diagonals
  • show/hide a normals helper as an indicator of vertex locations
  • show/hide the background map.

The most interesting part (from my point of view) is the Tessellation slider, which allows you to tessellate each quad from the “base geometry” into Level * Level sub-quads, thus simulating (preempting) the effect which a suitable shader hopefully would be able to provide. This shader would have to do a more proper interpolation of vertex normals than the currently implemented solution. See for yourself of the remarkable quality of reflections which could(!) be achieved even with very low face / vertex count LatheGeometries.

Presented as a JSFiddle: