/// <summary> /// Helper for ComputeBoundingBox(out Vector3 sceneMin, out Vector3 sceneMax) /// </summary> /// <param name="node"></param> /// <param name="min"></param> /// <param name="max"></param> /// <param name="trafo"></param> private void ComputeBoundingBox(Node node, ref Vector3 min, ref Vector3 max, ref Matrix4 trafo) { if (node.HasMeshes) { foreach (var mesh in node.MeshIndices.Select(index => _raw.Meshes[index])) { for (var i = 0; i < mesh.VertexCount; i++) { var tmp = AssimpToOpenTk.FromVector(mesh.Vertices[i]); Vector3.Transform(ref tmp, ref trafo, out tmp); min.X = Math.Min(min.X, tmp.X); min.Y = Math.Min(min.Y, tmp.Y); min.Z = Math.Min(min.Z, tmp.Z); max.X = Math.Max(max.X, tmp.X); max.Y = Math.Max(max.Y, tmp.Y); max.Z = Math.Max(max.Z, tmp.Z); } } } for (var i = 0; i < node.ChildCount; i++) { var prev = trafo; var mat = AssimpToOpenTk.FromMatrix(node.Children[i].Transform); mat.Transpose(); Matrix4.Mult(ref mat, ref prev, out prev); ComputeBoundingBox(node.Children[i], ref min, ref max, ref prev); } }
/// <summary> /// Recursive rendering function for semi-transparent (i.e. alpha-blended) meshes. /// /// Alpha blending is not globally on, meshes need to do that on their own. /// /// This render function is called _after_ solid geometry has been drawn, so the /// relative order between transparent and opaque geometry is maintained. There /// is no further ordering within the alpha rendering pass. /// </summary> /// <param name="node">Current node</param> /// <param name="visibleNodes">Set of visible meshes</param> /// <param name="flags">Rendering flags</param> /// <param name="animated">Play animation?</param> private void RecursiveRenderWithAlpha(Node node, Dictionary <Node, List <Mesh> > visibleNodes, RenderFlags flags, bool animated) { Matrix4 m; if (animated) { Owner.SceneAnimator.GetLocalTransform(node, out m); } else { m = AssimpToOpenTk.FromMatrix(node.Transform); } // TODO for some reason, all OpenTk matrices need a ^T - clarify our conventions somewhere m.Transpose(); // the following permutations could be compacted into one big loop with lots of // condition magic, but at the cost of readability and also performance. // we therefore keep it redundant and stupid. if (node.HasMeshes) { DrawAlphaMeshes(node, visibleNodes, flags, animated); } for (var i = 0; i < node.ChildCount; i++) { RecursiveRenderWithAlpha(node.Children[i], visibleNodes, flags, animated); } }
/// <summary> /// Recursive rendering function /// </summary> /// <param name="node">Current node</param> /// <param name="visibleMeshesByNode"> </param> /// <param name="flags">Rendering flags</param> /// <param name="animated">Play animation?</param> /// <returns>whether there is any need to do a second render pass with alpha blending enabled</returns> protected bool RecursiveRender(Node node, Dictionary <Node, List <Mesh> > visibleMeshesByNode, RenderFlags flags, bool animated, int currDispList) { var needAlpha = false; Matrix4 m; if (animated) { Owner.SceneAnimator.GetLocalTransform(node, out m); } else { m = AssimpToOpenTk.FromMatrix(node.Transform); } // TODO for some reason, all OpenTk matrices need a ^T - we should clarify our conventions somewhere RenderControl.GLError("A11"); m.Transpose(); PushWorld(ref m); RenderControl.GLError("B11"); if ((node.HasMeshes) && (currDispList == GetDispList(node.Name))) { needAlpha = DrawOpaqueMeshes(node, visibleMeshesByNode, flags, animated); } for (var i = 0; i < node.ChildCount; i++) { needAlpha = RecursiveRender(node.Children[i], visibleMeshesByNode, flags, animated, currDispList) || needAlpha; } RenderControl.GLError("C11"); PopWorld(); return(needAlpha); }
public static void DrawNormals(Node node, int meshIndex, Mesh mesh, CpuSkinningEvaluator skinner, float invGlobalScale, Matrix4 transform) { if (!mesh.HasNormals) { return; } // The normal directions are transformed using the transpose(inverse(transform)). // This ensures correct direction is used when non-uniform scaling is present. Matrix4 normalMatrix = transform; normalMatrix.Invert(); normalMatrix.Transpose(); // Scale by scene size because the scene will be resized to fit // the unit box, but the normals should have a fixed length. var scale = invGlobalScale * 0.05f; GL.Begin(BeginMode.Lines); GL.Disable(EnableCap.Lighting); GL.Disable(EnableCap.Texture2D); GL.Enable(EnableCap.ColorMaterial); GL.Color4(new Color4(0.0f, 1.0f, 0.0f, 1.0f)); for (uint i = 0; i < mesh.VertexCount; ++i) { Vector3 v; if (skinner != null && mesh.HasBones) { skinner.GetTransformedVertexPosition(node, mesh, i, out v); } else { v = AssimpToOpenTk.FromVector(mesh.Vertices[(int)i]); } v = Vector4.Transform(new Vector4(v, 1.0f), transform).Xyz; // Skip dividing by W component. It should always be 1, here. Vector3 n; if (skinner != null) { skinner.GetTransformedVertexNormal(node, mesh, i, out n); } else { n = AssimpToOpenTk.FromVector(mesh.Normals[(int)i]); } n = Vector4.Transform(new Vector4(n, 0.0f), normalMatrix).Xyz; // Toss the W component. It is non-sensical for normals. n.Normalize(); GL.Vertex3(v); GL.Vertex3(v + n * scale); } GL.End(); GL.Disable(EnableCap.ColorMaterial); }
/// <summary> /// Calculates the smallest AABB that encloses the scene. /// </summary> /// <param name="sceneMin"></param> /// <param name="sceneMax"></param> /// <param name="sceneCenter"> </param> /// <param name="node"> </param> /// <param name="omitNodeTrafo"> </param> private void ComputeBoundingBox(out Vector3 sceneMin, out Vector3 sceneMax, out Vector3 sceneCenter, Node node = null, bool omitRootNodeTrafo = false) { sceneMin = new Vector3(1e10f, 1e10f, 1e10f); sceneMax = new Vector3(-1e10f, -1e10f, -1e10f); var trafo = omitRootNodeTrafo ? Matrix4.Identity : AssimpToOpenTk.FromMatrix((node ?? _raw.RootNode).Transform); trafo.Transpose(); ComputeBoundingBox(node ?? _raw.RootNode, ref sceneMin, ref sceneMax, ref trafo); sceneCenter = (sceneMin + sceneMax) / 2.0f; }
/// <summary> /// Recursive render function for drawing normals with a constant size. /// </summary> /// <param name="node"></param> /// <param name="visibleMeshesByNode"></param> /// <param name="flags"></param> /// <param name="invGlobalScale"></param> /// <param name="animated"></param> /// <param name="transform"></param> private void RecursiveRenderNormals(Node node, Dictionary <Node, List <Mesh> > visibleMeshesByNode, RenderFlags flags, float invGlobalScale, bool animated, Matrix4 transform, int currDispList) { // TODO unify our use of OpenTK and Assimp matrices Matrix4 mConv; if (animated) { Owner.SceneAnimator.GetLocalTransform(node, out mConv); } else { Matrix4x4 m = node.Transform; mConv = AssimpToOpenTk.FromMatrix(ref m); } mConv.Transpose(); // The normal's position and direction are transformed differently, so we manually track the transform. transform = mConv * transform; if (flags.HasFlag(RenderFlags.ShowNormals)) { List <Mesh> meshList = null; if (node.HasMeshes && (visibleMeshesByNode == null || visibleMeshesByNode.TryGetValue(node, out meshList))) { foreach (var index in node.MeshIndices) { var mesh = Owner.Raw.Meshes[index]; if (meshList != null && !meshList.Contains(mesh)) { continue; } if (currDispList == GetDispList(node.Name)) { OverlayNormals.DrawNormals(node, index, mesh, mesh.HasBones && animated ? Skinner : null, invGlobalScale, transform); } } } } for (int i = 0; i < node.ChildCount; i++) { RecursiveRenderNormals(node.Children[i], visibleMeshesByNode, flags, invGlobalScale, animated, transform, currDispList); } }
public static void DrawNormals(Node node, int meshIndex, Mesh mesh, CpuSkinningEvaluator skinner, float invGlobalScale) { if (!mesh.HasNormals) { return; } // Scale by scene size because the scene will be resized to fit // the unit box but the normals should have a fixed length var scale = invGlobalScale * 0.05f; GL.Begin(BeginMode.Lines); GL.Disable(EnableCap.Lighting); GL.Disable(EnableCap.Texture2D); GL.Enable(EnableCap.ColorMaterial); GL.Color4(new Color4(0.0f, 1.0f, 0.0f, 1.0f)); for (uint i = 0; i < mesh.VertexCount; ++i) { Vector3 v; if (skinner != null && mesh.HasBones) { skinner.GetTransformedVertexPosition(node, meshIndex, i, out v); } else { v = AssimpToOpenTk.FromVector(mesh.Vertices[(int)i]); } Vector3 n; if (skinner != null) { skinner.GetTransformedVertexNormal(node, meshIndex, i, out n); } else { n = AssimpToOpenTk.FromVector(mesh.Normals[(int)i]); } GL.Vertex3(v); GL.Vertex3(v + n * scale); } GL.End(); GL.Disable(EnableCap.ColorMaterial); }
/// <summary> /// Internal method to (re-)cache all transformed vertex positions and normals /// </summary> private void Cache() { var boneMatrices = _scene.SceneAnimator.GetBoneMatricesForMesh(_lastNode, _source); // update entire mesh for (int i = 0; i < _cachedPositions.Length; ++i) { var v = AssimpToOpenTk.FromVector(_source.Vertices[i]); EvaluateBoneInfluences(ref v, (uint)i, boneMatrices, out _cachedPositions[i]); } for (int i = 0; i < _cachedNormals.Length; ++i) { var n = AssimpToOpenTk.FromVector(_source.Normals[i]); EvaluateBoneInfluences(ref n, (uint)i, boneMatrices, out _cachedNormals[i], true); } _dirty = false; }
/// <summary> /// Build a transformation matrix from rotation, scaling and translation components. /// The transformation order is scaling, rotation, translation (left to right). /// </summary> /// <param name="presentRotation"></param> /// <param name="presentScaling"></param> /// <param name="presentPosition"></param> /// <param name="outMatrix"></param> private static void BuildTransform(ref Assimp.Quaternion presentRotation, ref Vector3D presentScaling, ref Vector3D presentPosition, out Matrix4 outMatrix) { // build a transformation matrix from it var mat = new Matrix4x4(presentRotation.GetMatrix()); mat.A1 *= presentScaling.X; mat.B1 *= presentScaling.X; mat.C1 *= presentScaling.X; mat.A2 *= presentScaling.Y; mat.B2 *= presentScaling.Y; mat.C2 *= presentScaling.Y; mat.A3 *= presentScaling.Z; mat.B3 *= presentScaling.Z; mat.C3 *= presentScaling.Z; mat.A4 = presentPosition.X; mat.B4 = presentPosition.Y; mat.C4 = presentPosition.Z; outMatrix = AssimpToOpenTk.FromMatrix(ref mat); }
private NodeState CreateNodeTree(Node rootNode, NodeState parent) { var outNode = new NodeState { LocalTransform = AssimpToOpenTk.FromMatrix(rootNode.Transform) }; outNode.Parent = parent; // calculate transforms outNode.GlobalTransform = parent != null ? parent.GlobalTransform * outNode.LocalTransform : outNode.LocalTransform; // populate by-name map to quickly map nodes to their state _nodeStateByName[rootNode.Name] = outNode; // find the index of the animation track affecting this node, if any outNode.ChannelIndex = -1; if (ActiveAnimation != -1) { var channels = _raw.Animations[ActiveAnimation].NodeAnimationChannels; for (int i = 0; i < channels.Count; ++i) { if (channels[i].NodeName != rootNode.Name) { continue; } outNode.ChannelIndex = i; break; } } outNode.Children = new NodeState[rootNode.ChildCount]; // recursively add up children for (int i = 0; i < rootNode.ChildCount; ++i) { outNode.Children[i] = CreateNodeTree(rootNode.Children[i], outNode); } return(outNode); }
/// <summary> /// Recursive rendering function for opaque meshes that also checks whether there /// is any need for a second rendering pass to draw semi-transparent meshes. /// </summary> /// <param name="node">Current node</param> /// <param name="visibleMeshesByNode"> </param> /// <param name="flags">Rendering flags</param> /// <param name="animated">Play animation?</param> /// <returns>whether there is any need to do a second render pass with alpha blending enabled</returns> private bool RecursiveRender(Node node, Dictionary <Node, List <Mesh> > visibleMeshesByNode, RenderFlags flags, bool animated, ref Matrix4 world) { var needAlpha = false; Matrix4 m; if (animated) { Owner.SceneAnimator.GetLocalTransform(node, out m); } else { m = AssimpToOpenTk.FromMatrix(node.Transform); } // TODO for some reason, all OpenTk matrices need a ^T - we should clarify our conventions somewhere m.Transpose(); var newWorld = world * m; // the following permutations could be compacted into one big loop with lots of // condition magic, but at the cost of readability and also performance. // we therefore keep it redundant and stupid. if (node.HasMeshes) { needAlpha = DrawOpaqueMeshes(node, visibleMeshesByNode, flags, animated); } for (var i = 0; i < node.ChildCount; i++) { needAlpha = RecursiveRender(node.Children[i], visibleMeshesByNode, flags, animated, ref newWorld) || needAlpha; } return(needAlpha); }
/// <summary> /// Recursive rendering function /// </summary> /// <param name="node">Current node</param> /// <param name="visibleMeshesByNode"> </param> /// <param name="flags">Rendering flags</param> /// <param name="animated">Play animation?</param> /// <returns>whether there is any need to do a second render pass with alpha blending enabled</returns> private bool RecursiveRender(Node node, Dictionary <Node, List <Mesh> > visibleMeshesByNode, RenderFlags flags, bool animated) { var needAlpha = false; Matrix4 m; if (animated) { Owner.SceneAnimator.GetLocalTransform(node, out m); } else { m = AssimpToOpenTk.FromMatrix(node.Transform); } // TODO for some reason, all OpenTk matrices need a ^T - we should clarify our conventions somewhere m.Transpose(); GL.PushMatrix(); GL.MultMatrix(ref m); if (node.HasMeshes) { needAlpha = DrawOpaqueMeshes(node, visibleMeshesByNode, flags, animated); } for (var i = 0; i < node.ChildCount; i++) { needAlpha = RecursiveRender(node.Children[i], visibleMeshesByNode, flags, animated) || needAlpha; } GL.PopMatrix(); return(needAlpha); }
/// <summary> /// Set the pivot point for the scene to be at the world position /// of a given node. /// </summary> /// <param name="node">Node to place the pivot at. Pass null to reset the /// pivot to be the (natural) center of the scene</param> /// <param name="realCenter">Whether to compute the real center of the node (by getting /// the mid point of the geometric bounds or whether the origin of the local coordinate /// space is used.</param> public void SetPivot(Node node, bool realCenter = true) { if (node == null) { _pivot = realCenter ? _sceneCenter : Vector3.Zero; return; } var v = Vector3.Zero; if (realCenter) { Vector3 t1, t2; ComputeBoundingBox(out t1, out t2, out v, node, true); } do { var trafo = AssimpToOpenTk.FromMatrix(node.Transform); trafo.Transpose(); Vector3.Transform(ref v, ref trafo, out v); } while ((node = node.Parent) != null); _pivot = v; }
public static void DrawBoundingBox(Node node, int meshIndex, Mesh mesh, CpuSkinningEvaluator skinner) { GL.Disable(EnableCap.Lighting); GL.Disable(EnableCap.Texture2D); GL.Enable(EnableCap.ColorMaterial); GL.Color4(new Color4(1.0f, 0.0f, 0.0f, 1.0f)); var min = new Vector3(1e10f, 1e10f, 1e10f); var max = new Vector3(-1e10f, -1e10f, -1e10f); for (uint i = 0; i < mesh.VertexCount; ++i) { Vector3 tmp; if (skinner != null && mesh.HasBones) { skinner.GetTransformedVertexPosition(node, mesh, i, out tmp); } else { tmp = AssimpToOpenTk.FromVector(mesh.Vertices[(int)i]); } min.X = Math.Min(min.X, tmp.X); min.Y = Math.Min(min.Y, tmp.Y); min.Z = Math.Min(min.Z, tmp.Z); max.X = Math.Max(max.X, tmp.X); max.Y = Math.Max(max.Y, tmp.Y); max.Z = Math.Max(max.Z, tmp.Z); } GL.Begin(BeginMode.LineLoop); GL.Vertex3(min); GL.Vertex3(new Vector3(min.X, max.Y, min.Z)); GL.Vertex3(new Vector3(min.X, max.Y, max.Z)); GL.Vertex3(new Vector3(min.X, min.Y, max.Z)); GL.End(); GL.Begin(BeginMode.LineLoop); GL.Vertex3(new Vector3(max.X, min.Y, min.Z)); GL.Vertex3(new Vector3(max.X, max.Y, min.Z)); GL.Vertex3(new Vector3(max.X, max.Y, max.Z)); GL.Vertex3(new Vector3(max.X, min.Y, max.Z)); GL.End(); GL.Begin(BeginMode.Lines); GL.Vertex3(min); GL.Vertex3(new Vector3(max.X, min.Y, min.Z)); GL.Vertex3(new Vector3(min.X, max.Y, min.Z)); GL.Vertex3(new Vector3(max.X, max.Y, min.Z)); GL.Vertex3(new Vector3(min.X, max.Y, max.Z)); GL.Vertex3(new Vector3(max.X, max.Y, max.Z)); GL.Vertex3(new Vector3(min.X, min.Y, max.Z)); GL.Vertex3(new Vector3(max.X, min.Y, max.Z)); GL.End(); GL.Disable(EnableCap.ColorMaterial); }
private void ApplyFixedFunctionMaterial(Mesh mesh, Material mat, bool textured, bool shaded) { shaded = shaded && (mesh == null || mesh.HasNormals); if (shaded) { GL.Enable(EnableCap.Lighting); } else { GL.Disable(EnableCap.Lighting); } var hasColors = mesh != null && mesh.HasVertexColors(0); if (hasColors) { GL.Enable(EnableCap.ColorMaterial); GL.ColorMaterial(MaterialFace.FrontAndBack, ColorMaterialParameter.AmbientAndDiffuse); } else { GL.Disable(EnableCap.ColorMaterial); } // note: keep semantics of hasAlpha consistent with IsAlphaMaterial() var hasAlpha = false; var hasTexture = false; // note: keep this up-to-date with the code in UploadTextures() if (textured && mat.GetMaterialTextureCount(TextureType.Diffuse) > 0) { hasTexture = true; TextureSlot tex; mat.GetMaterialTexture(TextureType.Diffuse, 0, out tex); var gtex = _scene.TextureSet.GetOriginalOrReplacement(tex.FilePath); //hasAlpha = hasAlpha || gtex.HasAlpha == Texture.AlphaState.HasAlpha; hasAlpha = hasAlpha || mat.HasTextureOpacity; if (gtex.State == Texture.TextureState.GlTextureCreated) { GL.ActiveTexture(TextureUnit.Texture0); gtex.BindGlTexture(); GL.Enable(EnableCap.Texture2D); } else { GL.Disable(EnableCap.Texture2D); } } else { GL.Disable(EnableCap.Texture2D); } GL.Enable(EnableCap.Normalize); var alpha = 1.0f; // Assimp always return true with obj/mtl // suppress zero opacity, this is likely wrong input data // But this may cause alpha 0.0f not working if (mat.HasOpacity && mat.Opacity > AlphaSuppressionThreshold && mat.Opacity != 1.0f) { alpha = mat.Opacity; } var color = new Color4(.5f, .5f, .5f, 1.0f); Color4 c; if (mat.HasColorDiffuse && mat.Name != "DefaultMaterial") { color = AssimpToOpenTk.FromColor(mat.ColorDiffuse); if (color.A < AlphaSuppressionThreshold) // s.a. { color.A = 1.0f; } } color.A *= alpha; hasAlpha = hasAlpha || color.A < 1.0f; if (shaded) { // if the material has a texture but the diffuse color texture is all black, // then heuristically assume that this is an import/export flaw and substitute // white. if (hasTexture && color.R < 1e-3f && color.G < 1e-3f && color.B < 1e-3f) { GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Diffuse, new Color4(1.0f, 1.0f, 1.0f, alpha)); } else { GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Diffuse, color); } //color = new Color4(0, 0, 0, 1.0f); //color = new Color4(1.0f, 1.0f, 1.0f, 1.0f); color = new Color4(0.5f, 0.5f, 0.5f, 1.0f); if (mat.HasColorSpecular && mat.Name != "DefaultMaterial") { c = AssimpToOpenTk.FromColor(mat.ColorSpecular); if (c.R + c.G + c.B > 0.0f) { color = c; } } color.A *= alpha; GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Specular, color); // Assimp alaways returns HasColorAmbient with {0,0,0,1} when source file has no setting. // This makes ambient light becomes all black // (The same issue of diffuse color has been fixed above) // workaround: Shift color range from 1~0 to 1~(min ambient) float minAmb = 0.2f; color = new Color4(minAmb, minAmb, minAmb, 1.0f); //color = new Color4(.2f, .2f, .2f, 1.0f); if (mat.HasColorAmbient) { color = AssimpToOpenTk.FromColor(mat.ColorAmbient); float shiftC(float oc) => oc * (1.0f - minAmb) + minAmb; color.R = shiftC(color.R); color.G = shiftC(color.G); color.B = shiftC(color.B); } color.A *= alpha; GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Ambient, color); color = new Color4(0, 0, 0, 1.0f); if (mat.HasColorEmissive) { c = AssimpToOpenTk.FromColor(mat.ColorEmissive); if (c.R + c.G + c.B > 0.0f) { color = c; } } GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Emission, color); // Assimp seems convert obj/mtl Ns value to Shininess and the strength value is not set float shininess = 1; float strength = 1; if (mat.HasShininess && mat.Shininess > 0.0f) { shininess = mat.Shininess; } // todo: I don't even remember how shininess strength was supposed to be handled in assimp if (mat.HasShininessStrength && mat.ShininessStrength > 0.0f) { strength = mat.ShininessStrength; } var exp = shininess * strength; if (exp >= 128.0f) // 128 is the maximum exponent as per the Gl spec { exp = 128.0f; } GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Shininess, exp); } else if (!hasColors) { GL.Color3(color.R, color.G, color.B); } if (hasAlpha) { GL.Enable(EnableCap.Blend); GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); GL.DepthMask(false); } else { GL.Disable(EnableCap.Blend); GL.DepthMask(true); } }
/// <summary> /// Obtain the bone matrices for a given node mesh index at the /// current time. Calling this is costly, redundant invocations /// should thus be avoided. /// </summary> /// <param name="node">Node for which to query bone matrices</param> /// <param name="mesh">Mesh for which to query bone matrices. Must be /// one of the meshes attached to the node.</param> /// <returns>For each bone of the mesh the bone transformation /// matrix. The returned array is only valid for the rest of /// the frame or till the next call to GetBoneMatricesForMesh(). /// It may contain more entries than the mesh has bones, the extra entries /// should be ignored in this case.</returns> public Matrix4[] GetBoneMatricesForMesh(Node node, Mesh mesh) { Debug.Assert(node != null); Debug.Assert(mesh != null); // calculate the mesh's inverse global transform Matrix4 globalInverseMeshTransform; GetGlobalTransform(node, out globalInverseMeshTransform); globalInverseMeshTransform.Invert(); // Bone matrices transform from mesh coordinates in bind pose to mesh coordinates in skinned pose // Therefore the formula is offsetMatrix * currentGlobalTransform * inverseCurrentMeshTransform for (int a = 0; a < mesh.BoneCount; ++a) { var bone = mesh.Bones[a]; Matrix4 currentGlobalTransform; GetGlobalTransform(bone.Name, out currentGlobalTransform); _boneMatrices[a] = globalInverseMeshTransform * currentGlobalTransform * AssimpToOpenTk.FromMatrix(bone.OffsetMatrix); // TODO for some reason, all OpenTk matrices need a ^T - clarify our conventions somewhere _boneMatrices[a].Transpose(); } return(_boneMatrices); }
public override void ApplyMaterial(Mesh mesh, Material mat, bool textured, bool shaded, bool twoSided) { RenderControl.GLError("StartMaterialMapper"); ShaderGen.GenFlags flags = 0; var hasAlpha = false; var hasTexture = false; // note: keep this up-to-date with the code in MaterialMapper.UploadTextures() for (int i = 0; i < Renderer.modernGLUsedTextureTypeCount; i++) { TextureType currTextureType = (TextureType)((int)TextureType.Diffuse + i); GL.ActiveTexture((TextureUnit)((int)TextureUnit.Texture0 + i)); GL.BindTexture(TextureTarget.Texture2D, Renderer.modernGLTextureType[i]); //we use own texture always, even when textures off to supply preset values if (textured && mat.GetMaterialTextureCount(currTextureType) > 0) { hasTexture = true; //flags |= ShaderGen.GenFlags.Texture; flag not used, we always have some texture TextureSlot tex; mat.GetMaterialTexture(currTextureType, 0, out tex); var gtex = _scene.TextureSet.GetOriginalOrReplacement(tex.FilePath); hasAlpha = hasAlpha || gtex.HasAlpha == Texture.AlphaState.HasAlpha; if (gtex.State == Texture.TextureState.GlTextureCreated) { gtex.BindGlTexture(); } } } GL.ActiveTexture(TextureUnit.Texture0); RenderControl.GLError("EndTextureSettings"); if (shaded) { flags |= ShaderGen.GenFlags.Lighting; } var hasColors = mesh != null && mesh.HasVertexColors(0); if (hasColors) { flags |= ShaderGen.GenFlags.VertexColor; } if (_UseSceneLights) { flags |= ShaderGen.GenFlags.PhongSpecularShading; } if ((mat.IsTwoSided) || (twoSided)) { flags |= ShaderGen.GenFlags.TwoSide; } Shader shader = _shaderGen.GenerateOrGetFromCache(flags, _LightCount > 0 ? _LightCount : 1); shader.BindIfNecessary(); Matrix4 curView = Matrix4.CreateScale(_scene.Scale) * _View; shader.SetMat4("WorldViewProjection", _World * curView * _Perspective); Matrix4 cameraPos = _View.ClearRotation(); Matrix4 cameraRotation = _View.ClearTranslation(); Matrix4 cam = Matrix4.Identity; cam = cameraPos * cameraRotation; Vector3 cameraPosition = -cam.ExtractTranslation() / _scene.Scale;//does not work for orbitcontroller // cameraPosition = new Vector3(200,100,-100); //1m = 100units and positive cameraPosition.Z = -cameraPosition.Z; shader.SetVec3("CameraPosition", -cameraPosition); shader.SetMat4("World", _World); shader.SetMat4("WorldView", _World * curView); //_world* curView keeps light source at "fixed" position during rotating of the model shader.SetFloat("SceneBrightness", _SceneBrightness); shader.SetFloat("Material.diffuse", 0); shader.SetFloat("Material.ambient", 1); shader.SetFloat("Material.specular", 2); shader.SetFloat("Material.emissive", 3); shader.SetFloat("Material.height", 4); shader.SetFloat("Material.normal", 5); shader.SetLights(_GLLights, _LightCount); RenderControl.GLError("UniformSettings"); // note: keep semantics of hasAlpha consistent with IsAlphaMaterial() var alpha = 1.0f; if (mat.HasOpacity) { alpha = mat.Opacity; if (alpha < AlphaSuppressionThreshold) // suppress zero opacity, this is likely wrong input data { alpha = 1.0f; } } var color = new Color4(.8f, .8f, .8f, 1.0f); if (mat.HasColorDiffuse) { color = AssimpToOpenTk.FromColor(mat.ColorDiffuse); if (color.A < AlphaSuppressionThreshold) // s.a. { color.A = 1.0f; } } color.A *= alpha; hasAlpha = hasAlpha || color.A < 1.0f; if (shaded) { // if the material has a texture but the diffuse color texture is all black, // then heuristically assume that this is an import/export flaw and substitute // white. if (hasTexture && color.R < 1e-3f && color.G < 1e-3f && color.B < 1e-3f) { color = Color4.White; } shader.SetCol4("MaterialDiffuse_Alpha", color); color = new Color4(0, 0, 0, 1.0f); if (mat.HasColorSpecular) { color = AssimpToOpenTk.FromColor(mat.ColorSpecular); } shader.SetCol4("MaterialSpecular", color); color = new Color4(.2f, .2f, .2f, 1.0f); if (mat.HasColorAmbient) { color = AssimpToOpenTk.FromColor(mat.ColorAmbient); } shader.SetCol4("MaterialAmbient", color); color = new Color4(0, 0, 0, 1.0f); if (mat.HasColorEmissive) { color = AssimpToOpenTk.FromColor(mat.ColorEmissive); } shader.SetCol4("MaterialEmissive", color); float shininess = 1; float strength = 1; if (mat.HasShininess) { shininess = mat.Shininess; } // todo: I don't even remember how shininess strength was supposed to be handled in assimp .. Scales the specular color of the material.Not implemented here. if (mat.HasShininessStrength) { strength = mat.ShininessStrength; } var exp = shininess; if (exp >= 128.0f) // 128 is the maximum exponent as per the Gl spec { exp = 128.0f; } shader.SetFloat("MaterialShininess", exp); //Shininess may be at mat.ColorSpecular.a too..?? but in FBX is } else if (!hasColors) { shader.SetCol4("MaterialDiffuse_Alpha", color); } if (hasAlpha) { GL.Enable(EnableCap.Blend); GL.BlendFuncSeparate(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha, BlendingFactorSrc.SrcAlpha, BlendingFactorDest.One); GL.DepthMask(false); } else { GL.DepthMask(true); GL.Disable(EnableCap.Blend); } RenderControl.GLError("EndMaterialMapper"); }
/// <summary> /// Recursive render function for drawing opaque geometry with no scaling /// in the transformation chain. This is used for overlays, such as drawing /// the skeleton. /// </summary> /// <param name="node"></param> /// <param name="visibleMeshesByNode"></param> /// <param name="flags"></param> /// <param name="invGlobalScale"></param> /// <param name="animated"></param> private void RecursiveRenderNoScale(Node node, Dictionary <Node, List <Mesh> > visibleMeshesByNode, RenderFlags flags, float invGlobalScale, bool animated, int currDispList) { // TODO unify our use of OpenTK and Assimp matrices Matrix4x4 m; Matrix4 mConv; if (animated) { Owner.SceneAnimator.GetLocalTransform(node, out mConv); OpenTkToAssimp.FromMatrix(ref mConv, out m); } else { m = node.Transform; } // get rid of the scaling part of the matrix // TODO this can be done faster and Decompose() doesn't handle // non positively semi-definite matrices correctly anyway. Vector3D scaling; Assimp.Quaternion rotation; Vector3D translation; m.Decompose(out scaling, out rotation, out translation); rotation.Normalize(); m = new Matrix4x4(rotation.GetMatrix()) * Matrix4x4.FromTranslation(translation); mConv = AssimpToOpenTk.FromMatrix(ref m); mConv.Transpose(); if (flags.HasFlag(RenderFlags.ShowSkeleton)) { var highlight = false; if (visibleMeshesByNode != null) { List <Mesh> meshList; if (visibleMeshesByNode.TryGetValue(node, out meshList) && meshList == null) { // If the user hovers over a node in the tab view, all of its descendants // are added to the visible set as well. This is not the intended // behavior for skeleton joints, though! Here we only want to show the // joint corresponding to the node being hovered over. // Therefore, only highlight nodes whose parents either don't exist // or are not in the visible set. if (node.Parent == null || !visibleMeshesByNode.TryGetValue(node.Parent, out meshList) || meshList != null) { highlight = true; } } } OverlaySkeleton.DrawSkeletonBone(node, invGlobalScale, highlight); } GL.PushMatrix(); GL.MultMatrix(ref mConv); for (int i = 0; i < node.ChildCount; i++) { RecursiveRenderNoScale(node.Children[i], visibleMeshesByNode, flags, invGlobalScale, animated, currDispList); } GL.PopMatrix(); }
/// <summary> /// Draw a mesh using either its given material or a transparent "ghost" material. /// </summary> /// <param name="node">Current node</param> /// <param name="animated">Specifies whether animations should be played</param> /// <param name="showGhost">Indicates whether to substitute the mesh' material with a /// "ghost" surrogate material that allows looking through the geometry.</param> /// <param name="index">Mesh index in the scene</param> /// <param name="mesh">Mesh instance</param> /// <param name="flags"> </param> /// <returns></returns> protected override bool InternDrawMesh(Node node, bool animated, bool showGhost, int index, Mesh mesh, RenderFlags flags) { if (showGhost) { Owner.MaterialMapper.ApplyGhostMaterial(mesh, Owner.Raw.Materials[mesh.MaterialIndex], flags.HasFlag(RenderFlags.Shaded), flags.HasFlag(RenderFlags.ForceTwoSidedLighting)); } else { Owner.MaterialMapper.ApplyMaterial(mesh, Owner.Raw.Materials[mesh.MaterialIndex], flags.HasFlag(RenderFlags.Textured), flags.HasFlag(RenderFlags.Shaded), flags.HasFlag(RenderFlags.ForceTwoSidedLighting)); } if (GraphicsSettings.Default.BackFaceCulling) { GL.FrontFace(FrontFaceDirection.Ccw); GL.CullFace(CullFaceMode.Back); GL.Enable(EnableCap.CullFace); } else { GL.Disable(EnableCap.CullFace); } var hasColors = mesh.HasVertexColors(0); var hasTexCoords = mesh.HasTextureCoords(0); var skinning = mesh.HasBones && animated; foreach (var face in mesh.Faces) { BeginMode faceMode; switch (face.IndexCount) { case 1: faceMode = BeginMode.Points; break; case 2: faceMode = BeginMode.Lines; break; case 3: faceMode = BeginMode.Triangles; break; default: faceMode = BeginMode.Polygon; break; } GL.Begin(faceMode); for (var i = 0; i < face.IndexCount; i++) { var indice = face.Indices[i]; if (hasColors) { var vertColor = AssimpToOpenTk.FromColor(mesh.VertexColorChannels[0][indice]); GL.Color4(vertColor); } if (mesh.HasNormals) { Vector3 normal; if (skinning) { Skinner.GetTransformedVertexNormal(node, mesh, (uint)indice, out normal); } else { normal = AssimpToOpenTk.FromVector(mesh.Normals[indice]); } GL.Normal3(normal); } if (hasTexCoords) { var uvw = AssimpToOpenTk.FromVector(mesh.TextureCoordinateChannels[0][indice]); GL.TexCoord2(uvw.X, 1 - uvw.Y); } Vector3 pos; if (skinning) { Skinner.GetTransformedVertexPosition(node, mesh, (uint)indice, out pos); } else { pos = AssimpToOpenTk.FromVector(mesh.Vertices[indice]); } GL.Vertex3(pos); } GL.End(); } GL.Disable(EnableCap.CullFace); return(skinning); }
private void ApplyFixedFunctionMaterial(Mesh mesh, Material mat, bool textured, bool shaded) { shaded = shaded && (mesh == null || mesh.HasNormals); if (shaded) { GL.Enable(EnableCap.Lighting); } else { GL.Disable(EnableCap.Lighting); } var hasColors = mesh != null && mesh.HasVertexColors(0); if (hasColors) { GL.Enable(EnableCap.ColorMaterial); GL.ColorMaterial(MaterialFace.FrontAndBack, ColorMaterialParameter.AmbientAndDiffuse); } else { GL.Disable(EnableCap.ColorMaterial); } // note: keep semantics of hasAlpha consistent with IsAlphaMaterial() var hasAlpha = false; var hasTexture = false; // note: keep this up-to-date with the code in UploadTextures() if (textured && mat.GetMaterialTextureCount(TextureType.Diffuse) > 0) { hasTexture = true; TextureSlot tex; mat.GetMaterialTexture(TextureType.Diffuse, 0, out tex); var gtex = _scene.TextureSet.GetOriginalOrReplacement(tex.FilePath); hasAlpha = hasAlpha || gtex.HasAlpha == Texture.AlphaState.HasAlpha; if (gtex.State == Texture.TextureState.GlTextureCreated) { GL.ActiveTexture(TextureUnit.Texture0); gtex.BindGlTexture(); GL.Enable(EnableCap.Texture2D); } else { GL.Disable(EnableCap.Texture2D); } } else { GL.Disable(EnableCap.Texture2D); } GL.Enable(EnableCap.Normalize); var alpha = 1.0f; if (mat.HasOpacity) { alpha = mat.Opacity; if (alpha < AlphaSuppressionThreshold) // suppress zero opacity, this is likely wrong input data { alpha = 1.0f; } } var color = new Color4(.8f, .8f, .8f, 1.0f); if (mat.HasColorDiffuse) { color = AssimpToOpenTk.FromColor(mat.ColorDiffuse); if (color.A < AlphaSuppressionThreshold) // s.a. { color.A = 1.0f; } } color.A *= alpha; hasAlpha = hasAlpha || color.A < 1.0f; if (shaded) { // if the material has a texture but the diffuse color texture is all black, // then heuristically assume that this is an import/export flaw and substitute // white. if (hasTexture && color.R < 1e-3f && color.G < 1e-3f && color.B < 1e-3f) { GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Diffuse, Color4.White); } else { GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Diffuse, color); } color = new Color4(0, 0, 0, 1.0f); if (mat.HasColorSpecular) { color = AssimpToOpenTk.FromColor(mat.ColorSpecular); } GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Specular, color); color = new Color4(.2f, .2f, .2f, 1.0f); if (mat.HasColorAmbient) { color = AssimpToOpenTk.FromColor(mat.ColorAmbient); } GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Ambient, color); color = new Color4(0, 0, 0, 1.0f); if (mat.HasColorEmissive) { color = AssimpToOpenTk.FromColor(mat.ColorEmissive); } GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Emission, color); float shininess = 1; float strength = 1; if (mat.HasShininess) { shininess = mat.Shininess; } // todo: I don't even remember how shininess strength was supposed to be handled in assimp if (mat.HasShininessStrength) { strength = mat.ShininessStrength; } var exp = shininess * strength; if (exp >= 128.0f) // 128 is the maximum exponent as per the Gl spec { exp = 128.0f; } GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Shininess, exp); } else if (!hasColors) { GL.Color3(color.R, color.G, color.B); } if (hasAlpha) { GL.Enable(EnableCap.Blend); GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); GL.DepthMask(false); } else { GL.Disable(EnableCap.Blend); GL.DepthMask(true); } }