bharling
bharling

Reputation: 2850

Parsing THREE.js json mesh format normals errors

EDIT: Demo finally online: http://bharling.github.io/deferred/index.html use the dropdown to switch to torus models to see the problem live. NOTE: requires Webgl MRT extensions.

I've been in the process of developing my own deferred rendering engine in WebGL for some time now and have got to the stage where I have a working prototype using GBuffers and MRT extensions that renders some teapots quite satisfactorily. This is developed from scratch mainly for me to learn WebGL properly without using any frameworks, and to understand deferred rendering. For anyone interested - the source is on github here: https://github.com/bharling/webgl-defer

I've got to the stage where I'm tired of just seeing teapots and have tried to implement a loader for THREE.js JSON format models. I've ported ( copied ) the main parts of the loader and I can get meshes to appear with the correct vertices and index buffers which is great, but normals are consistently screwy. I'm choosing only to support indexed geometry with vertex UVs and vertex normals and a single material ( eventually this is supposed to be PBR based ), so I ignore anything else in the JSON and write only what I support straight to Float32Arrays (etc). Below is my import code, plus a screenshot of the weird normals I'm seeing.

  parseThreeJSModel: (data) =>

    isBitSet = (value, position) ->
      return value & ( 1 << position )

    vertices = data.vertices
    uvs = data.uvs
    indices = []
    normals = data.normals

    vertexNormals = []
    vertexUvs = []
    vertexPositions = []

    @vertexPositionBuffer = new DFIR.Buffer( new Float32Array( data.vertices ), 3, gl.STATIC_DRAW )
    @vertexTextureCoordBuffer = new DFIR.Buffer( new Float32Array( data.uvs[0] ), 2, gl.STATIC_DRAW )

    numUvLayers = data.uvs.length
    faces = data.faces

    zLength = faces.length
    offset = 0

    while offset < zLength
      type = faces[offset++]
      isQuad              = isBitSet( type, 0 )
      hasMaterial         = isBitSet( type, 1 )
      hasFaceVertexUv     = isBitSet( type, 3 )
      hasFaceNormal       = isBitSet( type, 4 )
      hasFaceVertexNormal = isBitSet( type, 5 )
      hasFaceColor       = isBitSet( type, 6 )
      hasFaceVertexColor  = isBitSet( type, 7 )

      if isQuad
        indices.push faces[ offset ]
        indices.push faces[ offset + 1 ]
        indices.push faces[ offset + 3 ]
        indices.push faces[ offset + 1 ]
        indices.push faces[ offset + 2 ]
        indices.push faces[ offset + 3 ]
        offset += 4

        if hasMaterial
          offset++

        if hasFaceVertexUv
          for i in [0 ... numUvLayers] by 1
            uvLayer = data.uvs[i]
            for j in [0 ... 4] by 1
              uvIndex = faces[offset++]
              u = uvLayer[ uvIndex * 2 ]
              v = uvLayer[ uvIndex * 2 + 1 ]

              if j isnt 2 
                vertexUvs.push u
                vertexUvs.push v
              if j isnt 0
                vertexUvs.push u
                vertexUvs.push v

        if hasFaceNormal
          offset++

        if hasFaceVertexNormal
          for i in [0 ... 4] by 1
              normalIndex = faces[ offset++ ] * 3
              normal = [ normalIndex++, normalIndex++, normalIndex ] 
              if i isnt 2
                vertexNormals.push normals[normal[0]]
                vertexNormals.push normals[normal[1]]
                vertexNormals.push normals[normal[2]]
              if i isnt 0
                vertexNormals.push normals[normal[0]]
                vertexNormals.push normals[normal[1]]
                vertexNormals.push normals[normal[2]]

        if hasFaceColor
          offset++

        if hasFaceVertexColor
          offset += 4
      else
        indices.push faces[offset++]
        indices.push faces[offset++]
        indices.push faces[offset++]

        if hasMaterial
          offset++
        if hasFaceVertexUv
          for i in [0 ... numUvLayers] by 1
            uvLayer = data.uvs[i]
            for j in [0 ... 3] by 1
              uvIndex = faces[offset++]
              u = uvLayer[ uvIndex * 2 ]
              v = uvLayer[ uvIndex * 2 + 1 ]
              if j isnt 2 
                vertexUvs.push u
                vertexUvs.push v
              if j isnt 0
                vertexUvs.push u
                vertexUvs.push v

        if hasFaceNormal
          offset++

        if hasFaceVertexNormal
          for i in [0 ... 3] by 1
            normalIndex = faces[ offset++ ] * 3

            vertexNormals.push normals[normalIndex++]
            vertexNormals.push normals[normalIndex++]
            vertexNormals.push normals[normalIndex]

        if hasFaceColor
          offset++

        if hasFaceVertexColor
          offset +=3

    @vertexNormalBuffer = new DFIR.Buffer( new Float32Array( vertexNormals ), 3, gl.STATIC_DRAW )
    @vertexIndexBuffer = new DFIR.Buffer( new Uint16Array( indices ), 1, gl.STATIC_DRAW, gl.ELEMENT_ARRAY_BUFFER )
    @loaded=true

Weird Normals from simple imported cube exported from blender using THREE.s official exporter

Screenshot above should be the expanded world-space normals gbuffer.

One big difference in my engine is that I don't store face information in classes ( like THREE.Face3 ) preferring just to write the data straight into buffer attributes, more like THREE.BufferGeometry.

Up until now I have been using just the utah teapot model from the 'Learning WebGL' course, specifically this link http://learningwebgl.com/blog/?p=1658 . This model works exactly right in my engine, and is supposedly an early version of the THREE JSON format. I load that model as in the tutorial by writing the json arrays for vertices, texcoords etc straight to webgl buffers, and this works perfectly in my engine, but even a simple cube exported from the latest blender exporter doesn't seem to work so well.

Any suggestions greatly appreciated, thanks!

EDIT: Screenshot of normals pass using the teapot model from webgl tutorials. Note: I'm not suggesting the THREE exporter is broken, rather my code that parses it. I've been over the GBuffer implementation many times in this engine over the last year or so, and am pretty sure this is correct now, Im just having a bit of problems understanding the THREE json model format.

enter image description here

Upvotes: 1

Views: 601

Answers (2)

WacławJasper
WacławJasper

Reputation: 3364

Not sure if your translation between Three.Face3 into the buffers are correct. Here is my own Three Json parser which I use to load animated skinned mesh from blender which may be of some help to you. Likewise, only partial features are supported. It returns indexed buffers to be used with .drawElements rather than .drawArrays.

function parsePackedArrayHelper(outArray, index, dataArray, componentSize){
    for (var c = 0; c<componentSize; c++){
        outArray.push(dataArray[index*componentSize + c]);
    }
}

function parseThreeJson(geometry){
    if (isString(geometry)){
        geometry = JSON.parse(geometry);
    }

    var faces = geometry["faces"];
    faces = convertQuadToTrig(faces); // can use the triangulate modifer in blender to skip this 
    // and others data etc... 

    var seenVertices = new Map();
    var currentIndex = 0;
    var maxIndex = 0;

    var c = 0; // current index into the .faces array
    while (c < faces.length){
        var bitInfo = faces[c];
        var hasMaterials = (bitInfo &(1<<1)) !== 0;
        var hasVertexUvs = (bitInfo &(1<<3)) !== 0;
        var hasVertexNormals = (bitInfo &(1<<5)) !== 0;
        var faceIndex = c+1;

        c += (
            4 + //1 for the bitflag and 3 for vertex positions
            (hasMaterials? 1: 0) +
            (hasVertexUvs? 3: 0) +
            (hasVertexNormals ? 3: 0)
        );

        for (var v = 0;v<3;v++){
            var s = 0; 
            var vertIndex = faces[faceIndex+v];
            var uvIndex = -1;
            var normalIndex = -1;
            var materialIndex = -1;
            if (hasMaterials){
                s += 1;
                materialIndex = faces[faceIndex+3];
            }
            if (hasVertexUvs){
                s += 3;
                uvIndex = faces[faceIndex+s+v];
            }
            if (hasVertexNormals){
                s += 3;
                normalIndex = faces[faceIndex+s+v];
            }

            var hash = ""+vertIndex+","+uvIndex+","+normalIndex;
            if (seenVertices.has(hash)){
                indices[currentIndex++] = seenVertices.get(hash);
            } else {
                seenVertices.set(hash, maxIndex);
                indices[currentIndex++] = maxIndex++;
                parsePackedArrayHelper(verticesOut, vertIndex, verticesData, 3);
                if (boneInfluences > 1){
                    // skinning data skipped
                }
                if (hasVertexUvs){
                    parsePackedArrayHelper(uvsOut, uvIndex, uvsData[0], 2);

                    // flip uv vertically; may or may not be needed
                    var lastV = uvsOut[uvsOut.length-1];
                    uvsOut[uvsOut.length-1] = 1.0 - lastV;

                }
                if (hasVertexNormals){
                    parsePackedArrayHelper(normalsOut, normalIndex, normalsData, 3);
                }

                if (hasMaterials){
                    materialIndexOut.push(materialIndex);
                }
            }
        }
    }
}

Upvotes: 1

Wilt
Wilt

Reputation: 44326

It is not directly the solution to your problem, but might be helpful anyway.

If you have primitive shapes like the boxes in the example above, you can also use THREE.FlatShading and skip setting normals all together. Might be easier in some cases:

var material = new THREE.???Material({ shading: THREE.FlatShading });

Upvotes: 0

Related Questions