/// <summary> /// Expands the declaration of a mesh to include tangent and binormal information /// </summary> /// <param name="device">The <see cref="Device"/> containing the mesh</param> /// <param name="mesh">The mesh to be manipulated</param> /// <param name="hadNormals">Did the original declaration already contain normals?</param> /// <param name="hadTangents">Did the original declaration already contain tangents?</param> /// <returns><c>true</c> if the mesh declaration was expanded, <c>false</c> if the declaration didn't need to be changed</returns> private static bool ExpandDeclaration(Device device, ref Mesh mesh, out bool hadNormals, out bool hadTangents) { #region Sanity checks if (device == null) { throw new ArgumentNullException(nameof(device)); } if (mesh == null) { throw new ArgumentNullException(nameof(mesh)); } #endregion // Check if vertex declaration of mesh already is already ok var decl = mesh.GetDeclaration(); if (CompareDecl(PositionNormalBinormalTangentTextured.GetVertexElements(), decl) || CompareDecl(PositionNormalMultiTextured.GetVertexElements(), decl)) { hadNormals = true; hadTangents = true; return(false); } #region Find existing info hadNormals = false; hadTangents = false; for (int i = 0; i < 6 && i < decl.Length; i++) { if (decl[i].Usage == DeclarationUsage.Normal) { hadNormals = true; break; } if (decl[i].Usage == DeclarationUsage.Tangent) { hadTangents = true; break; } } #endregion // Select the appropriate new vertex format decl = CompareDecl(PositionMultiTextured.GetVertexElements(), decl) ? PositionNormalMultiTextured.GetVertexElements() : PositionNormalBinormalTangentTextured.GetVertexElements(); // Clone the mesh to change the vertex format Mesh tempMesh = mesh.Clone(device, mesh.CreationOptions, decl); mesh.Dispose(); mesh = tempMesh; return(true); }
// Terrainblocks erzeugen (GeoMipMapping) // 0: True 1: False private int CreateTerrainBlocks() { try { // Deklarationen int startVertex = 0; int counter1 = 0; int counter2 = 0; PositionNormalMultiTextured[] verts = new PositionNormalMultiTextured[289]; // Initialisierung tBlocks = new TerrainBlock[(((Width-1)/16))*((Depth-1)/16)]; for (int x=0; x<Width-1; x+=16) { for (int z=0; z<Depth-1; z+=16) { startVertex = z+x*Depth; counter1 = 0; for (int u=0; u<17; u++) { for (int v=0; v<17; v++) { verts[counter1] = Vertices[startVertex+v+u*(Depth)]; counter1 ++; } } tBlocks[counter2] = new TerrainBlock(dev,verts,3); counter2 ++; } } return 0; } catch { return 1; } }
// Vertexliste erzeugen // 0: True 1: False (Vertexberechnung) 2: False (Normalenberechnung) 3: False (Vertexbuffer) private int CreateVertices() { // Deklarationen short[,] VertexPosition = {{0,0},{1,-1},{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1,},{0,-1}}; short[,] Faces = {{0,1,8},{0,2,1},{0,4,2},{4,3,2},{0,5,4},{0,6,5},{0,8,6},{8,7,6}}; Vector3[] TempVertices = new Vector3[9]; Vector3[] Normals = new Vector3[8]; try { // Vertexberechnung NumberOfVertices = Width * Depth; Vertices = new PositionNormalMultiTextured[NumberOfVertices]; for (int x=0; x<Width; x++) { for (int z=0; z<Depth; z++) { Vertices[z+x*Depth] = new PositionNormalMultiTextured(x*XScale,Heightmap[x,z]*YScale,z*ZScale,0.0f,1.0f,0.0f,1.0f/Width*x,1.0f/Depth*z,1.0f/Width*x,1.0f/Depth*z,x/20.0f,z/20.0f); } } } catch { return 1; } try { // Normalenberechnung for (int x=0; x<Width; x++) { for (int z=0; z<Depth; z++) { // Vertices for (int a=0; a<9; a++) { try { TempVertices[a].X = XScale*(x + VertexPosition[a,0]); TempVertices[a].Z = ZScale*(z + VertexPosition[a,1]); TempVertices[a].Y = YScale*(Heightmap[x + VertexPosition[a,0],z + VertexPosition[a,1]]); } catch { TempVertices[a].X = 0; TempVertices[a].Y = 0; TempVertices[a].Z = 0; } } // Normalen for (int a=0; a<8; a++) { Normals[a] = Vector3.Cross(Vector3.Normalize(Vector3.Subtract(TempVertices[Faces[a,0]],TempVertices[Faces[a,1]])), Vector3.Normalize(Vector3.Subtract(TempVertices[Faces[a,1]],TempVertices[Faces[a,2]]))); Normals[a] = Vector3.Normalize(Normals[a]); } // Vertexnormale for (int a=1; a<8; a++) { Normals[0] = Vector3.Add(Normals[0],Normals[a]); } Vertices[z+x*Depth].Normal.X = Normals[0].X; Vertices[z+x*Depth].Normal.Y = Normals[0].Y; Vertices[z+x*Depth].Normal.Z = Normals[0].Z; } } } catch { return 2; } // Vertexbuffer erzeugen if (CreateVertexBuffer() == 1) return 3; return 0; }
/// <summary> /// Generate normals and tangents if not present and convert into TangentVertex format for shaders. /// </summary> /// <param name="device">The <see cref="Device"/> containing the mesh</param> /// <param name="mesh">The mesh to be manipulated</param> /// <param name="weldVertexes">Weld vertexes before generating tangents. /// Useful for organic objects, stones, trees, etc. (anything with a lot of round surfaces). /// If a lot of single faces are not connected on the texture (e.g. rockets, buildings, etc.) do not use.</param> public static void GenerateNormalsAndTangents(Device device, ref Mesh mesh, bool weldVertexes) { #region Sanity checks if (device == null) { throw new ArgumentNullException(nameof(device)); } if (mesh == null) { throw new ArgumentNullException(nameof(mesh)); } #endregion bool hadNormals, hadTangents; if (!ExpandDeclaration(device, ref mesh, out hadNormals, out hadTangents)) { return; } var decl = mesh.GetDeclaration(); #region Check existing info bool gotMilkErmTexCoords = false; bool gotValidNormals = true; bool gotValidTangents = true; var vertexes = BufferHelper.ReadVertexBuffer <PositionNormalBinormalTangentTextured>(mesh); // Check all vertexes for (int num = 0; num < vertexes.Length; num++) { // We need at least 1 texture coordinate different from (0, 0) if (vertexes[num].Tu != 0.0f || vertexes[num].Tv != 0.0f) { gotMilkErmTexCoords = true; } // All normals and tangents must be valid, otherwise generate them below if (vertexes[num].Normal == default(Vector3)) { gotValidNormals = false; } if (vertexes[num].Tangent == default(Vector3)) { gotValidTangents = false; } // If we found valid texture coordinates and no normals or tangents, // there isn't anything left to check here if (gotMilkErmTexCoords && !gotValidNormals && !gotValidTangents) { break; } } // If declaration had normals, but we found no valid normals, // set hadNormals to false and generate valid normals (see below) if (!gotValidNormals) { hadNormals = false; } // Same check for tangents if (!gotValidTangents) { hadTangents = false; } // Generate dummy texture coordinates if (!gotMilkErmTexCoords) { for (int num = 0; num < vertexes.Length; num++) { vertexes[num].Tu = -0.75f + vertexes[num].Position.X / 2.0f; vertexes[num].Tv = +0.75f - vertexes[num].Position.Y / 2.0f; } } BufferHelper.WriteVertexBuffer(mesh, vertexes); #endregion if (!hadNormals) { using (new TimedLogEvent("Computed normals")) mesh.ComputeNormals(); } if (weldVertexes) { // Reduce amount of vertexes var weldEpsilons = new WeldEpsilons { Position = 0.0001f, Normal = 0.0001f }; mesh.WeldVertices(WeldFlags.WeldPartialMatches, weldEpsilons); if (!hadTangents) { #region Compute tangents using (new TimedLogEvent("Computed tangents")) { // If the vertexes for a smoothend point exist several times the // DirectX ComputeTangent method is not able to treat them all the // same way. // To circumvent this, we collapse all vertexes in a cloned mesh // even if the texture coordinates don't fit. Then we copy the // generated tangents back to the original mesh vertexes (duplicating // the tangents for vertexes at the same point with the same normals // if required). This happens usually with models exported from 3DSMax. // Clone mesh just for tangent generation Mesh dummyTangentGenerationMesh = mesh.Clone(device, mesh.CreationOptions, decl); // Reuse weldEpsilons, just change the TextureCoordinates, which we don't care about anymore weldEpsilons.TextureCoordinate1 = 1; weldEpsilons.TextureCoordinate2 = 1; weldEpsilons.TextureCoordinate3 = 1; weldEpsilons.TextureCoordinate4 = 1; weldEpsilons.TextureCoordinate5 = 1; weldEpsilons.TextureCoordinate6 = 1; weldEpsilons.TextureCoordinate7 = 1; weldEpsilons.TextureCoordinate8 = 1; // Rest of the weldEpsilons values can stay 0, we don't use them dummyTangentGenerationMesh.WeldVertices(WeldFlags.WeldPartialMatches, weldEpsilons); // Compute tangents if (!CompareDecl(PositionNormalMultiTextured.GetVertexElements(), decl)) { dummyTangentGenerationMesh.ComputeTangent(0, 0, 0, false); } var tangentVertexes = BufferHelper.ReadVertexBuffer <PositionNormalBinormalTangentTextured>(dummyTangentGenerationMesh); dummyTangentGenerationMesh.Dispose(); // Copy generated tangents back vertexes = BufferHelper.ReadVertexBuffer <PositionNormalBinormalTangentTextured>(mesh); for (int num = 0; num < vertexes.Length; num++) { // Search for tangent vertex with the exact same position and normal. for (int tangentVertexNum = 0; tangentVertexNum < tangentVertexes.Length; tangentVertexNum++) { if (vertexes[num].Position == tangentVertexes[tangentVertexNum].Position && vertexes[num].Normal == tangentVertexes[tangentVertexNum].Normal) { // Copy the tangent over vertexes[num].Tangent = tangentVertexes[tangentVertexNum].Tangent; // No more checks required, proceed with next vertex break; } } } BufferHelper.WriteVertexBuffer(mesh, vertexes); } #endregion } } else { if (!hadTangents && CompareDecl(PositionNormalMultiTextured.GetVertexElements(), decl)) { using (new TimedLogEvent("Computed tangents")) mesh.ComputeTangent(0, 0, D3DX.Default, false); } } Optimize(mesh); }