代码之家  ›  专栏  ›  技术社区  ›  deblocker

用于纹理立方投影的GLSL着色器

  •  1
  • deblocker  · 技术社区  · 6 年前

    我正在尝试在WebGL着色器中实现纹理立方体投影,如下图所示:

    Cubic projection

    到目前为止我尝试的是:

    我正在传递对象的边界框(图片中间的框),如下所示:

    uniform vec3 u_bbmin;
    uniform vec3 u_bbmax;
    

    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);
    

    最后,要从我的纹理中采样,我需要一个如下形式的贴图:

    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);
    

    我本来可以实现球形和圆柱形的投影,但我现在被困在如何得到这种立方体贴图,纹理应该延伸到整个边界框,纵横比没有关系。

    2 回复  |  直到 6 年前
        1
  •  1
  •   gman    6 年前

    我真的不知道这是否正确,但是。。。

    查看立方体映射的工作原理opengles2.0规范中有一个表

    Major Axis Direction|        Target             |sc |tc |ma |
    --------------------+---------------------------+---+---+---+
           +rx          |TEXTURE_CUBE_MAP_POSITIVE_X|−rz|−ry| rx|
           −rx          |TEXTURE_CUBE_MAP_NEGATIVE_X| rz|−ry| rx|
           +ry          |TEXTURE_CUBE_MAP_POSITIVE_Y| rx| rz| ry|
           −ry          |TEXTURE_CUBE_MAP_NEGATIVE_Y| rx|−rz| ry|
           +rz          |TEXTURE_CUBE_MAP_POSITIVE_Z| rx|−ry| rz|
           −rz          |TEXTURE_CUBE_MAP_NEGATIVE_Z|−rx|−ry| rz|
    --------------------+---------------------------+---+---+---+
    

    表3.21:基于纹理坐标长轴方向的立方体地图图像选择

    我用它写了这个函数

    #define RX 0
    #define RY 1
    #define RZ 2
    #define S 0
    #define T 1
    
    void majorAxisDirection(vec3 normal, inout mat4 uvmat) {
       vec3 absnorm = abs(normal);
       if (absnorm.x > absnorm.y && absnorm.x > absnorm.z) {
         // x major
         if (normal.x >= 0.0) {
           uvmat[RZ][S] = -1.;
           uvmat[RY][T] = -1.;
         } else {
           uvmat[RZ][S] =  1.;
           uvmat[RY][T] = -1.;
         }
       } else if (absnorm.y > absnorm.z) {
         // y major
         if (normal.y >= 0.0) {
           uvmat[RX][S] =  1.;
           uvmat[RZ][T] =  1.;
         } else {
           uvmat[RX][S] =  1.;
           uvmat[RZ][T] = -1.;
         }
       } else {
         // z major
         if (normal.z >= 0.0) {
           uvmat[RX][S] =  1.;
           uvmat[RY][T] = -1.;
         } else {
           uvmat[RX][S] = -1.;
           uvmat[RY][T] = -1.;
         }
       }
    }
    

    您传入一个矩阵,它将其设置为将正确的X、Y或Z移动到X和Y列(以转换为s和t)。换言之,你通过正常,它返回s和t。

    这将有效地给出一个单位立方体投影在原点的正边上。加入另一个矩阵,我们可以移动和缩放立方体。

    如果您希望它正好适合立方体,那么您需要设置比例、平移和方向以匹配立方体。

    "use strict";
    
    /* global document, twgl, requestAnimationFrame */
    
    const vs = `
    uniform mat4 u_model;
    uniform mat4 u_viewProjection;
    
    attribute vec4 position;
    attribute vec3 normal;
    attribute vec2 texcoord;
    
    varying vec2 v_texCoord;
    varying vec3 v_normal;
    varying vec3 v_position;
    
    void main() {
      v_texCoord = texcoord;
      vec4 position = u_model * position;
      gl_Position = u_viewProjection * position;
      v_position = position.xyz;
      v_normal = (u_model * vec4(normal, 0)).xyz;
    }
    `;
    const fs = `
    precision mediump float;
    
    varying vec3 v_position;
    varying vec2 v_texCoord;
    varying vec3 v_normal;
    
    uniform mat4 u_cubeProjection;
    uniform sampler2D u_diffuse;
    
    #define RX 0
    #define RY 1
    #define RZ 2
    #define S 0
    #define T 1
    
    #if BOX_PROJECTION
    
    void majorAxisDirection(vec3 normal, inout mat4 uvmat) {
       vec3 absnorm = abs(normal);
       if (absnorm.x > absnorm.y && absnorm.x > absnorm.z) {
         // x major
         if (normal.x >= 0.0) {
           uvmat[RZ][S] = -1.;
           uvmat[RY][T] = -1.;
         } else {
           uvmat[RZ][S] =  1.;
           uvmat[RY][T] = -1.;
         }
       } else if (absnorm.y > absnorm.z) {
         // y major
         if (normal.y >= 0.0) {
           uvmat[RX][S] =  1.;
           uvmat[RZ][T] =  1.;
         } else {
           uvmat[RX][S] =  1.;
           uvmat[RZ][T] = -1.;
         }
       } else {
         // z major
         if (normal.z >= 0.0) {
           uvmat[RX][S] =  1.;
           uvmat[RY][T] = -1.;
         } else {
           uvmat[RX][S] = -1.;
           uvmat[RY][T] = -1.;
         }
       }
    }
    
    #else  // cube projection
    
    void majorAxisDirection(vec3 normal, inout mat4 uvmat) {
       vec3 absnorm = abs(normal);
       if (absnorm.x > absnorm.y && absnorm.x > absnorm.z) {
         // x major
         uvmat[RZ][S] =  1.;
         uvmat[RY][T] = -1.;
       } else if (absnorm.y > absnorm.z) {
         uvmat[RX][S] =  1.;
         uvmat[RZ][T] =  1.;
       } else {
         uvmat[RX][S] =  1.;
         uvmat[RY][T] = -1.;
       }
    }
    
    #endif
    
    void main() {
      vec3 normal = normalize(v_normal);
      mat4 uvmat = mat4(
        vec4(0, 0, 0, 0),
        vec4(0, 0, 0, 0),
        vec4(0, 0, 0, 0),
        vec4(0, 0, 0, 1));
      majorAxisDirection(normal, uvmat);
      uvmat = mat4(
        abs(uvmat[0]),
        abs(uvmat[1]),
        abs(uvmat[2]),
        abs(uvmat[3]));
      
      vec2 uv = (uvmat * u_cubeProjection * vec4(v_position, 1)).xy;
      
      gl_FragColor = texture2D(u_diffuse, uv);
    }
    `;
    
    const m4 = twgl.m4;
    const gl = twgl.getWebGLContext(document.getElementById("c"));
    // compile shaders, look up locations
    const cubeProjProgramInfo = twgl.createProgramInfo(gl,
        [vs, '#define BOX_PROJECTION 0\n' + fs]);
    const boxProjProgramInfo = twgl.createProgramInfo(gl, 
        [vs, '#define BOX_PROJECTION 1\n' + fs]);
    
    let progNdx = 1;
    const programInfos = [
      cubeProjProgramInfo,
      boxProjProgramInfo,
    ];
    
    // create buffers
    const cubeBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);
    const sphereBufferInfo = twgl.primitives.createSphereBufferInfo(gl, 1, 60, 40);
    
    const ctx = document.createElement("canvas").getContext("2d");
    ctx.canvas.width = 256;
    ctx.canvas.height = 256;
    ctx.fillStyle = `hsl(${360}, 0%, 30%)`;
    ctx.fillRect(0, 0, 256, 256);
    for (let y = 0; y < 4; ++y) {
      for (let x = 0; x < 4; x += 2) {
        ctx.fillStyle = `hsl(${(x + y) / 16 * 360}, 100%, 75%)`;
        ctx.fillRect((x + (y & 1)) * 64, y * 64, 64, 64);
      }
    }
    ctx.lineWidth = 10;
    ctx.strokeRect(0, 0, 256, 256);
    ctx.font = "240px sans-serif";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.fillStyle = 'red';
    ctx.fillText("F", 128, 128);
    
    const texture = twgl.createTexture(gl, {
      src: ctx.canvas,
      wrap: gl.CLAMP_TO_EDGE,
      min: gl.LINEAR,  // no mips
    });
    
    function addElem(parent, type) {
      const elem = document.createElement(type);
      parent.appendChild(elem);
      return elem;
    }
    
    function makeRange(parent, obj, prop, min, max, name) {
      const divElem = addElem(parent, 'div');
      const inputElem = addElem(divElem, 'input');
      Object.assign(inputElem, {
        type: 'range',
        min: 0,
        max: 1000,
        value: (obj[prop] - min) / (max - min) * 1000,
      });
      const valueElem = addElem(divElem, 'span');
      valueElem.textContent = obj[prop].toFixed(2);
      const labelElem = addElem(divElem, 'label');
      labelElem.textContent = name;
      
      function update() {
        inputElem.value = (obj[prop] - min) / (max - min) * 1000,
        valueElem.textContent = obj[prop].toFixed(2);
      }
      
      inputElem.addEventListener('input', (e) => {
        obj[prop] = (e.target.value / 1000 * (max - min) + min);
        update();
      });
      
      return update;
    }
    
    const models = [
      cubeBufferInfo,
      sphereBufferInfo,
      cubeBufferInfo,
    ];
    const rotateSpeeds = [
      1,
      1,
      0,
    ];
    let modelNdx = 0;
    const ui = document.querySelector('#ui');
    const cubeMatrix = m4.translation([0.5, 0.5, 0.5]);
    const updaters = [
      makeRange(ui, cubeMatrix,  0, -2, 2, 'sx'),
      makeRange(ui, cubeMatrix,  5, -2, 2, 'sy'),
      makeRange(ui, cubeMatrix, 10, -2, 2, 'sz'),
      makeRange(ui, cubeMatrix, 12, -2, 2, 'tx'),
      makeRange(ui, cubeMatrix, 13, -2, 2, 'ty'),
      makeRange(ui, cubeMatrix, 14, -2, 2, 'tz'),
    ];
    document.querySelectorAll('input[name=shape]').forEach((elem) => {
      elem.addEventListener('change', (e) => {
        if (e.target.checked) {
          modelNdx = parseInt(e.target.value);
          if (modelNdx == 2) {
            m4.scaling([1/2, 1/2, 1/2], cubeMatrix);
            m4.translate(cubeMatrix, [1, 1, 1], cubeMatrix);
            updaters.forEach(f => f());
          }
        }
      })
    });
    document.querySelectorAll('input[name=proj]').forEach((elem) => {
      elem.addEventListener('change', (e) => {
        if (e.target.checked) {
          progNdx = parseInt(e.target.value);
        }
      })
    });
    
    
    const uniforms = {
      u_diffuse: texture,
      u_cubeProjection: cubeMatrix,
    };
    
    function render(time) {
      time *= 0.001;
      twgl.resizeCanvasToDisplaySize(gl.canvas);
      gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
      
      const programInfo = programInfos[progNdx];
      const bufferInfo = models[modelNdx];
    
      gl.enable(gl.DEPTH_TEST);
      gl.enable(gl.CULL_FACE);
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    
      const fov = 30 * Math.PI / 180;
      const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
      const zNear = 0.5;
      const zFar = 10;
      const projection = m4.perspective(fov, aspect, zNear, zFar);
      const eye = [0, 4, -4];
      const target = [0, 0, 0];
      const up = [0, 1, 0];
    
      const camera = m4.lookAt(eye, target, up);
      const view = m4.inverse(camera);
      const viewProjection = m4.multiply(projection, view);
      const model = m4.rotationY(time * rotateSpeeds[modelNdx]);
    
      uniforms.u_viewProjection = viewProjection;
      uniforms.u_model = model;
    
      gl.useProgram(programInfo.program);
      twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
      twgl.setUniforms(programInfo, uniforms);
      gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
    
      requestAnimationFrame(render);
    }
    requestAnimationFrame(render);
    body {
      margin: 0;
      font-family: monospace;
      color: white;
    }
    canvas {
      display: block;
      width: 100vw;
      height: 100vh;
      background: #444;
    }
    #ui {
      position: absolute;
      left: 0;
      top: 0;
    }
    #ui span {
      display: inline-block;
      width: 4em;
      text-align: right;
    }
    <canvas id="c"></canvas>
    
    <script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
    <div id="ui">
      <div>
        <input type="radio" name="proj" id="sphere" value="0">
        <label for="sphere">cubic projection</label>
        <input type="radio" name="proj" id="cube" value="1" checked>
        <label for="cube">box projection</label>
      </div> 
      <div>
        <input type="radio" name="shape" id="sphere" value="1">
        <label for="sphere">sphere</label>
        <input type="radio" name="shape" id="cube" value="0" checked>
        <label for="cube">cube</label>
        <input type="radio" name="shape" id="cube" value="2">
        <label for="cube">cube match</label>
      </div> 
    </div>
        2
  •  0
  •   deblocker    6 年前

    gman 通过使用矩阵作为 uv uniform 其他一般用途。

    此外,我甚至不需要区分所有的六个长轴,我只需要三个侧面的投影,所以这可以简化下来。当然,纹理将在相反的面上镜像。

    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);
    

    说明:

    1. 世界秩序 modelPos 协调。 示例:可以使用将纹理旋转90度 modelPos.yx 而不是 modelPos.xy
    2. 纹理投影的方向由 这个 型号POS 协调。 示例:可以使用在Y轴上镜像纹理 vec2(sX, sY) 而不是 vec2(sX, -sY)

    enter image description here

    编辑:

    值得在这里链接另一个答案 格曼 其中包含有关此主题的附加信息以及一些很酷的优化技术,以避免GLSL着色器中的条件: How to implement textureCube using 6 sampler2D .