Search code examples
webglprojectionfragment-shaderglsles

GLSL shader for texture cubic projection


I am trying to implement a texture cubic projection inside my WebGL shader, like in the picture below:

Cubic projection

What I tried so far:

I am passing the bounding box of my object (the box in the middle of the picture) as follows:

uniform vec3 u_bbmin;
uniform vec3 u_bbmax;

... so the eight vertexes of my projection box are:

vec3 v1 = vec3(u_bbmin.x, u_bbmin.y, u_bbmin.z);
vec3 v2 = vec3(u_bbmax.x, u_bbmin.y, u_bbmin.z);
vec3 v3 = vec3(u_bbmin.x, u_bbmax.y, u_bbmin.z);
...other combinations
vec3 v8 = vec3(u_bbmax.x, u_bbmax.y, u_bbmax.z);

At the end, to sample from my texture I need a map in the form of:

varying vec3 v_modelPos;
...
uniform sampler2D s_texture;
vec2 tCoords = vec2(0.0);

tCoords.s = s(x,y,z)
tCoords.t = t(y,y,z)

vec4 color = texture2D(s_texture, tCoords);

I was able to implement spherical and cylindrical projections, but I am stuck now how to get this kind of cubic map, The texture shall stretch to the whole bounding box, aspect ratio doesn't matter.

Maybe I am missing some key points and I need some hints. How should the math for a cubic projection looks like?


Solution

  • The key-point here is: normals shall be in object-space. Please note that gman's answer is more elegant than mine, by using a matrix for the uv computation. I am using instead the bounding box coordinates, which are already passed to the vertex shader as uniform for other general purposes.

    Moreover, I don't even need to distinguish all the six major axis, I just only need three sides projection, so this can be simplified down. Of course, the texture will be mirrored on the opposite faces.

    float sX = u_bbmax.x - u_bbmin.x;
    float sY = u_bbmax.y - u_bbmin.y;
    float sZ = u_bbmax.z - u_bbmin.z;
    
    /* --- BOX PROJECTION - THREE SIDES --- */
    if( (abs(modelNormal.x) > abs(modelNormal.y)) && (abs(modelNormal.x) > abs(modelNormal.z)) ) {
      uvCoords = modelPos.yz / vec2(sY, -sZ); // X axis
    } else if( (abs(modelNormal.z) > abs(modelNormal.x)) && (abs(modelNormal.z) > abs(modelNormal.y)) ) {
      uvCoords = modelPos.xy / vec2(sX, -sY); // Z axis
    } else {
      uvCoords = modelPos.xz / vec2(sX, -sZ); // Y axis
    }
    uvCoords += vec2(0.5);
    

    Explanation:

    1. The direction of the texture projection is determined by the order of the modelPos coordinates. Example: the texture can be rotated by 90 degrees by using modelPos.yx instead of modelPos.xy.
    2. The orientation of the texture projection is determined by the sign of the modelPos coordinates. Example: the texture can be mirrored on the Y-axis by using vec2(sX, sY) instead of vec2(sX, -sY).

    Result:

    enter image description here

    EDIT:

    It is worth to link here another answer from gman which contain additional information about this topic and also some cool optimization techniques to avoid conditionals inside GLSL shaders: How to implement textureCube using 6 sampler2D.