Advertisement

Terrain texture mapping using indirection texture

Started by October 01, 2018 10:25 AM
2 comments, last by Coderebel 6 years, 4 months ago

Hey guys!

In my 3D terrain generator, I calculate simple texture coordinates based on x,z (y being up down) coordinates as if terrain was flat - simple planar projection. That of course introduces texture stretching on sloped parts of the terrain. Trying to solve that I first implemented tri-planar mapping (like this), but it is really performance(PS) heavy and the results are very weird looking in some cases. Then I found another technique, which looks better, and most importantly, the heavy work is done in a preprocess - generating an indirection map of terrain which is then used in pixel shader to offset uv coords: Indirection mapping for on quasi-conformal relief texturing

Has anyone ever implemented this solution and is willing to share some code for indirection map generation (spring grid relaxation)? I couldnt find any implementation or sample, and am really not sure how to go about it.

Thanks!

Edit: Oops, did not read the paper well enough - my answer only covers how to relax UVs. I don't know anything about the indirection texture trick for POM. But for terrain the resulting UVs should resolve the stretching well.

-----------

 

I do this  a lot, but i have more complicated constraints so i don't have a full example, so 'll just dexcribe the algorithm:

 

Preprocessing:

First you start with the planar projecton you already have to get initial UVs for each triangle (each triangle has its own 3 UVs - they are not shared)

For each UV triangle: Calculate its area center and set this as the origin for the triangle. Also store a 2D projection from the 3D trianlge at its normal plane (orientation does not matter) and set area center as origin as well. 

Calculate average scaling from 3D to UV to obtain a target area for each UV triangle.

Finally for each triangle you have this data:

struct

{

vec2 centerUV;

vec2 UVs[3];

vec2 projectedFrom3D[3];

float targetUVArea;

}

 

Iterative solver:

1. For each triangle: Update UVs to target shape and set its orientation so the angular momentum relative to the current UV coords is zero (see code snippet), and scale to match target area.

2. For each vertex: calculate the average merged UV coord from all adjacent UV triangles (weighted by inverse area should work best). Keep the boundary vertices fixed if you want.

3. From those averaged vertex UVs calculate new triangle centers and UVs. Continue with step 1 until converged.

 

The algorithm is quite simple but robust if distortion is not too big (which for a hightmap should never happen).

Notice that there is solid research in this field, e.g. 'Least Squares Conformal Maps' or 'As Rigid As Possible parametrization', if naive approaches like mine or the paper you've mentioned are not good enough.

 


	 
	static float Deformed2DTriangleRotation (const vec2 *posDeformed, const vec2 *posReference) // both triangles must have their area center at (0,0)
    {
        float aMc = 0;
        float aMs = 0;
        for (int i=0; i<3; i++)
        {
            float sql = (posDeformed[i].SqL() + posReference[i].SqL()) * 0.5f; // SqL meand squared length, so just do product with itself
	            vec2 a0 = posDeformed[i];
            vec2 a1 (a0[1], -a0[0]);
	            float angle = atan2 (a1.Dot(posReference[i]), a0.Dot(posReference[i]));                        
            aMc += cos(angle) * sql;// * mass[i];
            aMs += sin(angle) * sql;// * mass[i];
	            //alternative without trig - todo: faster
            //aMs += a1.Unit().Dot(posReference[i].Unit()) * sql;
            //aMc += a0.Unit().Dot(posReference[i].Unit()) * sql;
        }
	        return atan2 (aMs, aMc);            
    }
	

posDeformed means UVcoords of triangle, and posReference is its target shape, so the projection from 3D

The function returns just an angle so step 1 looks like this:

for each triangle

{

float rotationAngle = Deformed2DTriangleRotation (triangle.UVs, triangle.projectedFrom3D);

triangle.UVs[0..2] = Rotate ( triangle.projectedFrom3D[0...2], rotationAngle); // Edit: more likely use -rotationAngle

SetTriangleArea (triangle.UVs, triangle.targetUVArea); // could optimize this away by doing it on the precomputed targetUVArea

}

So you set the UVs to their target shape in each iteration, but you minimize the amount of rotation caused by moving centers, which is the central idea of the algorithm.

 

Edit: Can't edit the code, typo in comment means SqL = Dot Product with itself

 

 

 

 

Advertisement

Hey, thanks for your answer JoeJ!

I think I get the basic idea - I agree, there is no need for the indirection texture if you can already manipulate vertex UV coords at load-time(even better, one lookup less in PS). I'll try to implement it later on today, will let you know if I succeed :)

Thanks a lot!

This topic is closed to new replies.

Advertisement