public static void CalculateTangentFrames( GeometryContent geom, string textureCoordinateChannelName, string tangentChannelName, string binormalChannelName) { var verts = geom.Vertices; var indices = geom.Indices; var channels = geom.Vertices.Channels; var normals = channels.Get <Vector3>(VertexChannelNames.Normal(0)); var uvs = channels.Get <Vector2>(textureCoordinateChannelName); CalculateTangentFrames( verts.Positions, indices, normals, uvs, out Vector3[] tangents, out Vector3[] bitangents);
/// <summary> /// Generates vertex normals by accumulation of triangle face normals. /// </summary> /// <param name="geom">The geometry which will recieve the normals.</param> /// <param name="overwriteExistingNormals">Overwrite or skip over geometry with existing normals.</param> /// <remarks> /// We use a "Mean Weighted Equally" method generate vertex normals from triangle /// face normals. If normal cannot be calculated from the geometry we set it to zero. /// </remarks> public static void CalculateNormals(GeometryContent geom, bool overwriteExistingNormals) { VertexChannel <Vector3> channel; // Look for an existing normals channel. if (!geom.Vertices.Channels.Contains(VertexChannelNames.Normal())) { // We don't have existing normals, so add a new channel. channel = geom.Vertices.Channels.Add <Vector3>(VertexChannelNames.Normal(), null); } else { // If we're not supposed to overwrite the existing // normals then we're done here. if (!overwriteExistingNormals) { return; } channel = geom.Vertices.Channels.Get <Vector3>(VertexChannelNames.Normal()); } var positionIndices = geom.Vertices.PositionIndices; Debug.Assert(positionIndices.Count == channel.Count, "The position and channel sizes were different!"); // Accumulate all the triangle face normals for each vertex. var normals = new Vector3[positionIndices.Count]; for (var i = 0; i < geom.Indices.Count; i += 3) { int ia = geom.Indices[i + 0]; int ib = geom.Indices[i + 1]; int ic = geom.Indices[i + 2]; var aa = geom.Vertices.Positions[ia]; var bb = geom.Vertices.Positions[ib]; var cc = geom.Vertices.Positions[ic]; var faceNormal = Vector3.Cross(cc - bb, bb - aa); var length = faceNormal.Length(); if (length > 0.0f) { faceNormal /= length; // We are using the "Mean Weighted Equally" method where each // face has an equal weight in the final normal calculation. // // We could maybe switch to "Mean Weighted by Angle" which is said // to look best in most cases, but is more expensive to calculate. // // There is also an idea of weighting by triangle area, but IMO the // triangle area doesn't always have a direct relationship to the // shape of a mesh. // // For more ideas see: // // "A Comparison of Algorithms for Vertex Normal Computation" // by Shuangshuang Jin, Robert R. Lewis, David West. // normals[positionIndices[ia]] += faceNormal; normals[positionIndices[ib]] += faceNormal; normals[positionIndices[ic]] += faceNormal; } } // Normalize the gathered vertex normals. for (var i = 0; i < normals.Length; i++) { var normal = normals[i]; var len = normal.Length(); if (len > 0.0f) { normals[i] = normal / len; } else { // TODO: It would be nice to be able to log this to // the pipeline so that it can be fixed in the model. // TODO: We could maybe void this by a better algorithm // above for generating the normals. // We have a zero length normal. You can argue that putting // anything here is better than nothing, but by leaving it to // zero it allows the caller to detect this and react to it. normals[i] = Vector3.Zero; } } // Set the new normals on the vertex channel. for (var i = 0; i < channel.Count; i++) { channel[i] = normals[geom.Indices[i]]; } }
private int SetupVertexDeclaration(VertexBufferContent result) { var offset = 0; // We always have a position channel result.VertexDeclaration.VertexElements.Add( new VertexElement(offset, VertexElementFormat.Vector3, VertexElementUsage.Position, 0)); offset += VertexElementFormat.Vector3.GetSize(); // Optional channels foreach (var channel in Channels) { VertexElementFormat format; // Try to determine the vertex format if (channel.ElementType == typeof(float)) { format = VertexElementFormat.Single; } else if (channel.ElementType == typeof(Vector2)) { format = VertexElementFormat.Vector2; } else if (channel.ElementType == typeof(Vector3)) { format = VertexElementFormat.Vector3; } else if (channel.ElementType == typeof(Vector4)) { format = VertexElementFormat.Vector4; } else if (channel.ElementType == typeof(Color)) { format = VertexElementFormat.Color; } else if (channel.ElementType == typeof(Byte4)) { format = VertexElementFormat.Byte4; } else if (channel.ElementType == typeof(Short2)) { format = VertexElementFormat.Short2; } else if (channel.ElementType == typeof(Short4)) { format = VertexElementFormat.Short4; } else if (channel.ElementType == typeof(NormalizedShort2)) { format = VertexElementFormat.NormalizedShort2; } else if (channel.ElementType == typeof(NormalizedShort4)) { format = VertexElementFormat.NormalizedShort4; } else if (channel.ElementType == typeof(HalfVector2)) { format = VertexElementFormat.HalfVector2; } else if (channel.ElementType == typeof(HalfVector4)) { format = VertexElementFormat.HalfVector4; } else { throw new InvalidContentException(string.Format("Unrecognized vertex content type: '{0}'", channel.ElementType)); } // Try to determine the vertex usage if (!VertexChannelNames.TryDecodeUsage(channel.Name, out VertexElementUsage usage)) { throw new InvalidContentException(string.Format("Unknown vertex element usage for channel '{0}'", channel.Name)); } // Try getting the usage index var usageIndex = VertexChannelNames.DecodeUsageIndex(channel.Name); result.VertexDeclaration.VertexElements.Add(new VertexElement(offset, format, usage, usageIndex)); offset += format.GetSize(); } result.VertexDeclaration.VertexStride = offset; return(offset); }