Unity系统Cube的法线

本文深入探讨了在Unity引擎中使用Voxel进行项目开发时遇到的法线与切线问题,详细解释了切线数据在bump-mapped着色器中的作用,以及如何正确地计算和应用切线以实现立体效果。同时,通过代码示例展示了如何检查和显示Voxel网格的法线数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前几天在搞一个Voxel的项目,盒子的表现不美观,于是想给Voxel加上法线增强盒子的立体效果,给shader里加了法线,但是显示出来法线效果不对,绕了很大一圈。
一开始怀疑是法线方向不对,法线反了就看不到啊,应该也没反。
UV顺序不对吗?UV没顶点重合不会看不到。
后来才发现MagicVoxel For unity的包是没有切线数据的,没切线数据无法正确显示。

切线的描述:

Mesh.tangents 网格切线
var tangents : Vector4[]
Description描述
The tangents of the mesh.
网格的切线
Tangents are mostly used in bump-mapped shaders. A tangent is a unit length vector that follows mesh surface along horizontal (U) texture direction. Tangents in Unity are represented as Vector4, with x,y,z components defining the vector, and w used to flip the binormal if needed.
切线主要用于bump-mapped shaders。切线是一个有方向的单位长度,沿着网格表面指向水平(U)纹理方向。在Unity中切线被描述为Vector4,包括x,y,z这些组件定义的矢量,如果需要,及w用来翻转副法线。
Unity calculates the other surface vector (binormal) by taking a cross product between normal and tangent, and multiplying result by tangent.w. Thus w should always be 1 or -1.
Unity通过计算向量和法线的叉乘来计算其他表面的向量(副法线),并乘以tangent.w。因此w应该总是1或者-1。
You should calculate tangents yourself if you plan to use bump-mapped shaders on the mesh. Assign tangents after assigning normals or using RecalculateNormals
如果你计划在网格上用凹凸贴图着色器,你应该自己计算切线。在赋值法线或者用RecalculateNormals之后再赋值切线。

另外看MagicVoxel的源码,发现法线数据都是和面同一方向的,记得unity的Cube法线貌似不全是垂直于平面的,今天抽空写个脚本显示出来看看。

先上图

cube normal

每个面的4个顶点是一种颜色,数字编号是顶点的顺序编号,看起来直观一些。
白色的法线是世界Forward方向,也是第一个面。
黑色是面向我们的面,这个面的上面两个顶点的法线是向上的。和上方的面的重合的顶点的法线互换了。(这里后期研究下)
左右和下方两个面法线是垂直于面的。

贴上代码:方便下次研究

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

public class CNVTest : MonoBehaviour
{
    MeshFilter mf;
    Color[] c = new Color[6];

    void Start()
    {
        mf = GetComponent<MeshFilter>();
        int i, no;
        int m;
        GameObject oParent = null;

        c[0] = Color.white;
        c[1] = Color.yellow;
        c[2] = Color.red;
        c[3] = Color.green;
        c[4] = Color.blue;
        c[5] = Color.cyan;

     
        no = 0;
        m = 0;
        for (i=0; i < mf.mesh.uv.Length; i++)
        {
            m = i % 4;
            if (m == 0)
            {
                no = i / 4;
                oParent = new GameObject("v_"+no.ToString());
                oParent.transform.SetParent(transform);
                oParent.transform.localPosition = Vector3.zero;
                oParent.transform.localRotation = Quaternion.identity;
            }
            //顶点
            GameObject ob = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            ob.transform.SetParent(oParent.transform);
            ob.transform.localScale = Vector3.one * 0.07f;
            ob.transform.localRotation = Quaternion.identity;
            ob.transform.localPosition = mf.mesh.vertices[i];
            CNormal nm = ob.AddComponent<CNormal>();
            nm.dir = mf.mesh.normals[i];
            nm.c = c[no];
            nm.txt = string.Format("vertice:{0},uv:{1}", i, mf.mesh.uv[i]);
            

            Debug.Log("cube:i=" + i + ",ver:" + mf.mesh.vertices[i] + ",uv:" + mf.mesh.uv[i] + ",normal:" + mf.mesh.normals[i]);
        }
    }

    
}

这个脚本会自动添加到顶点。

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

public class CNormal : MonoBehaviour
{
    public Vector3 dir;
    public Color c;
    public string txt;

    GUIStyle fontStyle = new GUIStyle();

    public Vector3[] lines = new Vector3[2];

    //划线显示法线
    LineRenderer linerender;
    Material mat;
    Camera cam;
    void Start()
    {
        fontStyle.normal.background = null;    //设置背景填充
        fontStyle.normal.textColor = c;// new Color(1, 0, 0);   //设置字体颜色

        linerender = gameObject.AddComponent<LineRenderer>();

        mat = new Material(Shader.Find("Standard"));
        mat.color = c;
        linerender.material = mat;
        linerender.startWidth = 0.01f;// .SetWidth(0.01f, 0.01f);
        linerender.endWidth = 0.01f;
        

        cam = Camera.main;
    }

    void OnGUI()
    {
        lines[0] = transform.position;
        lines[1] = transform.position + transform.rotation * dir;
        linerender.SetPositions(lines);

        Vector3 screenPos = cam.WorldToScreenPoint(lines[1]);
        GUI.Label(new Rect(screenPos.x, Screen.height - screenPos.y, 680, 50), txt, fontStyle);
    }

}


补一张带UV的,能看到法线和UV。
在这里插入图片描述

cube:i=0,ver:(0.5, -0.5, 0.5),uv:(0.0, 0.0),normal:(0.0, 0.0, 1.0)

cube:i=1,ver:(-0.5, -0.5, 0.5),uv:(1.0, 0.0),normal:(0.0, 0.0, 1.0)

cube:i=2,ver:(0.5, 0.5, 0.5),uv:(0.0, 1.0),normal:(0.0, 0.0, 1.0)

cube:i=3,ver:(-0.5, 0.5, 0.5),uv:(1.0, 1.0),normal:(0.0, 0.0, 1.0)

cube:i=4,ver:(0.5, 0.5, -0.5),uv:(0.0, 1.0),normal:(0.0, 1.0, 0.0)

cube:i=5,ver:(-0.5, 0.5, -0.5),uv:(1.0, 1.0),normal:(0.0, 1.0, 0.0)

cube:i=6,ver:(0.5, -0.5, -0.5),uv:(0.0, 1.0),normal:(0.0, 0.0, -1.0)

cube:i=7,ver:(-0.5, -0.5, -0.5),uv:(1.0, 1.0),normal:(0.0, 0.0, -1.0)

cube:i=8,ver:(0.5, 0.5, 0.5),uv:(0.0, 0.0),normal:(0.0, 1.0, 0.0)

cube:i=9,ver:(-0.5, 0.5, 0.5),uv:(1.0, 0.0),normal:(0.0, 1.0, 0.0)

cube:i=10,ver:(0.5, 0.5, -0.5),uv:(0.0, 0.0),normal:(0.0, 0.0, -1.0)

cube:i=11,ver:(-0.5, 0.5, -0.5),uv:(1.0, 0.0),normal:(0.0, 0.0, -1.0)

cube:i=12,ver:(0.5, -0.5, -0.5),uv:(0.0, 0.0),normal:(0.0, -1.0, 0.0)

cube:i=13,ver:(0.5, -0.5, 0.5),uv:(0.0, 1.0),normal:(0.0, -1.0, 0.0)

cube:i=14,ver:(-0.5, -0.5, 0.5),uv:(1.0, 1.0),normal:(0.0, -1.0, 0.0)

cube:i=15,ver:(-0.5, -0.5, -0.5),uv:(1.0, 0.0),normal:(0.0, -1.0, 0.0)

cube:i=16,ver:(-0.5, -0.5, 0.5),uv:(0.0, 0.0),normal:(-1.0, 0.0, 0.0)

cube:i=17,ver:(-0.5, 0.5, 0.5),uv:(0.0, 1.0),normal:(-1.0, 0.0, 0.0)

cube:i=18,ver:(-0.5, 0.5, -0.5),uv:(1.0, 1.0),normal:(-1.0, 0.0, 0.0)

cube:i=19,ver:(-0.5, -0.5, -0.5),uv:(1.0, 0.0),normal:(-1.0, 0.0, 0.0)

cube:i=20,ver:(0.5, -0.5, -0.5),uv:(0.0, 0.0),normal:(1.0, 0.0, 0.0)

cube:i=21,ver:(0.5, 0.5, -0.5),uv:(0.0, 1.0),normal:(1.0, 0.0, 0.0)

cube:i=22,ver:(0.5, 0.5, 0.5),uv:(1.0, 1.0),normal:(1.0, 0.0, 0.0)

cube:i=23,ver:(0.5, -0.5, 0.5),uv:(1.0, 0.0),normal:(1.0, 0.0, 0.0)

### 创建和操作 Cube 对象 在 Unity 中,可以通过多种方式创建和操作 `Cube` 对象。一种常见的方式是利用内置的 `GameObject.CreatePrimitive` 方法来快速生成基本形状的对象。 #### 使用 GameObject.CreatePrimitive 创建 Cube 此方法允许开发者迅速地在场景中添加预定义的基本几何体,如立方体 (Cube),球体 (Sphere) 等等。对于 cube 来说,可以这样实现: ```csharp // 创建一个位于原点位置的基础立方体 GameObject myCube = GameObject.CreatePrimitive(PrimitiveType.Cube); myCube.transform.position = new Vector3(0, 0, 0); // 设置其初始坐标为 (0,0,0) ``` 这种方法简单快捷,适合于那些不需要特别定制化网格结构的情况[^1]。 #### 动态生成自定义 Mesh 的 Cube 如果希望更深入控制 mesh 数据,则可以选择手动构建顶点、法线向量以及三角形索引列表等方式来自动生成所需的三维模型。这种方式提供了极大的灵活性,可用于模拟 Minecraft 类型的游戏环境或其他复杂的应用场合。 #### 复制现有 Cube 实例 当需要多次重复相同的物体时,可采用 `Instantiate` 函数来进行克隆处理。该函数能够复制指定的游戏对象并将其放置到新的位置上。下面是一个简单的例子展示如何使用它来增加多个相同类型的方块: ```csharp public class InstantiateExample : MonoBehaviour { public GameObject prefab; // 需要被实例化的预制件 void Start(){ for(int i=0;i<5;i++){ Instantiate(prefab,new Vector3(i*2f,0,0),Quaternion.identity); } } } ``` 上述代码片段展示了怎样在一个循环里调用 `Instantiate` 来沿 X 轴方向排列五个间距相隔两单位长度的新立方体副本[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thinbug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值