unity代码生成圆柱网格并使用曲面细分顶点

文章介绍了如何使用Blender导出OBJ文件,并在Unity中创建和修改圆柱网格,包括设置顶点、三角形和纹理坐标。此外,还提及了如何在Unity的URP中实现基础的曲面细分着色器。
摘要由CSDN通过智能技术生成

想要使用生成网格,首先要对网格数据有初步的理解,如果有了解请跳过这一步。

使用blender导出一个cube的obj文件,用记事本打开。

# Blender 4.1.0
# www.blender.org
mtllib untitled.mtl
o Cube
v 1.000000 1.000000 -1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 1.000000 -1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 1.000000
vn -0.0000 1.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -1.0000 -0.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vt 0.625000 0.500000
vt 0.875000 0.500000
vt 0.875000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.750000
vt 0.625000 1.000000
vt 0.375000 1.000000
vt 0.375000 0.000000
vt 0.625000 0.000000
vt 0.625000 0.250000
vt 0.375000 0.250000
vt 0.125000 0.500000
vt 0.375000 0.500000
vt 0.125000 0.750000
s 0
usemtl Material
f 1/1/1 5/2/1 7/3/1 3/4/1
f 4/5/2 3/4/2 7/6/2 8/7/2
f 8/8/3 7/9/3 5/10/3 6/11/3
f 6/12/4 2/13/4 4/5/4 8/14/4
f 2/13/5 1/1/5 3/4/5 4/5/5
f 6/11/6 5/10/6 1/1/6 2/13/6

解释一下这些部分都是什么

  1. o Cube:定义对象名称为"Cube"。
  2. v 行:定义顶点坐标。例如,v 1.000000 1.000000 -1.000000 表示一个顶点的x、y和z坐标分别为1.0、1.0和-1.0。
  3. vn 行:定义法线坐标。法线用于描述模型表面的方向,常用于光照计算。
  4. vt 行:定义纹理坐标。这些坐标用于在模型表面映射纹理。
  5. s 0:这通常是一个平滑组标记,用于定义哪些面应该共享相同的光照属性。在这里,所有的面都属于同一个平滑组。
  6. usemtl Material:指定接下来的面应该使用哪个材质。这里的材质名称是"Material"。
  7. f 行:定义面。每个面由三个或更多的顶点组成,并且每个顶点都有一个顶点索引、一个法线索引和一个纹理坐标索引。例如,f 1/1/1 5/2/1 7/3/1 3/4/1 表示一个面,其顶点按照给定的索引从顶点、法线和纹理坐标列表中选取。

 在unity中,构成模型的面都是三角形,而决定三角形形成的就是顶点顺序,如果用过OpenGL去h绘制模型的话应该不难理解。

接下来要知道unity的mesh类

Unity - Scripting API: Mesh

 接下来大致思路就是,创建一个网格,使用这两个方法去设置顶点和三角形索引,最后使用unity渲染。

生成圆柱网格类:

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

namespace KerzhMesh
{
    public class CylinderController
    {
        private GameObject m_CylinderObj;  // 圆柱体
        private MeshRenderer m_MeshRenderer;
        private MeshFilter m_MeshFilter;

        public void Init(Material material)
        {
            m_CylinderObj = new GameObject("Cylinder");
            m_MeshFilter = m_CylinderObj.AddComponent<MeshFilter>();
            m_MeshRenderer = m_CylinderObj.AddComponent<MeshRenderer>();
            m_MeshRenderer.shadowCastingMode = ShadowCastingMode.On;
            m_MeshRenderer.receiveShadows = true;
            m_MeshRenderer.material = material;
        }
        
        public void RefreshCylinderMesh(int radialSegments = 5, int heightSegments = 1, float radius = 1.0f, float height = 2.0f)
        {
        UnityEngine.Mesh mesh = new UnityEngine.Mesh();
        
        //  radialSegments是一圈的顶点数,heightSegments是中间切了几次
        //  对于顶点总数,圆柱自身上下各两圈,每切一层多一圈,故总层数是2 + heightSegments
        //  那么,每层的顶点数乘以层数,可以获得侧边的总顶点数
        int sideVertexCount = radialSegments * (2 + heightSegments);
        //  而在圆柱的最上和最下两个面的中心,需要两个点,以生成上下两个面的三角形,故顶点总数为sideVertexCount + 2
        int allVertexCount = sideVertexCount + 2;
        Debug.Log("allVertexCount:" + allVertexCount);
        Vector3[] vertices = new Vector3[allVertexCount];
        
        //  对于三角形总数,首先计算侧边总数,对于每一层,上下的四个顶点会生成两个三角形,一共会有radialSegments - 1对四个顶点
        //  所以每一层就会有(radialSegments - 1) * 2个三角形,而一共有heightSegments + 2 - 1层
        int sideTriangleCount = radialSegments * 2 * (heightSegments + 2 - 1);
        //  对于最上和最下两层,层上有radialSegments + 1个顶点,其中多的那一个是层中心,而在生成三角形时,除了第一次占用两个顶点,都是占用上次的第二个顶点和一个新的顶点去生成三角形
        //  故生成的三角形总数为radialSegments - 1,但这是一层的,上下一共两层所以乘二
        int upDownTriangleCount = radialSegments * 2;
        int[] triangles = new int[(sideTriangleCount + upDownTriangleCount)*3];
        Debug.Log((sideTriangleCount + upDownTriangleCount)*3);

        #region 创建顶点
        //  标记每一部分的顶点下标范围::设计为闭区间,即包含两端
        //  startIdxXXX每一段的开始,endIdxXXX每一段的结束
        int vertexIdx = 0;  //  顶点下标
        int triangleIdx = 0;  //  三角形顶点索引

        #region 底部顶点

        int startIdxBtm = vertexIdx;
        {
            // 创建底部圆形顶点
            for (int i = 0; i < radialSegments; i++)
            {
                float angle = 2 * Mathf.PI * i / radialSegments;
                vertices[vertexIdx++] = new Vector3(Mathf.Cos(angle) * radius, 0, Mathf.Sin(angle) * radius);
            }
            //  创建底部圆形中心顶点
            vertices[vertexIdx++] = new Vector3(0, 0, 0);
        }
        int endIdxBtm = vertexIdx - 1;
        
        #endregion

        #region 侧边顶点

        int startIdxSide = vertexIdx;
        {
            // 创建侧面顶点
            for (int i = 0; i < heightSegments; i++)
            {
                float t = (i + 1) / (float)(2 + (heightSegments - 1));
                float heightOffset = t * height;
                for (int j = 0; j < radialSegments; j++)
                {
                    float angle = 2 * Mathf.PI * j / radialSegments;
                    vertices[vertexIdx++] = new Vector3(Mathf.Cos(angle) * radius, heightOffset, Mathf.Sin(angle) * radius);
                }
            }
        }
        int endIdxSide = vertexIdx - 1;

        #endregion

        #region 顶部顶点

        int startIdxTop = vertexIdx;
        {
            // 创建顶部圆形顶点
            for (int i = 0; i < radialSegments; i++)
            {
                float angle = 2 * Mathf.PI * i / radialSegments;
                vertices[vertexIdx++] = new Vector3(Mathf.Cos(angle) * radius, height, Mathf.Sin(angle) * radius);
            }
            //  创建顶部圆形中心顶点
            vertices[vertexIdx++] = new Vector3(0, height, 0);
        }
        int endIdxTop = vertexIdx - 1;

        #endregion
        
        #endregion

        #region 创建索引
        
        #region 底部圆形

        //  创建底部圆形顶点索引
        //  底部圆形中心点在顶点数组的索引:endIdx
        //  使用记录的顶点范围,分配三角形顶点索引,注意这里法线应该是向下的,所以采用逆时针的方向
        int nowUseIdx = startIdxBtm;
        for (int i = 0; i < radialSegments; i++)
        {
            triangles[triangleIdx++] = endIdxBtm;
            if (i == 0)  //  第一个
            {
                triangles[triangleIdx++] = nowUseIdx++;
                triangles[triangleIdx++] = nowUseIdx;
            }
            else if (i == radialSegments - 1)  //  最后一个
            {
                triangles[triangleIdx++] = nowUseIdx;
                triangles[triangleIdx++] = startIdxBtm;
            }
            else
            {
                triangles[triangleIdx++] = nowUseIdx;
                triangles[triangleIdx++] = ++nowUseIdx;
            }
        }

        #endregion

        #region 顶部圆形
        //  创建底部圆形顶点索引
        //  底部圆形中心点在顶点数组的索引:endIdx
        //  使用记录的顶点范围,分配三角形顶点索引,注意这里法线应该是向上的,所以采用顺时针的方向
        nowUseIdx = startIdxTop;
        for (int i = 0; i < radialSegments; i++)
        {
            triangles[triangleIdx++] = endIdxTop;
            if (i == 0)  //  第一个
            {
                triangles[triangleIdx++] = ++nowUseIdx;
                triangles[triangleIdx++] = nowUseIdx - 1;
            }
            else if (i == radialSegments - 1)  //  最后一个
            {
                triangles[triangleIdx++] = startIdxTop;
                triangles[triangleIdx++] = nowUseIdx;
            }
            else
            {
                triangles[triangleIdx++] = ++nowUseIdx;
                triangles[triangleIdx++] = nowUseIdx - 1;
            }
        }
        #endregion
        
        #region 侧面柱形
        //  创建底部圆形顶点索引
        //  使用记录的顶点范围,分配三角形顶点索引
        //  根据中间切了几次,可知总共有几组上下平面,原本两层为一组,每切一层多一组
        int maxGroupNum = 1 + heightSegments;

        //对于每一组,group代表当前组数
        for (int group = 1; group <= maxGroupNum; group++)
        {
            //  找顶点下标时注意:第一层和最后一层在末尾添加了中心顶点以辅助生成三角形
            //  获得上层和下层的起始顶点下标
            int btmStartIdx;
            int topStartIdx;
            if (group == 1)  //  如果是第一组,
            {
                btmStartIdx = startIdxBtm;
                topStartIdx = endIdxBtm + 1;
            }
            else
            {
                btmStartIdx = endIdxBtm + 1 + (group - 2) * radialSegments;
                topStartIdx = endIdxBtm + 1 + (group - 1) * radialSegments;
            }

            //  找到所有上下对应的四个顶点,生成两个三角形
            for (int i = 0; i < radialSegments; i++)
            {
                //可以找到当前的四个下标:下层的i,下层的i+1,上层的i,上层的i+1
                int btmi, btmiplus1, topi, topiplys1;
                
                if (i == radialSegments - 1)  //  最后一个
                {
                    btmi = btmStartIdx + i;
                    btmiplus1 = btmStartIdx;
                    topi = topStartIdx + i;
                    topiplys1 = topStartIdx;
                }
                else
                {
                    btmi = btmStartIdx + i;
                    btmiplus1 = btmStartIdx + i + 1;
                    topi = topStartIdx + i;
                    topiplys1 = topStartIdx + i + 1;
                }
                
                //  使用这四个点进行三角形构建
                triangles[triangleIdx++] = topi;
                triangles[triangleIdx++] = btmiplus1;
                triangles[triangleIdx++] = btmi;
                
                triangles[triangleIdx++] = topiplys1;
                triangles[triangleIdx++] = btmiplus1;
                triangles[triangleIdx++] = topi;
            }
        }
        
        #endregion

        #endregion

        // 设置网格的顶点和三角形索引
        mesh.vertices = vertices;
        mesh.triangles = triangles;

        // 计算法线(可选,用于更好的光照效果)
        mesh.RecalculateNormals();
        m_MeshFilter.mesh = mesh;
    }
        
    }
}

这里把圆柱区分为上下面和侧面,分步骤添加顶点和三角形索引,最后赋值给网格。再创建一个启动类挂在场景中。

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

public class MeshTest : MonoBehaviour
{
    public Material material;
    [Range(1, 100)]
    public int radialSegments = 5;
    [Range(1, 10)]
    public int heightSegments = 1;
    [Range(0.01f, 10.0f)]
    public float radius = 1.0f;
    [Range(1.0f, 10.0f)]
    public float height = 2.0f;

    CylinderController cylinderController = new CylinderController();
    
    private void Start()
    {
        cylinderController.Init(material);
    }

    private void Update()
    {
        cylinderController.RefreshCylinderMesh(radialSegments, heightSegments, radius, height);
    }
}

运行场景调整生成参数即可,最后在赋值的材质shader中,添加曲面细分顶点,关于这部分可以看上一篇文章,写的也很清楚。

Unity URP 如何写基础的曲面细分着色器_urp 曲面细分-CSDN博客

最终效果:

  • 10
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值