Unity利用噪声生成动态地形

news2025/3/18 20:06:09

引言

在游戏开发中,地形是构建游戏世界的基础元素之一。传统的地形创建方法通常依赖于手动建模或预设资源,这种方式虽然精确但缺乏灵活性,且工作量巨大。而使用噪声算法生成地形则提供了一种程序化、动态且高效的解决方案。本文将详细介绍如何在Unity中利用噪声函数生成动态地形,从基本概念到实际实现,帮助开发者掌握这一强大技术。

噪声函数基础

什么是噪声函数?

噪声函数是一种数学函数,能够生成看似随机但实际上是确定性的值。在地形生成中,最常用的噪声函数包括:

  1. Perlin噪声:由Ken Perlin在1980年代开发,能生成自然流畅的随机模式
  2. Simplex噪声:Perlin噪声的改进版,计算效率更高,尤其在高维度空间
  3. 分形布朗运动(FBM):通过叠加不同频率和振幅的噪声来创造更复杂的模式
  4. Worley噪声:也称为Cellular噪声,可以创建类似细胞或蜂窝状的模式

噪声参数解析

理解以下关键参数对掌握噪声地形生成至关重要:

  • 频率(Frequency):控制噪声变化的密集程度,高频率产生更多细节
  • 振幅(Amplitude):控制噪声值的范围大小,影响地形的高度变化
  • 八度(Octaves):叠加的噪声层数,增加八度数可以增加地形细节
  • 持续度(Persistence):控制每个八度的振幅如何变化
  • 粗糙度(Lacunarity):控制每个八度的频率如何变化
  • 种子(Seed):初始化噪声生成的随机数,相同种子产生相同地形

Unity中实现噪声地形生成

基本方法

在Unity中生成噪声地形主要有两种方式:

  1. Mesh生成法:直接创建和修改网格顶点
  2. Unity地形系统:利用Unity内置的Terrain系统

我们将重点介绍Mesh生成法,因为它提供了更多的灵活性和控制力。

实现步骤

1. 创建基础项目结构

首先,创建一个新的Unity项目并设置基本场景:

using UnityEngine;

public class TerrainGenerator : MonoBehaviour
{
    [Header("地形设置")]
    public int width = 256;
    public int height = 256;
    public float scale = 20f;
    
    [Header("噪声设置")]
    public int octaves = 4;
    public float persistence = 0.5f;
    public float lacunarity = 2f;
    public int seed = 42;
    public Vector2 offset = Vector2.zero;
    
    [Header("地形网格")]
    public float heightMultiplier = 10f;
    public AnimationCurve heightCurve;
    
    private MeshFilter meshFilter;
    private MeshRenderer meshRenderer;
    
    void Start()
    {
        // 初始化组件
        meshFilter = GetComponent<MeshFilter>();
        meshRenderer = GetComponent<MeshRenderer>();
        
        // 生成地形
        GenerateTerrain();
    }
    
    // 更新地形(可在运行时调用以动态更新)
    public void GenerateTerrain()
    {
        // 实现地形生成逻辑
    }
}
2. 实现噪声函数

接下来,我们需要实现噪声计算函数:

// 生成噪声高度图
float[,] GenerateNoiseMap()
{
    float[,] noiseMap = new float[width, height];
    
    // 使用种子初始化随机数生成器
    System.Random prng = new System.Random(seed);
    Vector2[] octaveOffsets = new Vector2[octaves];
    
    for (int i = 0; i < octaves; i++)
    {
        float offsetX = prng.Next(-100000, 100000) + offset.x;
        float offsetY = prng.Next(-100000, 100000) + offset.y;
        octaveOffsets[i] = new Vector2(offsetX, offsetY);
    }
    
    float maxNoiseHeight = float.MinValue;
    float minNoiseHeight = float.MaxValue;
    
    // 计算噪声中心点,使缩放效果以地图中心为基准
    float halfWidth = width / 2f;
    float halfHeight = height / 2f;
    
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            float amplitude = 1;
            float frequency = 1;
            float noiseHeight = 0;
            
            // 计算多个八度的噪声叠加
            for (int i = 0; i < octaves; i++)
            {
                float sampleX = (x - halfWidth) / scale * frequency + octaveOffsets[i].x;
                float sampleY = (y - halfHeight) / scale * frequency + octaveOffsets[i].y;
                
                // 使用Unity内置的Perlin噪声函数
                float perlinValue = Mathf.PerlinNoise(sampleX, sampleY) * 2 - 1;
                noiseHeight += perlinValue * amplitude;
                
                // 应用持续度和粗糙度
                amplitude *= persistence;
                frequency *= lacunarity;
            }
            
            // 记录最大和最小噪声高度,用于后续归一化
            if (noiseHeight > maxNoiseHeight)
                maxNoiseHeight = noiseHeight;
            if (noiseHeight < minNoiseHeight)
                minNoiseHeight = noiseHeight;
                
            noiseMap[x, y] = noiseHeight;
        }
    }
    
    // 归一化噪声值到0-1范围
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            noiseMap[x, y] = Mathf.InverseLerp(minNoiseHeight, maxNoiseHeight, noiseMap[x, y]);
        }
    }
    
    return noiseMap;
}
3. 生成网格

使用噪声高度图生成实际的地形网格:

void GenerateTerrain()
{
    // 生成噪声高度图
    float[,] noiseMap = GenerateNoiseMap();
    
    // 创建网格数据
    Mesh mesh = new Mesh();
    
    Vector3[] vertices = new Vector3[(width + 1) * (height + 1)];
    int[] triangles = new int[width * height * 6];
    Vector2[] uvs = new Vector2[(width + 1) * (height + 1)];
    
    // 设置顶点和UV坐标
    for (int i = 0, y = 0; y <= height; y++)
    {
        for (int x = 0; x <= width; x++, i++)
        {
            // 计算顶点高度
            float heightValue = 0;
            if (x < width && y < height)
            {
                heightValue = noiseMap[x, y];
                // 应用高度曲线和乘数
                heightValue = heightCurve.Evaluate(heightValue) * heightMultiplier;
            }
            
            vertices[i] = new Vector3(x, heightValue, y);
            uvs[i] = new Vector2((float)x / width, (float)y / height);
        }
    }
    
    // 设置三角形索引
    int vert = 0;
    int tris = 0;
    
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            triangles[tris + 0] = vert + 0;
            triangles[tris + 1] = vert + width + 1;
            triangles[tris + 2] = vert + 1;
            triangles[tris + 3] = vert + 1;
            triangles[tris + 4] = vert + width + 1;
            triangles[tris + 5] = vert + width + 2;
            
            vert++;
            tris += 6;
        }
        vert++;
    }
    
    // 设置网格数据
    mesh.Clear();
    mesh.vertices = vertices;
    mesh.triangles = triangles;
    mesh.uv = uvs;
    
    // 重新计算法线和边界
    mesh.RecalculateNormals();
    mesh.RecalculateBounds();
    
    // 应用到网格过滤器
    meshFilter.sharedMesh = mesh;
}
4. 添加材质和纹理

为了使地形更加真实,我们可以添加基于高度的纹理:

[Header("纹理设置")]
public Texture2D[] terrainTextures;  // 不同高度的纹理
public float[] textureHeights;       // 纹理对应的高度阈值

// 在GenerateTerrain方法中添加
void ApplyTerrainTexture(float[,] noiseMap)
{
    int textureWidth = width + 1;
    int textureHeight = height + 1;
    
    // 创建颜色图
    Texture2D texture = new Texture2D(textureWidth, textureHeight);
    Color[] colorMap = new Color[textureWidth * textureHeight];
    
    for (int y = 0; y < textureHeight; y++)
    {
        for (int x = 0; x < textureWidth; x++)
        {
            if (x < width && y < height)
            {
                float currentHeight = noiseMap[x, y];
                
                // 根据高度选择纹理
                for (int i = 0; i < textureHeights.Length; i++)
                {
                    if (currentHeight <= textureHeights[i])
                    {
                        // 从纹理中采样颜色
                        float u = (float)x / width * terrainTextures[i].width;
                        float v = (float)y / height * terrainTextures[i].height;
                        colorMap[y * textureWidth + x] = terrainTextures[i].GetPixelBilinear(u, v);
                        break;
                    }
                }
            }
            else
            {
                // 边缘处理
                colorMap[y * textureWidth + x] = Color.black;
            }
        }
    }
    
    texture.SetPixels(colorMap);
    texture.Apply();
    
    // 应用到渲染器
    meshRenderer.material.mainTexture = texture;
}

5. 实现动态更新

为了实现动态地形,我们可以添加实时更新功能:

[Header("动态更新")]
public bool autoUpdate = true;
public float updateInterval = 1f;
private float updateTimer = 0f;

void Update()
{
    if (autoUpdate)
    {
        updateTimer += Time.deltaTime;
        if (updateTimer >= updateInterval)
        {
            // 更新偏移以创建移动效果
            offset += new Vector2(0.01f, 0.01f);
            GenerateTerrain();
            updateTimer = 0f;
        }
    }
}

高级技术与优化

LOD系统实现

对于大型地形,实现级别细节(LOD)系统至关重要:

[Header("LOD设置")]
public int maxLOD = 3;
public float[] lodDistances = new float[] { 50f, 100f, 200f };

void UpdateLOD()
{
    // 获取到相机的距离
    float distanceToCamera = Vector3.Distance(Camera.main.transform.position, transform.position);
    
    // 根据距离确定LOD级别
    int currentLOD = maxLOD;
    for (int i = 0; i < lodDistances.Length; i++)
    {
        if (distanceToCamera < lodDistances[i])
        {
            currentLOD = i;
            break;
        }
    }
    
    // 根据LOD级别调整网格细节
    int lodWidth = width >> currentLOD;  // 位移操作,相当于除以2的currentLOD次方
    int lodHeight = height >> currentLOD;
    
    // 使用调整后的分辨率生成地形
    GenerateTerrainWithResolution(lodWidth, lodHeight);
}

void GenerateTerrainWithResolution(int resWidth, int resHeight)
{
    // 类似GenerateTerrain,但使用指定分辨率
    // ...
}

多线程优化

噪声计算是CPU密集型操作,可以使用多线程优化:

using System.Threading;
using System.Collections.Generic;

[Header("多线程设置")]
public bool useMultithreading = true;
public int threadCount = 4;

// 多线程生成噪声图
float[,] GenerateNoiseMapMultithreaded()
{
    float[,] noiseMap = new float[width, height];
    
    if (!useMultithreading)
    {
        return GenerateNoiseMap();
    }
    
    // 准备线程参数
    int rowsPerThread = height / threadCount;
    Thread[] threads = new Thread[threadCount];
    NoiseMapThreadInfo[] threadInfos = new NoiseMapThreadInfo[threadCount];
    
    // 启动线程
    for (int i = 0; i < threadCount; i++)
    {
        int threadIndex = i;
        threadInfos[i] = new NoiseMapThreadInfo(
            threadIndex * rowsPerThread,
            (threadIndex == threadCount - 1) ? height : (threadIndex + 1) * rowsPerThread
        );
        
        threads[i] = new Thread(() => GenerateNoiseMapPart(noiseMap, threadInfos[threadIndex]));
        threads[i].Start();
    }
    
    // 等待所有线程完成
    foreach (Thread thread in threads)
    {
        thread.Join();
    }
    
    // 归一化处理
    NormalizeNoiseMap(noiseMap);
    
    return noiseMap;
}

// 线程信息类
class NoiseMapThreadInfo
{
    public int startY;
    public int endY;
    public float maxNoiseHeight;
    public float minNoiseHeight;
    
    public NoiseMapThreadInfo(int startY, int endY)
    {
        this.startY = startY;
        this.endY = endY;
        this.maxNoiseHeight = float.MinValue;
        this.minNoiseHeight = float.MaxValue;
    }
}

// 生成部分噪声图
void GenerateNoiseMapPart(float[,] noiseMap, NoiseMapThreadInfo threadInfo)
{
    // 类似GenerateNoiseMap中的循环,但只处理指定范围的行
    // ...
}

GPU加速

对于更高性能需求,可以使用计算着色器在GPU上生成噪声:

[Header("GPU加速")]
public bool useGPU = true;
public ComputeShader noiseComputeShader;

// 使用GPU生成噪声图
float[,] GenerateNoiseMapGPU()
{
    if (!useGPU || noiseComputeShader == null)
    {
        return GenerateNoiseMap();
    }
    
    // 创建结果缓冲区
    float[] noiseData = new float[width * height];
    ComputeBuffer noiseBuffer = new ComputeBuffer(width * height, sizeof(float));
    noiseBuffer.SetData(noiseData);
    
    // 设置计算着色器参数
    int kernelHandle = noiseComputeShader.FindKernel("CSMain");
    noiseComputeShader.SetBuffer(kernelHandle, "NoiseBuffer", noiseBuffer);
    noiseComputeShader.SetInt("Width", width);
    noiseComputeShader.SetInt("Height", height);
    noiseComputeShader.SetFloat("Scale", scale);
    noiseComputeShader.SetInt("Octaves", octaves);
    noiseComputeShader.SetFloat("Persistence", persistence);
    noiseComputeShader.SetFloat("Lacunarity", lacunarity);
    noiseComputeShader.SetInt("Seed", seed);
    noiseComputeShader.SetVector("Offset", new Vector4(offset.x, offset.y, 0, 0));
    
    // 执行计算着色器
    int threadGroupsX = Mathf.CeilToInt(width / 8.0f);
    int threadGroupsY = Mathf.CeilToInt(height / 8.0f);
    noiseComputeShader.Dispatch(kernelHandle, threadGroupsX, threadGroupsY, 1);
    
    // 获取结果
    noiseBuffer.GetData(noiseData);
    noiseBuffer.Release();
    
    // 转换为二维数组
    float[,] noiseMap = new float[width, height];
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            noiseMap[x, y] = noiseData[y * width + x];
        }
    }
    
    return noiseMap;
}

实际应用案例

无限地形生成

通过分块加载和卸载,可以实现"无限"地形:

[Header("无限地形设置")]
public Transform viewer;           // 通常是玩家或相机
public float viewDistance = 300f;  // 可见距离
public int chunkSize = 256;        // 每个地形块的大小
public int chunksVisibleInViewDst = 5;

Dictionary<Vector2, TerrainChunk> terrainChunks = new Dictionary<Vector2, TerrainChunk>();
List<TerrainChunk> visibleTerrainChunks = new List<TerrainChunk>();

void Update()
{
    // 获取玩家当前所在的地形块坐标
    Vector2 viewerPosition = new Vector2(viewer.position.x, viewer.position.z);
    Vector2 viewerChunkCoord = new Vector2(
        Mathf.RoundToInt(viewerPosition.x / chunkSize),
        Mathf.RoundToInt(viewerPosition.y / chunkSize)
    );
    
    // 更新可见地形块
    for (int yOffset = -chunksVisibleInViewDst; yOffset <= chunksVisibleInViewDst; yOffset++)
    {
        for (int xOffset = -chunksVisibleInViewDst; xOffset <= chunksVisibleInViewDst; xOffset++)
        {
            Vector2 chunkCoord = new Vector2(viewerChunkCoord.x + xOffset, viewerChunkCoord.y + yOffset);
            
            // 检查该块是否已加载
            if (terrainChunks.ContainsKey(chunkCoord))
            {
                terrainChunks[chunkCoord].UpdateChunk();
            }
            else
            {
                // 创建新的地形块
                terrainChunks.Add(chunkCoord, new TerrainChunk(chunkCoord, chunkSize, transform, material));
            }
        }
    }
}

// 地形块类
public class TerrainChunk
{
    GameObject meshObject;
    Vector2 position;
    Bounds bounds;
    
    public TerrainChunk(Vector2 coord, int size, Transform parent, Material material)
    {
        position = coord * size;
        bounds = new Bounds(position, Vector2.one * size);
        
        // 创建地形块游戏对象
        meshObject = new GameObject("Terrain Chunk");
        meshObject.transform.position = new Vector3(position.x, 0, position.y);
        meshObject.transform.parent = parent;
        
        // 添加组件
        MeshRenderer meshRenderer = meshObject.AddComponent<MeshRenderer>();
        meshRenderer.material = material;
        MeshFilter meshFilter = meshObject.AddComponent<MeshFilter>();
        
        // 生成地形
        // ...
    }
    
    public void UpdateChunk()
    {
        // 检查是否在视距内
        float viewerDistanceFromNearestEdge = bounds.SqrDistance(viewerPosition);
        bool visible = viewerDistanceFromNearestEdge <= viewDistance * viewDistance;
        
        meshObject.SetActive(visible);
    }
}

地形编辑器

创建一个自定义编辑器工具,方便调整地形参数:

#if UNITY_EDITOR
using UnityEditor;

[CustomEditor(typeof(TerrainGenerator))]
public class TerrainGeneratorEditor : Editor
{
    public override void OnInspectorGUI()
    {
        TerrainGenerator terrainGen = (TerrainGenerator)target;
        
        // 绘制默认检查器
        DrawDefaultInspector();
        
        // 添加生成按钮
        if (GUILayout.Button("生成地形"))
        {
            terrainGen.GenerateTerrain();
        }
        
        // 添加随机种子按钮
        if (GUILayout.Button("随机种子"))
        {
            terrainGen.seed = Random.Range(0, 100000);
            terrainGen.GenerateTerrain();
        }
        
        // 添加保存地形按钮
        if (GUILayout.Button("保存地形"))
        {
            SaveTerrainMesh(terrainGen);
        }
    }
    
    void SaveTerrainMesh(TerrainGenerator terrainGen)
    {
        // 获取当前网格
        Mesh mesh = terrainGen.GetComponent<MeshFilter>().sharedMesh;
        
        // 保存为资源
        if (mesh != null)
        {
            string path = EditorUtility.SaveFilePanelInProject(
                "保存地形网格",
                "TerrainMesh",
                "asset",
                "请选择保存位置"
            );
            
            if (path.Length > 0)
            {
                AssetDatabase.CreateAsset(mesh, path);
                AssetDatabase.SaveAssets();
            }
        }
    }
}
#endif

总结与展望

通过本文的介绍,我们详细探讨了如何在Unity中利用噪声函数生成动态地形。从基本的Perlin噪声应用到高级的多线程和GPU优化,从简单的网格生成到无限地形系统,这些技术为游戏开发者提供了强大的工具,可以创建丰富多样的游戏环境。

随着技术的不断发展,基于噪声的程序化地形生成还将继续演进。未来的发展方向包括:

  1. 机器学习辅助地形生成:利用GAN等技术学习真实地形特征
  2. 混合噪声算法:结合多种噪声函数创造更自然的地形
  3. 实时全球尺度地形:支持行星级别的无缝地形生成和探索
  4. 地形与生态系统集成:基于地形特征自动生成匹配的植被和生态系统

无论是独立游戏开发者还是大型游戏工作室,掌握噪声地形生成技术都能显著提升游戏开发效率和游戏世界的丰富度。希望本文能为您的Unity开发之旅提供有价值的参考。

参考资源

  • Unity官方文档 - 程序化地形生成
  • Sebastian Lague - 程序化地形生成教程
  • The Nature of Code - 噪声算法
  • GPU Gems 3 - 实时程序化地形技术

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2317398.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python编写的一个打砖块小游戏

游戏介绍 打砖块是一款经典的街机游戏&#xff0c;玩家控制底部的挡板&#xff0c;使球反弹以击碎上方的砖块。当球击中砖块时&#xff0c;砖块消失&#xff0c;球反弹&#xff1b;若球碰到挡板&#xff0c;则改变方向继续运动&#xff1b;若球掉出屏幕底部&#xff0c;玩家失…

【菜鸟飞】通过vsCode用python访问公网deepseek-r1等模型(Tocken模式)

目标 通过vsCode用python访问deepseek。 环境准备 没有环境的&#xff0c;vscode环境准备请参考之前的文章&#xff0c;另外需安装ollama&#xff1a; 【菜鸟飞】用vsCode搭建python运行环境-CSDN博客 AI入门1&#xff1a;AI模型管家婆ollama的安装和使用-CSDN博客 选读文章…

Figma介绍(基于云的协作式界面设计工具,主要用于UI/UX设计、原型制作和团队协作)

文章目录 注册和登录简单操作说明Figma介绍**核心特点**1. **云端协作与实时同步**2. **跨平台兼容**3. **高效设计工具**4. **原型交互与动效**5. **开发对接友好**6. **插件生态**7. **版本控制与历史记录** **适用场景**- **团队协作**&#xff1a;远程团队共同设计、评审、…

Text-to-SQL将自然语言转换为数据库查询语句

有关Text-To-SQL方法&#xff0c;可以查阅我的另一篇文章&#xff0c;Text-to-SQL方法研究 直接与数据库对话-text2sql Text2sql就是把文本转换为sql语言&#xff0c;这段时间公司有这方面的需求&#xff0c;调研了一下市面上text2sql的方法&#xff0c;比如阿里的Chat2DB,麻…

XSS漏洞靶场---(复现)

XSS漏洞靶场—&#xff08;复现&#xff09; 反射型 XSS 的特点是攻击者诱导用户点击包含恶意脚本的 URL&#xff0c;服务器接收到请求后将恶意脚本反射回响应页面&#xff0c;浏览器执行该脚本从而造成攻击&#xff0c;恶意脚本不会在服务器端存储。 Level 1(反射型XSS) 此漏…

基于ssm的电子病历系统(全套)

一、系统架构 前端&#xff1a;jsp | bootstrap | jquery 后端&#xff1a;spring | springmvc | mybatis 环境&#xff1a;jdk1.8 | mysql | maven | tomcat | idea 二、代码及数据库 三、功能介绍 01. 登录 02. 主页 03. 管理员-个人中心-修改密码…

Linux-数据结构-线性表-单链表

一.链表的概念 【1】线性表的链式存储 解决顺序存储的缺点&#xff0c;插入和删除&#xff0c;动态存储问题。 【2】特点&#xff1a; 线性表链式存储结构的特点是一组任意的存储单位存储线性表的数据元素&#xff0c;存储单元可以是连续的&#xff0c;也可以不连续。可以被存…

基于SpringBoot的Mybatis和纯MyBatis项目搭建的区别

【由于之前学习MyBatis的时候是跟着视频敲的纯MyBatis项目&#xff0c;以至于在突然看到别人在SpringBoot项目里搭建MyBatis方式的时候很懵比…特此文字形式记录一下区别&#xff08;应该还有好多种其他方式是我不知道的&#xff0c;主要应该就是要知道关键的流程步骤&#xff…

大数据学习(68)- Flink和Spark Streaming

&#x1f34b;&#x1f34b;大数据学习&#x1f34b;&#x1f34b; &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一…

Fastdata极数:中国民宿行业发展趋势报告2025

2024年&#xff0c;中国游客出行次数大幅上涨&#xff0c;旅游相关支出也复苏强劲。2025年中国旅游业还将持续稳健的复苏及增长。同时&#xff0c;中国旅游业将见证一场深刻的变革&#xff0c;这场变革的推动力是消费者对旅游期望的转变&#xff0c;经济因素和年轻人全新价值观…

图论——广度优先搜索实现

99. 岛屿数量 题目描述 给定一个由 1(陆地)和 0(水)组成的矩阵,你需要计算岛屿的数量。岛屿由水平方向或垂直方向上相邻的陆地连接而成,并且四周都是水域。你可以假设矩阵外均被水包围。 输入描述 第一行包含两个整数 N, M,表示矩阵的行数和列数。 后续 N 行,每行…

鸿蒙路由 HMrouter 配置及使用一

1、学习链接 HMRouter地址 https://gitee.com/hadss/hmrouter/blob/dev/HMRouterLibrary/README.md 2、工程配置 下载安装 ohpm install hadss/hmrouter 添加编译插件配置 在工程目录下的build-profile.json5中&#xff0c;配置useNormalizedOHMUrl属性为true (我这项目创…

各省水资源平台 水资源遥测终端机都用什么协议

各个省水资源平台 水资源遥测终端机 的建设大部分从2012年开始启动&#xff0c;经过多年建设&#xff0c;基本都已经形成了稳定的通讯要求&#xff1b;河北瑾航科技 遥测终端机&#xff0c;兼容了大部分省市的通讯协议&#xff0c;如果需要&#xff0c;可以咨询和互相学习&…

需求分析、定义、验证、变更、跟踪(高软47)

系列文章目录 需求分析、定义、验证、变更、跟踪 文章目录 系列文章目录前言一、需求分析二、需求定义三、需求验证四、需求变更五、需求跟踪六、真题总结 前言 本节讲明需求分析、定义、验证、变更、跟踪相关知识。 一、需求分析 二、需求定义 三、需求验证 四、需求变更 五、…

从零开始 | C语言基础刷题DAY3

❤个人主页&#xff1a;折枝寄北的博客 目录 1.打印3的倍数的数2.从大到小输出3. 打印素数4.打印闰年5.最大公约数 1.打印3的倍数的数 题目&#xff1a; 写一个代码打印1-100之间所有3的倍数的数字 代码&#xff1a; int main(){int i 0;for (i 1; i < 100; i){if (i % …

docker入门篇

使用docker可以很快部署相同的环境,这也是最快的环境构建,接下来就主要对docker中的基础内容进行讲解.Docker 是一个用于开发、交付和运行应用程序的开源平台&#xff0c;它可以让开发者将应用程序及其依赖打包到一个容器中&#xff0c;然后在任何环境中运行这个容器&#xff0…

Unity Shader - UI Sprite Shader之简单抠图效果

Sprite抠图效果&#xff1a; 前言 在PhotoShop中我们经常会用到抠图操作&#xff0c;现在就用Shader实现一个简单的抠图效果。 实现原理&#xff1a; 使用当前像素颜色与需要抠掉的颜色相减作比较&#xff0c;然后与一个指定的阈值比较以决定是否将其显示出来&#xff1b; U…

vllm-openai多服务器集群部署AI模型

服务器配置是两台ubantu系统电脑,每台电脑安装两张4090-48G显存的显卡,共计192G显存。 服务器1 服务器2 准备工作: 1.两台电脑都已经安装了docker 2.两台电脑都已经安装了nvidia驱动 参考vllm官方资料 https://docs.vllm.ai/en/latest/serving/distributed_serving.html…

在Spring Boot项目中接入DeepSeek深度求索,感觉笨笨的呢

文章目录 引言1. 什么是DeepSeek&#xff1f;2. 准备工作2.1 注册DeepSeek账号 3.实战演示3.1 application增加DS配置3.2 编写service3.3 编写controller3.4 编写前端界面chat.html3.5 测试 总结 引言 在当今快速发展的数据驱动时代&#xff0c;企业越来越重视数据的价值。为了…

STM32---FreeRTOS事件标志组

一、简介 事件标志位&#xff1a;用一个位&#xff0c;来表示事件是否发生 事件标志组&#xff1a;一组事件标志位的集合&#xff0c;可以简单的理解时间标志组&#xff0c;就是一个整体。 事件标志租的特点&#xff1a; 它的每一个位表示一个时间&#xff08;高8位不算&…