webgl、纹理坐标和 obj

ada*_*aly 1 javascript webgl

我发现渲染数据时很难理解顶点和纹理坐标之间的相关性。我有一个使用从 obj 解析的 drawElements 表单数据绘制的立方体。我在某个接近于使用简单平面的地方得到了纹理,其中位置和纹理坐标的顶点数,但是一旦我使用更复杂的模型,甚至只是更复杂的 uv 展开,我最终会得到纹理全部错误。

从我读到的内容来看,没有看到像使用顶点位置一样使用纹理坐标索引的方法,这是不幸的,因为 obj 具有该信息。我让它接近工作的方法是通过 obj.txt 中的索引数据构建一个纹理坐标数组。但由于顶点和纹理坐标数组的长度不同(例如,在立方体的 obj 中,有 8 个顶点和最多 36 个纹理坐标,具体取决于网格是否展开),因此它们不相关。

使用drawElements 并将顶点映射到正确的纹理坐标的正确工作流程是什么?

gma*_*man 6

你是对的,你不能轻易地对不同的属性使用不同的索引(在你的情况下位置和纹理坐标)。

一个常见的例子是立方体。如果你想渲染一个带有光照的立方体,你需要法线。立方体上只有 8 个位置,但立方体的每个面的相同位置需要 3 个不同的法线,共享该位置的每个面需要一个法线。这意味着总共需要 24 个顶点,立方体 6 个面各 4 个。

如果您的文件格式对不同属性有单独的索引,则需要将它们展开,以便每个独特的属性组合(位置、法线、纹理坐标等)都在缓冲区中。

大多数游戏引擎都会离线执行此类操作。换句话说,他们会编写一些工具来读取 OBJ 文件,扩展各种属性,然后将预扩​​展的数据写回。这是因为,如果您尝试优化数据并仅保留唯一的顶点,则对于大型模型,在运行时生成扩展数据可能会非常耗时。

如果您不关心最佳数据,那么只需根据索引进行扩展即可。每种属性的索引数量应该相同。

注:职位并不特殊。我提出这个问题是因为你说没有一种方法可以像使用顶点位置一样使用纹理坐标索引。WebGL 没有“位置”的概念。它仅具有描述如何从缓冲区中提取数据的属性。这些属性中的内容(位置、法线、随机数据等)取决于您。gl.drawElements对您提供的整个属性组合进行索引。如果您传入索引 7,它将为您提供每个属性的元素 7。

请注意,上面描述了几乎所有用 WebGL 编写的 3D 引擎的工作原理。也就是说,如果你真的想的话,你可以发挥创意。

这是一个在纹理中存储位置和法线的程序。然后它将索引放入缓冲区中。由于纹理是随机访问的,因此它可以具有不同的位置和法线索引

var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
var ext = gl.getExtension("OES_texture_float");
if (!ext) {
    alert("need OES_texture_float extension cause I'm lazy");
    //return;
}
if (gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) < 2) {
    alert("need to be able to access textures from vertex shaders");
    //return;
}

var m4 = twgl.m4;
var v3 = twgl.v3;
var programInfo = twgl.createProgramInfo(gl, ["vshader", "fshader"]);

// Cube data
var positions = [
    -1, -1, -1, // 0 lbb
    +1, -1, -1, // 1 rbb      2---3
    -1, +1, -1, // 2 ltb     /|  /|
    +1, +1, -1, // 3 rtb    6---7 |
    -1, -1, +1, // 4 lbf    | | | |
    +1, -1, +1, // 5 rbf    | 0-|-1
    -1, +1, +1, // 6 ltf    |/  |/
    +1, +1, +1, // 7 rtf    4---5
];
var positionIndices = [
  3, 7, 5, 3, 5, 1, // right
  6, 2, 0, 6, 0, 4, // left
  6, 7, 3, 6, 3, 2, // top
  0, 1, 5, 0, 5, 4, // bottom
  7, 6, 4, 7, 4, 5, // front
  2, 3, 1, 2, 1, 0, // back
];
var normals = [
 +1,  0,  0,
 -1,  0,  0,
  0, +1,  0,
  0, -1,  0,
  0,  0, +1,
  0,  0, -1,
]
var normalIndices = [
  0, 0, 0, 0, 0, 0,  // right
  1, 1, 1, 1, 1, 1,  // left
  2, 2, 2, 2, 2, 2,  // top
  3, 3, 3, 3, 3, 3,  // bottom
  4, 4, 4, 4, 4, 4,  // front
  5, 5, 5, 5, 5, 5,  // back
];

function degToRad(deg) {
  return deg * Math.PI / 180;
}

var bufferInfo = twgl.createBufferInfoFromArrays(gl, {
  a_positionIndex: { size: 1, data: positionIndices },
  a_normalIndex: { size: 1, data: normalIndices, },
});

var textures = twgl.createTextures(gl, {
  positions: {
    format: gl.RGB,
    type: gl.FLOAT,
    height: 1,
    src: positions,
    min: gl.NEAREST,
    mag: gl.NEAREST,
    wrap: gl.CLAMP_TO_EDGE,
  },
  normals: {
    format: gl.RGB,
    type: gl.FLOAT,
    height: 1,
    src: normals,
    min: gl.NEAREST,
    mag: gl.NEAREST,
    wrap: gl.CLAMP_TO_EDGE,
  },
});

var xRot = degToRad(30);
var yRot = degToRad(20);

var lightDir = v3.normalize([-0.2, -0.1, 0.5]);

function draw(time) {
  time *= 0.001;  // convert to seconds

  twgl.resizeCanvasToDisplaySize(gl.canvas);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  yRot = time;

  gl.enable(gl.DEPTH_TEST);
  gl.enable(gl.CULL_FACE);

  gl.useProgram(programInfo.program);

  var persp = m4.perspective(
    degToRad(45),
    gl.canvas.clientWidth / gl.canvas.clientHeight,
    0.1, 100.0);

  var mat = m4.identity();
  mat = m4.translate(mat, [0.0, 0.0, -5.0]);
  mat = m4.rotateX(mat, xRot);
  mat = m4.rotateY(mat, yRot);

  var uniforms = {
    u_positions: textures.positions,
    u_positionsSize: [positions.length / 3, 1],
    u_normals: textures.normals,
    u_normalsSize: [normals.length / 3, 1],
    u_mvpMatrix: m4.multiply(persp, mat),
    u_mvMatrix: mat,
    u_color: [0.5, 0.8, 1, 1],
    u_lightDirection: lightDir,
  };

  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  twgl.setUniforms(programInfo, uniforms);
  twgl.drawBufferInfo(gl, bufferInfo);

  requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
Run Code Online (Sandbox Code Playgroud)
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
Run Code Online (Sandbox Code Playgroud)
<script src="//twgljs.org/dist/2.x/twgl-full.min.js"></script>
<script id="vshader" type="whatever">
attribute float a_positionIndex;
attribute float a_normalIndex;
attribute vec4 a_pos;

uniform sampler2D u_positions;
uniform vec2 u_positionsSize;
uniform sampler2D u_normals;
uniform vec2 u_normalsSize;
uniform mat4 u_mvpMatrix;
uniform mat4 u_mvMatrix;

varying vec3 v_normal;

  // to index the value in the texture we need to
  // compute a texture coordinate that will access
  // the correct texel. To do that we need access from
  // the middle of the first texel to the middle of the
  // last texel.
  //
  // In other words if we had 3 values (and therefore
  // 3 texels) we'd have something like this
  //
  //     ------3x1 ----- texels ----------
  //     [         ][         ][         ]
  // 0.0 |<----------------------------->| 1.0
  //
  // If we just did index / numValues we'd get
  //
  //     [         ][         ][         ]
  //     |          |          |
  //     0.0       0.333       0.666
  //
  // Which is right between texels so we add a
  // a halfTexel to get this
  //
  //     [         ][         ][         ]
  //          |          |          |
  //        0.167       0.5       0.833

  // note: In WebGL2 we could just use `textureFetch`
  // which takes integer pixel locations

vec2 texCoordFromIndex(const float index, const vec2 textureSize) {
   vec2 colRow = vec2(
       mod(index, textureSize.x),      // columm
       floor(index / textureSize.x));  // row
   return vec2((colRow + 0.5) / textureSize);
}


void main() {
  vec2 ptc = texCoordFromIndex(a_positionIndex, u_positionsSize);
  vec3 position = texture2D(u_positions, ptc).rgb;
  vec2 ntc = texCoordFromIndex(a_normalIndex, u_normalsSize);
  vec3 normal = texture2D(u_normals, ntc).rgb;
  gl_Position = u_mvpMatrix * vec4(position, 1);
  v_normal = (u_mvMatrix * vec4(normal, 0)).xyz;
}
</script>
<script id="fshader" type="whatever">
precision mediump float;

uniform vec4 u_color;
uniform vec3 u_lightDirection;

varying vec3 v_normal;

void main() {
  float light = dot(
      normalize(v_normal), u_lightDirection) * 0.5 + 0.5;
  gl_FragColor = vec4(u_color.rgb * light, u_color.a);
}
</script>
<canvas id="c"></canvas>
Run Code Online (Sandbox Code Playgroud)