Browse Source

Morph targets with color channels require the colors to be encoded with full floating point to allow negative values.

Vicente Penades 3 years ago
parent
commit
74dfa10c4e

+ 50 - 11
src/SharpGLTF.Core/Transforms/MeshTransforms.cs

@@ -5,13 +5,21 @@ using System.Numerics;
 using System.Text;
 using System.Text;
 
 
 using TRANSFORM = System.Numerics.Matrix4x4;
 using TRANSFORM = System.Numerics.Matrix4x4;
+using V2 = System.Numerics.Vector2;
 using V3 = System.Numerics.Vector3;
 using V3 = System.Numerics.Vector3;
 using V4 = System.Numerics.Vector4;
 using V4 = System.Numerics.Vector4;
 
 
 namespace SharpGLTF.Transforms
 namespace SharpGLTF.Transforms
 {
 {
+    public interface IMaterialTransform
+    {
+        V2 MorphTexCoord(V2 texCoord, IReadOnlyList<V2> morphTargets);
+
+        V4 MorphColors(V4 color, IReadOnlyList<V4> morphTargets);
+    }
+
     /// <summary>
     /// <summary>
-    /// Interface for a mesh transform object
+    /// Interface for a vertex transform object
     /// </summary>
     /// </summary>
     public interface IGeometryTransform
     public interface IGeometryTransform
     {
     {
@@ -58,8 +66,6 @@ namespace SharpGLTF.Transforms
         /// <param name="skinWeights">The skin weights of the vertex, or default.</param>
         /// <param name="skinWeights">The skin weights of the vertex, or default.</param>
         /// <returns>A tangent in world space.</returns>
         /// <returns>A tangent in world space.</returns>
         V4 TransformTangent(V4 tangent, IReadOnlyList<V3> tangentDeltas, in SparseWeight8 skinWeights);
         V4 TransformTangent(V4 tangent, IReadOnlyList<V3> tangentDeltas, in SparseWeight8 skinWeights);
-
-        V4 MorphColors(V4 color, IReadOnlyList<V4> morphTargets);
     }
     }
 
 
     public interface IGeometryInstancing
     public interface IGeometryInstancing
@@ -70,7 +76,7 @@ namespace SharpGLTF.Transforms
         IReadOnlyList<RigidTransform> WorldTransforms { get; }
         IReadOnlyList<RigidTransform> WorldTransforms { get; }
     }
     }
 
 
-    public abstract class MorphTransform
+    public abstract class MorphTransform : IMaterialTransform
     {
     {
         #region constructor
         #region constructor
 
 
@@ -123,7 +129,7 @@ namespace SharpGLTF.Transforms
 
 
         #region API
         #region API
 
 
-        public void Update(SparseWeight8 morphWeights, bool useAbsoluteMorphTargets = false)
+        public void Update(in SparseWeight8 morphWeights, bool useAbsoluteMorphTargets = false)
         {
         {
             _AbsoluteMorphTargets = useAbsoluteMorphTargets;
             _AbsoluteMorphTargets = useAbsoluteMorphTargets;
 
 
@@ -136,6 +142,34 @@ namespace SharpGLTF.Transforms
             _Weights = morphWeights.GetNormalizedWithComplement(COMPLEMENT_INDEX);
             _Weights = morphWeights.GetNormalizedWithComplement(COMPLEMENT_INDEX);
         }
         }
 
 
+        protected V2 MorphVectors(V2 value, IReadOnlyList<V2> morphTargets)
+        {
+            if (morphTargets == null || morphTargets.Count == 0) return value;
+
+            if (_Weights.Index0 == COMPLEMENT_INDEX && _Weights.Weight0 == 1) return value;
+
+            var p = V2.Zero;
+
+            if (_AbsoluteMorphTargets)
+            {
+                foreach (var (index, weight) in _Weights.GetNonZeroWeights())
+                {
+                    var val = index == COMPLEMENT_INDEX ? value : morphTargets[index];
+                    p += val * weight;
+                }
+            }
+            else
+            {
+                foreach (var (index, weight) in _Weights.GetNonZeroWeights())
+                {
+                    var val = index == COMPLEMENT_INDEX ? value : value + morphTargets[index];
+                    p += val * weight;
+                }
+            }
+
+            return p;
+        }
+
         protected V3 MorphVectors(V3 value, IReadOnlyList<V3> morphTargets)
         protected V3 MorphVectors(V3 value, IReadOnlyList<V3> morphTargets)
         {
         {
             if (morphTargets == null || morphTargets.Count == 0) return value;
             if (morphTargets == null || morphTargets.Count == 0) return value;
@@ -174,18 +208,18 @@ namespace SharpGLTF.Transforms
 
 
             if (_AbsoluteMorphTargets)
             if (_AbsoluteMorphTargets)
             {
             {
-                foreach (var pair in _Weights.GetNonZeroWeights())
+                foreach (var (index, weight) in _Weights.GetNonZeroWeights())
                 {
                 {
-                    var val = pair.Index == COMPLEMENT_INDEX ? value : morphTargets[pair.Index];
-                    p += val * pair.Weight;
+                    var val = index == COMPLEMENT_INDEX ? value : morphTargets[index];
+                    p += val * weight;
                 }
                 }
             }
             }
             else
             else
             {
             {
-                foreach (var pair in _Weights.GetNonZeroWeights())
+                foreach (var (index, weight) in _Weights.GetNonZeroWeights())
                 {
                 {
-                    var val = pair.Index == COMPLEMENT_INDEX ? value : value + morphTargets[pair.Index];
-                    p += val * pair.Weight;
+                    var val = index == COMPLEMENT_INDEX ? value : value + morphTargets[index];
+                    p += val * weight;
                 }
                 }
             }
             }
 
 
@@ -197,6 +231,11 @@ namespace SharpGLTF.Transforms
             return MorphVectors(color, morphTargets);
             return MorphVectors(color, morphTargets);
         }
         }
 
 
+        public V2 MorphTexCoord(V2 texCoord, IReadOnlyList<V2> morphTargets)
+        {
+            return MorphVectors(texCoord, morphTargets);
+        }
+
         #endregion
         #endregion
     }
     }
 
 

+ 9 - 4
src/SharpGLTF.Toolkit/Geometry/MorphTargetBuilder.cs

@@ -36,6 +36,7 @@ namespace SharpGLTF.Geometry
     /// <see cref="PrimitiveBuilder{TMaterial, TvG, TvM, TvS}._UseMorphTarget(int)"/>
     /// <see cref="PrimitiveBuilder{TMaterial, TvG, TvM, TvS}._UseMorphTarget(int)"/>
     /// </summary>
     /// </summary>
     /// <typeparam name="TvG">The vertex fragment type with Position, Normal and Tangent.</typeparam>
     /// <typeparam name="TvG">The vertex fragment type with Position, Normal and Tangent.</typeparam>
+    /// <typeparam name="TvM">The vertex fragment type with Color0, Color1, TexCoord0, TexCoord1.</typeparam>
     class PrimitiveMorphTargetBuilder<TvG, TvM> : IPrimitiveMorphTargetReader
     class PrimitiveMorphTargetBuilder<TvG, TvM> : IPrimitiveMorphTargetReader
         where TvG : struct, IVertexGeometry
         where TvG : struct, IVertexGeometry
         where TvM : struct, IVertexMaterial
         where TvM : struct, IVertexMaterial
@@ -388,14 +389,18 @@ namespace SharpGLTF.Geometry
 
 
         void IMorphTargetBuilder.SetVertex(IVertexGeometry meshVertex, IVertexGeometry morphVertex)
         void IMorphTargetBuilder.SetVertex(IVertexGeometry meshVertex, IVertexGeometry morphVertex)
         {
         {
-            SetVertex(meshVertex.ConvertToGeometry<TvG>(), 
-                new VertexBuilder<TvG, TvM, VertexEmpty>(morphVertex.ConvertToGeometry<TvG>(), default(VertexEmpty).ConvertToMaterial<TvM>()));
+            SetVertex(
+                meshVertex.ConvertToGeometry<TvG>(),
+                new VertexBuilder<TvG, TvM, VertexEmpty>(morphVertex.ConvertToGeometry<TvG>(), default(VertexEmpty).ConvertToMaterial<TvM>())
+                );
         }
         }
 
 
         void IMorphTargetBuilder.SetVertex(IVertexGeometry meshVertex, IVertexGeometry morphVertex, IVertexMaterial morphMaterial)
         void IMorphTargetBuilder.SetVertex(IVertexGeometry meshVertex, IVertexGeometry morphVertex, IVertexMaterial morphMaterial)
         {
         {
-            SetVertex(meshVertex.ConvertToGeometry<TvG>(),
-                new VertexBuilder<TvG, TvM, VertexEmpty>(morphVertex.ConvertToGeometry<TvG>(), morphMaterial.ConvertToMaterial<TvM>()));
+            SetVertex(
+                meshVertex.ConvertToGeometry<TvG>(),
+                new VertexBuilder<TvG, TvM, VertexEmpty>(morphVertex.ConvertToGeometry<TvG>(), morphMaterial.ConvertToMaterial<TvM>())
+                );
         }
         }
 
 
         void IMorphTargetBuilder.SetVertexDelta(IVertexGeometry meshVertex, VertexGeometryDelta geometryDelta)
         void IMorphTargetBuilder.SetVertexDelta(IVertexGeometry meshVertex, VertexGeometryDelta geometryDelta)

+ 1 - 0
src/SharpGLTF.Toolkit/Geometry/Packed/PackedEncoding.cs

@@ -9,6 +9,7 @@ namespace SharpGLTF.Geometry
 {
 {
     class PackedEncoding
     class PackedEncoding
     {
     {
+        public ENCODING? ColorEncoding;
         public ENCODING? JointsEncoding;
         public ENCODING? JointsEncoding;
         public ENCODING? WeightsEncoding;
         public ENCODING? WeightsEncoding;
 
 

+ 16 - 2
src/SharpGLTF.Toolkit/Geometry/Packed/PackedMeshBuilder.cs

@@ -48,10 +48,24 @@ namespace SharpGLTF.Geometry
                 {
                 {
                     if (srcPrim.Vertices.Count == 0) continue;
                     if (srcPrim.Vertices.Count == 0) continue;
 
 
-                    var dstPrim = dstMesh.AddPrimitive(srcPrim.Material, srcPrim.VerticesPerPrimitive);
+                    vertexEncodings.ColorEncoding = null;
 
 
                     bool useStrided = settings.UseStridedBuffers;
                     bool useStrided = settings.UseStridedBuffers;
-                    if (srcPrim.MorphTargets.Count > 0) useStrided = false; // if the primitive has morphing, it is better not to use strided vertex buffers.
+
+                    if (srcPrim.MorphTargets.Count > 0)
+                    {
+                        // if the primitive has morphing, it is better not to use strided vertex buffers.
+                        useStrided = false;
+
+                        // if the primitive has color morphing, we need to ensure the vertex
+                        // color attribute encoding is FLOAT to allow negative delta values.
+                        if (PackedPrimitiveBuilder<TMaterial>._HasColorMorphTargets(srcPrim))
+                        {
+                            vertexEncodings.ColorEncoding = EncodingType.FLOAT;
+                        }
+                    }
+
+                    var dstPrim = dstMesh.AddPrimitive(srcPrim.Material, srcPrim.VerticesPerPrimitive);
 
 
                     if (useStrided) dstPrim.SetStridedVertices(srcPrim, vertexEncodings);
                     if (useStrided) dstPrim.SetStridedVertices(srcPrim, vertexEncodings);
                     else dstPrim.SetStreamedVertices(srcPrim, vertexEncodings);
                     else dstPrim.SetStreamedVertices(srcPrim, vertexEncodings);

+ 19 - 0
src/SharpGLTF.Toolkit/Geometry/Packed/PackedPrimitiveBuilder.cs

@@ -87,6 +87,25 @@ namespace SharpGLTF.Geometry
             _IndexAccessors = iAccessor;
             _IndexAccessors = iAccessor;
         }
         }
 
 
+        public static bool _HasColorMorphTargets(IPrimitiveReader<TMaterial> srcPrim)
+        {
+            var vertexEncodings = new PackedEncoding();
+            vertexEncodings.ColorEncoding = EncodingType.FLOAT;
+
+            for (int i = 0; i < srcPrim.MorphTargets.Count; ++i)
+            {
+                var mtv = srcPrim.MorphTargets[i].GetMorphTargetVertices(srcPrim.Vertices.Count);
+
+                var c0Accessor = VertexTypes.VertexUtils.CreateVertexMemoryAccessor(mtv, "COLOR_0DELTA", vertexEncodings);
+                if (c0Accessor != null && c0Accessor.Data.Any(b => b != 0)) return true;
+
+                var c1Accessor = VertexTypes.VertexUtils.CreateVertexMemoryAccessor(mtv, "COLOR_1DELTA", vertexEncodings);
+                if (c1Accessor != null && c1Accessor.Data.Any(b => b != 0)) return true;
+            }
+
+            return false;
+        }
+
         public void SetMorphTargets(IPrimitiveReader<TMaterial> srcPrim, PackedEncoding vertexEncodings)
         public void SetMorphTargets(IPrimitiveReader<TMaterial> srcPrim, PackedEncoding vertexEncodings)
         {
         {
             bool hasPositions = _VertexAccessors.Any(item => item.Attribute.Name == "POSITION");
             bool hasPositions = _VertexAccessors.Any(item => item.Attribute.Name == "POSITION");

+ 48 - 6
src/SharpGLTF.Toolkit/Geometry/VertexBufferColumns.cs

@@ -162,19 +162,27 @@ namespace SharpGLTF.Geometry
             // since the attributes we want to overwrite might be bound directly to the model's buffer
             // since the attributes we want to overwrite might be bound directly to the model's buffer
             // data, and we don't want to modify the source data, we isolate the columns to be overwritten.
             // data, and we don't want to modify the source data, we isolate the columns to be overwritten.
 
 
-            this.Positions = _IsolateColumn(this.Positions);
+            this.Positions = _IsolateColumn(this.Positions); // Position, normal and tangent can be modified by morphing and skinning
             this.Normals = _IsolateColumn(this.Normals);
             this.Normals = _IsolateColumn(this.Normals);
             this.Tangents = _IsolateColumn(this.Tangents);
             this.Tangents = _IsolateColumn(this.Tangents);
-            this.Colors0 = _IsolateColumn(this.Colors0);
+            this.Colors0 = _IsolateColumn(this.Colors0);     // colors0,1 and texCoords0,1 can be modified by morphing
+            this.Colors1 = _IsolateColumn(this.Colors1);
+            this.TexCoords0 = _IsolateColumn(this.TexCoords0);
+            this.TexCoords1 = _IsolateColumn(this.TexCoords1);
 
 
             // prepare animation data, if available
             // prepare animation data, if available
 
 
             var skinning = default(Transforms.SparseWeight8);
             var skinning = default(Transforms.SparseWeight8);
 
 
+            Transforms.IMaterialTransform morphMaterial = transform as Transforms.IMaterialTransform;
+
             Vector3[] morphPositions = null;
             Vector3[] morphPositions = null;
             Vector3[] morphNormals = null;
             Vector3[] morphNormals = null;
             Vector3[] morphTangents = null;
             Vector3[] morphTangents = null;
-            Vector4[] morphColors0 = null; // we clone it because it can be affected by morph targets
+            Vector4[] morphColors0 = null;
+            Vector4[] morphColors1 = null;
+            Vector2[] morphTexcrd0 = null;
+            Vector2[] morphTexcrd1 = null;
 
 
             if (_MorphTargets != null)
             if (_MorphTargets != null)
             {
             {
@@ -182,6 +190,9 @@ namespace SharpGLTF.Geometry
                 if (_MorphTargets.All(item => item.Normals != null)) morphNormals = new Vector3[this.MorphTargets.Count];
                 if (_MorphTargets.All(item => item.Normals != null)) morphNormals = new Vector3[this.MorphTargets.Count];
                 if (_MorphTargets.All(item => item.Tangents != null)) morphTangents = new Vector3[this.MorphTargets.Count];
                 if (_MorphTargets.All(item => item.Tangents != null)) morphTangents = new Vector3[this.MorphTargets.Count];
                 if (_MorphTargets.All(item => item.Colors0 != null)) morphColors0 = new Vector4[this.MorphTargets.Count];
                 if (_MorphTargets.All(item => item.Colors0 != null)) morphColors0 = new Vector4[this.MorphTargets.Count];
+                if (_MorphTargets.All(item => item.Colors1 != null)) morphColors1 = new Vector4[this.MorphTargets.Count];
+                if (_MorphTargets.All(item => item.TexCoords0 != null)) morphTexcrd0 = new Vector2[this.MorphTargets.Count];
+                if (_MorphTargets.All(item => item.TexCoords1 != null)) morphTexcrd1 = new Vector2[this.MorphTargets.Count];
             }
             }
 
 
             // loop over every vertex
             // loop over every vertex
@@ -214,10 +225,31 @@ namespace SharpGLTF.Geometry
                     Tangents[i] = transform.TransformTangent(Tangents[i], morphTangents, skinning);
                     Tangents[i] = transform.TransformTangent(Tangents[i], morphTangents, skinning);
                 }
                 }
 
 
-                if (this.Colors0 != null)
+                if (morphMaterial != null)
                 {
                 {
-                    _FillMorphData(morphColors0, vc => vc.Colors0[i]);
-                    Colors0[i] = transform.MorphColors(Colors0[i], morphColors0);
+                    if (this.Colors0 != null)
+                    {
+                        _FillMorphData(morphColors0, vc => vc.Colors0[i]);
+                        Colors0[i] = morphMaterial.MorphColors(Colors0[i], morphColors0);
+                    }
+
+                    if (this.Colors1 != null)
+                    {
+                        _FillMorphData(morphColors1, vc => vc.Colors1[i]);
+                        Colors1[i] = morphMaterial.MorphColors(Colors1[i], morphColors1);
+                    }
+
+                    if (this.TexCoords0 != null)
+                    {
+                        _FillMorphData(morphTexcrd0, vc => vc.TexCoords0[i]);
+                        TexCoords0[i] = morphMaterial.MorphTexCoord(TexCoords0[i], morphTexcrd0);
+                    }
+
+                    if (this.TexCoords1 != null)
+                    {
+                        _FillMorphData(morphTexcrd1, vc => vc.TexCoords1[i]);
+                        TexCoords1[i] = morphMaterial.MorphTexCoord(TexCoords1[i], morphTexcrd1);
+                    }
                 }
                 }
             }
             }
 
 
@@ -231,6 +263,16 @@ namespace SharpGLTF.Geometry
             Weights1 = null;
             Weights1 = null;
         }
         }
 
 
+        private void _FillMorphData(Vector2[] array, Converter<VertexBufferColumns, Vector2> selector)
+        {
+            if (array == null) return;
+
+            for (int i = 0; i < this._MorphTargets.Count; ++i)
+            {
+                array[i] = selector(this._MorphTargets[i]);
+            }
+        }
+
         private void _FillMorphData(Vector3[] array, Converter<VertexBufferColumns, Vector3> selector)
         private void _FillMorphData(Vector3[] array, Converter<VertexBufferColumns, Vector3> selector)
         {
         {
             if (array == null) return;
             if (array == null) return;

+ 14 - 1
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.Accessors.cs

@@ -114,7 +114,20 @@ namespace SharpGLTF.Geometry.VertexTypes
             foreach (var finfo in tvm.GetFields())
             foreach (var finfo in tvm.GetFields())
             {
             {
                 var attribute = _GetMemoryAccessInfo(finfo);
                 var attribute = _GetMemoryAccessInfo(finfo);
-                if (attribute.HasValue) attributes.Add(attribute.Value);
+                if (attribute.HasValue)
+                {
+                    var a = attribute.Value;
+                    if (a.Name.StartsWith("COLOR_", StringComparison.OrdinalIgnoreCase))
+                    {
+                        if (vertexEncoding.ColorEncoding.HasValue)
+                        {
+                            a.Encoding = vertexEncoding.ColorEncoding.Value;
+                            a.Normalized = a.Encoding != ENCODING.FLOAT;
+                        }
+                    }
+
+                    attributes.Add(a);
+                }
             }
             }
 
 
             foreach (var finfo in tvs.GetFields())
             foreach (var finfo in tvs.GetFields())

+ 103 - 90
tests/SharpGLTF.ThirdParty.Tests/AceCebovTests.cs

@@ -1,90 +1,103 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-using System.Text;
-using System.Threading.Tasks;
-using NUnit.Framework;
-using SharpGLTF.Geometry;
-using SharpGLTF.Geometry.VertexTypes;
-using SharpGLTF.Materials;
-using VB = SharpGLTF.Geometry.VertexBuilder<SharpGLTF.Geometry.VertexTypes.VertexPosition,
-    SharpGLTF.Geometry.VertexTypes.VertexColor1,
-    SharpGLTF.Geometry.VertexTypes.VertexEmpty>;
-
-namespace SharpGLTF.ThirdParty
-{
-    internal class AceCebovTests
-    {
-        [Test]
-        public void TestMorphTargets()
-        {
-            // create material
-            var material = new MaterialBuilder()
-                .WithDoubleSide(true)
-                .WithMetallicRoughnessShader();
-
-            // create a mesh with two primitives, one for each material
-
-            var triangle = new MeshBuilder<VertexPosition, VertexColor1>("mesh");
-
-            var prim = triangle.UsePrimitive(material);
-            var redColor = new Vector4(1f, 0f, 0f, 1f);
-            prim.AddTriangle(new VB(new VertexPosition(-10, 0, 0), redColor),
-                new VB(new VertexPosition(10, 0, 0), redColor),
-                new VB(new VertexPosition(0, 10, 0), redColor));
-
-            // create a scene
-            var scene = new Scenes.SceneBuilder();
-
-            scene.AddRigidMesh(triangle, Matrix4x4.Identity);
-
-            var greenColor = new Vector4(0f, 1f, 0f, 1f);
-
-            // create a morph target that will move the triangle in X axis by 1 unit
-            // and change the color from red to green
-            var morphTargetBuilder = triangle.UseMorphTarget(0);
-            foreach (var vertexPosition in morphTargetBuilder.Vertices)
-            {
-                var newVertexPosition = vertexPosition;
-
-                // new vertex position is moved in X direction by 1 unit
-                newVertexPosition.Position.X += 1;
-
-                morphTargetBuilder.SetVertex(vertexPosition, new VB(newVertexPosition,
-                    // morph to green color
-                    greenColor));
-            }
-
-            Assert.AreEqual(3, morphTargetBuilder.Vertices.Count);
-
-            // save the model in different formats
-            var model = scene.ToGltf2();
-
-            var animation = model.CreateAnimation();
-
-            // create a morph channel
-            animation.CreateMorphChannel(model.LogicalNodes[0],
-                new Dictionary<float, float[]>
-                {
-                    { 0f, new[] { 0f } },
-                    { 1f, new[] { 1f } }
-                }, 1);
-
-            // save the model in different formats
-            // model.AttachToCurrentTest("ColorAndTextureMorphing.glb");
-            // model.AttachToCurrentTest("ColorAndTextureMorphing.gltf");
-
-            // bypassing AttachToCurrentTest until glTFValidator is fixed.
-
-            var outPath = TestContext.CurrentContext.GetAttachmentPath("ColorAndTextureMorphing.glb", true);
-            model.Save(outPath);
-            TestContext.AddTestAttachment(outPath);
-
-            outPath = TestContext.CurrentContext.GetAttachmentPath("ColorAndTextureMorphing.gltf", true);
-            model.Save(outPath);
-            TestContext.AddTestAttachment(outPath);
-
-        }
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+using System.Threading.Tasks;
+using NUnit.Framework;
+
+using SharpGLTF.Geometry;
+using SharpGLTF.Geometry.VertexTypes;
+using SharpGLTF.Materials;
+
+using VB = SharpGLTF.Geometry.VertexBuilder<SharpGLTF.Geometry.VertexTypes.VertexPosition,
+    SharpGLTF.Geometry.VertexTypes.VertexColor1,
+    SharpGLTF.Geometry.VertexTypes.VertexEmpty>;
+
+namespace SharpGLTF.ThirdParty
+{
+    internal class AceCebovTests
+    {
+        [Test]
+        public void TestMorphTargets()
+        {
+            // create material
+            var material = new MaterialBuilder()
+                .WithDoubleSide(true)
+                .WithMetallicRoughnessShader();
+
+            // create a mesh with two primitives, one for each material
+
+            var triangle = new MeshBuilder<VertexPosition, VertexColor1>("mesh");
+
+            var prim = triangle.UsePrimitive(material);
+            var redColor = new Vector4(1f, 0f, 0f, 1f);
+            prim.AddTriangle(new VB(new VertexPosition(-10, 0, 0), redColor),
+                new VB(new VertexPosition(10, 0, 0), redColor),
+                new VB(new VertexPosition(0, 10, 0), redColor));
+
+            // create a scene
+            var scene = new Scenes.SceneBuilder();
+
+            scene.AddRigidMesh(triangle, Matrix4x4.Identity);
+
+            var greenColor = new Vector4(0f, 1f, 0f, 1f);
+
+            // create a morph target that will move the triangle in X axis by 1 unit
+            // and change the color from red to green
+            var morphTargetBuilder = triangle.UseMorphTarget(0);
+            foreach (var vertexPosition in morphTargetBuilder.Vertices)
+            {
+                var newVertexPosition = vertexPosition;
+
+                // new vertex position is moved in X direction by 1 unit
+                newVertexPosition.Position.X += 1;
+
+                morphTargetBuilder.SetVertex(vertexPosition, new VB(newVertexPosition,
+                    // morph to green color
+                    greenColor));
+            }
+
+            Assert.AreEqual(3, morphTargetBuilder.Vertices.Count);
+
+            // save the model in different formats
+            var model = scene.ToGltf2();
+
+            var animation = model.CreateAnimation();
+
+            // create a morph channel
+            animation.CreateMorphChannel(model.LogicalNodes[0],
+                new Dictionary<float, float[]>
+                {
+                    { 0f, new[] { 0f } },
+                    { 1f, new[] { 1f } }
+                }, 1);
+
+            // evaluate triangles at animation 0.5, and get the color of the first pixel of the first triangle
+
+            var triangles = Schema2.Toolkit
+                .EvaluateTriangles(model.DefaultScene, null, model.LogicalAnimations[0], 0.5f)
+                .ToArray();
+
+            var morphedColor = triangles[0].A.GetMaterial().GetColor(0);
+            Assert.AreEqual(0.5f, morphedColor.X);
+            Assert.AreEqual(0.5f, morphedColor.Y);
+            Assert.AreEqual(0, morphedColor.Z);
+            Assert.AreEqual(1, morphedColor.W);
+
+            // save the model in different formats
+            // model.AttachToCurrentTest("ColorAndTextureMorphing.glb");
+            // model.AttachToCurrentTest("ColorAndTextureMorphing.gltf");
+
+            // bypassing AttachToCurrentTest until glTFValidator is fixed.
+
+            var outPath = TestContext.CurrentContext.GetAttachmentPath("ColorAndTextureMorphing.glb", true);
+            model.Save(outPath);
+            TestContext.AddTestAttachment(outPath);
+
+            outPath = TestContext.CurrentContext.GetAttachmentPath("ColorAndTextureMorphing.gltf", true);
+            model.Save(outPath);
+            TestContext.AddTestAttachment(outPath);
+        }
+    }
+}