WebGL加载OBJ模型无显示问题排查及代码求助
WebGL加载OBJ模型无法显示问题排查与修复
问题现象
开发WebGL项目时,尝试加载并显示简单OBJ模型,但模型始终无法渲染。控制台出现OBJ关键字未处理警告,以及WebGL顶点数据不匹配的错误。打印顶点数据长度显示为36,与OBJ文件一致,但WebGL提示顶点获取所需数据与属性提供的不匹配。
控制台报错信息
Unhandled Model Keyword: mtllib at line 1 loadModel.js:85:21 Unhandled Model Keyword: o at line 2 loadModel.js:85:21 Unhandled Model Keyword: usemtl at line 37 loadModel.js:85:21 Unhandled Model Keyword: s at line 38 loadModel.js:85:21 <empty string> app.js:68:13 <empty string> app.js:74:13 WebGL warning: drawArraysInstanced: Vertex fetch requires 36, but attribs only supply 26. 32 After reporting 32, no further warnings will be reported for this WebGL context.
相关代码
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="index.css" rel="stylesheet"> <link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro&display=swap" rel="stylesheet"> <script src="gl-matrix-min.js" defer></script> <script type="module" src="app.js" defer></script> </head> <body> <canvas></canvas> <script> const c = document.getElementsByTagName("canvas")[0]; c.width = window.innerWidth; c.height = window.innerHeight; </script> </body> </html>
App.js
import "./gl-matrix-min.js"; const { mat2, mat2d, mat4, mat3, quat, quat2, vec2, vec3, vec4 } = glMatrix; import { loadTexture } from "./loadTexture.js" import { parseOBJ } from "./loadModel.js"; import { loadTextResource, repeat } from "./util.js"; window.onload = function (){ loadTextResource('/Shaders/shader.vs.glsl', function(vsErr, vsText){ if(vsErr){ alert("Error getting vertex shader (check console for more info)"); console.error(vsErr); }else{ loadTextResource('/Shaders/shader.fs.glsl', function(fsErr, fsText){ if(fsErr){ alert("Error getting fragment shader (check console for more info)"); console.error(fsErr); }else{ RunScene(vsText, fsText); } }); } }); } async function RunScene(vsText, fsText){ const canvas = document.querySelector('canvas'); const gl = canvas.getContext('webgl'); if(!gl){ console.error("WebGL not supported on this browser!"); } const response = await fetch('Models/test.obj'); const text = await response.text(); const data = parseOBJ(text); //Vertex Data const vertexData = data.positions; // Image UV Data const uvData = data.texCoords; // F|L|B|R|T|U const normalData = data.normals; // console.log(normalData); const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexData), gl.STATIC_DRAW); const uvBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(uvData), gl.STATIC_DRAW); const normalBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normalData), gl.STATIC_DRAW); const picture = loadTexture(gl, `Textures/Pablo.png`); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, picture); const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vsText); gl.compileShader(vertexShader); console.log(gl.getShaderInfoLog(vertexShader)); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fsText); gl.compileShader(fragmentShader); console.log(gl.getShaderInfoLog(fragmentShader)); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); const positionLocation = gl.getAttribLocation(program, 'position'); gl.enableVertexAttribArray(positionLocation); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0); const uvLocation = gl.getAttribLocation(program, 'uv'); gl.enableVertexAttribArray(uvLocation); gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer); gl.vertexAttribPointer(uvLocation, 2, gl.FLOAT, false, 0, 0); const normalLocation = gl.getAttribLocation(program, 'normal'); gl.enableVertexAttribArray(normalLocation); gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer); gl.vertexAttribPointer(normalLocation, 3, gl.FLOAT, false, 0, 0); gl.useProgram(program); gl.enable(gl.DEPTH_TEST); const uniformLocations = { matrix: gl.getUniformLocation(program, 'matrix'), normalMatrix: gl.getUniformLocation(program, 'normalMatrix'), textureLocation: gl.getUniformLocation(program, 'textureID'), }; const modelMatrix = mat4.create(); const viewMatrix = mat4.create(); const projectionMatrix = mat4.create(); mat4.perspective(projectionMatrix, 75 * Math.PI/180, canvas.width/canvas.height, 1e-4, 1e4); const mvMatrix = mat4.create(); const mvpMatrix = mat4.create(); const normalMatrix = mat4.create(); mat4.translate(viewMatrix, viewMatrix, [0, 0.01, 2]); mat4.invert(viewMatrix, viewMatrix); gl.uniform1i(uniformLocations.textureLocation, 0); function animate(){ requestAnimationFrame(animate); mat4.rotateX(modelMatrix, modelMatrix, Math.PI/100); mat4.rotateY(modelMatrix, modelMatrix, Math.PI/200); mat4.multiply(mvMatrix, viewMatrix, modelMatrix); mat4.multiply(mvpMatrix, projectionMatrix, mvMatrix); mat4.invert(normalMatrix, mvMatrix); mat4.transpose(normalMatrix, normalMatrix); gl.uniformMatrix4fv(uniformLocations.matrix, false, mvpMatrix); gl.uniformMatrix4fv(uniformLocations.normalMatrix, false, normalMatrix); gl.drawArrays(gl.TRIANGLES, 0, vertexData.length/3); } animate(); }
loadModel.js
export function parseOBJ(text){ const objPositions = [[0, 0, 0]]; const objTexCoords = [[0, 0]]; const objNormals = [[0, 0, 0]]; const objVertexData = [ objPositions, objTexCoords, objNormals, ]; let webglVertexData = [ [], //Verts [], //UV [], //Normals ]; function addVertex(vert) { const ptn = vert.split('/'); ptn.forEach((objIndexStr, i) => { if (!objIndexStr) { return; } const objIndex = parseInt(objIndexStr); const dataArray = objVertexData[i]; if (dataArray && dataArray.length > 0) { let index = objIndex > 0 ? objIndex - 1 : dataArray.length + objIndex; // Ensure the index is within bounds index = Math.max(0, Math.min(index, dataArray.length - 1)); const data = dataArray[index]; if (data !== undefined) { webglVertexData[i].push(...dataArray[index]); } else { console.error(`Error: Undefined data at index ${index} for objVertexData[${i}]`); } } else { console.error(`Error: No data found for objVertexData[${i}]`); } }); } const keywords = { v(parts){ objPositions.push(parts.map(parseFloat)); }, vn(parts){ objTexCoords.push(parts.map(parseFloat)); }, vt(parts){ objNormals.push(parts.map(parseFloat)); }, f(parts){ const numTriangles = parts.length - 2; for(let tri = 0; tri < numTriangles; ++tri){ addVertex(parts[0]); addVertex(parts[tri + 1]); addVertex(parts[tri + 2]); } } }; const keywordRE = /(\w*)(?: )*(.*)/; const lines = text.split('\n'); for(let lineNum = 0; lineNum < lines.length; ++lineNum){ const line = lines[lineNum].trim(); if(line === '' || line.startsWith('#')){ continue; } const m = keywordRE.exec(line); if(!m){ continue; } const [, keyword, unparsedArgs] = m; const parts = line.split(/\s+/).slice(1); const handler = keywords[keyword]; if(!handler){ console.warn('Unhandled Model Keyword:', keyword, 'at line', lineNum + 1); continue; } handler(parts, unparsedArgs); } return { positions: webglVertexData[0], texCoords: webglVertexData[1], normals: webglVertexData[2] }; }
问题根源与修复方案
1. 核心错误:OBJ关键字映射颠倒
在loadModel.js的keywords对象中,vn(法线)和vt(纹理坐标)的处理逻辑完全颠倒:
- 原本
vn应该处理法线数据,却错误地往objTexCoords中添加数据 - 原本
vt应该处理纹理坐标,却错误地往objNormals中添加数据
这导致UV数据和法线数据被交换,WebGL获取顶点属性时,实际数据的数量与预期不匹配,引发报错。
修复后的keywords对象:
const keywords = { v(parts){ objPositions.push(parts.map(parseFloat)); }, vn(parts){ objNormals.push(parts.map(parseFloat)); // 修正:vn对应法线 }, vt(parts){ objTexCoords.push(parts.map(parseFloat)); // 修正:vt对应UV坐标 }, f(parts){ const numTriangles = parts.length - 2; for(let tri = 0; tri < numTriangles; ++tri){ addVertex(parts[0]); addVertex(parts[tri + 1]); addVertex(parts[tri + 2]); } }, // 添加以下空处理消除未处理关键字警告 mtllib() {}, o() {}, usemtl() {}, s() {} };
2. 消除未处理关键字警告
添加mtllib、o、usemtl、s的空处理函数,避免控制台输出警告信息,这些关键字不影响模型渲染,仅用于OBJ文件的材质和分组定义。
3. 验证顶点数据一致性
修复后,重新打印data.positions、data.texCoords、data.normals的长度,确保三者的顶点数量一致(每个顶点对应一组位置、UV、法线数据),WebGL就能正确获取所有顶点属性,渲染出模型。
内容的提问来源于stack exchange,提问作者Homemade Tech
相关产品推荐
相关产品推荐

