Mesh--材质--Shader
1: Mesh 是网格,包括顶点,法线,纹理坐标,切线,三角形。在每一个3D模型节点里面,有一个Mesh Filter组件来提取模型里面的网格数据;
2: Shader渲染算法,3D模型是按照什么方式怎么样绘制出来的;3: 材质是给渲染算法的输入数据,当我们新创建一个材质的时候,先要选Shader,在Shader文件里面关联好输入,材质实际上就是给Shader提供初始化数据的,比如Shader里面需要贴图纹理,材质就把自己的贴图纹理数据传递给自己关联的Shader,作为Shader的输入。
或者这样理解,材质选好一种Shader,这个Shader就要求材质提供一些输入数据,材质就是把这些数据准备好,填好传递给Shader,Shader有了这些数据就能准确地绘制出来。
有了网格数据后,使用Shader的算法,我们就能把3D模型绘制出来
4: 代码修改材质参数,能修改给渲染算法的数据从而获得不同的效果;
模型的网格里面,有很多三角形的小网格,每一个小网格的在贴图上是有一个纹理坐标的,我们把每一个小网格的顶点在贴图上的纹理坐标叫做UV,我这样理解,UV坐标(0,0)对应的就是贴图中心的那个像素点,两者关联绑定好,模型的(0,0)对应贴图的(0,0),贴图的(0,0)什么颜色,模型的(0,0)上面贴的也就是什么颜色
绘制的过程
小网格的顶点在模型上会给定一个UV坐标,那么这个时候,每一个像素点的颜色就是在这个附近的网格顶点的纹理的UV坐标决定的
实际上就是每一个小网格顶点的纹理坐标决定了每个顶点之间要绘制什么纹理,有一个插值计算的过程。
最终小三角形网格就着色成了对应贴图里面对应坐标的那个颜色
由每个小三角形网格的顶点的纹理坐标决定要加入哪些图像,把图像从贴图拷贝到网格上绘制出来。
旋涡特效原理
改变纹理坐标的寻址,使用一个算法扭曲画面,其实是扭曲完纹理坐标后,再去寻址,这样寻到的就是扭曲后的图像,也就是要把每一个小三角形网格的顶点的纹理坐标进行数学扭曲,扭曲好了以后再寻址贴图,把图像贴上去绘制。
扭曲一下每一个顶点所在原贴图的纹理坐标,这样在贴图寻址的时候找到的是扭曲后的坐标,旋涡效果就出来了。
漩涡特效粗糙原因和解决方案
1: mesh的点不够多,导致旋转的精度不够细;
2: 加大mesh点的密度; 3: 漩涡特效在场景地图切换中的使用: 截图生成纹理对象,设置到漩涡的材质,运行漩涡特效
旋涡特效搭建
1: 创建一个平面;
2: 给这个平面关联一个材质;3: 纹理会自动对齐到2^N方,如果要原始大小,可以设置为”Advance”;4: 创建一个材质,先使用默认的Diffuse Shader;5: 旋转平面, 调整能正常显示出来,注意平面的正反面,反面摄像机默认是不绘制的(剔除);6: 调整这个平面的大小和距离, 让它能盖住屏幕;
旋涡特效实例
1.创建Unity工程和文件目录,保存场景
2.导入旋涡纹理ui_main_tex_b_06.png到Resources,导入后发现贴图的宽高都变成2的n次方大小
其实可以修改成原始大小,选择纹理---->(Texture Tyoe)---->Advanced---->Non Power of 2---->none---->Apply
3.创建一个平面plane,设置大小为旋涡纹理一样大,所以ScaleX=14.22,ScaleZ=8
4.创建一个材质球plane,设置材质球的shader---->mobile---->diffuse,把旋涡纹理拖进材质球,关联材质球到plane节点
5.调整plane到可以看到整个平面无黑边,调整过程中旋转的时候如果只是改变Inspector面板里面的Rotation值有时会没有效果,因为欧拉角旋转有一个顺序
这时候有两种方法,一种是在Scene视图里面手动转,不准,一种是把Scale的X值变成负的可以上下颠倒贴图方向,Scale的Z值变成负的可以左右颠倒贴图方向
6.创建一个文件夹Shaders,进入创建一个Shader---->unlit shader,叫vortex
打开vortex.shader
Shader "Custom/vortex"{ Properties { _MainTex ("Texture", 2D) = "white" {} radius("Radius",Float)=0.0//绑定到材质属性里面,作为初始输入数据 angle("Angle",Float)=0.0//绑定到材质属性里面,作为初始输入数据 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0;//UV纹理坐标 }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float radius; //扭曲的半径 float angle; //扭曲的角度 //顶点 v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); //自己干预纹理坐标,下面的代码就是UV坐标的变换 float2 uv = v.uv;//获取顶点的纹理坐标[0,1] //这两个参数不能写死在Shader里面,我们要改变这个值,才会有旋涡的动画效果 //float radius = 0.5f; //半径,表示多少范围内的图像发生扭曲,纹理坐标才0到1,所以0.5挺大的了 //float angle = 1.0f;//角度,单位是弧度,表示扭曲的程度,正弦计算的时候要用到的角度 uv -= float2(0.5, 0.5);//把UV坐标的原点转移到图像中心,就是以中心点为UV的原点,左下角变成了[-0.5,-0.5] float dist = length(uv);//计算出当前的坐标到纹理中心的距离 float percent = (radius - dist) / radius;//计算出当前的坐标到纹理中心的距离占半径的百分比 if ( percent < 1.0 && percent >= 0.0) { //如果百分比在0到1,说明当前的坐标在扭曲的范围内,执行扭曲 //扭曲算法 float theta = percent * percent * angle * 8.0; float s = sin(theta); float c = cos(theta); uv = float2(dot(uv, float2(c, -s)), dot(uv, float2(s, c))); } uv += float2(0.5, 0.5);//变换回纹理坐标寻址的原点 o.uv = uv;//这样,顶点就有了对应纹理的坐标 return o; } //着色 fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG } }}
UV纹理坐标范围
半径0.5的长度
UV原点转移
关联到材质属性面板
7.再写一个脚本vertex挂载在plane节点下,来控制材质球plane的radius和angle属性动态改变,实现旋涡的效果
using UnityEngine;using System.Collections;using System.Collections.Generic;public class vortex : MonoBehaviour{ // (1)关联材质对象带代码,手动拖或者使用代码加载 public Material mat; // 手动拖,理解为用来配置shader输入数据的文件; float radius_speed = 0.1f; // 半径的扩散速度,2s变换到 1 radis; float angle_speed = 0.5f; // 弧度的改变速度,2s --> 2弧度 float total_time = 6.0f; // 特效持续的时间; float run_time = 0.0f; // 当前特效的运行的时间; float now_radius = 0.0f; // 现在的波及范围; float now_angle = 0.0f; // 现在的波及角度; // Use this for initialization void Start() { //这里面的代码是为了提高网格顶点的数量,为了提高旋涡的精度而写的 //之前有讲过网格顶点的扩充 for (int j = 0; j < 3; j++) { // 执行一次, 10, 20, 40, 80 Mesh self_mesh = this.GetComponent().mesh; List vertices = new List (); List triangles = new List (); List normals = new List (); List uv = new List (); List tangents = new List (); for (int i = 0; i < self_mesh.triangles.Length / 3; i++) { Vector3 t0 = self_mesh.vertices[self_mesh.triangles[i * 3 + 0]]; Vector3 t1 = self_mesh.vertices[self_mesh.triangles[i * 3 + 1]]; Vector3 t2 = self_mesh.vertices[self_mesh.triangles[i * 3 + 2]]; Vector3 t3 = Vector3.Lerp(t0, t1, 0.5f); Vector3 t4 = Vector3.Lerp(t1, t2, 0.5f); Vector3 t5 = Vector3.Lerp(t0, t2, 0.5f); int count = vertices.Count; vertices.Add(t0); // count + 0 vertices.Add(t1); // count + 1 vertices.Add(t2); // count + 2 vertices.Add(t3); // count + 3 vertices.Add(t4); // count + 4 vertices.Add(t5); // count + 5 triangles.Add(count + 0); triangles.Add(count + 3); triangles.Add(count + 5); triangles.Add(count + 3); triangles.Add(count + 1); triangles.Add(count + 4); triangles.Add(count + 4); triangles.Add(count + 2); triangles.Add(count + 5); triangles.Add(count + 3); triangles.Add(count + 4); triangles.Add(count + 5); Vector3 n0 = self_mesh.normals[self_mesh.triangles[i * 3 + 0]]; Vector3 n1 = self_mesh.normals[self_mesh.triangles[i * 3 + 1]]; Vector3 n2 = self_mesh.normals[self_mesh.triangles[i * 3 + 2]]; Vector3 n3 = Vector3.Lerp(n0, n1, 0.5f); Vector3 n4 = Vector3.Lerp(n1, n2, 0.5f); Vector3 n5 = Vector3.Lerp(n0, n2, 0.5f); normals.Add(n0); // count + 0 normals.Add(n1); // count + 1 normals.Add(n2); // count + 2 normals.Add(n3); // count + 3 normals.Add(n4); // count + 4 normals.Add(n5); // count + 5 Vector2 uv0 = self_mesh.uv[self_mesh.triangles[i * 3 + 0]]; Vector2 uv1 = self_mesh.uv[self_mesh.triangles[i * 3 + 1]]; Vector2 uv2 = self_mesh.uv[self_mesh.triangles[i * 3 + 2]]; Vector2 uv3 = Vector3.Lerp(uv0, uv1, 0.5f); Vector2 uv4 = Vector3.Lerp(uv1, uv2, 0.5f); Vector2 uv5 = Vector3.Lerp(uv0, uv2, 0.5f); uv.Add(uv0); // count + 0 uv.Add(uv1); // count + 1 uv.Add(uv2); // count + 2 uv.Add(uv3); // count + 3 uv.Add(uv4); // count + 4 uv.Add(uv5); // count + 5 Vector4 tan0 = self_mesh.tangents[self_mesh.triangles[i * 3 + 0]]; Vector4 tan1 = self_mesh.tangents[self_mesh.triangles[i * 3 + 1]]; Vector4 tan2 = self_mesh.tangents[self_mesh.triangles[i * 3 + 2]]; Vector4 tan3 = Vector3.Lerp(tan0, tan1, 0.5f); Vector4 tan4 = Vector3.Lerp(tan1, tan2, 0.5f); Vector4 tan5 = Vector3.Lerp(tan0, tan2, 0.5f); tangents.Add(tan0); // count + 0 tangents.Add(tan1); // count + 1 tangents.Add(tan2); // count + 2 tangents.Add(tan3); // count + 3 tangents.Add(tan4); // count + 4 tangents.Add(tan5); // count + 5 } self_mesh.Clear(); self_mesh.vertices = vertices.ToArray(); self_mesh.triangles = triangles.ToArray(); self_mesh.normals = normals.ToArray(); self_mesh.uv = uv.ToArray(); self_mesh.tangents = tangents.ToArray(); self_mesh.RecalculateBounds(); } } // Update is called once per frame void Update() { float dt = Time.deltaTime; this.run_time += dt;//当前的时间不断累加 this.now_radius += (this.radius_speed * dt);//随着时间的推移不断改变半径 //if (this.now_radius >= 0.5f)//如果波及范围的半径大于0.5,就不用再扩散了 //{ //this.now_radius = 1.0f; //} this.now_angle += (this.angle_speed * dt);//随着时间的推移不断改变弧度 //在代码中设置所关联的材质球面板的属性 this.mat.SetFloat("radius", this.now_radius); this.mat.SetFloat("angle", this.now_angle); // 当前时间大于特效时间,表示当前特效结束了 if (this.run_time >= this.total_time) { // 重新转一次 this.now_angle = 0; this.now_radius = 0; this.run_time = 0; // end } }}
顶点初始时的网格
顶点扩充后的网格
漩涡特效总结
1: 创建一个Shader: Unlit-->Shader;
2: 漩涡特效分析: (1)纹理坐标的范围[0, 1];,不是像素那种的坐标,这样的好处是不管材质贴图是多少乘多少像素的,都可以用百分比对应到 (2)扭曲顶点的纹理坐标, 扭曲的角度+波及的半径; (3)将扭曲的角度与半径数据绑定到材质; (4)设置扭曲的角速度, 随着时间的推移加大扭曲角度; (5)设置波及范围的速度,随着时间的推移不断的加大波及半径; (6)编写代码来控制参数,实现动态的旋转;3: 扭曲代码:float2 uv = v.uv;float radius = 0.5f; float angle = 1.0f;uv -= float2(0.5, 0.5);float dist = length(uv);float percent = (radius - dist) / radius;if ( percent < 1.0 && percent >= 0.0) {float theta = percent * percent * angle * 8.0;float s = sin(theta);float c = cos(theta);uv = float2(dot(uv, float2(c, -s)), dot(uv, float2(s, c)));}uv += float2(0.5, 0.5);