znlgis 博客

GIS开发与技术分享

第九章:渲染系统与Three.js集成

9.1 渲染系统概述

9.1.1 双引擎渲染架构

LightCAD采用独特的双渲染引擎策略,同时集成了Three.js4Net和OpenTK两个渲染后端:

┌──────────────────────────────────────────┐
│              渲染抽象层                    │
│         LightCAD.RenderUtils              │
├────────────────┬─────────────────────────┤
│  Three.js4Net  │        OpenTK           │
│  (WebGL渲染) │    (OpenGL渲染)        │
└────────────────┴─────────────────────────┘

Three.js4Net:是Three.js JavaScript 3D库的.NET封装版本,提供了高层的场景图管理、材质系统和渲染管线。作为LightCAD的主要3D渲染引擎。

OpenTK:是.NET平台的OpenGL绑定库,提供了直接的GPU访问能力,用于需要低级图形控制的场景。

9.1.2 渲染模块结构

LightCAD.RenderUtils/
├── 3dcontrols/              # 3D交互控件
│   ├── TransformGizmo.cs    # 变换手柄
│   ├── RotationGizmo.cs     # 旋转手柄
│   └── ScaleGizmo.cs        # 缩放手柄
├── AssetManagers/           # 资源管理器
│   ├── RenderMaterialManager.cs  # 材质管理
│   ├── TextureManager.cs         # 纹理管理
│   └── ShaderManager.cs          # 着色器管理
├── ThreeUtils/              # Three.js工具集
│   ├── SceneManager.cs      # 场景管理
│   ├── CameraManager.cs     # 相机管理
│   ├── LightManager.cs      # 光照管理
│   └── GeometryBuilder.cs   # 几何体构建器
└── Events/                  # 渲染事件
    ├── RenderEvent.cs       # 渲染事件
    └── PickEvent.cs         # 拾取事件

9.2 Three.js4Net集成

9.2.1 Three.js4Net概述

Three.js4Net是将著名的Three.js JavaScript库移植到.NET平台的实现。它保持了Three.js的API风格和设计理念,但完全运行在.NET运行时中:

// Three.js4Net的核心概念与Three.js完全一致
// 场景 (Scene) → 相机 (Camera) → 渲染器 (Renderer)

// 创建场景
var scene = new Scene();

// 创建相机
var camera = new PerspectiveCamera(
    fov: 75,
    aspect: 16.0 / 9.0,
    near: 0.1,
    far: 1000
);
camera.Position.Set(0, 50, 100);

// 添加光源
var ambientLight = new AmbientLight(0x404040);
scene.Add(ambientLight);

var directionalLight = new DirectionalLight(0xffffff, 0.8);
directionalLight.Position.Set(100, 100, 100);
scene.Add(directionalLight);

9.2.2 场景管理

public class SceneManager
{
    private Scene scene;
    private Dictionary<long, Object3D> entityObjects = new();

    public SceneManager()
    {
        scene = new Scene();
        scene.Background = new Color(0xf0f0f0);
        SetupDefaultLighting();
    }

    /// <summary>
    /// 添加实体到场景
    /// </summary>
    public void AddEntity(LcEntity entity)
    {
        var object3d = ConvertToThreeObject(entity);
        if (object3d != null)
        {
            scene.Add(object3d);
            entityObjects[entity.Handle] = object3d;
        }
    }

    /// <summary>
    /// 从场景移除实体
    /// </summary>
    public void RemoveEntity(LcEntity entity)
    {
        if (entityObjects.TryGetValue(entity.Handle, out var obj))
        {
            scene.Remove(obj);
            entityObjects.Remove(entity.Handle);
        }
    }

    /// <summary>
    /// 更新实体的显示
    /// </summary>
    public void UpdateEntity(LcEntity entity)
    {
        RemoveEntity(entity);
        AddEntity(entity);
    }

    /// <summary>
    /// 将LightCAD实体转换为Three.js对象
    /// </summary>
    private Object3D ConvertToThreeObject(LcEntity entity)
    {
        return entity switch
        {
            LcLine line => CreateLineMesh(line),
            LcCircle circle => CreateCircleMesh(circle),
            LcArc arc => CreateArcMesh(arc),
            LcPolyline polyline => CreatePolylineMesh(polyline),
            LcSolid3d solid => CreateSolidMesh(solid),
            LcText text => CreateTextMesh(text),
            _ => null
        };
    }

    /// <summary>
    /// 设置默认光照
    /// </summary>
    private void SetupDefaultLighting()
    {
        var ambient = new AmbientLight(0x404040);
        scene.Add(ambient);

        var directional = new DirectionalLight(0xffffff, 0.8);
        directional.Position.Set(1, 1, 1);
        scene.Add(directional);

        var fill = new DirectionalLight(0xffffff, 0.3);
        fill.Position.Set(-1, 0.5, -1);
        scene.Add(fill);
    }
}

9.2.3 几何体构建器

public class GeometryBuilder
{
    /// <summary>
    /// 从LcLine创建线段几何体
    /// </summary>
    public static Object3D CreateLineMesh(LcLine line)
    {
        var geometry = new BufferGeometry();
        var positions = new float[]
        {
            (float)line.StartPoint.X, (float)line.StartPoint.Y, 0,
            (float)line.EndPoint.X, (float)line.EndPoint.Y, 0
        };

        geometry.SetAttribute("position",
            new BufferAttribute(positions, 3));

        var material = new LineBasicMaterial
        {
            Color = ConvertColor(line.Color),
            LineWidth = (float)line.LineWeight
        };

        return new Line(geometry, material);
    }

    /// <summary>
    /// 从LcCircle创建圆的几何体
    /// </summary>
    public static Object3D CreateCircleMesh(LcCircle circle)
    {
        var segments = 64;
        var geometry = new BufferGeometry();
        var positions = new float[(segments + 1) * 3];

        for (int i = 0; i <= segments; i++)
        {
            var angle = 2 * Math.PI * i / segments;
            positions[i * 3] = (float)(circle.Center.X +
                circle.Radius * Math.Cos(angle));
            positions[i * 3 + 1] = (float)(circle.Center.Y +
                circle.Radius * Math.Sin(angle));
            positions[i * 3 + 2] = 0;
        }

        geometry.SetAttribute("position",
            new BufferAttribute(positions, 3));

        var material = new LineBasicMaterial
        {
            Color = ConvertColor(circle.Color)
        };

        return new Line(geometry, material);
    }

    /// <summary>
    /// 从LcSolid3d创建实体网格
    /// </summary>
    public static Object3D CreateSolidMesh(LcSolid3d solid)
    {
        var meshData = solid.GenerateMesh();
        var geometry = new BufferGeometry();

        // 设置顶点位置
        var positions = new float[meshData.Vertices.Count * 3];
        for (int i = 0; i < meshData.Vertices.Count; i++)
        {
            positions[i * 3] = (float)meshData.Vertices[i].X;
            positions[i * 3 + 1] = (float)meshData.Vertices[i].Y;
            positions[i * 3 + 2] = (float)meshData.Vertices[i].Z;
        }
        geometry.SetAttribute("position",
            new BufferAttribute(positions, 3));

        // 设置法向量
        if (meshData.Normals.Count > 0)
        {
            var normals = new float[meshData.Normals.Count * 3];
            for (int i = 0; i < meshData.Normals.Count; i++)
            {
                normals[i * 3] = (float)meshData.Normals[i].X;
                normals[i * 3 + 1] = (float)meshData.Normals[i].Y;
                normals[i * 3 + 2] = (float)meshData.Normals[i].Z;
            }
            geometry.SetAttribute("normal",
                new BufferAttribute(normals, 3));
        }

        // 设置面索引
        geometry.SetIndex(new BufferAttribute(
            meshData.Indices.ToArray(), 1));

        var material = new MeshPhongMaterial
        {
            Color = ConvertColor(solid.Material?.Color ?? LcColor.White),
            Shininess = 30,
            Side = Side.Double
        };

        return new Mesh(geometry, material);
    }

    private static Color ConvertColor(LcColor color)
    {
        return new Color(
            color.R / 255.0f,
            color.G / 255.0f,
            color.B / 255.0f
        );
    }
}

9.3 相机系统

9.3.1 相机类型

public class CameraManager
{
    private Camera currentCamera;

    /// <summary>
    /// 创建透视相机(3D视图)
    /// </summary>
    public PerspectiveCamera CreatePerspectiveCamera(
        double fov = 60,
        double aspect = 16.0 / 9.0,
        double near = 0.1,
        double far = 10000)
    {
        var camera = new PerspectiveCamera(fov, aspect, near, far);
        camera.Position.Set(0, 100, 200);
        camera.LookAt(new Vector3(0, 0, 0));
        return camera;
    }

    /// <summary>
    /// 创建正交相机(2D视图/工程视图)
    /// </summary>
    public OrthographicCamera CreateOrthographicCamera(
        double width, double height,
        double near = -10000, double far = 10000)
    {
        var halfWidth = width / 2;
        var halfHeight = height / 2;
        var camera = new OrthographicCamera(
            -halfWidth, halfWidth,
            halfHeight, -halfHeight,
            near, far
        );
        camera.Position.Set(0, 0, 1000);
        camera.LookAt(new Vector3(0, 0, 0));
        return camera;
    }

    /// <summary>
    /// 设置标准视图
    /// </summary>
    public void SetStandardView(StandardView view)
    {
        switch (view)
        {
            case StandardView.Top:
                currentCamera.Position.Set(0, 0, 1000);
                currentCamera.Up.Set(0, 1, 0);
                break;
            case StandardView.Front:
                currentCamera.Position.Set(0, -1000, 0);
                currentCamera.Up.Set(0, 0, 1);
                break;
            case StandardView.Right:
                currentCamera.Position.Set(1000, 0, 0);
                currentCamera.Up.Set(0, 0, 1);
                break;
            case StandardView.Isometric:
                currentCamera.Position.Set(500, -500, 500);
                currentCamera.Up.Set(0, 0, 1);
                break;
        }
        currentCamera.LookAt(new Vector3(0, 0, 0));
    }
}

public enum StandardView
{
    Top,         // 俯视
    Bottom,      // 仰视
    Front,       // 前视
    Back,        // 后视
    Left,        // 左视
    Right,       // 右视
    Isometric    // 等轴测
}

9.3.2 相机控制

public class CameraController
{
    private Camera camera;
    private Vector3 target = new Vector3(0, 0, 0);

    /// <summary>
    /// 轨道旋转(鼠标中键拖动)
    /// </summary>
    public void Orbit(double deltaX, double deltaY)
    {
        var offset = camera.Position.Clone().Sub(target);
        var spherical = new Spherical();
        spherical.SetFromVector3(offset);

        spherical.Theta -= deltaX * 0.005;
        spherical.Phi -= deltaY * 0.005;
        spherical.Phi = Math.Max(0.01, Math.Min(Math.PI - 0.01, spherical.Phi));

        offset.SetFromSpherical(spherical);
        camera.Position.Copy(target).Add(offset);
        camera.LookAt(target);
    }

    /// <summary>
    /// 平移(Shift+中键拖动)
    /// </summary>
    public void Pan(double deltaX, double deltaY)
    {
        var panSpeed = GetPanSpeed();
        var right = new Vector3();
        var up = new Vector3();

        right.SetFromMatrixColumn(camera.MatrixWorld, 0);
        up.SetFromMatrixColumn(camera.MatrixWorld, 1);

        var panOffset = right.MultiplyScalar(-deltaX * panSpeed)
            .Add(up.MultiplyScalar(deltaY * panSpeed));

        target.Add(panOffset);
        camera.Position.Add(panOffset);
    }

    /// <summary>
    /// 缩放(鼠标滚轮)
    /// </summary>
    public void Zoom(double delta)
    {
        if (camera is PerspectiveCamera perspCamera)
        {
            var offset = camera.Position.Clone().Sub(target);
            var distance = offset.Length();
            var newDistance = distance * (1 - delta * 0.1);
            newDistance = Math.Max(1, Math.Min(100000, newDistance));

            offset.Normalize().MultiplyScalar(newDistance);
            camera.Position.Copy(target).Add(offset);
        }
        else if (camera is OrthographicCamera orthoCamera)
        {
            var zoomFactor = 1 - delta * 0.1;
            orthoCamera.Zoom *= zoomFactor;
            orthoCamera.UpdateProjectionMatrix();
        }
    }

    /// <summary>
    /// 缩放到适合所有对象
    /// </summary>
    public void ZoomToFit(BoundingBox3d bounds)
    {
        var center = bounds.Center;
        var size = bounds.Size;
        var maxDim = Math.Max(size.X, Math.Max(size.Y, size.Z));

        target = new Vector3(center.X, center.Y, center.Z);

        if (camera is PerspectiveCamera perspCamera)
        {
            var fov = perspCamera.Fov * Math.PI / 180;
            var distance = maxDim / (2 * Math.Tan(fov / 2)) * 1.5;
            camera.Position.Copy(target).Add(
                new Vector3(distance, -distance, distance));
        }

        camera.LookAt(target);
    }
}

9.4 材质系统

9.4.1 材质管理器

public class RenderMaterialManager
{
    private Dictionary<string, Material> materialCache = new();

    /// <summary>
    /// 获取或创建线框材质
    /// </summary>
    public LineBasicMaterial GetLineMaterial(LcColor color, double lineWidth = 1)
    {
        var key = $"line_{color.R}_{color.G}_{color.B}_{lineWidth}";
        if (!materialCache.TryGetValue(key, out var material))
        {
            material = new LineBasicMaterial
            {
                Color = new Color(color.R / 255.0f, color.G / 255.0f,
                    color.B / 255.0f),
                LineWidth = (float)lineWidth
            };
            materialCache[key] = material;
        }
        return (LineBasicMaterial)material;
    }

    /// <summary>
    /// 获取或创建Phong材质(3D实体用)
    /// </summary>
    public MeshPhongMaterial GetPhongMaterial(MaterialInfo info)
    {
        var key = $"phong_{info.GetHashCode()}";
        if (!materialCache.TryGetValue(key, out var material))
        {
            material = new MeshPhongMaterial
            {
                Color = ConvertColor(info.DiffuseColor),
                Specular = ConvertColor(info.SpecularColor),
                Shininess = (float)info.Shininess,
                Opacity = (float)info.Opacity,
                Transparent = info.Opacity < 1.0,
                Side = Side.Double
            };
            materialCache[key] = material;
        }
        return (MeshPhongMaterial)material;
    }

    /// <summary>
    /// 创建选中状态材质
    /// </summary>
    public Material GetSelectionMaterial()
    {
        return new MeshBasicMaterial
        {
            Color = new Color(0.0f, 0.5f, 1.0f),
            Opacity = 0.3f,
            Transparent = true,
            Side = Side.Double
        };
    }

    /// <summary>
    /// 创建高亮状态材质
    /// </summary>
    public Material GetHighlightMaterial()
    {
        return new MeshBasicMaterial
        {
            Color = new Color(1.0f, 1.0f, 0.0f),
            Opacity = 0.2f,
            Transparent = true,
            Side = Side.Double
        };
    }
}

public class MaterialInfo
{
    public LcColor DiffuseColor { get; set; } = LcColor.White;
    public LcColor SpecularColor { get; set; } = new LcColor(50, 50, 50);
    public double Shininess { get; set; } = 30;
    public double Opacity { get; set; } = 1.0;
    public string TexturePath { get; set; }
    public string Name { get; set; }
}

9.5 光照系统

9.5.1 光源管理

public class LightManager
{
    private List<Light> lights = new();

    /// <summary>
    /// 添加环境光
    /// </summary>
    public AmbientLight AddAmbientLight(
        LcColor color, double intensity = 0.4)
    {
        var light = new AmbientLight(
            new Color(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f),
            (float)intensity);
        lights.Add(light);
        return light;
    }

    /// <summary>
    /// 添加方向光
    /// </summary>
    public DirectionalLight AddDirectionalLight(
        Vector3d direction, LcColor color, double intensity = 0.8)
    {
        var light = new DirectionalLight(
            new Color(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f),
            (float)intensity);
        light.Position.Set(
            -(float)direction.X,
            -(float)direction.Y,
            -(float)direction.Z);
        lights.Add(light);
        return light;
    }

    /// <summary>
    /// 添加点光源
    /// </summary>
    public PointLight AddPointLight(
        Point3d position, LcColor color,
        double intensity = 1.0, double distance = 0)
    {
        var light = new PointLight(
            new Color(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f),
            (float)intensity, (float)distance);
        light.Position.Set(
            (float)position.X,
            (float)position.Y,
            (float)position.Z);
        lights.Add(light);
        return light;
    }

    /// <summary>
    /// 设置CAD标准光照方案
    /// </summary>
    public void SetupCADLighting()
    {
        lights.Clear();

        // 环境光(避免完全黑暗)
        AddAmbientLight(new LcColor(64, 64, 64), 0.4);

        // 主光源(从右上方前方照射)
        AddDirectionalLight(
            new Vector3d(-1, -1, -1).Normalize(),
            LcColor.White, 0.6);

        // 填充光(从左方照射,较弱)
        AddDirectionalLight(
            new Vector3d(1, -0.5, 0.5).Normalize(),
            LcColor.White, 0.3);

        // 背光(从后方照射,很弱)
        AddDirectionalLight(
            new Vector3d(0, 1, -0.5).Normalize(),
            LcColor.White, 0.15);
    }
}

9.6 OpenTK渲染后端

9.6.1 OpenGL控制接口

public interface IGLControl
{
    /// <summary>
    /// 初始化OpenGL上下文
    /// </summary>
    void Initialize();

    /// <summary>
    /// 渲染一帧
    /// </summary>
    void Render();

    /// <summary>
    /// 调整视口大小
    /// </summary>
    void Resize(int width, int height);

    /// <summary>
    /// 获取OpenGL版本信息
    /// </summary>
    string GetGLInfo();
}

9.6.2 OpenTK渲染器

public class OpenTKRenderer : IGLControl
{
    public void Initialize()
    {
        // 设置OpenGL状态
        GL.Enable(EnableCap.DepthTest);
        GL.Enable(EnableCap.Blend);
        GL.BlendFunc(BlendingFactor.SrcAlpha,
            BlendingFactor.OneMinusSrcAlpha);
        GL.ClearColor(0.94f, 0.94f, 0.94f, 1.0f);

        // 启用抗锯齿
        GL.Enable(EnableCap.Multisample);
        GL.Enable(EnableCap.LineSmooth);
        GL.Hint(HintTarget.LineSmoothHint, HintMode.Nicest);
    }

    public void Render()
    {
        GL.Clear(ClearBufferMask.ColorBufferBit |
                 ClearBufferMask.DepthBufferBit);

        // 设置投影矩阵
        SetProjectionMatrix();

        // 设置视图矩阵
        SetViewMatrix();

        // 渲染场景中的所有对象
        RenderScene();

        // 渲染辅助元素(网格、坐标轴等)
        RenderHelpers();
    }

    public void Resize(int width, int height)
    {
        GL.Viewport(0, 0, width, height);
        UpdateProjectionMatrix(width, height);
    }
}

9.7 渲染管线

9.7.1 完整渲染流程

1. 数据准备阶段
   ├── 收集可见实体
   ├── 计算包围盒
   └── 视锥剔除

2. 几何处理阶段
   ├── 实体 → 网格数据(MeshData)
   ├── 曲线离散化
   └── 文本轮廓生成

3. 场景构建阶段
   ├── 创建Three.js几何体
   ├── 设置材质和纹理
   └── 构建场景图

4. 渲染执行阶段
   ├── 设置相机和光照
   ├── 深度排序
   ├── 渲染不透明物体
   ├── 渲染透明物体
   └── 渲染叠加层(标注、网格等)

5. 后处理阶段
   ├── 抗锯齿
   ├── 选择高亮
   └── 输出到屏幕

9.7.2 视锥剔除

public class GpuVisibleFilter
{
    /// <summary>
    /// 视锥剔除,过滤不在视锥内的对象
    /// </summary>
    public List<LcEntity> FilterVisible(
        IEnumerable<LcEntity> entities, Camera camera)
    {
        var frustum = new Frustum();
        var projScreenMatrix = camera.ProjectionMatrix.Clone()
            .Multiply(camera.MatrixWorldInverse);
        frustum.SetFromProjectionMatrix(projScreenMatrix);

        return entities.Where(entity =>
        {
            var bounds = entity.Bounds;
            var box3 = new Box3(
                new Vector3(bounds.Min.X, bounds.Min.Y, -1000),
                new Vector3(bounds.Max.X, bounds.Max.Y, 1000)
            );
            return frustum.IntersectsBox(box3);
        }).ToList();
    }
}

9.8 3D交互控件

9.8.1 变换手柄(Gizmo)

public class TransformGizmo
{
    private GizmoMode mode = GizmoMode.Translate;

    /// <summary>
    /// 创建平移手柄
    /// </summary>
    public Object3D CreateTranslateGizmo()
    {
        var group = new Group();

        // X轴(红色箭头)
        var xArrow = CreateArrow(Vector3d.XAxis,
            new LcColor(255, 0, 0));
        group.Add(xArrow);

        // Y轴(绿色箭头)
        var yArrow = CreateArrow(Vector3d.YAxis,
            new LcColor(0, 255, 0));
        group.Add(yArrow);

        // Z轴(蓝色箭头)
        var zArrow = CreateArrow(Vector3d.ZAxis,
            new LcColor(0, 0, 255));
        group.Add(zArrow);

        return group;
    }

    /// <summary>
    /// 创建旋转手柄
    /// </summary>
    public Object3D CreateRotateGizmo()
    {
        var group = new Group();

        // X轴旋转环(红色)
        group.Add(CreateRotationRing(Vector3d.XAxis,
            new LcColor(255, 0, 0)));
        // Y轴旋转环(绿色)
        group.Add(CreateRotationRing(Vector3d.YAxis,
            new LcColor(0, 255, 0)));
        // Z轴旋转环(蓝色)
        group.Add(CreateRotationRing(Vector3d.ZAxis,
            new LcColor(0, 0, 255)));

        return group;
    }
}

public enum GizmoMode
{
    Translate,  // 平移
    Rotate,     // 旋转
    Scale       // 缩放
}

9.9 渲染性能优化

9.9.1 LOD(细节层次)

public class LODManager
{
    /// <summary>
    /// 根据距离选择合适的细节层次
    /// </summary>
    public int GetMeshQuality(double distanceToCamera,
                               double entitySize)
    {
        var relativeSize = entitySize / distanceToCamera;

        if (relativeSize > 0.5) return 3;    // 高精度
        if (relativeSize > 0.1) return 2;    // 中精度
        if (relativeSize > 0.01) return 1;   // 低精度
        return 0;                             // 最低精度
    }
}

9.9.2 实例化渲染

/// <summary>
/// 对于大量相同几何体,使用实例化渲染
/// </summary>
public InstancedMesh CreateInstancedMesh(
    BufferGeometry geometry, Material material,
    List<Matrix4d> transforms)
{
    var instancedMesh = new InstancedMesh(
        geometry, material, transforms.Count);

    for (int i = 0; i < transforms.Count; i++)
    {
        var matrix = ConvertMatrix(transforms[i]);
        instancedMesh.SetMatrixAt(i, matrix);
    }

    instancedMesh.InstanceMatrix.NeedsUpdate = true;
    return instancedMesh;
}

9.10 本章小结

本章详细介绍了LightCAD的渲染系统和Three.js集成。LightCAD采用双渲染引擎策略,以Three.js4Net为主要3D渲染引擎,以OpenTK为补充。渲染系统涵盖了场景管理、相机控制、材质系统、光照系统和3D交互控件等方面。通过视锥剔除、LOD和实例化渲染等优化技术,确保了大规模CAD模型的渲染性能。


上一章第八章:实体建模系统

下一章第十章:二维绘图与视口管理