三维渲染与模型生成

第九章:三维渲染与模型生成

9.1 三维渲染系统概述

9.1.1 ThreeJs4Net简介

FY_Layout使用ThreeJs4Net作为三维渲染引擎,这是Three.js的.NET实现,基于OpenGL提供高性能的3D渲染能力。

┌─────────────────────────────────────────┐
│        IElement3dAction                  │
│         (三维操作接口)                    │
├─────────────────────────────────────────┤
│           ThreeJs4Net                    │
│    Scene | Camera | Renderer | Mesh     │
├─────────────────────────────────────────┤
│             OpenGL / OpenTK              │
│           (底层图形API)                   │
├─────────────────────────────────────────┤
│              GPU 硬件                     │
└─────────────────────────────────────────┘

9.1.2 核心类

类名 功能
Object3D 所有三维对象的基类
Mesh 网格对象,由几何体和材质组成
Geometry 几何数据(顶点、面等)
Material 材质(颜色、纹理、反射等)
Scene 场景,包含所有三维对象
Camera 相机,定义观察视角

9.2 IElement3dAction接口

9.2.1 接口定义

public interface IElement3dAction
{
    /// <summary>
    /// 获取元素的三维对象
    /// </summary>
    /// <param name="element">元素实例</param>
    /// <param name="docRt">文档运行时</param>
    /// <returns>三维对象</returns>
    Object3D Get3dObject(LcElement element, DocumentRuntime docRt);
}

9.2.2 基本实现

public class MyElement3dAction : IElement3dAction
{
    public Object3D Get3dObject(LcElement element, DocumentRuntime docRt)
    {
        var myEle = element as MyElement;
        
        // 创建几何体
        var geometry = CreateGeometry(myEle);
        
        // 创建材质
        var material = CreateMaterial(myEle);
        
        // 创建网格
        var mesh = new Mesh(geometry, material);
        
        // 设置位置
        mesh.Position.X = myEle.Position.X;
        mesh.Position.Z = myEle.Position.Y;  // 2D的Y对应3D的Z
        
        return mesh;
    }
}

9.3 几何体创建

9.3.1 内置几何体

// 立方体
var boxGeometry = new BoxGeometry(width, height, depth);

// 球体
var sphereGeometry = new SphereGeometry(radius, widthSegments, heightSegments);

// 圆柱体
var cylinderGeometry = new CylinderGeometry(radiusTop, radiusBottom, height, segments);

// 平面
var planeGeometry = new PlaneGeometry(width, height);

// 圆环
var torusGeometry = new TorusGeometry(radius, tube, radialSegments, tubularSegments);

9.3.2 自定义几何体

public static Geometry CreateCustomGeometry()
{
    var geometry = new Geometry();
    
    // 添加顶点
    geometry.Vertices.Add(new Vector3(0, 0, 0));
    geometry.Vertices.Add(new Vector3(100, 0, 0));
    geometry.Vertices.Add(new Vector3(100, 100, 0));
    geometry.Vertices.Add(new Vector3(0, 100, 0));
    
    // 添加面(三角形)
    geometry.Faces.Add(new Face3(0, 1, 2));
    geometry.Faces.Add(new Face3(0, 2, 3));
    
    // 计算法线
    geometry.ComputeFaceNormals();
    geometry.ComputeVertexNormals();
    
    return geometry;
}

9.3.3 BufferGeometry

对于大量数据,使用BufferGeometry获得更好的性能:

public static BufferGeometry CreateBufferGeometry(double[] vertices, int[] indices)
{
    var geometry = new BufferGeometry();
    
    // 设置顶点位置
    geometry.SetAttribute("position", new BufferAttribute(vertices, 3));
    
    // 设置索引
    geometry.SetIndex(new BufferAttribute(indices, 1));
    
    // 计算法线
    geometry.ComputeVertexNormals();
    
    return geometry;
}

9.4 从2D轮廓生成3D模型

9.4.1 拉伸几何体

将2D轮廓拉伸成3D模型:

public Object3D ExtrudeFromOutline(Polyline2d outline, double height)
{
    // 创建Shape(2D形状)
    var shape = new Shape();
    
    var points = outline.Curve2ds.Select(c => c.GetStartPoint()).ToList();
    if (points.Count < 3) return null;
    
    // 移动到第一个点
    shape.MoveTo(points[0].X, points[0].Y);
    
    // 连接其余点
    for (int i = 1; i < points.Count; i++)
    {
        shape.LineTo(points[i].X, points[i].Y);
    }
    
    // 闭合
    shape.LineTo(points[0].X, points[0].Y);
    
    // 创建拉伸几何体
    var extrudeSettings = new ExtrudeGeometryOptions
    {
        Depth = height,
        BevelEnabled = false
    };
    
    var geometry = new ExtrudeGeometry(shape, extrudeSettings);
    var material = new MeshLambertMaterial { Color = new ThreeJs4Net.Color(0xCCCCCC) };
    
    return new Mesh(geometry, material);
}

9.4.2 基坑三维模型

基坑需要生成坡面,比简单拉伸更复杂:

public class FoundationPit3dAction : IElement3dAction
{
    public Object3D Get3dObject(LcElement element, DocumentRuntime docRt)
    {
        var pit = element as QdFoundationPit;
        var root = new Object3D();
        
        // 获取参数
        var outline = pit.Outline;
        var bottom = pit.Bottom;
        var elevation = pit.Elevation;
        var factor = pit.Factor;
        var pattern = pit.Pattern;
        
        // 计算放坡宽度
        var slopeWidth = (elevation - bottom) * factor;
        
        // 获取顶部和底部轮廓
        var topCurves = GetOffsetCurves(outline, pattern == 0 ? slopeWidth : 0);
        var bottomCurves = GetOffsetCurves(outline, pattern == 0 ? 0 : -slopeWidth);
        
        // 创建坑底面
        var pitBottom = CreatePitBottom(bottomCurves, bottom);
        root.Add(pitBottom);
        
        // 创建坡面
        var slopes = CreateSlopes(topCurves, bottomCurves, elevation, bottom);
        foreach (var slope in slopes)
        {
            root.Add(slope);
        }
        
        return root;
    }
    
    private Mesh CreatePitBottom(List<Curve3d> curves, double elevation)
    {
        // 创建底面几何体
        var posArr = new List<double>();
        var idxArr = new List<int>();
        
        // 三角化底面
        var points = curves.SelectMany(c => c.GetPoints()).ToList();
        var triangles = Triangulate(points);
        
        int idxOffset = 0;
        foreach (var tri in triangles)
        {
            posArr.Add(tri.A.X); posArr.Add(elevation); posArr.Add(tri.A.Y);
            posArr.Add(tri.B.X); posArr.Add(elevation); posArr.Add(tri.B.Y);
            posArr.Add(tri.C.X); posArr.Add(elevation); posArr.Add(tri.C.Y);
            
            idxArr.Add(idxOffset);
            idxArr.Add(idxOffset + 1);
            idxArr.Add(idxOffset + 2);
            idxOffset += 3;
        }
        
        var geometry = new BufferGeometry();
        geometry.SetAttribute("position", new BufferAttribute(posArr.ToArray(), 3));
        geometry.SetIndex(new BufferAttribute(idxArr.ToArray(), 1));
        geometry.ComputeVertexNormals();
        
        var material = new MeshLambertMaterial { Color = new ThreeJs4Net.Color(0x808080) };
        return new Mesh(geometry, material);
    }
    
    private List<Mesh> CreateSlopes(List<Curve3d> topCurves, List<Curve3d> bottomCurves, 
                                      double topElevation, double bottomElevation)
    {
        var meshes = new List<Mesh>();
        
        for (int i = 0; i < topCurves.Count; i++)
        {
            var topLine = topCurves[i] as Line3d;
            var bottomLine = bottomCurves[i] as Line3d;
            
            // 创建坡面四边形
            var vertices = new double[]
            {
                topLine.Start.X, topElevation, topLine.Start.Z,
                topLine.End.X, topElevation, topLine.End.Z,
                bottomLine.End.X, bottomElevation, bottomLine.End.Z,
                bottomLine.Start.X, bottomElevation, bottomLine.Start.Z
            };
            
            var indices = new int[] { 0, 1, 2, 0, 2, 3 };
            
            var geometry = new BufferGeometry();
            geometry.SetAttribute("position", new BufferAttribute(vertices, 3));
            geometry.SetIndex(new BufferAttribute(indices, 1));
            geometry.ComputeVertexNormals();
            
            var material = new MeshLambertMaterial { Color = new ThreeJs4Net.Color(0xA0522D) };
            meshes.Add(new Mesh(geometry, material));
        }
        
        return meshes;
    }
}

9.5 材质系统

9.5.1 基本材质

// 基础材质(无光照影响)
var basicMaterial = new MeshBasicMaterial
{
    Color = new ThreeJs4Net.Color(0xFF0000),
    Transparent = true,
    Opacity = 0.5
};

// Lambert材质(漫反射)
var lambertMaterial = new MeshLambertMaterial
{
    Color = new ThreeJs4Net.Color(0x00FF00),
    Side = Side.DoubleSide
};

// Phong材质(高光)
var phongMaterial = new MeshPhongMaterial
{
    Color = new ThreeJs4Net.Color(0x0000FF),
    Specular = new ThreeJs4Net.Color(0xFFFFFF),
    Shininess = 30
};

// 标准PBR材质
var standardMaterial = new MeshStandardMaterial
{
    Color = new ThreeJs4Net.Color(0xFFFFFF),
    Roughness = 0.5,
    Metalness = 0.5
};

9.5.2 MaterialManager

FY_Layout使用MaterialManager管理预定义材质:

public static class MaterialManager
{
    // 预定义材质UUID
    public static readonly string LawnUuid = "lawn-material-uuid";
    public static readonly string ConcreteUuid = "concrete-material-uuid";
    public static readonly string Metal1Uuid = "metal1-material-uuid";
    public static readonly string AsphaltUuid = "asphalt-material-uuid";
    
    private static Dictionary<string, MaterialInfo> Materials = new Dictionary<string, MaterialInfo>();
    
    public static void Initialize()
    {
        // 注册草坪材质
        Materials[LawnUuid] = new MaterialInfo
        {
            Uuid = LawnUuid,
            Name = "草坪",
            Color = new ThreeJs4Net.Color(0x228B22),
            Roughness = 0.9,
            Metalness = 0.0
        };
        
        // 注册混凝土材质
        Materials[ConcreteUuid] = new MaterialInfo
        {
            Uuid = ConcreteUuid,
            Name = "混凝土",
            Color = new ThreeJs4Net.Color(0x808080),
            Roughness = 0.8,
            Metalness = 0.1
        };
        
        // 注册金属材质
        Materials[Metal1Uuid] = new MaterialInfo
        {
            Uuid = Metal1Uuid,
            Name = "金属",
            Color = new ThreeJs4Net.Color(0xC0C0C0),
            Roughness = 0.3,
            Metalness = 0.8
        };
    }
    
    public static MaterialInfo GetMaterial(string uuid)
    {
        return Materials.TryGetValue(uuid, out var mat) ? mat : null;
    }
}

public class MaterialInfo
{
    public string Uuid { get; set; }
    public string Name { get; set; }
    public ThreeJs4Net.Color Color { get; set; }
    public double Roughness { get; set; }
    public double Metalness { get; set; }
    public string TexturePath { get; set; }
}

9.6 草坪三维实现

9.6.1 Lawn3dAction

public class Lawn3dAction : IElement3dAction
{
    public Object3D Get3dObject(LcElement element, DocumentRuntime docRt)
    {
        var lawn = element as QdLawn;
        
        // 获取轮廓点
        var outline = lawn.Outline;
        var points = outline.Curve2ds.SelectMany(c => c.GetPoints()).ToList();
        
        if (points.Count < 3) return new Object3D();
        
        // 创建形状
        var shape = new Shape();
        shape.MoveTo(points[0].X, points[0].Y);
        for (int i = 1; i < points.Count; i++)
        {
            shape.LineTo(points[i].X, points[i].Y);
        }
        shape.LineTo(points[0].X, points[0].Y);
        
        // 创建几何体(薄层)
        var geometry = new ExtrudeGeometry(shape, new ExtrudeGeometryOptions
        {
            Depth = 50,  // 草坪厚度50mm
            BevelEnabled = false
        });
        
        // 获取草坪材质
        var materialInfo = lawn.Material ?? MaterialManager.GetMaterial(MaterialManager.LawnUuid);
        var material = new MeshLambertMaterial
        {
            Color = materialInfo.Color
        };
        
        var mesh = new Mesh(geometry, material);
        mesh.Position.Y = lawn.Bottom;  // 设置底部标高
        
        return mesh;
    }
}

9.7 围栏三维实现

9.7.1 Fence3dAction

围栏需要生成立柱和横杆:

public class Fence3dAction : IElement3dAction
{
    public Object3D Get3dObject(LcElement element, DocumentRuntime docRt)
    {
        var fence = element as QdFence;
        var root = new Object3D();
        
        var path = fence.Path;
        var height = fence.Height;
        var postSpacing = fence.PostSpacing;
        
        // 沿路径生成立柱
        double accumulatedLength = 0;
        foreach (var curve in path.Curve2ds)
        {
            var curveLength = curve.Length;
            var startLength = accumulatedLength;
            
            // 在曲线上生成立柱
            while (accumulatedLength - startLength < curveLength)
            {
                var t = (accumulatedLength - startLength) / curveLength;
                var point = curve.GetPointAt(t);
                
                var post = CreatePost(height);
                post.Position.X = point.X;
                post.Position.Z = point.Y;
                root.Add(post);
                
                accumulatedLength += postSpacing;
            }
            
            accumulatedLength = startLength + curveLength;
        }
        
        // 生成横杆
        var rails = CreateRails(path, height);
        foreach (var rail in rails)
        {
            root.Add(rail);
        }
        
        return root;
    }
    
    private Mesh CreatePost(double height)
    {
        var geometry = new BoxGeometry(100, height, 100);  // 100mm见方的立柱
        var material = new MeshLambertMaterial
        {
            Color = new ThreeJs4Net.Color(0x404040)
        };
        var mesh = new Mesh(geometry, material);
        mesh.Position.Y = height / 2;  // 中心点在中间
        return mesh;
    }
    
    private List<Object3D> CreateRails(Polyline2d path, double fenceHeight)
    {
        var rails = new List<Object3D>();
        
        // 生成多层横杆
        var railHeights = new double[] { fenceHeight * 0.3, fenceHeight * 0.6, fenceHeight * 0.9 };
        
        foreach (var railHeight in railHeights)
        {
            foreach (var curve in path.Curve2ds)
            {
                if (curve is Line2d line)
                {
                    var rail = CreateRailSegment(line, railHeight);
                    rails.Add(rail);
                }
            }
        }
        
        return rails;
    }
    
    private Mesh CreateRailSegment(Line2d line, double height)
    {
        var length = line.Length;
        var angle = Math.Atan2(line.End.Y - line.Start.Y, line.End.X - line.Start.X);
        var center = new Vector2((line.Start.X + line.End.X) / 2, (line.Start.Y + line.End.Y) / 2);
        
        var geometry = new BoxGeometry(length, 50, 50);  // 50mm方形横杆
        var material = new MeshLambertMaterial
        {
            Color = new ThreeJs4Net.Color(0x606060)
        };
        
        var mesh = new Mesh(geometry, material);
        mesh.Position.X = center.X;
        mesh.Position.Y = height;
        mesh.Position.Z = center.Y;
        mesh.Rotation.Y = -angle;
        
        return mesh;
    }
}

9.8 Provider系统集成

9.8.1 Provider概述

QdLayoutProvider项目提供了另一种三维模型生成方式,通过Provider机制实现参数化建模:

// Provider注册
public class QdLayoutDllProviderImporter : IDllProviderImporter
{
    public (ShapeProviderCollection, SolidProviderCollection) GetImportProviders()
    {
        QdFenceProvider.RegistProviders();
        QdLawnProvider.RegistProviders();
        QdFoundationPitProvider.RegistProviders();
        QdGroundProvider.RegistProviders();
        // ... 更多Provider注册
        
        return (ShapeProviders, SolidProviders);
    }
}

9.8.2 Provider实现示例

internal static class QdFoundationPitProvider
{
    internal static void RegistProviders()
    {
        // 注册Shape Provider(二维图案)
        ConvertToProviders(new List<(string uuid, string name, CreateShape creator)>
        {
            ("69FBC6C4-F356-23B2-2704-56C0809CBF3B", "基坑", 基坑)
        });
        
        // 注册Solid Provider(三维模型)
        ConvertToProvider(
            "3E2422F7-F11D-27E4-B026-F4EE87ED08A6", 
            nameof(GetSolid_基坑), 
            GetSolid_基坑, 
            GetSolidMats
        );
    }
    
    /// <summary>
    /// 生成基坑二维图案
    /// </summary>
    internal static Curve2dGroupCollection 基坑(LcParameterSet pset, ShapeCreator creator)
    {
        var outline = (creator.ComIns as DirectComponent).BaseCurve as Polyline2d;
        var bottom = pset.GetValue<double>("Bottom");
        var pattern = pset.GetValue<int>("Pattern");
        var factor = pset.GetValue<double>("Factor");
        var elevation = pset.GetValue<double>("Elevation");
        
        var width = (elevation - bottom) * factor;
        
        // 生成放坡图案...
        var curves = new List<Curve2d>();
        // ... 计算逻辑
        
        return new Curve2dGroupCollection { new Curve2dGroup { Curve2ds = curves } };
    }
    
    /// <summary>
    /// 生成基坑三维模型
    /// </summary>
    private static Solid3dCollection GetSolid_基坑(
        LcComponentDefinition definition, 
        LcParameterSet pset, 
        SolidCreator creator)
    {
        var outline = pset.GetValue<Polyline2d>("Outline");
        var bottom = pset.GetValue<double>("Bottom");
        var elevation = pset.GetValue<double>("Elevation");
        var factor = pset.GetValue<double>("Factor");
        var pattern = pset.GetValue<int>("Pattern");
        
        // 生成三维几何数据...
        var solids = new Solid3dCollection();
        
        // 坑底
        var pitSolid = new Solid3d
        {
            Name = "Pit",
            Geometry = CreatePitGeometry(outline, bottom)
        };
        solids.Add(pitSolid);
        
        // 坡面
        var slopeSolid = new Solid3d
        {
            Name = "Slope",
            Geometry = CreateSlopeGeometry(outline, bottom, elevation, factor, pattern)
        };
        solids.Add(slopeSolid);
        
        return solids;
    }
    
    /// <summary>
    /// 获取材质
    /// </summary>
    private static MaterialInfo[] GetSolidMats(
        LcComponentDefinition definition, 
        LcParameterSet pset, 
        SolidCreator creator, 
        Solid3d solid)
    {
        return new MaterialInfo[]
        {
            MaterialManager.GetMaterial(MaterialManager.Metal1Uuid),
            MaterialManager.GetMaterial(MaterialManager.ConcreteUuid)
        };
    }
}

9.9 性能优化

9.9.1 几何体合并

public static BufferGeometry MergeGeometries(List<BufferGeometry> geometries)
{
    var allVertices = new List<double>();
    var allIndices = new List<int>();
    int indexOffset = 0;
    
    foreach (var geometry in geometries)
    {
        var positions = geometry.GetAttribute("position").Array;
        var indices = geometry.Index.Array;
        
        allVertices.AddRange(positions);
        allIndices.AddRange(indices.Select(i => i + indexOffset));
        
        indexOffset += positions.Length / 3;
    }
    
    var merged = new BufferGeometry();
    merged.SetAttribute("position", new BufferAttribute(allVertices.ToArray(), 3));
    merged.SetIndex(new BufferAttribute(allIndices.ToArray(), 1));
    merged.ComputeVertexNormals();
    
    return merged;
}

9.9.2 LOD(细节层次)

public Object3D CreateLODObject(LcElement element)
{
    var lod = new LOD();
    
    // 高细节模型(近距离)
    var highDetail = CreateHighDetailMesh(element);
    lod.AddLevel(highDetail, 0);
    
    // 中细节模型(中距离)
    var mediumDetail = CreateMediumDetailMesh(element);
    lod.AddLevel(mediumDetail, 5000);
    
    // 低细节模型(远距离)
    var lowDetail = CreateLowDetailMesh(element);
    lod.AddLevel(lowDetail, 20000);
    
    return lod;
}

9.9.3 实例化渲染

对于大量相同物体,使用实例化渲染:

public Object3D CreateInstancedMesh(List<Vector3> positions, Geometry geometry, Material material)
{
    var instancedMesh = new InstancedMesh(geometry, material, positions.Count);
    
    for (int i = 0; i < positions.Count; i++)
    {
        var matrix = new Matrix4().MakeTranslation(positions[i].X, positions[i].Y, positions[i].Z);
        instancedMesh.SetMatrixAt(i, matrix);
    }
    
    instancedMesh.InstanceMatrix.NeedsUpdate = true;
    
    return instancedMesh;
}

9.10 本章小结

本章详细介绍了FY_Layout的三维渲染与模型生成:

  1. 三维渲染架构:ThreeJs4Net和OpenGL的使用
  2. IElement3dAction接口:三维操作类的实现方式
  3. 几何体创建:内置几何体和自定义几何体
  4. 2D到3D转换:从轮廓拉伸生成三维模型
  5. 材质系统:MaterialManager和各种材质类型
  6. 具体实现:草坪、围栏、基坑的三维生成
  7. Provider系统:参数化三维模型生成
  8. 性能优化:几何体合并、LOD、实例化渲染

掌握这些三维渲染技术,可以为CAD元素创建专业的三维可视化效果。下一章我们将学习Provider系统与参数化设计。


posted @ 2026-01-31 16:03  我才是银古  阅读(2)  评论(0)    收藏  举报