three.js에서 GLTFLoader.load가 index.html을 content로 가져오는 문제

FGPRJS·2021년 11월 17일
0

1. 문제사항

  • GLTFLoader의 Example을 따른것 같으나 GLTFLoader에서 오류가 발생하여, GLTF파일계열을 불러 올 수 없다(.gltf(JSON규격 데이터) / .glb).
    • 다음과 같은 문제가 발생한다.

      SyntaxError: Unexpected token < in JSON at position 0
      at JSON.parse (< anonymous >)
      at GLTFLoader.parse (GLTFLoader.js:304)
      at Object.onLoad (GLTFLoader.js:194)
      at three.module.js:39290

    • JSON파일에서 <라는 예상하지 못한 토큰이 발생된 문법 오류이다.

2. 문제 원인


3. 해결 방안

  • 상기 문답에서 제시하는 해결책으로는 webpack을 사용하라는 것이다.
  • parcel을 사용한다면 다음과 같은 절차를 따르라.
    1. parcel-bundler (v1) 일 경우 다음을, parcel(v2)일 경우 다음을 참고하여, 해당 패키지를 설치한다.
      여기서는 parcel(v2)를 기준으로 설명한다.
      상기 링크에 사용방법까지 기재되어 있으므로 참고할 수 있다.
    2. package.json에 다음을 추가한다. 디폴트 경로는 원하는 경로로 한다.

      "staticFiles":{
      "staticPath": "{디폴트 경로}"
      }

    3. 다음 내용을 포함한 .parcelrc파일을 프로젝트 최상단에 추가한다. 이는 플러그인과 관련이 있다.

      {
      "extends": ["@parcel/config-default"],
      "reporters": ["...", "parcel-reporter-static-files-copy"]
      }

    4. 다음 프로젝트 경로와 2.에 정한 디폴트 경로, 그리고 하기 사진을 참조하여 사용한다.
    • 프로젝트 사진
      -> scene.gltf 파일에만 주목할 것
    • loader.load에 줄 url 경로

      'model/mobile_anti_aircraft/scene.gltf'


4. 비고

  • 없음

5. 문제파악을 위한 시행 착오

  • 해당 문제가 되는 부분을 디버깅하여,
    GLTFLoader.js의 parse( data, path, onLoad, onError) 의 contenthtml파일 그 자체를 가리키는 것을 확인했다.
    그런데 이것을 JSON.parse 시도한다.
    GLTFLoader의 content가 무엇을 가리키는 것인지는 모르지만, 적어도 JSON 방식의 데이터이어야 할 것 같다.
    (예상 : .gltf 파일이 들어가지 않을까?)
  • 따라서, GLTFLoader에 있는 three.js가 제시하는 예제로 content안에 무엇이 들어있는지 확인해 보아야 한다.
  • 다음과 같은 링크의 예시를 확인해본다.
    webgl example - animation_skinning_blending
  • 문제가 되는 코드 부문은 다음과 같다.
    (in GLTFLoader.js)
parse( data, path, onLoad, onError ) {

  //content는 let으로 선언된 변수로
  //parse함수 괄호 내에서만 유효하다.
  let content;
  const extensions = {};
  const plugins = {};

  //여러가지 분기를 거쳐 content를 결정한다.
  
  if ( typeof data === 'string' ) {
    content = data;
  } else {
    const magic = LoaderUtils.decodeText(
        new Uint8Array( data, 0, 4 ) 
    );
    
    if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) {
      try {
        extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = 
          new GLTFBinaryExtension( data );
      } catch ( error ) {
        if ( onError ) onError( error );
        return;
      }

      content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content;
    } else {
      content = LoaderUtils.decodeText( new Uint8Array( data ) );
    }
  }

  //이 과정이 끝나면, content가 무엇인지 결정된다.
  
  //오류가 나는 부분.
  const json = JSON.parse( content );

  if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) {
    if ( onError ) onError( 
      new Error( 
        'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' 
      ) 
    );
    return;
  }

    //이후 생략
  • three.js에서 제시한 example에서의 요소들은 다음과 같다.

    • parse의 인자들
      1. pathmodels/gltf/로서, 실제 모델 glb파일이 존재하는 디렉토리까지 표시한다. 해당 파일을 직접 표시하지는 않는다.
    • magicBINARY_EXTENSION_HEADER_MAGIC이다.
    • content
      extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content 이다.
    • 궁극적으로, content는 어떤 JSON파일이 된다.
  • 그러나, 현재 코드에서의 요소는 다음과 같다.

    • parse의 인자들
      1. path자체에는 문제가 없이, gltf파일이 존재하는 디렉토리까지 표시한다.
    • magicBINARY_EXTENSION_HEADER_MAGIC이 아니다.
    • 궁극적으로, content는 html이 된다.
      (가능성 : this처럼 찾지 못해서 window가 대상이 되었는가?)
  • 이것만으로는 판단하기 어려워, parse를 호출하는 load함수를 체크해본다.
    load함수는 다음과 같으며, 사용자(개발자)가 실제로 GLTFLoader에서 사용하는 함수이다.

//url : 로드할 파일 경로(파일명 포함)
//onload : 로드가 완료되었을 때 호출할 콜백
//onProgress : 로드가 진행중일 때 호출할 콜백.
//로딩 진행도를 인자로 주고 콜백을 호출한다.
//onError : 오류 발생시 호출할 콜백. 
//오류 정보 error를 인자로 주고 콜백을 호출한다.
load( url, onLoad, onProgress, onError ) {

  const scope = this;
  let resourcePath;

  //GLTFLoader는 Loader를 상속받은 class이다.
  //따라서 this는 현재 GLTFLoader class자체이다.
  //default resourcePath를 정할 수 있는 듯.
  if ( this.resourcePath !== '' ) {
    resourcePath = this.resourcePath;
  } else if ( this.path !== '' ) {
    resourcePath = this.path;
  } else {
    //default? resourcePath가 없다면, 파라미터로 받아왔던 url을 extractUrlBase로 처리한 결과를 받는다.
    resourcePath = LoaderUtils.extractUrlBase( url );
  }

  // Tells the LoadingManager to track an extra item, which resolves after
  // the model is fully loaded. This means the count of items loaded will
  // be incorrect, but ensures manager.onLoad() does not fire early.
  this.manager.itemStart( url );

  const _onError = function ( e ) {
    if ( onError ) {
      onError( e );
    } else {
      console.error( e );
    }
    
    scope.manager.itemError( url );
    scope.manager.itemEnd( url );
  };

  const loader = new FileLoader( this.manager );

  loader.setPath( this.path );
  loader.setResponseType( 'arraybuffer' );
  loader.setRequestHeader( this.requestHeader );
  loader.setWithCredentials( this.withCredentials );

  //loader에게 load를 맡긴다.
  //load가 완료될시 이하 콜백함수 실행.
  loader.load( url, function ( data ) {

    try {
      //여기서 parse가 발생한다.
      //data는 콜백함수에서 전달받는 인자이다.
      scope.parse( data, resourcePath, function ( gltf ) {
        onLoad( gltf );
        scope.manager.itemEnd( url );
      }, _onError );
    } catch ( e ) {
      _onError( e );
    }
  }, onProgress, _onError );
}
  • url까지는 제대로 받지만, loader가 load하는 파일이 이상하다.

  • 테스트 : 존재하지 않는 이상한 경로로 시도해본다.

    • 시도 : 존재하지 않는 이상한 경로 '123'을 기재하였다.
    • 결과 : content는 html파일이 되어버렸다.

6. 문제 해결을 위한 시행착오

  • 경로 문제?

    • 시도 : __filename, __dirname 시도해보기
    • 결과 : 해결하지 못함.
  • 다음과 같은 프로젝트 경로를 갖는다.

  • 여기서 절대경로로 시도하였다.

    • 시도: 'resources/model/mobile_anti_aircraft_gun/scene.gltf'
    • 결과: 실패
  • 여기서 상대경로로 시도하였다.

    • 시도: 'resources/model/mobile_anti_aircraft_gun/scene.gltf`
    • 결과: 실패
  • node.js환경에 의한 것?
    three.js는 node.js에서의 사용을 권장하지 않는 편이다. 특히 three.js는 ESM(EcmaScript Module)기반인데, node.js는 자체적인 CJS(CommonJS)의 모듈을 사용하기 때문이다. 이는 공식 문서에도 기재되어있다.

    • 시도 : node -> parcel을 통하지 않고, live server를 사용한다.
    • 결과 : 해결되지 않았다.
  • 프로젝트 뒤집기

    • 시도: three-jsm 프로젝트로 교체
      -> three.js의 ES6모듈을 사용하기 위한 공식 예시 프로젝트
    • 결과 : 궁극적으로는 오류 발생을 막을 수 없었음.
  • 소스에 파일이 추가되지 않음을 확인. 소스에 파일을 추가할 수 있는 방법에 대하여
    • 참고

      • Your web server does not serve a glb but HTML content
      • parcel의 웹 서버에서 glb파일을 지원할 수 없다는 문제
    • 참고2

      • babylon.js에서 확인한 gltf파일 읽기
      • v2parcel에 대응하는 static reader
      • 네트워크에 파일이 올라간 것을 확인 할 수 있다.
      • 하지만 여전히 오류는 발생한다.
    • 시도 :

      1. npm install -D parce-reporter-static-files-copy 명령어를 통해 일부 디렉토리에서 출력 디렉토리로 정적 파일을 복사하는 ParcelJS v2 플러그인을 설치한다.

      2. static reader의 예시를 참고하여, .parcelrc파일을 만들어, 예제와 같게 하였다.

      3. package.json파일을 수정하였다.
        "staticFiles" 키를 만들고, "staticPath"를 "resources"로 한 것을 객체로 넣었다.

      4. 경로 수정
        이제 파일의 기본 경로는 resources가 되었고,
        경로를
        'model/mobile_anti_aircraft/scene.gltf'
        로 변경하였다.

    • 결과 :

      • 더이상 해당 오류가 발생하지 않는다.
      • 하지만, 다음 오류가 발생한다.

      [.WebGL-0x29e4001a1400] GL_INVALID_OPERATION: Texture format does not support mipmap generation.

profile
FGPRJS

0개의 댓글