개발 / / 2024. 4. 27. 01:51

webgpu ObjLoader 오류 - face triangle count, vertex qaud indices

반응형

어떻게 이 오류를 해결한담. 원인을 모르겠다.

 

아래 코드는 ObjLoader의 일부분인데

 

ObjLoader.ts
    // Use these intermediate arrays to leverage Array API (.push)
    const finalPosition: toBeFloat32[] = [];
    const finalNormals: toBeFloat32[] = [];
    const finalUvs: toBeFloat32[] = [];
    const finalIndices: toBeUInt16[] = [];
    
    ...(skip)...
    
    return {
      positions: new Float32Array(finalPosition),
      uvs: new Float32Array(finalUvs),
      normals: new Float32Array(finalNormals),
      indices: new Uint16Array(finalIndices),
    };

흠?.. 가만보니 ObjLoader에서 return하는 4개의 요소 중

 

main.ts
  gpuCanvas.device.queue.writeBuffer(
    meshBuffers.position.data,
    0,
    mesh.positions,
    0,
    meshBuffers.position.length
  );
  gpuCanvas.device.queue.writeBuffer(
    meshBuffers.normals.data,
    0,
    mesh.normals,
    0,
    meshBuffers.normals.length
  );

실질적으로 buffer에 write하고 draw로 canvas에 그릴 때는 positoin과 normal만 이용하는 모습이 보인다.

이 부분에 대해서 의문을 품고는 있었지만, indices를 사용하지 않으면 vertex간의 연결되는 face가 vertex의 순서로만 자동으로 지정될 것이다. 그러면 당연히 indices정보를 받지 못한 vertex 끼리는 face가 이루어지지 못한다.

 

이게 문제가 아닐까 싶다.

 

DefaultProgram.ts

 

  render(
    renderPassAPI: RenderPassAPI,
    vertexData: VertexBuffers
  ): DefaultProgram {
    super.render(renderPassAPI);
    renderPassAPI.setBindGroup(2, this.lightModelBindGroup);
    renderPassAPI.setVertexBuffer(0, vertexData.position.data);
    renderPassAPI.setVertexBuffer(1, vertexData.normals.data);
    renderPassAPI.setIndexBuffer(vertexData.indices.data, "uint16");
    renderPassAPI.drawIndexed(vertexData.indices.length);
    return this;
  }

보면 renderPass에 vertexBuffer 세팅 밑에 setIndexBuffer가 있긴 한데

그럼 어디서 index 관련 에러가 생기는 걸까.

 

 

webgpu object model loading 참고 영상 보러가기

 

 

webgpu for beginners: loading obj models

line by line으로 obj 파일을 읽고, v vt vn f 를 구분해서 parsing 해넣는것 까지는 가지고 있는 코드와 비슷하다.

 

  parse(file: ObjFile): Mesh {
    const lines = file?.split("\n");

    // Store what's in the object file here
    const cachedVertices: CacheArray<CacheVertice> = [];
    const cachedFaces: CacheArray<CacheFace> = [];
    const cachedNormals: CacheArray<CacheNormal> = [];
    const cachedUvs: CacheArray<CacheUv> = [];

    // Read out data from file and store into appropriate source buckets
    {
      for (const untrimmedLine of lines) {
        const line = untrimmedLine.trim(); // remove whitespace
        const [startingChar, ...data] = line.split(" ");
        
        switch (startingChar) {
          case "v":
            cachedVertices.push(data.map(parseFloat));
            break;
          case "vt":
            cachedUvs.push(data.map(Number));
            break;
          case "vn":
            cachedNormals.push(data.map(parseFloat));
            break;
          case "f":
            cachedFaces.push(data);
            break;
        }
      }
    }

    // Use these intermediate arrays to leverage Array API (.push)
    const finalPosition: toBeFloat32[] = [];
    const finalNormals: toBeFloat32[] = [];
    const finalUvs: toBeFloat32[] = [];
    const finalIndices: toBeUInt16[] = [];

 

obj 에는 v와 vt vn f가 있다. 

  • v는 vertex의 position으로 x,y,z로 구분한다.
  • vt는 vertex당 할당되는 uv 텍스처 좌표이며
    vertex와 동일한 갯수만큼 있어서 같은 index에 대응하는 vertex가 가진 uv 좌표를 의미한다.
  • vn은 vertex의 normal을 의미하고 nx,ny,nz로 구분한다.
    normal의 경우 vertex normal도 있지만 face normal도 있어서 헷갈릴 수 있다.
    하지만 face normal은 면의 수직방향으로 정해져있고, vertex normal은 꼭짓점 마다 자유로운 값을 가질 수 있다.
  • f는 face를 말하는데, 보통은 vertex 세 개를 묶어서 하나의 면 face로 취급한다.
    face는 v/vt/vn이 한 쌍으로 있다.

여기서 face가 문제인데, 삼각형인 경우 face 한 줄에 세 개의 vertex에 대한 정보가 있지만

f v/vt/vn v/vt/vn v/vt/vn

위처럼 세 개가 아니라 네 개인 경우 quad라고 하여

 

이렇게 사각형으로 한 face가 정의된다.

 

quad의 형태를 triangle로 인지

 

예상컨데 문제의 원인이 quad를 triangle로 해결하려다가 문제가 생기는게 아닐까 싶다.

근데 현재 코드에서는 아무리 봐도 face를 3 개씩 분리해서 처리하는 구간이 안보인다.

 

small cloth

smallCloth.obj
# Blender v3.0.0 OBJ File: 'cloth_30_45_l.blend'
# www.blender.org
mtllib cloth_20_30_l.mtl
o Grid
v -0.500000 -0.750000 -0.000000
v -0.450000 -0.750000 -0.000000
v -0.400000 -0.750000 -0.000000
v -0.500000 -0.700000 -0.000000
v -0.450000 -0.700000 -0.000000
v -0.400000 -0.700000 -0.000000
vt 0.000000 0.000000
vt 0.050000 0.000000
vt 0.050000 0.033333
vt 0.000000 0.000000
vt 0.050000 0.000000
vt 0.050000 0.033333
vn 0.0000 -0.0000 1.0000
usemtl None
s off
f 1/1/1 2/2/1 4/3/1 5/4/1
f 2/2/1 3/5/1 6/6/1 5/3/1

너무 헷갈려서 작은 형태로 만들어서 테스트 해보기로 했다.

 

렌더링 해보니 에러는 안뜨고 이렇게 나온다.

 

그림으로 보면 이런 구조로 obj가 이루어져 있는데

 

이렇게 나온 것이다.

그럼 (1,2,5) 삼각형이랑 (2,3,6) 삼각형은 어디간거야?

잠시만 obj 파일 face가 잘못 입력된 것 같다.

f 1/1/1 2/2/1 4/3/1 5/4/1

5번 vertex가 나오고 나서 4번 vertex가 나와야한다.

그것보다 사실 뒤집혀있는 구조라서 그림의 vertex 순서가 잘못되었다 밑에서부터 1,2 순서인데 obj를 다시 짜야한다.

 

o Grid
v -0.500000 0.750000 -0.000000
v -0.450000 0.750000 -0.000000
v -0.400000 0.750000 -0.000000
v -0.500000 0.700000 -0.000000
v -0.450000 0.700000 -0.000000
v -0.400000 0.700000 -0.000000
vt 0.000000 0.000000
vt 0.050000 0.000000
vt 0.050000 0.033333
vt 0.000000 0.000000
vt 0.050000 0.000000
vt 0.050000 0.033333
vn 0.0000 -0.0000 1.0000
usemtl None
s off
f 1/1/1 2/2/1 5/3/1 4/4/1

vertex의 y축을 뒤집어서 위에서부터 정렬된다.

이걸 출력하면

 

이렇게 된다. 결국 face는 quad를 의미하는데 삼각형 (1,2,5)만 렌더링 되는 것이 보인다.

 

f 4/1/1 1/2/1 6/1/1 1/1/1

face 4,1, 6, 1을 추가하면

 

이렇게 (1,5,4) 삼각형도 마저 렌더링 된다.

f 1/1/1 2/2/1 5/3/1 5/4/1
f 4/1/1 1/2/1 6/1/1 1/1/1

즉 앞에서부터 3 개 씩 끊어서 (1,2,5) (5,3,1) 삼각형 두 개가 렌더링된 것이다. 남은 (6,1) 버텍스는 삼각형을 이루지 못했다. 즉 예상대고 quad를 렌더링하지 못하고 3개씩 자동으로 끊겨서 이상하게 렌더링 되었던 것이다.

 

참고 영상에서는 face 한 세트의 vertex 개수에 따라 삼각형이 몇 개 들어가는지를 계산한다. 이 방법을 여기 코드에서도 적용할 필요가 있다. indices를 분리만 해서 buffer에 그대로 넣으면 기본적으로 triangle base로 렌더링 되었던 것이 문제인 모양이다.

 

    {
      const cache: Record<string, number> = {};
      let i = 0;
      for (const faces of cachedFaces) {
        for (const faceString of faces) {
          // If we already saw this, add to indices list.
          if (cache[faceString] !== undefined) {
            finalIndices.push(cache[faceString]);
            continue;
          }

          cache[faceString] = i;
          finalIndices.push(i);

          // Need to convert strings to integers, and subtract by 1 to get to zero index.
          const [vI, uvI, nI] = faceString
            .split("/")
            .map((s: string) => Number(s) - 1);

          vI > -1 && finalPosition.push(...cachedVertices[vI]);
          uvI > -1 && finalUvs.push(...cachedUvs[uvI]);
          nI > -1 && finalNormals.push(...cachedNormals[nI]);

          i += 1;
        }
      }
    }

위 코드에서 finalIndices는 순차적인 값이 들어가면서 의미가 없어진다.

결국 indices 순서대로 v,vt,vn을 꺼내서 finalPosition,Uv,Normals에 차례로 넣는 방식이다.

이 로직 안에 face가 quad 혹은 그 이상인 경우에도 렌더링 할 수 있도록 face의 삼각형의 개수를 구하는 로직을 넣어보자.

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유