Unity Shader:Unity网格(1)---顶点,三角形朝向,法线,uv,以及双面渲染三角形

1,顶点

顶点是网格最基础的组成部分,可通过mesh.vertices获取和赋值。mesh.vertices是一个Vector3的数组,每个Vector3代表了此顶点在世界空间中的位置每个Vector3为此顶点与此游戏物体的相对坐标(local position)。每个顶点的位置,总共顶点的数量没有任何限制。几个同样的顶点可以组合成若干不同形状,不同数量的三角形(例如在白纸上画5个点最多可以连成9个不同的三角形)。

手动设置顶点:

mesh = new Mesh ();
Vector3[] v3s=new Vector3[3];
v3s [0] = new Vector3 (0, 0, 0);
v3s [1] = new Vector3 (0, 1, 0);
v3s [2] = new Vector3 (1, 0, 0);
mesh.vertices = v3s;

2,由顶点组成三角形

mesh.triangles决定了网格中的三角形的形状和朝向。triangles是一个int数组,此数组长度永远是3的倍数,每三个int代表的是由哪三个顶点并由什么顺序(朝向)来组成一个三角形,例如:

手动设置三角形:

		int[] index = new int[3];
		index [0] = 0;
		index [1] = 1;
		index [2] = 2;
		mesh.triangles = index;

等号右侧的0,1,2表明网格中将会有一个以 mesh.vertices[0],mesh.vertices[1],和mesh.vertices[2]组成的三角形。此三角形的形状有此三个顶点的位置决定,而它的朝向则由此三个顶点的旋转方向决定。

Unity是左手坐标系,如下图三维空间,z轴方向与视线方向相同,由a-b-c组成的三角形将会背对z轴,既背对我们的视线,
这里写图片描述

而由b-a-c组成的三角形将会正对z轴,既面对我们的视线:
这里写图片描述

b-a-c,a-c-b,c-b-a,这些顺序效果相同。任何一个三角形必须单独设定顶点组合顺序,且此顺序与其他三角形不发生关系。mesh内的顶点可以用来组合三角形,也可不用来组合三角形,triangles数组与vertices数组无绝对的对应关系。

3,法线

给网格赋值顶点并组成三角形,将此网格赋值给场景中的meshfilter,meshrenderer就可以将此三角形渲染出来了,
这里写图片描述
但是法线并不一定是正确的,例如上面声明的三个顶点

v3s [0] = new Vector3 (0, 0, 0);
v3s [1] = new Vector3 (0, 1, 0);
v3s [2] = new Vector3 (1, 0, 0);

不论是由0-1-2还是2-1-0的顺序组成三角形,此三角形的法线总是指向z轴方向,在有光照的场景中会出现错误的效果,这里unity提供了一个方法:

mesh.RecalculateNormals();

此方法将会自动将每个顶点的法线调整到正确方向,既是与面的朝向一致。还提供了一个方法是:mesh.RecalculateBounds();。将会重新计算mesh.bounds,通过mesh.bounds可访问size,center等一些网格的属性。所以在将手动设置的mesh赋值给meshfilter前需要先调用此两方法。

4,UV

uv是为了防止与xy混淆的另一种写法。u为x轴,v为y轴,最小值为0,最大值为1无数值限制(材质上的uv值一般是0-1,当进行采样的uv值大于1时会只取小数部分)。uv值用处很多,例如材质texture的采样,也既是贴图。或是利用它的一些特性(范围0-1;大于1时去小数部分)进行GPU内的一些计算。

当每个顶点被赋予uv值后,在片段着色阶段,片段着色器将会根据顶点的uv值将此三角形覆盖的每个像素的uv值进行自动插值:
这里写图片描述
例如此三角形中,假设三角形ABC的边bc由10个像素块组成,ABC三个顶点的UV值如括号内所示,那么BC直线(假设与x轴完全平行)上每个像素的uv值将左起依次为(0,0),(0,0.1),(0,0.2),(0,0.3)…(0,1)。

uv插值是以三角形为单位进行的,既三角形ABC内各个像素的uv值与其他周边三角形各顶点的uv值是无关的。假如出现D点,那么ACD内各像素的uv值将会由A,C,D的uv值来决定:
这里写图片描述
当D的uv值为(1,1)时进行的2D采样贴图:

		Vector2[] uvs = new Vector2[4];
		uvs [0] = Vector2.zero; //(0,0)
		uvs [1] = Vector2.up;   //(1,0)
		uvs [2] = Vector2.right;//(0,1)
		uvs [3] = Vector2.one;  //(1,1)
		mesh.uv = uvs;

mesh.uv作为一个Vector2数组与mesh.vertices的长度必须一致,每个uv的顺序与vertices的顺序也是一一对应,既uv[0]代表的是vertices[0]的uv值。而与三角形组成的顺序无关。
(下图)当D的uv值为(1,1)时:
这里写图片描述
(下图)当D的uv值为(0,0)时进行的2D采样贴图:
这里写图片描述
(下图)当D的世界坐标位置为(3,3,0),uv值为(0,0)时进行的2D采样贴图:
这里写图片描述

顶点uv值的赋值除了必须在0-1之内以外没有任何其他限制,如有特殊需要可以都为(0,0),或没有上下左右顺序。但很多GPU的方法的实现都需要一个有秩序的uv,例如上面作为例子的2D采样贴图,之所以会出现"正确的"和“错误的”效果,是由于原图材质作为sampler2D纹理缓存入GPU之后,纹理的每个像素点都有一个独立的uv值(由左下角(0,0)开始插值直至右上角(1,1))。

这里写图片描述

而在进入片段着色器后,tex2D命令将会根据当前三角形内像素的uv值去寻找sampler2D上对应uv的颜色,再根据此颜色对三角形内的此像素进行渲染。而由于顶点uv值的不同而导致的三角形内uv值与纹理uv映射关系的改变,以上例子中出现了不同的效果。

附:双面网格

可以利用上面的知识实现一个双面的网格,既是利用四个顶点以顺时针方向组成两个三角形再以逆时针方向组成两个三角形,四个顶点赋uv值后每个点的正反两面的uv皆相同,进而出现镜像效果。此效果与shader调用cull off选项后的效果相同,以下为实现方法:

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

public class Triangle : MonoBehaviour {

	public MeshFilter meshFilter;
	private Mesh mesh;

	// Use this for initialization
	void Start () {
		mesh = new Mesh ();
        //四个顶点位置
        Vector3[] v3s = new Vector3[4];

		v3s [0] = new Vector3 (0, 0, 0);
		v3s [1] = new Vector3 (0, 1, 0);
		v3s [2] = new Vector3 (1, 0, 0);
		v3s [3] = new Vector3 (1, 1, 0);

        mesh.vertices = v3s;

        //组合成四个三角形
		int[] index = new int[12];

		index [0] = 0;
		index [1] = 1;
		index [2] = 2;

		index [3] = 3;
		index [4] = 2;
		index [5] = 1;

		index [6] = 1;
		index [7] = 0;
		index [8] = 2;

		index [9] = 3;
		index [10] = 1;
		index [11] = 2;

		mesh.triangles = index;
		//Debug.Log (mesh.uv.Length);

        //uv赋值
		Vector2[] uvs = new Vector2[4];
		uvs [0] = Vector2.zero;
		uvs [1] = Vector2.up;
		uvs [2] = Vector2.right;
		uvs [3] = Vector2.one;

		mesh.uv = uvs;

        //手动计算顶点法线。在此双面网格中,只用4个顶点会产生一个与Unity实时光照相关的bug,因为mesh.vertices数组长度要与mesh.normals数组长度对应,所以最多只能提供四个法线,那么网格两边参与实时光的渲染结果是相同的(正反两侧永远都是同样的亮度),既有一边是不正确的。如果需要正确响应实时光照,需要使用8个vertice以容纳8个normal。
        Vector3[] normals = new Vector3[4];  
        normals[0] = Vector3.Normalize(Vector3.Cross(v3s[0] - v3s[1],v3s[0]-v3s[2]));
        normals[1] = Vector3.Normalize(Vector3.Cross(v3s[1] - v3s[3], v3s[1] - v3s[0]));
        normals[2] = Vector3.Normalize(Vector3.Cross(v3s[2] - v3s[0], v3s[2] - v3s[3]));
        normals[3] = Vector3.Normalize(Vector3.Cross(v3s[3] - v3s[2], v3s[3] - v3s[1]));

        mesh.normals = normals;

	//	mesh.RecalculateBounds();
    //	mesh.RecalculateNormals();
    //  mesh.RecalculateTangents();

		meshFilter.mesh = mesh;
	}

	// Update is called once per frame
	void Update () {

	}
}

————————————————————————————
维护日志:
2017-8-22:更改了标题
2017-10-11:Review,更改了一些错误。
2018-1-9: 更新了代码,增加手动计算法线部分。
2020-2-7:review

  • 6
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Unity中,代码是用来实现游戏逻辑和功能的关键部分。Unity支持使用C#或UnityScript(一种基于JavaScript的脚本语言)编写代码。 在Unity中,代码通常被组织在脚本文件中,这些脚本文件可以附加到游戏对象上。当游戏运行时,这些脚本将被执行,并且可以通过调用函数、访问变量等方式来实现游戏的各种功能。 以下是一些常见的Unity代码概念和功能: 1. MonoBehaviour:MonoBehaviour是Unity中所有脚本的基类,它提供了一些常用的函数,如Start()、Update()等。通过继承MonoBehaviour类,可以创建自定义的脚本,并将其附加到游戏对象上。 2. GameObject:GameObject是Unity中的基本对象,它代表了游戏场景中的一个实体。可以通过代码创建、修改和销毁游戏对象,以及访问其属性和组件。 3. 组件(Component):组件是附加到游戏对象上的模块化功能单元。例如,Transform组件用于控制游戏对象的位置、旋转和缩放;Rigidbody组件用于模拟物理行为;MeshRenderer组件用于渲染3D模型等。可以通过代码访问和操作组件。 4. 事件(Event):Unity中的事件系统允许在特定条件下触发代码。例如,当玩家点击按钮时,可以触发一个OnClick事件,然后执行相应的代码逻辑。 5. 协程(Coroutine):协程是一种特殊的函数,可以在一段时间内暂停和恢复执行。协程常用于处理复杂的异步操作,如延迟执行、动画序列等。 6. 资源管理:Unity提供了资源管理系统,可以加载、实例化和销毁各种资源,如模型、纹理、声音等。通过代码可以访问和操作这些资源。 关于Unity代码的更多详细信息和示例,请参考Unity官方文档和教程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值