1
votes

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

2
Have you first confirmed that the problem lies in the json mesh importer and not in your g-buffer or in rendering of the world normals? Also are you using smooth or flat shading in blender?WacławJasper
I'm pretty sure the problem is not with my gbuffer implementation, as I said I can use the sample teapot model from the webgl tutorials site and this works fine ( I'll try and attach a screenshot of that to the question ). As for normals, I'm just exporting the default cube straight out of blender so the normals should probably be flat rather than smoothed. I've also tried exporting suzanne with similar results, the mesh and indices are fine, normals are all over the place.bharling

2 Answers

1
votes

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);
                }
            }
        }
    }
}
0
votes

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 });