适用于WPF 3D的glTF简介

目录

介绍

背景

提取3D模型

生成WPF 3D glTF查看器


介绍

我在求微分方程的项目中有点偏离了方向。我真的很想展示如何计算太阳系中行星的轨迹,并对广义相对论进行校正。如果只使用牛顿引力定律,有些行星实际上不会有正确的路径,因此您需要求解一些时间步长相对较大的非线性微分方程,这对于反向欧拉积分方法来说是完美的。

无论如何,我想以3D形式展示由此产生的行星,以便您可以看到轨道。但是不同的行星有不同的颜色和构成,所以为了将它们分开,我需要一些着色或等效ImageBrushes的纹理,如果发生这种情况的话。就在那时,我偶然发现了NASA的3D资源。它们以glTF文件格式(*.glb)存储了所有3D图像,您可以下载和使用,但是如何在WPF 3D中显示这些文件,这些文件到底是什么?

我只打算使用这个工具来获得一些非常简单的3D形状,您必须重写代码才能使其以一般方式工作。

背景

glTF 代表 graphics language Transmission Format,似乎是每个人在存储和发送3D图形组件时都在实施的新标准。当前版本规范由 Khronos Group 维护,该集团还在其Github帐户上发布了大量开发人员材料。

此处提供了描述交换格式的新标准2.0版本的文档。可以在以下快速指南pdf文档中查看该格式的快速指南。

文档类型实际上排列非常简单,取自快速指南:

因此,在*.glb中加载的代码实际上非常简单:

// Load all byte arrays from the Binary file glTF version 2
using (var stream = File.Open(filename, FileMode.Open))
{
    using (var reader = new BinaryReader(stream, Encoding.UTF8, false))
    {
        // Reading the initial data that determines the file type
        Magic = reader.ReadUInt32();
        Version = reader.ReadUInt32();
        TotalFileLength  = reader.ReadUInt32();
        
        // Read the JSON data
        JsonChuckLength = reader.ReadUInt32();
        UInt32 chunckType = reader.ReadUInt32();
        // Should be equal to JSON_hex 0x4E4F534A;         
        string hexValue = chunckType.ToString("X");
        
        JSON_data = reader.ReadBytes((int)JsonChuckLength);
        
        // Read the binary data
        BinChuckLength = reader.ReadUInt32();
        UInt32 chunckType2 = reader.ReadUInt32();
        // Should be equal to BIN_hex 0x004E4942;
        string hexValue2 = chunckType2.ToString("X");
        
        BIN_data = reader.ReadBytes((int)BinChuckLength);
    }
}

现在,我们已经将JSON数据和二进制数据提取到两个不同的数组中。但是,已经创建了一个工具,用于从JSON中提取所有信息,该工具可在github 上获得,也可作为NuGet包使用。但是,这只会提取有关文件如何组织、在哪里等的信息。实际数据要么在二进制部分,要么在单独的文件中。

KhoronosGroup Github帐户上有很多资源,但它们主要用于C#以外的编程语言。

提取3D模型

任何3D模型都会有一些位置数据,这些数据通常用三角形索引进行组织,并且每个三角形都有一个给出其方向的法向量。此外,像我的情况一样,也可能有一些图像具有一些纹理坐标。

描述每个对象中使用的数据是在从glTF JSON中提取的Meshes对象中给出的。由于我只想显示一颗行星,因此每个文件只包含一个网格,但一般来说,与土星及其光环一样,每个glb文件可能有许多网格。但为了简单起见并显示原则,我排除了更困难的文件类型。

每个文件都有所谓的访问器,这些访问器将指向二进制文件中存储实际信息的位置。因此,在这里,我提取每个网格(在本例中为网格)的信息。

for (int i = 0; i < glTFFile.Accessors.Count(); i++)
{
    Accessor CurrentAccessor = glTFFile.Accessors[i];
    
    // Read the byte positions and offsets for each accessors
    var BufferViewIndex = CurrentAccessor.BufferView;
    BufferView BufferView = glTFFile.BufferViews[(int)BufferViewIndex];
    var Offset = BufferView.ByteOffset;
    var Length = BufferView.ByteLength;
    
    // Check which type of accessor it is
    string type = "";
    if (AttrebutesIndex.ContainsKey(i))
        type = AttrebutesIndex[i];
        
    if (type == "POSITION")
    {
        // Used to scale all planets to +/- 1
        float[] ScalingFactorForVariables = new float[3];
        
        if (CurrentAccessor.Max == null)
            ScalingFactorForVariables = new float[3] { 1.0f, 1.0f, 1.0f };
        else
            ScalingFactorForVariables = CurrentAccessor.Max;
            
        // Upscaling factor
        float UpscalingFactor = 1.5f;
        
        Point3DCollection PointsPosisions = new Point3DCollection();
        
        for (int n = Offset; n < Offset + Length; n += 4)
        {
            float x = BitConverter.ToSingle(BIN_data, n) / 
                                   ScalingFactorForVariables[0] * UpscalingFactor;
            n += 4;
            float y = BitConverter.ToSingle(BIN_data, n) / 
                                   ScalingFactorForVariables[1] * UpscalingFactor;
            n += 4;
            float z = BitConverter.ToSingle(BIN_data, n) / 
                                   ScalingFactorForVariables[2] * UpscalingFactor;
            
            PointsPosisions.Add(new Point3D(x, y, z));
        }
        MaterialPoints = PointsPosisions;
    }
    else if (type == "NORMAL")
    {
        Vector3DCollection NormalsForPosisions = new Vector3DCollection();
        for (int n = Offset; n < Offset + Length; n += 4)
        {
            float x = BitConverter.ToSingle(BIN_data, n);
            n += 4;
            float y = BitConverter.ToSingle(BIN_data, n);
            n += 4;
            float z = BitConverter.ToSingle(BIN_data, n);
            
            NormalsForPosisions.Add(new Vector3D(x, y, z));
        }
        
        NormalPoints = NormalsForPosisions;
    }
    else if (type.Contains("TEXCOORD"))
    {
        // Assuming texture positions
        PointCollection vec2 = new PointCollection();
        for (int n = Offset; n < Offset + Length; n += 4)
        {
            double x = (double)BitConverter.ToSingle(BIN_data, n);
            n += 4;
            double y = (double)BitConverter.ToSingle(BIN_data, n);
            
            vec2.Add(new Point(x, y));
        }
        
        TexturePoints = vec2;
    }
    else
    {
        if (CurrentAccessor.ComponentType == Accessor.ComponentTypeEnum.UNSIGNED_SHORT)
        {
            for (int n = Offset; n < Offset + Length; n += 2)
            {
                UInt16 TriangleItem = BitConverter.ToUInt16(BIN_data, n);
                Indecies.Add((Int32)TriangleItem);
            }
        }
    }
}

如果您有纹理坐标,还可以从二进制部分或单独的文件中加载图像。

foreach (glTFLoader.Schema.Image item in glTFFile.Images)
{
	//var ImageType = item.MimeType;
	int BufferViewIndex = (int)item.BufferView;
	BufferView BufferView = glTFFile.BufferViews[BufferViewIndex];
	var Offset = BufferView.ByteOffset;
	var Length = BufferView.ByteLength;

	// Copy the relevant data from binary part
	byte[] ImageBytes = new byte[Length];
	Array.Copy(BIN_data, Offset, ImageBytes, 0, Length);

	// Convert to image
	MemoryStream ms = new MemoryStream(ImageBytes);
	BitmapImage Img = new BitmapImage();
	Img.BeginInit();
	Img.StreamSource = ms;
	Img.EndInit();
	Images.Add(Img);             
}

生成WPF 3D glTF查看器

将所有这些信息添加到ModelVisual3D中,可以用于在Viewport3d中显示它,这是相对简单的。

// Construct the WPF 3D ViewPort model
Model3D = new MeshGeometry3D();
Model3D.TriangleIndices = Indecies;
Model3D.Positions = MaterialPoints;
Model3D.Normals = NormalPoints;
Model3D.TextureCoordinates = TexturePoints;

// Geometry model
GeoModel3D.Geometry = Model3D;
GeoModel3D.Material = new DiffuseMaterial() { Brush = new ImageBrush(Images[0]) };

// ModelVisual3D for showing this component
Visualisation.Content = GeoModel3D;

Viewport3D很简单,我只需要定位相机并为场景提供一些灯光。所有行星都以原点(0,0,0)为中心。

<Viewport3D Name="viewport3D1" Width="400" Height="400">
	<Viewport3D.Camera>
		<PerspectiveCamera x:Name="camMain" 
		Position="6 5 4" LookDirection="-6 -5 -4">
		</PerspectiveCamera>
	</Viewport3D.Camera>
	<ModelVisual3D>
		<ModelVisual3D.Content>
			<DirectionalLight x:Name="dirLightMain" Direction="-1,-1,-1">
			</DirectionalLight>
		</ModelVisual3D.Content>
	</ModelVisual3D>    
</Viewport3D>

我从这个网站上偷走了一些关于简单缩放和旋转的想法。

https://www.codeproject.com/Articles/5360022/Introduction-to-glTF-for-WPF-3D

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值