草庐IT

从灰度图到地形图

averagePerson 2025-02-19 原文

大概就是根据一个灰度图,生成一个地形。

分两步来实现吧;首先,用随机数生成地形;然后,根据灰度图生成地形。

小白,没啥基础,所以只能慢慢来。

参考:

【萌新图形学】地形网格生成入门 含动画说明哦_哔哩哔哩_bilibili

【萌新图形学】地形生成下篇——随机大地形与真实地形_哔哩哔哩_bilibili

首先,得有一些基本概念的:

00.一些基本概念

演示

我是个小白,所以,刚开始,来点直观的吧

新建了一个空物体gameobject,手动添加了3样东西给它:

  1. MeshFilter组件

  1. MeshRender组件

  1. 考虑到没有材质球会成粉色,所以新建了个默认的材质球给它。

前两个组件是主要的,下面的动图就简单的演示了这两个组件的作用。

顺便提一下,这里有个线框模式显示的开关。

大概有了个朦胧的认识:

  • MeshFilter里的Mesh可以控制物体的形状

  • MeshRender负责物体的显示

现在,开始文档里的正式介绍。

Mesh

Unity - Scripting API: Mesh (unity3d.com)

《inherits from Object》

里面按一定规则存着模型的数据,比如顶点什么的。【就是顶点着色器里的那个顶点】

看定义可能比较朦胧,看这个示例代码,就很清楚它是什么了:

MeshFilter

Unity - Manual: Mesh Filter component (unity3d.com)

这个组件,也很简单呐;里面就一个Mesh。。

具体可以这么用:

Unity - Scripting API: MeshFilter.mesh (unity3d.com)

从代码里可以看到,这个组件里的Mesh,就是上面的那个Mesh类

MeshRender

Unity - Manual: Mesh Renderer component (unity3d.com)

材质球,UnityShader,就是拖给这个组件的。再结合它的名字猜一下——它负责把网格画出来

小结

  • MeshFilterMeshRender是一对

  • MeshFilter组件和Mesh类是一对

  • 负责提供数据

  • 如顶点在模型坐标系下的坐标

  • MeshRender组件和材质球,shader是一对

  • 定义了如何使用数据

  • 比如在片元着色器里把quad给discard成ball,point sprite就是这么来的

后面就是按着视频来了。

01.大小为1的平面

这个是视频里的代码。

结合上面介绍的基本概念,大概知道它在干什么吧。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Terrian : MonoBehaviour
{
    public float width = 0.1f;

    MeshRenderer meshRenderer;
    MeshFilter meshFilter;

    // 用来存放顶点数据
    List<Vector3> verts;
    List<int> indices;

    private void Awake()
    {
    }

    private void Start()
    {
        verts = new List<Vector3>();
        indices = new List<int>();

        meshRenderer = GetComponent<MeshRenderer>();
        meshFilter = GetComponent<MeshFilter>();

        Generate();
    }

    public void Generate()
    {
        ClearMeshData();

        // 把数据填写好
        AddMeshData();

        // 把数据传递给Mesh,生成真正的网格
        Mesh mesh = new Mesh();
        mesh.vertices = verts.ToArray();
        //mesh.uv = uvs.ToArray();
        mesh.triangles = indices.ToArray();

        mesh.RecalculateNormals();
        mesh.RecalculateBounds();

        meshFilter.mesh = mesh;
    }

    void ClearMeshData()
    {
        verts.Clear();
        indices.Clear();
    }

    void AddMeshData()
    {
        verts.Add(new Vector3(0, 0, 0));
        verts.Add(new Vector3(0, 0, 1));
        verts.Add(new Vector3(1, 0, 1));
        verts.Add(new Vector3(1, 0, 0));

        indices.Add(0);  indices.Add(1);  indices.Add(2);
        indices.Add(0);  indices.Add(2);  indices.Add(3);
    }
    
}

在上面的那个演示的基础上,把它拖给空物体,就可以了。

02.更大规模的平面

灌数据到Mesh的原理

主要就俩数组,一个是顶点,一个是索引。

视频里这个图挺好的。

顶点数据

索引数据

稍微复杂一点,因为顶点是单独的,这个是相互关联的。

很形象的图,涉及到二维逻辑地址和一维物理地址的换算。

从特殊到一般:

最终应用【顺序是比较重要的,因为单面剔除,cull on,cull off之类的】

试一试

修改前:

void AddMeshData()
{
    verts.Add(new Vector3(0, 0, 0));
    verts.Add(new Vector3(0, 0, 1));
    verts.Add(new Vector3(1, 0, 1));
    verts.Add(new Vector3(1, 0, 0));

    indices.Add(0);  indices.Add(1);  indices.Add(2);
    indices.Add(0);  indices.Add(2);  indices.Add(3);
}

修改后

void AddMeshData()
{
    int N = 10;
    //01填充顶点数据
    for (int z = 0; z < N; ++z)//按先x后z的顶点排列顺序,所以最外层的循环是z不是x
    {
        for(int x = 0; x < N; ++x)
        {
            Vector3 temp = new Vector3(x, 0, z);
            verts.Add(temp);
        }
    }
    //02填充索引数据
    for(int z = 0; z < N - 1; ++z)
    {
        for(int x = 0; x < N - 1; ++x)
        {
            int index_lb = z * N + x;//index of the left bottom vertex. lb = left bottom
            int index_lt = (z + 1) * N + x;
            int index_rt = (z + 1) * N + x + 1;
            int index_rb = z * N + x + 1;

            indices.Add(index_lb);indices.Add(index_lt);indices.Add(index_rt);
            indices.Add(index_rt);indices.Add(index_rb);indices.Add(index_lb);
        }
    }
}

结果,符合预期;在原点那里放了个cube,作参照。

03.从平面到地形

这个不难,加一行

void AddMeshData()
{
int N = 10;
//01填充顶点数据
for (int z = 0; z < N; ++z)//按先x后z的顶点排列顺序,所以先循环的是z
{
    for(int x = 0; x < N; ++x)
    {
        float height = Random.Range(0.1f, 1.0f);//随机加个高度
        Vector3 temp = new Vector3(x, height, z);
        verts.Add(temp);
    }
}
//02填充索引数据
for(int z = 0; z < N - 1; ++z)
{
    for(int x = 0; x < N - 1; ++x)
    {
        int index_lb = z * N + x;//index of the left bottom vertex. lb = left bottom
        int index_lt = (z + 1) * N + x;
        int index_rt = (z + 1) * N + x + 1;
        int index_rb = z * N + x + 1;

        indices.Add(index_lb);indices.Add(index_lt);indices.Add(index_rt);
        indices.Add(index_rt);indices.Add(index_rb);indices.Add(index_lb);
    }
}


}

结果,有高度起伏了。

这个是10*10规模的,更大的规模也是一样的。计算机擅长重复。

04.从随机数到灰度图

准备

首先,得有个地形灰度图;这里用的是这个:

其次,得能从C#脚本里读到纹理的值。

Unity - Scripting API: Texture2D (unity3d.com)

GetPixel函数

Unity - Scripting API: Texture2D.GetPixel (unity3d.com)

解释的很详细了:

从下图可以看出,这个xy不是归一化后的uv:

这个返回值color,倒是归一化的:Unity - Scripting API: Color (unity3d.com)

视频里用的是更快的这个函数:

Unity - Scripting API: Texture2D.GetPixels32 (unity3d.com)

但是,我是小白,能搞出来就已经是极限了,哪里还顾得上什么性能问题。

试一试

代码

接着改那个函数就行

void AddMeshData()
{
    int N = 100;
    //01填充顶点数据
    for (int z = 0; z < N; ++z)//按先x后z的顶点排列顺序,所以先循环的是z
    {
        for(int x = 0; x < N; ++x)
        {
            int u = Mathf.FloorToInt(1.0f * x / N * texture2dHeightMap.width);//没归一化的uv
            int v = Mathf.FloorToInt(1.0f * z / N * texture2dHeightMap.height);
            float grayValue = texture2dHeightMap.GetPixel(u,v).grayscale;
    
            float height = grayValue*heightRatio;//灰度值范围是[0,1],所以得缩放一下
            Vector3 temp = new Vector3(x, height, z);
            verts.Add(temp);
        }
    }
    //02填充索引数据
    for(int z = 0; z < N - 1; ++z)
    {
        for(int x = 0; x < N - 1; ++x)
        {
            ……//这部分是没改,省略
        }
    }
}

符合预期吧

完整的代码

新建一个空物体,上面添加上MeshFilter,MeshRender两个组件,然后把这个脚本拖给这个空物体,再点击play,大概就行了。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Terrian : MonoBehaviour
{

    public Texture2D texture2dHeightMap;
    [Range(1,100)]
    public float heightRatio = 30.0f;//一个系数,控制地形总体的高度的

    MeshRenderer meshRenderer;
    MeshFilter meshFilter;

    // 用来存放顶点数据
    List<Vector3> verts;
    List<int> indices;

    private void Awake()
    {
    }

    private void Start()
    {
        verts = new List<Vector3>();
        indices = new List<int>();

        meshRenderer = GetComponent<MeshRenderer>();
        meshFilter = GetComponent<MeshFilter>();

        
    }

    private void Update()
    {
        Generate();
    }

    public void Generate()
    {
        ClearMeshData();

        // 把数据填写好
        AddMeshData();

        // 把数据传递给Mesh,生成真正的网格
        Mesh mesh = new Mesh();
        mesh.vertices = verts.ToArray();
        mesh.triangles = indices.ToArray();

        mesh.RecalculateNormals();
        mesh.RecalculateBounds();

        meshFilter.mesh = mesh;
    }

    void ClearMeshData()
    {
        verts.Clear();
        indices.Clear();
    }

    void AddMeshData()
    {
        int N = 100;
        //01填充顶点数据
        for (int z = 0; z < N; ++z)//按先x后z的顶点排列顺序,所以先循环的是z
        {
            for(int x = 0; x < N; ++x)
            {
                int u = Mathf.FloorToInt(1.0f * x / N * texture2dHeightMap.width);
                int v = Mathf.FloorToInt(1.0f * z / N * texture2dHeightMap.height);
                float grayValue = texture2dHeightMap.GetPixel(u,v).grayscale;

                float height = grayValue*heightRatio;
                Vector3 temp = new Vector3(x, height, z);
                verts.Add(temp);
            }
        }
        //02填充索引数据
        for(int z = 0; z < N - 1; ++z)
        {
            for(int x = 0; x < N - 1; ++x)
            {
                int index_lb = z * N + x;//index of the left bottom vertex. lb = left bottom
                int index_lt = (z + 1) * N + x;
                int index_rt = (z + 1) * N + x + 1;
                int index_rb = z * N + x + 1;

                indices.Add(index_lb);indices.Add(index_lt);indices.Add(index_rt);
                indices.Add(index_rt);indices.Add(index_rb);indices.Add(index_lb);
            }
        }



    }
    
}

后记

还想搞搞颜色,低的用绿色,高的用红色,中间用渐变色;更好的可视化一下;

但是,那个可能得写着色器。那很明显不是我这种小白干的来的。所以,忽略。

有关从灰度图到地形图的更多相关文章

  1. ruby - 在 StockChart (highchart) 中以编程方式显示柱形图的工具提示 - 2

    我有一个Highstock图表(带有标记和阴影的线条),并且想以编程方式显示一个highstock工具提示,例如,当我选择某个表上的一行(包含图表数据)我想显示相应的highstock工具提示。这可能吗? 最佳答案 股票图表thissolution不起作用:在thisexample你必须更换这个:chart.tooltip.refresh(chart.series[0].data[i]);为此:chart.tooltip.refresh([chart.series[0].points[i]]);解决方案可用here.

  2. 《安富莱嵌入式周报》第301期:ThreadX老大离开微软推出PX5 RTOS第5代系统,支持回流焊的自焊接PCB板设计,单色屏实现多级灰度播放视频效果 - 2

    往期周报汇总地址:嵌入式周报-uCOS&uCGUI&emWin&embOS&TouchGFX&ThreadX-硬汉嵌入式论坛-PoweredbyDiscuz! 祝大家开工大吉视频版:https://www.bilibili.com/video/BV1GT411o7zr1、ThreadX老大离开微软,开发的第5代RTOS系统PX5RTOS正式上线最早是看到IAR的一条消息,全面支持PX5RTOS,然后就进一步上他们的官方下载白皮书了解相关消息当看到这两个名字时,很熟悉,这不就是ThreadX的老大BillLamie。 经过信息检索,应该是实锤了,领英上已经更新了他的工作经历: 然后再结合Azur

  3. javascript - 将 y 轴标签添加到 NVD3 多条形图 - 2

    我正在尝试将轴标签添加到NVD3多条形图,但它似乎只适用于x轴。有什么办法解决这个问题吗?我在这里设置了一个例子:http://jsfiddle.net/msts1jha/2/varchart=nv.models.multiBarChart();chart.xAxis.tickFormat(d3.format(',f'));chart.yAxis.tickFormat(d3.format(',.1f'));chart.xAxis.axisLabel("xaxis");chart.yAxis.axisLabel("yaxis"); 最佳答案

  4. javascript - D3.js 结合条形图和折线图 - 2

    我想通过组合条形图和折线图来创建多系列图表。当我使用rangeBands()设置输出范围时,线从图表中第一个柱的开始处开始绘制,并在最后一个柱的开始处结束。我应该更改什么以使该线从第一个刻度开始并在最后一个刻度结束?vardata=[{date:'1-May-12',close:58.13,open:7.41},{date:'2-May-12',close:53.98,open:45.55},{date:'3-May-12',close:67.00,open:11.78}];varmargin={top:30,right:40,bottom:30,left:50}, width=600

  5. javascript - 当 xaxis 是文本时,如何显示 jqplot 堆积条形图? - 2

    我似乎无法让我的jqplot条形图堆叠。我有以下代码://Pass/Failratesperrequest$.jqplot('passFailPerRequestStats',[passRate,failRate],{title:'AutomationPassCountPerTestPlan',//stackSeries:true,seriesDefaults:{renderer:$.jqplot.BarRenderer,renderOptions:{barMargin:25},pointLabels:{show:true,stackedValue:true}},axesDefault

  6. javascript - d3.js 如何向条形图添加线条 - 2

    我有一个包含4个值的数据集。[ABCD]。目前它们显示在条形图中,每个值一个条。现在,由于值c和d是平均值,我想将它们显示为a和b栏后面的线。d3可以吗?如何在同一个数据数组中切换条形或线形显示?感谢您的帮助。 最佳答案 我在这里发布了一个示例,因为没有一个答案在jsbin或jsfiddle等中提供了带线的条形图的工作示例。http://jsbin.com/gisinomo/1/edit该示例是d3wiki上简单条形图的一个分支。http://bl.ocks.org/mbostock/3885304CSSbody{font:10px

  7. 从灰度图到地形图 - 2

    序大概就是根据一个灰度图,生成一个地形。分两步来实现吧;首先,用随机数生成地形;然后,根据灰度图生成地形。小白,没啥基础,所以只能慢慢来。参考:【萌新图形学】地形网格生成入门含动画说明哦_哔哩哔哩_bilibili【萌新图形学】地形生成下篇——随机大地形与真实地形_哔哩哔哩_bilibili首先,得有一些基本概念的:00.一些基本概念演示我是个小白,所以,刚开始,来点直观的吧新建了一个空物体gameobject,手动添加了3样东西给它:MeshFilter组件MeshRender组件考虑到没有材质球会成粉色,所以新建了个默认的材质球给它。前两个组件是主要的,下面的动图就简单的演示了这两个组件的

  8. javascript - Highchart 条形图内外的数据标签颜色可以不同吗 - 2

    我想知道如果文本不适合条形长度,条形图(plotOptions.bar.dataLabels.color)内的文本颜色可能会有所不同。例如:代码在这里:$(function(){$('#container').highcharts({chart:{type:'bar',height:700},xAxis:{categories:['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']},plotOptions:{bar:{stacking:'normal',pointPadding:0,group

  9. javascript - 在 ChartJS 条形图上更改 fontFamily - 2

    我已经部分实现了ChartJS在一个项目中,但我不知道如何更改显示在条形图的X轴和Y轴上的字体。我读过ChartJSdocumentation,在GitHub上搜索示例等。我不确定我做错了什么,但我确信解决方案将涉及一行代码,并且对我来说将是一个非常愚蠢的疏忽。此代码绘制我想要的图表,但使用默认字体:varbarChartLanguage=Chart.Bar(myCanvas,{data:dataLanguages,options:options,defaults:defaults});我尝试更改defaults中的字体但没有成功:vardefaults={global:{//exam

  10. javascript - 隐藏 y 轴 dc.js 条形图上的所有文本 - 2

    我正在创建一个基于DC.JS的仪表板,但想弄清楚如何隐藏条形图y轴上的所有文本以及如何设置x轴上文本的样式。谢谢 最佳答案 您应该将刻度减少到零:yourChart.yAxis().ticks(0);请注意,这不会返回对图表的引用,因此您不能链接此方法。您必须将其放在单独的行中。像这样:varyourChart=dc.barChart('#your-chart').width(1024).height(50);yourChart.yAxis().ticks(0); 关于javascrip

随机推荐