React 中的 Webpack 无法加载带有 GLTF 扩展的 3D 模型,显示 404 not found

Webpack in React can't load the 3D model with a GLTF extension, shows 404 not found

我正在尝试在 React with Typescript 中加载扩展名为 .gltf 的 3D 模型。 3D 模型文件夹中的文件为.gltf、.png 和.bin 文件。用于此任务的工具是 webpack 和 drei 库中的 useGLTFLoader。我尝试过不同的工具。主要来自 three.js 库,没有效果。错误显示未找到 3D 模型 404(如下所示)并且在应放置 3D 模型的位置没有显示任何内容。

GET http://localhost:3000/assets/models/Duck/glTF/Duck.gltf 404 (Not Found)

我渲染 3D 模型的组件如下所示:

import React, { Suspense } from 'react';
import { Canvas } from 'react-three-fiber';
import { useGLTFLoader } from 'drei';

const DuckModel = () => {
 const gltf = useGLTFLoader('../../assets/models/Duck/glTF/Duck.gltf', true);
 return <primitive object={gltf.scene} dispose={null} />;
};

export const ThreeDimensionComponent = () => {
 return (
  <>
   <Canvas camera={{ position: [0, 0, 10], fov: 70 }}>
    <Suspense fallback={null}>
     <mesh position={[0, 250, 0]}>
      <DuckModel />
     </mesh>
    </Suspense>
   </Canvas>
  </>
 );
};

下面我分享我的webpack配置。

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

const root = __dirname;

const gsapPath = '/node_modules/gsap/src/uncompressed/';

module.exports = {
 devtool: 'source-map',
 mode: 'development',
 entry: path.join(__dirname, 'src', 'index.tsx'),
 watch: true,
 output: {
  filename: '[name].js',
  path: path.resolve(__dirname, 'dist'),
  sourceMapFilename: '[name].js.map'
 },
 module: {
  rules: [
   {
    test: /\.(tsx|ts)$/,
    use: ['babel-loader', 'ts-loader', 'tslint-loader']
   },
   {
    test: /\.scss$/,
    use: [
     'style-loader',
     {
      loader: 'css-loader',
      options: {
       sourceMap: true
      }
     },
     {
      loader: 'postcss-loader',
      options: {
       plugins: [require('autoprefixer')()],
       sourceMap: true
      }
     },
     {
      loader: 'sass-loader',
      options: {
       sourceMap: true
      }
     }
    ]
   },
   {
    test: /\.(png|jp(e*)g|svg|gif)$/,
    use: [
     {
      loader: 'url-loader',
      options: {
       limit: 8000,
       sourceMap: true
      }
     }
    ]
   },
   {
    test: /\.(ttf|eot|woff|woff2)$/,
    use: {
     loader: 'file-loader',
     options: {
      name: 'fonts/[name].[ext]',
      sourceMap: true
     }
    }
   },
   {
    test: /\.(glb|gltf)$/,
    use: [
     {
      loader: 'file-loader'
      // options: {
      //  outputPath: 'assets/models'
      // }
     }
    ]
   },
   {
    test: /\.(bin)$/,
    use: [
     {
      loader: 'file-loader'
     }
    ]
   }
  ]
 },
 resolve: {
  extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
  modules: ['node_modules', path.resolve(__dirname, 'src')],
  alias: {
   TweenLite: 'gsap',
   CSSPlugin: 'gsap',
   Draggable: path.join(root, gsapPath + 'utils/Draggable.js'),
   ScrollToPlugin: path.join(root, gsapPath + 'plugins/ScrollToPlugin.js')
  }
 },
 devServer: {
  historyApiFallback: true,
  contentBase: './dist',
  inline: true,
  host: 'localhost',
  port: 3000
 },
 plugins: [
  new CleanWebpackPlugin({ verbose: true }),
  new HtmlWebpackPlugin({
   template: path.join(__dirname, 'src', 'index.html')
  }),
  new webpack.ProvidePlugin({
   TweenMax: 'gsap'
  }),
  new CopyWebpackPlugin({
   patterns: [{ from: 'src/assets' }]
  })
 ]
};

我的 webpack.config.js 文件位于项目的根目录中。资产文件夹位于 src 文件夹中。最后,包含 React 代码的文件位于 src/components/ThreeDimensionComponent/ThreeDimensionComponent.tsx(因此它的路径是正确的)。

您要么需要导入模型并使用从中获得的 url (url-loader),要么将其放入 public 文件夹中。您的路径在捆绑输出中无处指向。

还有一点,就是@react-three/drei 和 useGLTF(url).

这是一个加载了 3D 模型的工作示例,以备不时之需。我将 hpalu 的答案标记为正确,因为它帮助我解决了这个问题。

我需要将 Suspense 与不是 HTML 元素但来自 react-three-fiber.

的组件一起使用

也帮我解决了bug的post:

这是 React 组件:

import { useGLTF } from '@react-three/drei';
import React, { Suspense } from 'react';
import { Canvas } from 'react-three-fiber';

const DuckModel = () => {
 const gltf = useGLTF('./models/Duck/glTF/Duck.gltf', true);
 return <primitive object={gltf.scene} dispose={null} />;
};

export const ThreeDimensionComponent = () => {
 return (
  <>
   <Canvas camera={{ position: [0, 0, 3], fov: 80 }}>
    <ambientLight intensity={0.3} />
    <Suspense
     fallback={
      <mesh>
       <boxBufferGeometry args={[1, 1, 1]} />
       <meshStandardMaterial />
      </mesh>
     }
    >
     <DuckModel />
    </Suspense>
   </Canvas>
  </>
 );
};

这里是这个例子的 webpack 配置:

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

const gsapPath = '/node_modules/gsap/src/uncompressed/';

module.exports = {
 devtool: 'source-map',
 mode: 'development',
 entry: path.join(__dirname, 'src', 'index.tsx'),
 watch: true,
 output: {
  filename: '[name].js',
  path: path.resolve(__dirname, 'dist'),
  sourceMapFilename: '[name].js.map',
  publicPath: '/'
 },
 module: {
  rules: [
   {
    test: /\.(tsx|ts)$/,
    use: ['babel-loader', 'ts-loader', 'tslint-loader']
   },
   {
    test: /\.scss$/,
    use: [
     'style-loader',
     {
      loader: 'css-loader',
      options: {
       sourceMap: true
      }
     },
     {
      loader: 'postcss-loader',
      options: {
       plugins: [require('autoprefixer')()],
       sourceMap: true
      }
     },
     {
      loader: 'sass-loader',
      options: {
       sourceMap: true
      }
     }
    ]
   },
   {
    test: /\.(png|jp(e*)g|svg|gif)$/,
    use: [
     {
      loader: 'url-loader',
      options: {
       limit: 8000,
       sourceMap: true
      }
     }
    ]
   },
   {
    test: /\.(ttf|eot|woff|woff2)$/,
    use: {
     loader: 'file-loader',
     options: {
      name: 'fonts/[name].[ext]'
      // sourceMap: true
     }
    }
   },
   {
    test: /\.(glb|gltf)$/,
    use: [
     {
      loader: 'file-loader',
      options: {
       outputPath: 'assets/models',
       sourceMap: true
      }
     }
    ]
   },
   {
    test: /\.(bin)$/,
    use: [
     {
      loader: 'file-loader',
      options: {
       outputPath: 'assets/models',
       sourceMap: true
      }
     }
    ]
   }
  ]
 },
 resolve: {
  extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
  modules: ['node_modules', path.resolve(__dirname, 'src')],
  alias: {
   TweenLite: 'gsap',
   CSSPlugin: 'gsap',
   Draggable: path.join(__dirname, gsapPath + 'utils/Draggable.js'),
   ScrollToPlugin: path.join(__dirname, gsapPath + 'plugins/ScrollToPlugin.js')
  }
 },
 devServer: {
  historyApiFallback: true,
  contentBase: './dist',
  inline: true,
  host: 'localhost',
  port: 3000
 },
 plugins: [
  new CleanWebpackPlugin({ verbose: true }),
  new HtmlWebpackPlugin({
   template: path.join(__dirname, 'src', 'index.html')
  }),
  new webpack.ProvidePlugin({
   TweenMax: 'gsap'
  }),
  new CopyWebpackPlugin({
   patterns: [{ from: 'src/assets' }]
  })
 ]
};