public static void CreateVAO(this StaticBackground background) { float y0, y1; if (background.KeepAspectRatio) { int tw = background.Texture.Width; int th = background.Texture.Height; double hh = System.Math.PI * background.BackgroundImageDistance * th / (tw * background.Repetition); y0 = (float)(-0.5 * hh); y1 = (float)(1.5 * hh); } else { y0 = (float)(-0.125 * background.BackgroundImageDistance); y1 = (float)(0.375 * background.BackgroundImageDistance); } const int n = 32; Vector3f[] bottom = new Vector3f[n]; Vector3f[] top = new Vector3f[n]; double angleValue = 2.61799387799149 - 3.14159265358979 / n; const double angleIncrement = 6.28318530717958 / n; /* * To ensure that the whole background cylinder is rendered inside the viewing frustum, * the background is rendered before the scene with z-buffer writes disabled. Then, * the actual distance from the camera is irrelevant as long as it is inside the frustum. * */ for (int i = 0; i < n; i++) { float x = (float)(background.BackgroundImageDistance * System.Math.Cos(angleValue)); float z = (float)(background.BackgroundImageDistance * System.Math.Sin(angleValue)); bottom[i] = new Vector3f(x, y0, z); top[i] = new Vector3f(x, y1, z); angleValue += angleIncrement; } float textureStart = 0.5f * (float)background.Repetition / n; float textureIncrement = -(float)background.Repetition / n; float textureX = textureStart; List <LibRenderVertex> vertexData = new List <LibRenderVertex>(); List <ushort> indexData = new List <ushort>(); for (int i = 0; i < n; i++) { int j = (i + 1) % n; int indexOffset = vertexData.Count; // side wall vertexData.Add(new LibRenderVertex { Position = top[i], UV = new Vector2f(textureX, 0.005f), Color = Color128.White }); vertexData.Add(new LibRenderVertex { Position = bottom[i], UV = new Vector2f(textureX, 0.995f), Color = Color128.White }); vertexData.Add(new LibRenderVertex { Position = bottom[j], UV = new Vector2f(textureX + textureIncrement, 0.995f), Color = Color128.White }); vertexData.Add(new LibRenderVertex { Position = top[j], UV = new Vector2f(textureX + textureIncrement, 0.005f), Color = Color128.White }); indexData.AddRange(new[] { 0, 1, 2, 3 }.Select(x => x + indexOffset).Select(x => (ushort)x)); // top cap vertexData.Add(new LibRenderVertex { Position = new Vector3f(0.0f, top[i].Y, 0.0f), UV = new Vector2f(textureX + 0.5f * textureIncrement, 0.1f), Color = Color128.White }); indexData.AddRange(new[] { 0, 3, 4 }.Select(x => x + indexOffset).Select(x => (ushort)x)); // bottom cap vertexData.Add(new LibRenderVertex { Position = new Vector3f(0.0f, bottom[i].Y, 0.0f), UV = new Vector2f(textureX + 0.5f * textureIncrement, 0.9f), Color = Color128.White }); indexData.AddRange(new[] { 5, 2, 1 }.Select(x => x + indexOffset).Select(x => (ushort)x)); // finish textureX += textureIncrement; } VertexArrayObject VAO = (VertexArrayObject)background.VAO; VAO?.UnBind(); VAO?.Dispose(); VAO = new VertexArrayObject(); VAO.Bind(); VAO.SetVBO(new VertexBufferObject(vertexData.ToArray(), BufferUsageHint.StaticDraw)); VAO.SetIBO(new IndexBufferObject(indexData.ToArray(), BufferUsageHint.StaticDraw)); VAO.UnBind(); background.VAO = VAO; }
/// <summary>Create an OpenGL/OpenTK VAO for a mesh</summary> /// <param name="isDynamic"></param> public static void CreateVAO(ref Mesh mesh, bool isDynamic) { var hint = isDynamic ? BufferUsageHint.DynamicDraw : BufferUsageHint.StaticDraw; var vertexData = new List <LibRenderVertex>(); var indexData = new List <ushort>(); var normalsVertexData = new List <LibRenderVertex>(); var normalsIndexData = new List <ushort>(); for (int i = 0; i < mesh.Faces.Length; i++) { mesh.Faces[i].IboStartIndex = indexData.Count; mesh.Faces[i].NormalsIboStartIndex = normalsIndexData.Count; foreach (var vertex in mesh.Faces[i].Vertices) { var data = new LibRenderVertex { Position = mesh.Vertices[vertex.Index].Coordinates, Normal = vertex.Normal, UV = new Vector2f(mesh.Vertices[vertex.Index].TextureCoordinates) }; var coloredVertex = mesh.Vertices[vertex.Index] as ColoredVertex; if (coloredVertex != null) { data.Color = coloredVertex.Color; } else { data.Color = Color128.White; } vertexData.Add(data); var normalsData = new LibRenderVertex[2]; normalsData[0].Position = data.Position; normalsData[1].Position = data.Position + data.Normal; for (int j = 0; j < normalsData.Length; j++) { normalsData[j].Color = Color128.White; } normalsVertexData.AddRange(normalsData); } indexData.AddRange(Enumerable.Range(mesh.Faces[i].IboStartIndex, mesh.Faces[i].Vertices.Length).Select(x => (ushort)x)); normalsIndexData.AddRange(Enumerable.Range(mesh.Faces[i].NormalsIboStartIndex, mesh.Faces[i].Vertices.Length * 2).Select(x => (ushort)x)); } VertexArrayObject VAO = (VertexArrayObject)mesh.VAO; VAO?.UnBind(); VAO?.Dispose(); VAO = new VertexArrayObject(); VAO.Bind(); VAO.SetVBO(new VertexBufferObject(vertexData.ToArray(), hint)); VAO.SetIBO(new IndexBufferObject(indexData.ToArray(), hint)); VAO.UnBind(); mesh.VAO = VAO; VertexArrayObject NormalsVAO = (VertexArrayObject)mesh.NormalsVAO; NormalsVAO?.UnBind(); NormalsVAO?.Dispose(); NormalsVAO = new VertexArrayObject(); NormalsVAO.Bind(); NormalsVAO.SetVBO(new VertexBufferObject(normalsVertexData.ToArray(), hint)); NormalsVAO.SetIBO(new IndexBufferObject(normalsIndexData.ToArray(), hint)); NormalsVAO.UnBind(); mesh.NormalsVAO = NormalsVAO; }
public void RenderFace(Shader Shader, ObjectState State, MeshFace Face, Matrix4D modelMatrix, Matrix4D modelViewMatrix, bool IsDebugTouchMode = false) { if (State.Prototype.Mesh.Vertices.Length < 1) { return; } MeshMaterial material = State.Prototype.Mesh.Materials[Face.Material]; VertexArrayObject VAO = (VertexArrayObject)State.Prototype.Mesh.VAO; if (lastVAO != VAO.handle) { VAO.Bind(); lastVAO = VAO.handle; } if (!OptionBackFaceCulling || (Face.Flags & MeshFace.Face2Mask) != 0) { GL.Disable(EnableCap.CullFace); } else if (OptionBackFaceCulling) { if ((Face.Flags & MeshFace.Face2Mask) == 0) { GL.Enable(EnableCap.CullFace); } } // matrix Shader.SetCurrentModelViewMatrix(modelViewMatrix); Shader.SetCurrentTextureMatrix(State.TextureTranslation); if (OptionWireFrame || IsDebugTouchMode) { if (material.Color != lastColor) { Shader.SetMaterialAmbient(material.Color); lastColor = material.Color; } Shader.SetOpacity(1.0f); Shader.SetBrightness(1.0f); VAO.Draw(PrimitiveType.LineLoop, Face.IboStartIndex, Face.Vertices.Length); return; } // lighting if (OptionLighting) { if (material.Color != lastColor) { Shader.SetMaterialAmbient(material.Color); Shader.SetMaterialDiffuse(material.Color); Shader.SetMaterialSpecular(material.Color); //TODO: Ambient and specular colors are not set by any current parsers } if ((material.Flags & MeshMaterial.EmissiveColorMask) != 0) { Shader.SetMaterialEmission(material.EmissiveColor); Shader.SetMaterialEmissive(true); } else { Shader.SetMaterialEmissive(false); } Shader.SetMaterialShininess(1.0f); } else { if (material.Color != lastColor) { Shader.SetMaterialAmbient(material.Color); } //As lighting is disabled, the face cannot be emitting light.... Shader.SetMaterialEmissive(false); } lastColor = material.Color; PrimitiveType DrawMode; switch (Face.Flags & MeshFace.FaceTypeMask) { case MeshFace.FaceTypeTriangles: DrawMode = PrimitiveType.Triangles; break; case MeshFace.FaceTypeTriangleStrip: DrawMode = PrimitiveType.TriangleStrip; break; case MeshFace.FaceTypeQuads: DrawMode = PrimitiveType.Quads; break; case MeshFace.FaceTypeQuadStrip: DrawMode = PrimitiveType.QuadStrip; break; default: DrawMode = PrimitiveType.Polygon; break; } // daytime polygon { // texture if (material.DaytimeTexture != null && currentHost.LoadTexture(material.DaytimeTexture, (OpenGlTextureWrapMode)material.WrapMode)) { Shader.SetIsTexture(true); if (LastBoundTexture != material.DaytimeTexture.OpenGlTextures[(int)material.WrapMode]) { GL.BindTexture(TextureTarget.Texture2D, material.DaytimeTexture.OpenGlTextures[(int)material.WrapMode].Name); LastBoundTexture = material.DaytimeTexture.OpenGlTextures[(int)material.WrapMode]; } } else { Shader.SetIsTexture(false); } // Calculate the brightness of the poly to render float factor; if (material.BlendMode == MeshMaterialBlendMode.Additive) { //Additive blending- Full brightness factor = 1.0f; GL.Enable(EnableCap.Blend); GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.One); Shader.SetIsFog(false); } else if ((material.Flags & MeshMaterial.EmissiveColorMask) != 0) { //As material is emitting light, it must be at full brightness factor = 1.0f; } else if (material.NighttimeTexture == null || material.NighttimeTexture == material.DaytimeTexture) { //No nighttime texture or both are identical- Darken the polygon to match the light conditions float blend = inv255 * material.DaytimeNighttimeBlend + 1.0f - Lighting.OptionLightingResultingAmount; if (blend > 1.0f) { blend = 1.0f; } factor = 1.0f - 0.7f * blend; } else { //Valid nighttime texture- Blend the two textures by DNB at max brightness factor = 1.0f; } Shader.SetBrightness(factor); float alphaFactor; GlowAttenuationMode mode = GlowAttenuationMode.None; if (material.GlowAttenuationData != 0) { alphaFactor = (float)Glow.GetDistanceFactor(modelMatrix, State.Prototype.Mesh.Vertices, ref Face, material.GlowAttenuationData, out mode); } else { alphaFactor = 1.0f; } if (material.BlendMode == MeshMaterialBlendMode.Additive) { Shader.SetMaterialAdditive(1 + (int)mode); } else { Shader.SetMaterialAdditive(0); } Shader.SetOpacity(inv255 * material.Color.A * alphaFactor); // render polygon VAO.Draw(DrawMode, Face.IboStartIndex, Face.Vertices.Length); } // nighttime polygon if (material.NighttimeTexture != null && material.NighttimeTexture != material.DaytimeTexture && currentHost.LoadTexture(material.NighttimeTexture, (OpenGlTextureWrapMode)material.WrapMode)) { // texture Shader.SetIsTexture(true); if (LastBoundTexture != material.NighttimeTexture.OpenGlTextures[(int)material.WrapMode]) { GL.BindTexture(TextureTarget.Texture2D, material.NighttimeTexture.OpenGlTextures[(int)material.WrapMode].Name); LastBoundTexture = material.NighttimeTexture.OpenGlTextures[(int)material.WrapMode]; } GL.Enable(EnableCap.Blend); // alpha test GL.Enable(EnableCap.AlphaTest); GL.AlphaFunc(AlphaFunction.Greater, 0.0f); // blend mode float alphaFactor; if (material.GlowAttenuationData != 0) { alphaFactor = (float)Glow.GetDistanceFactor(modelMatrix, State.Prototype.Mesh.Vertices, ref Face, material.GlowAttenuationData); float blend = inv255 * material.DaytimeNighttimeBlend + 1.0f - Lighting.OptionLightingResultingAmount; if (blend > 1.0f) { blend = 1.0f; } alphaFactor *= blend; } else { alphaFactor = inv255 * material.DaytimeNighttimeBlend + 1.0f - Lighting.OptionLightingResultingAmount; if (alphaFactor > 1.0f) { alphaFactor = 1.0f; } } Shader.SetOpacity(inv255 * material.Color.A * alphaFactor); // render polygon VAO.Draw(DrawMode, Face.IboStartIndex, Face.Vertices.Length); RestoreBlendFunc(); RestoreAlphaFunc(); } // normals if (OptionNormals) { Shader.SetIsTexture(false); Shader.SetBrightness(1.0f); Shader.SetOpacity(1.0f); VertexArrayObject NormalsVAO = (VertexArrayObject)State.Prototype.Mesh.NormalsVAO; NormalsVAO.Bind(); lastVAO = NormalsVAO.handle; NormalsVAO.Draw(PrimitiveType.Lines, Face.NormalsIboStartIndex, Face.Vertices.Length * 2); } // finalize if (material.BlendMode == MeshMaterialBlendMode.Additive) { RestoreBlendFunc(); Shader.SetIsFog(OptionFog); } }
/// <summary>Create an OpenGL/OpenTK VAO for a mesh</summary> /// <param name="isDynamic"></param> public static void CreateVAO(ref Mesh mesh, bool isDynamic, VertexLayout vertexLayout, BaseRenderer renderer) { try { var hint = isDynamic ? BufferUsageHint.DynamicDraw : BufferUsageHint.StaticDraw; var vertexData = new List <LibRenderVertex>(); var indexData = new List <uint>(); var normalsVertexData = new List <LibRenderVertex>(); var normalsIndexData = new List <uint>(); for (int i = 0; i < mesh.Faces.Length; i++) { mesh.Faces[i].IboStartIndex = indexData.Count; mesh.Faces[i].NormalsIboStartIndex = normalsIndexData.Count; foreach (var vertex in mesh.Faces[i].Vertices) { var data = new LibRenderVertex { Position = mesh.Vertices[vertex.Index].Coordinates, Normal = vertex.Normal, UV = new Vector2f(mesh.Vertices[vertex.Index].TextureCoordinates) }; var coloredVertex = mesh.Vertices[vertex.Index] as ColoredVertex; if (coloredVertex != null) { data.Color = coloredVertex.Color; } else { data.Color = Color128.Transparent; } vertexData.Add(data); var normalsData = new LibRenderVertex[2]; normalsData[0].Position = data.Position; normalsData[1].Position = data.Position + data.Normal; for (int j = 0; j < normalsData.Length; j++) { normalsData[j].Color = Color128.White; } normalsVertexData.AddRange(normalsData); } indexData.AddRange(Enumerable.Range(mesh.Faces[i].IboStartIndex, mesh.Faces[i].Vertices.Length).Select(x => (uint)x)); normalsIndexData.AddRange(Enumerable.Range(mesh.Faces[i].NormalsIboStartIndex, mesh.Faces[i].Vertices.Length * 2).Select(x => (uint)x)); } VertexArrayObject VAO = (VertexArrayObject)mesh.VAO; VAO?.UnBind(); VAO?.Dispose(); VAO = new VertexArrayObject(); VAO.Bind(); VAO.SetVBO(new VertexBufferObject(vertexData.ToArray(), hint)); if (indexData.Count > 65530) { //Marginal headroom, although it probably doesn't matter VAO.SetIBO(new IndexBufferObjectI(indexData.ToArray(), hint)); } else { VAO.SetIBO(new IndexBufferObjectU(indexData.Select(x => (ushort)x).ToArray(), hint)); } VAO.SetAttributes(vertexLayout); VAO.UnBind(); mesh.VAO = VAO; VertexArrayObject NormalsVAO = (VertexArrayObject)mesh.NormalsVAO; NormalsVAO?.UnBind(); NormalsVAO?.Dispose(); NormalsVAO = new VertexArrayObject(); NormalsVAO.Bind(); NormalsVAO.SetVBO(new VertexBufferObject(normalsVertexData.ToArray(), hint)); if (normalsIndexData.Count > 65530) { //Marginal headroom, although it probably doesn't matter NormalsVAO.SetIBO(new IndexBufferObjectI(normalsIndexData.ToArray(), hint)); } else { NormalsVAO.SetIBO(new IndexBufferObjectU(normalsIndexData.Select(x => (ushort)x).ToArray(), hint)); } NormalsVAO.SetAttributes(vertexLayout); NormalsVAO.UnBind(); mesh.NormalsVAO = NormalsVAO; } catch (Exception e) { renderer.ForceLegacyOpenGL = true; renderer.currentHost.AddMessage(MessageType.Error, false, "Creating VAO failed with the following error: " + e); } }
public void RenderFace(Shader Shader, ObjectState State, MeshFace Face, Vector3 EyePosition, bool IsDebugTouchMode = false) { if (State.Prototype.Mesh.Vertices.Length < 1) { return; } VertexTemplate[] vertices = State.Prototype.Mesh.Vertices; MeshMaterial material = State.Prototype.Mesh.Materials[Face.Material]; VertexArrayObject VAO = (VertexArrayObject)State.Prototype.Mesh.VAO; VertexArrayObject NormalsVAO = (VertexArrayObject)State.Prototype.Mesh.NormalsVAO; if (!OptionBackFaceCulling || (Face.Flags & MeshFace.Face2Mask) != 0) { GL.Disable(EnableCap.CullFace); } else if (OptionBackFaceCulling) { if ((Face.Flags & MeshFace.Face2Mask) == 0) { GL.Enable(EnableCap.CullFace); } } // matrix Matrix4D modelMatrix = State.Scale * State.Rotate * State.Translation * Matrix4D.CreateTranslation(-EyePosition); Matrix4D modelViewMatrix = modelMatrix * CurrentViewMatrix; Shader.SetCurrentProjectionMatrix(CurrentProjectionMatrix); Shader.SetCurrentModelViewMatrix(modelViewMatrix); Shader.SetCurrentNormalMatrix(Matrix4D.Transpose(Matrix4D.Invert(modelViewMatrix))); Shader.SetCurrentTextureMatrix(State.TextureTranslation); if (OptionWireFrame || IsDebugTouchMode) { VAO.Bind(); VAO.Draw(Shader.VertexLayout, PrimitiveType.LineLoop, Face.IboStartIndex, Face.Vertices.Length); VAO.UnBind(); return; } // lighting if (material.NighttimeTexture == null) { if (OptionLighting) { Shader.SetIsLight(true); Shader.SetLightPosition(new Vector3(Lighting.OptionLightPosition.X, Lighting.OptionLightPosition.Y, -Lighting.OptionLightPosition.Z)); Shader.SetLightAmbient(new Color4(Lighting.OptionAmbientColor.R, Lighting.OptionAmbientColor.G, Lighting.OptionAmbientColor.B, 255)); Shader.SetLightDiffuse(new Color4(Lighting.OptionDiffuseColor.R, Lighting.OptionDiffuseColor.G, Lighting.OptionDiffuseColor.B, 255)); Shader.SetLightSpecular(new Color4(Lighting.OptionSpecularColor.R, Lighting.OptionSpecularColor.G, Lighting.OptionSpecularColor.B, 255)); Shader.SetMaterialAmbient(new Color4(material.Color.R, material.Color.G, material.Color.B, material.Color.A)); // TODO Shader.SetMaterialDiffuse(new Color4(material.Color.R, material.Color.G, material.Color.B, material.Color.A)); Shader.SetMaterialSpecular(new Color4(material.Color.R, material.Color.G, material.Color.B, material.Color.A)); // TODO if ((material.Flags & MeshMaterial.EmissiveColorMask) != 0) { Shader.SetMaterialEmission(new Color4(material.EmissiveColor.R, material.EmissiveColor.G, material.EmissiveColor.B, 255)); } else { Shader.SetMaterialEmission(new Color4(0.0f, 0.0f, 0.0f, 1.0f)); } Shader.SetMaterialShininess(1.0f); Lighting.OptionLightingResultingAmount = (Lighting.OptionAmbientColor.R + Lighting.OptionAmbientColor.G + Lighting.OptionAmbientColor.B) / 480.0f; if (Lighting.OptionLightingResultingAmount > 1.0f) { Lighting.OptionLightingResultingAmount = 1.0f; } } else { Shader.SetMaterialAmbient(new Color4(material.Color.R, material.Color.G, material.Color.B, material.Color.A)); // TODO } } else { Shader.SetMaterialAmbient(new Color4(material.Color.R, material.Color.G, material.Color.B, material.Color.A)); // TODO } // fog if (OptionFog) { Shader.SetIsFog(true); Shader.SetFogStart(Fog.Start); Shader.SetFogEnd(Fog.End); Shader.SetFogColor(new Color4(Fog.Color.R, Fog.Color.G, Fog.Color.B, 255)); } PrimitiveType DrawMode; switch (Face.Flags & MeshFace.FaceTypeMask) { case MeshFace.FaceTypeTriangles: DrawMode = PrimitiveType.Triangles; break; case MeshFace.FaceTypeTriangleStrip: DrawMode = PrimitiveType.TriangleStrip; break; case MeshFace.FaceTypeQuads: DrawMode = PrimitiveType.Quads; break; case MeshFace.FaceTypeQuadStrip: DrawMode = PrimitiveType.QuadStrip; break; default: DrawMode = PrimitiveType.Polygon; break; } // daytime polygon { // texture if (material.DaytimeTexture != null) { if (currentHost.LoadTexture(material.DaytimeTexture, (OpenGlTextureWrapMode)material.WrapMode)) { GL.Enable(EnableCap.Texture2D); Shader.SetIsTexture(true); Shader.SetTexture(0); GL.BindTexture(TextureTarget.Texture2D, material.DaytimeTexture.OpenGlTextures[(int)material.WrapMode].Name); } } // blend mode float factor; if (material.BlendMode == MeshMaterialBlendMode.Additive) { factor = 1.0f; GL.Enable(EnableCap.Blend); GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.One); Shader.SetIsFog(false); } else if (material.NighttimeTexture == null) { float blend = inv255 * material.DaytimeNighttimeBlend + 1.0f - Lighting.OptionLightingResultingAmount; if (blend > 1.0f) { blend = 1.0f; } factor = 1.0f - 0.7f * blend; } else { factor = 1.0f; } Shader.SetBrightness(factor); float alphaFactor; if (material.GlowAttenuationData != 0) { alphaFactor = (float)Glow.GetDistanceFactor(modelMatrix, vertices, ref Face, material.GlowAttenuationData); } else { alphaFactor = 1.0f; } Shader.SetOpacity(inv255 * material.Color.A * alphaFactor); // render polygon VAO.Bind(); VAO.Draw(Shader.VertexLayout, DrawMode, Face.IboStartIndex, Face.Vertices.Length); VAO.UnBind(); GL.BindTexture(TextureTarget.Texture2D, 0); } // nighttime polygon if (material.NighttimeTexture != null && currentHost.LoadTexture(material.NighttimeTexture, (OpenGlTextureWrapMode)material.WrapMode)) { // texture GL.Enable(EnableCap.Texture2D); Shader.SetIsTexture(true); Shader.SetTexture(0); GL.BindTexture(TextureTarget.Texture2D, material.NighttimeTexture.OpenGlTextures[(int)material.WrapMode].Name); GL.Enable(EnableCap.Blend); // alpha test GL.Enable(EnableCap.AlphaTest); GL.AlphaFunc(AlphaFunction.Greater, 0.0f); // blend mode float alphaFactor; if (material.GlowAttenuationData != 0) { alphaFactor = (float)Glow.GetDistanceFactor(modelMatrix, vertices, ref Face, material.GlowAttenuationData); float blend = inv255 * material.DaytimeNighttimeBlend + 1.0f - Lighting.OptionLightingResultingAmount; if (blend > 1.0f) { blend = 1.0f; } alphaFactor *= blend; } else { alphaFactor = inv255 * material.DaytimeNighttimeBlend + 1.0f - Lighting.OptionLightingResultingAmount; if (alphaFactor > 1.0f) { alphaFactor = 1.0f; } } Shader.SetOpacity(alphaFactor); // render polygon VAO.Bind(); VAO.Draw(Shader.VertexLayout, DrawMode, Face.IboStartIndex, Face.Vertices.Length); VAO.UnBind(); GL.BindTexture(TextureTarget.Texture2D, 0); RestoreBlendFunc(); RestoreAlphaFunc(); } GL.Disable(EnableCap.Texture2D); // normals if (OptionNormals) { Shader.SetIsTexture(false); Shader.SetBrightness(1.0f); Shader.SetOpacity(1.0f); NormalsVAO.Bind(); NormalsVAO.Draw(Shader.VertexLayout, PrimitiveType.Lines, Face.NormalsIboStartIndex, Face.Vertices.Length * 2); NormalsVAO.UnBind(); } // finalize if (material.BlendMode == MeshMaterialBlendMode.Additive) { RestoreBlendFunc(); } }