Reputation: 2850
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
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.
Upvotes: 1
Views: 601
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
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