2013-03-09 11:23:47 minitail_ 阅读数 4797

这两天在研究画线的东西。直到昨天才小有成效。拿出来和大家分享一下。先上图:




以前曾经尝试过用LineRender写画线的功能,但是在拐弯的时候会出现变形和扭曲。所以才决定用绘制Mesh的方法写一个画线的功能。

东西其实很简单,稍微有一点数学的知识,用到了三角函数。还有一些关于构造Mesh的相关代码。下面有草图一张:



黑色的线框代表画出来的模型线,PointA和PointB代表获取的起始点和终结点,A,B,C,D为Mesh的四个顶点。

已知起始点和终结点和线宽,就可以求出四个顶点的坐标。并根据这四个顶点绘制出Mesh.

至于具体的实现细节,大家看代码吧。


DrawMeshLine类:

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

public class DrawMeshLine : MonoBehaviour 
{
	//线容器
	List<Vector3> LinePointContainer;
	//线宽
	public float LineWidth = 0.5f;
	//地形层
	public int Layer = 8;
	//线物体父物体
	GameObject LineObj;
	
	void Start () 
	{
		LinePointContainer = new List<Vector3>();
		LineObj = new GameObject("Lines");
	}
	
	void Update () 
	{
		if(Input.GetMouseButtonDown(0))
		{
			Ray ray = Camera.mainCamera.ScreenPointToRay(Input.mousePosition);
			RaycastHit hit;
			if(Physics.Raycast(ray,out hit))
			{
				if(hit.transform.gameObject.layer != Layer)return;
				LinePointContainer.Add(hit.point);
			}
			if(LinePointContainer.Count == 1)
			{
				//二维初始面片圆
//				GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
//				sphere.transform.parent = LineObj.transform;
//				sphere.transform.position = hit.point;
//				sphere.transform.localScale = new Vector3(LineWidth * 2,0.01f,LineWidth * 2);
//				sphere.renderer.material.shader = Shader.Find("GUI/Text Shader");
//				sphere.renderer.material.SetColor("_Color",new Color(255,0,0,255) / 255);
				
				//三维初始球
				GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
				sphere.transform.parent = LineObj.transform;
				sphere.transform.position = new Vector3(hit.point.x,hit.point.y + LineWidth,hit.point.z);
				sphere.transform.localScale = new Vector3(LineWidth * Mathf.Sqrt(8.0f),LineWidth * Mathf.Sqrt(8.0f),LineWidth * Mathf.Sqrt(8.0f));
			}
			if(LinePointContainer.Count > 1)
			{
				//画二维面片线
				//DrawLine2D(LinePointContainer[LinePointContainer.Count - 2],LinePointContainer[LinePointContainer.Count - 1]);
				//画三维立体线
				DrawLine3D(LinePointContainer[LinePointContainer.Count - 2],LinePointContainer[LinePointContainer.Count - 1]);
			}
		}
		if(Input.GetMouseButtonDown(1))
		{
			//清空线容器
			if(LinePointContainer.Count < 3)return;
			DrawLine3D(LinePointContainer[LinePointContainer.Count - 1],LinePointContainer[0]);
			LinePointContainer.Clear();
		}
		if(Input.GetKeyDown(KeyCode.Escape))
		{
			//清除所有线物体
			LinePointContainer.Clear();
			ClearLineObjects();
		}
	}
	
	/// <summary>
	/// 二维线
	/// </summary>
	/// <param name='PointA'>
	/// 初始点
	/// </param>
	/// <param name='PointB'>
	/// 结束点
	/// </param>
	void DrawLine2D(Vector3 PointA,Vector3 PointB)
	{
		float HorDisABx = PointB.x - PointA.x;
		float HorDisABz = PointB.z - PointA.z;
		float HorDisAB = Mathf.Sqrt(Mathf.Pow(HorDisABx,2) + Mathf.Pow(HorDisABz,2));
		
		float offsetX = HorDisABz * LineWidth / HorDisAB;
		float offsetZ = HorDisABx * LineWidth / HorDisAB;
		
		Vector3 Point1 = new Vector3(PointA.x - offsetX,PointA.y,PointA.z + offsetZ);
		Vector3 Point2 = new Vector3(PointA.x + offsetX,PointA.y,PointA.z - offsetZ);
		Vector3 Point3 = new Vector3(PointB.x + offsetX,PointB.y,PointB.z - offsetZ);
		Vector3 Point4 = new Vector3(PointB.x - offsetX,PointB.y,PointB.z + offsetZ);
		
		//点位置 测试用
//		GameObject go1 = GameObject.CreatePrimitive(PrimitiveType.Cube);
//		go1.transform.position = Point1;
//		GameObject go2 = GameObject.CreatePrimitive(PrimitiveType.Cube);
//		go2.transform.position = Point2;
//		GameObject go3 = GameObject.CreatePrimitive(PrimitiveType.Cube);
//		go3.transform.position = Point3;
//		GameObject go4 = GameObject.CreatePrimitive(PrimitiveType.Cube);
//		go4.transform.position = Point4;
		
		GameObject go = new GameObject((LinePointContainer.Count - 1).ToString());
		go.transform.parent = LineObj.transform;
		Mesh mesh = go.AddComponent<MeshFilter>().mesh;
		go.AddComponent<MeshRenderer>();
		mesh.vertices = new Vector3[]{Point1,Point2,Point3,Point4};
		mesh.triangles = new int[]{2,1,0,0,3,2};
		
		GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
		sphere.transform.parent = LineObj.transform;
		sphere.transform.position = PointB;
		sphere.transform.localScale = new Vector3(LineWidth * 2,0.01f,LineWidth * 2);
	}
	
	/// <summary>
	/// 三维线
	/// </summary>
	/// <param name='PointA'>
	/// 初始点
	/// </param>
	/// <param name='PointB'>
	/// 结束点
	/// </param>
	void DrawLine3D(Vector3 PointA,Vector3 PointB)
	{
		float HorDisABx = PointB.x - PointA.x;
		float HorDisABz = PointB.z - PointA.z;
		float HorDisAB = Mathf.Sqrt(Mathf.Pow(HorDisABx,2) + Mathf.Pow(HorDisABz,2));
		
		float offsetX = HorDisABz * LineWidth / HorDisAB;
		float offsetZ = HorDisABx * LineWidth / HorDisAB;
		
		Vector3 Point1 = new Vector3(PointA.x - offsetX,PointA.y,PointA.z + offsetZ);
		Vector3 Point2 = new Vector3(PointA.x + offsetX,PointA.y,PointA.z - offsetZ);
		Vector3 Point3 = new Vector3(PointB.x + offsetX,PointB.y,PointB.z - offsetZ);
		Vector3 Point4 = new Vector3(PointB.x - offsetX,PointB.y,PointB.z + offsetZ);
		
		GameObject go1 = new GameObject((LinePointContainer.Count - 1).ToString() + "_1");
		go1.transform.parent = LineObj.transform;
		Mesh mesh1 = go1.AddComponent<MeshFilter>().mesh;
		go1.AddComponent<MeshRenderer>();
		mesh1.vertices = new Vector3[]{Point1,Point2,Point3,Point4};
		mesh1.triangles = new int[]{2,1,0,0,3,2};
		
		Vector3 Point5 = new Vector3(PointA.x - offsetX,PointA.y + 2 * LineWidth,PointA.z + offsetZ);
		Vector3 Point6 = new Vector3(PointA.x + offsetX,PointA.y + 2 * LineWidth,PointA.z - offsetZ);
		Vector3 Point7 = new Vector3(PointB.x + offsetX,PointB.y + 2 * LineWidth,PointB.z - offsetZ);
		Vector3 Point8 = new Vector3(PointB.x - offsetX,PointB.y + 2 * LineWidth,PointB.z + offsetZ);
		
		GameObject go2 = new GameObject((LinePointContainer.Count - 1).ToString() + "_2");
		go2.transform.parent = LineObj.transform;
		Mesh mesh2 = go2.AddComponent<MeshFilter>().mesh;
		go2.AddComponent<MeshRenderer>();
		mesh2.vertices = new Vector3[]{Point5,Point6,Point7,Point8};
		mesh2.triangles = new int[]{2,1,0,0,3,2};
		
		GameObject go3 = new GameObject((LinePointContainer.Count - 1).ToString() + "_3");
		go3.transform.parent = LineObj.transform;
		Mesh mesh3 = go3.AddComponent<MeshFilter>().mesh;
		go3.AddComponent<MeshRenderer>();
		mesh3.vertices = new Vector3[]{Point6,Point2,Point3,Point7};
		mesh3.triangles = new int[]{2,1,0,0,3,2};
		
		GameObject go4 = new GameObject((LinePointContainer.Count - 1).ToString() + "_4");
		go4.transform.parent = LineObj.transform;
		Mesh mesh4 = go4.AddComponent<MeshFilter>().mesh;
		go4.AddComponent<MeshRenderer>();
		mesh4.vertices = new Vector3[]{Point1,Point5,Point8,Point4};
		mesh4.triangles = new int[]{2,1,0,0,3,2};
		
		GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
		sphere.transform.parent = LineObj.transform;
		sphere.transform.position = new Vector3(PointB.x,PointB.y + LineWidth,PointB.z);
		sphere.transform.localScale = new Vector3(LineWidth * Mathf.Sqrt(8.0f),LineWidth * Mathf.Sqrt(8.0f),LineWidth * Mathf.Sqrt(8.0f));
	}
	
	void ClearLineObjects ()
	{
		for(int i = 0 ; i < LineObj.transform.childCount;i ++)
		{
			GameObject go = LineObj.transform.GetChild(i).gameObject;
			Destroy(go);
		}
	}
}


工程包地址:http://www.vdisk.cn/down/index/12525372


2015-01-22 15:00:45 wolf96 阅读数 11134

卡通着色的目的是为了让被着色物体显得过渡的不那么好,明暗交界线很明显,等等卡通风格的一系列特征,

也叫Non-photorealisticrendering非真实渲染

重点要做到两点:

1.    描边

2.    着色

另:本片中cg函数均用绿色标明,想了解函数作用和函数内部构成请看这篇文章 NVIDIA CG语言 函数之所有数学类函数(Mathematical Functions)


就从最初的描边开始

首先声明变量
_Outline挤出描边的粗细
_Factor挤出多远

Properties {
		_Color("Main Color",color)=(1,1,1,1)
		_Outline("Thick of Outline",range(0,0.1))=0.02
		_Factor("Factor",range(0,1))=0.5
	}



我们的挤出操作在第一个pass中进行
Cull Front 裁剪了物体的前面(对着相机的),把背面挤出
ZWrite On 像素的深度写入深度缓冲,如果关闭的话,物体与物体交叠处将不会被描边,因为此处无z值后渲染的物体会把此处挤出的描边“盖住”

在处理顶点的函数vert中把点挤出

dir=normalize(v.vertex.xyz);
建立一个float3方向变量dir
把该点的位置作为距离几何中心的方向的单位向量

float3 dir2=v.normal;
建立一个float3方向变量dir
dir2为法线方向

D=dot(dir,dir2);
D为计算该点位置朝向和法线方向的点积,通过正负值可以确定是指向还是背离几何中心的,正为背离,负为指向

dir=dir*sign(D);
乘上正负值,真正的方向值

dir=dir*_Factor+dir2*(1-_Factor);

把该点位置朝向与法线方向按外部变量_Factor的比重混合,来控制挤出多远

v.vertex.xyz+=dir*_Outline;
把物体背面的点向外挤出
		v2f vert (appdata_full v) {
			v2f o;
			float3 dir=normalize(v.vertex.xyz);
			float3 dir2=v.normal;
			float D=dot(dir,dir2);
			dir=dir*sign(D);
			dir=dir*_Factor+dir2*(1-_Factor);
			v.vertex.xyz+=dir*_Outline;
			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
			return o;
		}


顶点函数结束,
接下来为描边上色
在frag函数中

挤出轮廓的颜色,此处颜色随意

效果如下:

清楚地描出了轮廓,可以在材质中改变_Outline的值来改变粗细


描边shader如下:

Shader "Tut/Shader/Toon/miaobian" {
Properties {
		_Color("Main Color",color)=(1,1,1,1)
		_Outline("Thick of Outline",range(0,0.1))=0.02
		_Factor("Factor",range(0,1))=0.5
	}
	SubShader {
		pass{
		Tags{"LightMode"="Always"}
		Cull Front
		ZWrite On
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"
		float _Outline;
		float _Factor;
		float4 _Color;
		struct v2f {
			float4 pos:SV_POSITION;
		};

		v2f vert (appdata_full v) {
			v2f o;
			float3 dir=normalize(v.vertex.xyz);
			float3 dir2=v.normal;
			float D=dot(dir,dir2);
			dir=dir*sign(D);
			dir=dir*_Factor+dir2*(1-_Factor);
			v.vertex.xyz+=dir*_Outline;
			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
			return o;
		}
		float4 frag(v2f i):COLOR
		{
			float4 c = _Color / 5;
			return c;
		}
		ENDCG
		}
		pass{
		Tags{"LightMode"="ForwardBase"}
		Cull Back
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"

		float4 _LightColor0;
		float4 _Color;
		float _Steps;
		float _ToonEffect;

		struct v2f {
			float4 pos:SV_POSITION;
			float3 lightDir:TEXCOORD0;
			float3 viewDir:TEXCOORD1;
			float3 normal:TEXCOORD2;
		};

		v2f vert (appdata_full v) {
			v2f o;
			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
			o.normal=v.normal;
			o.lightDir=ObjSpaceLightDir(v.vertex);
			o.viewDir=ObjSpaceViewDir(v.vertex);

			return o;
		}
		float4 frag(v2f i):COLOR
		{
			float4 c=1;
			float3 N=normalize(i.normal);
			float3 viewDir=normalize(i.viewDir);
			float3 lightDir=normalize(i.lightDir);
			float diff=max(0,dot(N,i.lightDir));
			diff=(diff+1)/2;
			diff=smoothstep(0,1,diff);
			c=_Color*_LightColor0*(diff);
			return c;
		}
		ENDCG
		}//
	} 
}





开始卡通着色旅程

描边之后就是重头戏着色了

简单的举几个例子

说明一下卡通着色


把diffuse漫反射颜色变成了很明显的几个色阶(本例为四个)




普通的diffuse漫反射龙,没有色阶层,颜色过渡的很好,没有卡通的感觉

普通的漫反射材质





上图的可爱的大河酱就是由二阶颜色着色而成,再加上边缘黑色的描边,这个是真正的卡通,不是渲染出来的= =



《深渊传说》是传说系列的早期作品之一,用的也是卡通渲染



感觉大部分都是卡通式纹理贴图在出力


《仙乐传说》的战斗结束画面


有明显的明暗交界线(两个色阶),并随摄像头(view direction)的变化而变化,人物有明显的描边处理,卡通着色起了很大作用



《无尽传说2》,是传说系列比较近的作品,画面明显比前做好了许多,但万变不离其宗,还是用的卡通着色,(= =没玩过这作)


人物有着明显的描边处理

另外我感觉泛光的效果很好啊,应该是bloom或者是hdr之类的,跑题了= =



开始动手操刀卡通着色


第一个pass就是上面的描边pass


对漫反射的卡通着色在第二个pass中


先声明变量
_Color物体的颜色
_Outline挤出描边的粗细
_Factor挤出多远
_ToonEffect卡通化程度(二次元与三次元的交界线)
_Steps色阶层数

	Properties {
		_Color("Main Color",color)=(1,1,1,1)//物体的颜色
		_Outline("Thick of Outline",range(0,0.1))=0.02//挤出描边的粗细
		_Factor("Factor",range(0,1))=0.5//挤出多远
		_ToonEffect("Toon Effect",range(0,1))=0.5//卡通化程度(二次元与三次元的交界线)
		_Steps("Steps of toon",range(0,9))=3//色阶层数
	}


卡通着色主要在着色函数frag中进行
float diff=max(0,dot(N,i.lightDir));
求出正常的漫反射颜色


diff=(diff+1)/2;
做亮化处理


diff=smoothstep(0,1,diff);
使颜色平滑的在[0,1]范围之内


float toon=floor(diff*_Steps)/_Steps;
把颜色做离散化处理,把diffuse颜色限制在_Steps种(_Steps阶颜色),简化颜色,这样的处理使色阶间能平滑的显示


diff=lerp(diff,toon,_ToonEffect);
根据外部我们可控的卡通化程度值_ToonEffect,调节卡通与现实的比重


c=_Color*_LightColor0*(diff);
把最终颜色混合

第二个pass结束,

		float4 frag(v2f i):COLOR
		{
			float4 c=1;
			float3 N=normalize(i.normal);
			float3 viewDir=normalize(i.viewDir);
			float3 lightDir=normalize(i.lightDir);
			float diff=max(0,dot(N,i.lightDir));//求出正常的漫反射颜色
			diff=(diff+1)/2;//做亮化处理
			diff=smoothstep(0,1,diff);//使颜色平滑的在[0,1]范围之内
			float toon=floor(diff*_Steps)/_Steps;//把颜色做离散化处理,把diffuse颜色限制在_Steps种(_Steps阶颜色),简化颜色,这样的处理使色阶间能平滑的显示
			diff=lerp(diff,toon,_ToonEffect);//根据外部我们可控的卡通化程度值_ToonEffect,调节卡通与现实的比重

			c=_Color*_LightColor0*(diff);//把最终颜色混合
			return c;
		}



第三个pass就是在第二个pass的基础之上加上离散化的高光

建立float变量dist为求出距离光源的距离float

float atten=1/(dist);
根据距光源的距离求出衰减;

漫反射部分与第二个pass相同;

half3 h = normalize (lightDir + viewDir);

求出半角向量

float nh = max (0, dot (N, h));
float spec = pow (nh, 32.0);

求出正常情况下的高光强度

float toonSpec=floor(spec*atten*2)/ 2;
把高光也离散化

spec=lerp(spec,toonSpec,_ToonEffect);
调节卡通与现实高光的比重
		float4 frag(v2f i):COLOR
		{
			float4 c=1;
			float3 N=normalize(i.normal);
			float3 viewDir=normalize(i.viewDir);
			float dist=length(i.lightDir);//求出距离光源的距离
			float3 lightDir=normalize(i.lightDir);
			float diff=max(0,dot(N,i.lightDir));
			diff=(diff+1)/2;
			diff=smoothstep(0,1,diff);
			float atten=1/(dist);//根据距光源的距离求出衰减
			float toon=floor(diff*atten*_Steps)/_Steps;
			diff=lerp(diff,toon,_ToonEffect);

			half3 h = normalize (lightDir + viewDir);//求出半角向量
			float nh = max (0, dot (N, h));
			float spec = pow (nh, 32.0);//求出高光强度
			float toonSpec=floor(spec*atten*2)/ 2;//把高光也离散化
			spec=lerp(spec,toonSpec,_ToonEffect);//调节卡通与现实高光的比重

			
			c=_Color*_LightColor0*(diff+spec);//求出最终颜色
			return c;
		}


就可以得到这种卡通效果:


shader如下:

Shader "Tut/Shader/Toon/toon" {
	Properties {
		_Color("Main Color",color)=(1,1,1,1)//物体的颜色
		_Outline("Thick of Outline",range(0,0.1))=0.02//挤出描边的粗细
		_Factor("Factor",range(0,1))=0.5//挤出多远
		_ToonEffect("Toon Effect",range(0,1))=0.5//卡通化程度(二次元与三次元的交界线)
		_Steps("Steps of toon",range(0,9))=3//色阶层数
	}
	SubShader {
		pass{//处理光照前的pass渲染
		Tags{"LightMode"="Always"}
		Cull Front
		ZWrite On
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"
		float _Outline;
		float _Factor;
		struct v2f {
			float4 pos:SV_POSITION;
		};

		v2f vert (appdata_full v) {
			v2f o;
			float3 dir=normalize(v.vertex.xyz);
			float3 dir2=v.normal;
			float D=dot(dir,dir2);
			dir=dir*sign(D);
			dir=dir*_Factor+dir2*(1-_Factor);
			v.vertex.xyz+=dir*_Outline;
			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
			return o;
		}
		float4 frag(v2f i):COLOR
		{
			float4 c=0;
			return c;
		}
		ENDCG
		}//end of pass
		pass{//平行光的的pass渲染
		Tags{"LightMode"="ForwardBase"}
		Cull Back
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"

		float4 _LightColor0;
		float4 _Color;
		float _Steps;
		float _ToonEffect;

		struct v2f {
			float4 pos:SV_POSITION;
			float3 lightDir:TEXCOORD0;
			float3 viewDir:TEXCOORD1;
			float3 normal:TEXCOORD2;
		};

		v2f vert (appdata_full v) {
			v2f o;
			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);//切换到世界坐标
			o.normal=v.normal;
			o.lightDir=ObjSpaceLightDir(v.vertex);
			o.viewDir=ObjSpaceViewDir(v.vertex);

			return o;
		}
		float4 frag(v2f i):COLOR
		{
			float4 c=1;
			float3 N=normalize(i.normal);
			float3 viewDir=normalize(i.viewDir);
			float3 lightDir=normalize(i.lightDir);
			float diff=max(0,dot(N,i.lightDir));//求出正常的漫反射颜色
			diff=(diff+1)/2;//做亮化处理
			diff=smoothstep(0,1,diff);//使颜色平滑的在[0,1]范围之内
			float toon=floor(diff*_Steps)/_Steps;//把颜色做离散化处理,把diffuse颜色限制在_Steps种(_Steps阶颜色),简化颜色,这样的处理使色阶间能平滑的显示
			diff=lerp(diff,toon,_ToonEffect);//根据外部我们可控的卡通化程度值_ToonEffect,调节卡通与现实的比重

			c=_Color*_LightColor0*(diff);//把最终颜色混合
			return c;
		}
		ENDCG
		}//
		pass{//附加点光源的pass渲染
		Tags{"LightMode"="ForwardAdd"}
		Blend One One
		Cull Back
		ZWrite Off
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"

		float4 _LightColor0;
		float4 _Color;
		float _Steps;
		float _ToonEffect;

		struct v2f {
			float4 pos:SV_POSITION;
			float3 lightDir:TEXCOORD0;
			float3 viewDir:TEXCOORD1;
			float3 normal:TEXCOORD2;
		};

		v2f vert (appdata_full v) {
			v2f o;
			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
			o.normal=v.normal;
			o.viewDir=ObjSpaceViewDir(v.vertex);
			o.lightDir=_WorldSpaceLightPos0-v.vertex;

			return o;
		}
		float4 frag(v2f i):COLOR
		{
			float4 c=1;
			float3 N=normalize(i.normal);
			float3 viewDir=normalize(i.viewDir);
			float dist=length(i.lightDir);//求出距离光源的距离
			float3 lightDir=normalize(i.lightDir);
			float diff=max(0,dot(N,i.lightDir));
			diff=(diff+1)/2;
			diff=smoothstep(0,1,diff);
			float atten=1/(dist);//根据距光源的距离求出衰减
			float toon=floor(diff*atten*_Steps)/_Steps;
			diff=lerp(diff,toon,_ToonEffect);

			half3 h = normalize (lightDir + viewDir);//求出半角向量
			float nh = max (0, dot (N, h));
			float spec = pow (nh, 32.0);//求出高光强度
			float toonSpec=floor(spec*atten*2)/ 2;//把高光也离散化
			spec=lerp(spec,toonSpec,_ToonEffect);//调节卡通与现实高光的比重

			
			c=_Color*_LightColor0*(diff+spec);//求出最终颜色
			return c;
		}
		ENDCG
		}//
	} 
}


这样已经能做出很好的卡通效果了,各位可以在此基础上研制加强版

 

加强版1:

加上了边缘光Rim

漂亮的水蓝色星球(= =;纹理)


就是在第二个pass中加上rim值

添加的外部变量;
        
_RimPower边缘光亮度程度
        
_ToonRimStep边缘光色阶数


在frag函数中
float rim = 1.0 - saturate(dot(N,normalize (viewDir)));
求出正常的边缘光程度

rim = rim+1;
使之加亮
            
rim = pow(rim, _RimPower);
外部变量_RimPower控制边缘光亮度大小
            
float toonRim = floor(rim * _ToonRimStep) / _ToonRimStep;
再对边缘光进行离散化
            
rim = lerp(rim, toonRim, _ToonEffect);
调节卡通与现实的比重
            
c=_Color*_LightColor0*(diff) * rim * mc*2;

进行最终颜色混合


		float4 frag(v2f i):COLOR
		{
			half4 mc = tex2D (_MainTex, i.uv_MainTex);
			float4 c=1;
			float3 N=normalize(i.normal);
			float3 viewDir=normalize(i.viewDir);
			float3 lightDir=normalize(i.lightDir);
			float diff=max(0,dot(N,i.lightDir));
			diff=(diff+1)/2;
			diff=smoothstep(0,1,diff);
			float toon=floor(diff*_Steps)/_Steps;
			diff=lerp(diff,toon,_ToonEffect);
			float rim = 1.0 - saturate(dot(N, normalize (viewDir)));//求出正常的边缘光程度
			rim = rim+1;//使之加亮
			rim = pow(rim, _RimPower);//外部变量_RimPower控制边缘光亮度大小
			float toonRim = floor(rim * _ToonRimStep) / _ToonRimStep;//再对边缘光进行离散化
			rim = lerp(rim, toonRim, _ToonEffect);//调节卡通与现实的比重
			c=_Color*_LightColor0*(diff) * rim * mc*2;//进行最终颜色混合
			return c;
		}


活在三次元世界的3d布料和活在二次元世界的3d布料


加强版2:

带纹理贴图版



加强版3:

带纹理贴图Rim版



带纹理贴图Rim版变种



不建议把纹理贴图也离散化,效果实在是不好,= =;




接下来就有待各位看官们继续开发了,有什么更好的效果一定要告诉我



                                                                                                                 -----------------------by wolf96http://blog.csdn.net/wolf96




2015-11-23 18:59:56 chenghai37 阅读数 2843
3Dmax建模必须注意的细节。(注意!!是必须!!)可以简单参考我做的插头的命名和模型的各个物体的塌陷程度等~

建模原则:
1模型结构要正确
2尽量不要出现破面黑面
3附加物体不要出现穿插。
4拼装位置一定要准确

1.由于Unity看不到单面模型的反面,故导出请仔细检查单面物体。如玻璃.
2.材质、贴图、模型名称中不能出现中文。进unity,不要出现中文名,以免出现乱码,报错。
3.unity只支持3dmax的标准材质(Standard),贴图通道也只支持位图(Bitmap)和自发光。(所以什么vary材质什么的就不要用了。)
4.max默认的uv平铺在unity里是出错的,要加uvw贴图修改器去调整。
5.相同材质的模型用同一个材质,以减轻系统与后期材质制作负担。
6.可以塌陷的模型尽量塌陷。(就是将每个做好的模型转换成可编辑多边形)
7.单个模型四边面数不能超过3万。极限是三万,大概在两万五就可以。(经过我的实际测试单个模型点、面最多不能超过65534,否则unity会自动将单个物体进行切片处理以达到unity的自身存储的要求,我猜测是unity软件编写的时候用的是int类型来存储每个模型的数据,int类型正好是2^32,最大的范围是65536,去掉2个正负号正好65534)
8.工具轴心要统一.将工具对好要拆卸的物体,然后调整物体轴心,确保物体工具轴心统一。(这个非常重要,unity中的轴心完全依靠建模软件,所以每个物体的轴心都需要在3dmax中与物体校准!)
9.物体导入unity报错?
1框选整个模型,看看是否要重复的点,焊接点。
2多个角度渲染看看,模型有无黑面,有黑面就会导致出错,布尔过的物体最容易出错,线不够,就会导致边塌到里面去。布尔过的物体要塌陷成多边形,其它加修改器的物体也要塌陷成多边形。
3检查模型的布线,布线不规整,或是布线的点,一条边上的点在边上,另外一个跑到很远或是插到模型的其他部位,都会导致出错。
4烘焙过的物体材质你要重新上一个。采用法线贴图烘焙过的物体,模型本身虽然没有什么问题,但是你导出时也是会报错的。
2015-04-10 23:53:48 dbtxdxy 阅读数 5127

  现实生活中有许多半透明的情形:玻璃、饮料瓶、塑料袋等等。半透明一个很重要的特点就是可以显示它后面的物体的部分颜色。这个“部分”具体是多少我们常用透明度来表示。

一个简单的透明例子

  想做出透明效果,第一步需要通知Shader我们要使用透明效果了。最简单的方法是在#pragma后面增加一个alpha参数,例如#pragma surface surf Lambert alpha。

  开启透明后,只要对SurfaceOutput的Alpha分量赋值就可以了,范围是[0,1],下面是一个简单的透明Shader:

Shader "Custom/SimpleAlpha" {
    Properties {
        _MainColor ("Main Color", Color) = (1, 1, 1, 1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _Alpha ("Alpha", Range(0, 1)) = 0.5
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert alpha

        fixed4 _MainColor;
        sampler2D _MainTex;
        fixed _Alpha;

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutput o) {
            half4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = _MainColor.rgb * c.rgb;
            o.Alpha = _Alpha;
        }
        ENDCG
    } 
    FallBack "Diffuse"
}

  调整Alpha值,可以看到透明的效果:

  这里写图片描述

  为了更好地观察透明效果,我们可以在立方体后面添加一个红色的平面。这时候,你可能发现一个奇怪的现象,立方体明明在平面的前面,但是看上去却被平面遮挡了,像这样:

  这里写图片描述

  产生这种情况的原因是没有指定渲染顺序。渲染顺序,顾名思义,就是渲染的前后顺序,也可以理解为优先级。可以通过在Tags中指定键值对来指定Shader的渲染顺序。不过,作为一种全局的顺序机制,Unity已经为我们指定好了一些层级,分别是Background、Geometry、AlphaTest、Transparent、Overlay,分别表示1000, 2000, 2450、3000、4000,顺序越小的越先渲染。默认情况下,Shader的渲染顺序是2000,也就是Geometry,我们可以手动指定渲染顺序:

Tags { "Queue" = "Geometry" "RenderType"="Opaque" }

  这样就代表我们这个Shader的渲染顺序为2000。为了解决上面那个奇怪的显示问题,我们可以把我们的Shader的渲染顺序稍微延后一点:

Tags { "Queue" = "Geometry+1" "RenderType"="Opaque" }

  此时渲染顺序就正确了:

  这里写图片描述

  其实Unity已经给我们定义了专门放置透明材质的层次,我们干嘛不用呢,所以最好的方法是这样写:

Tags { "Queue" = "Transparent" "RenderType"="Opaque" }

使用AlphaTest实现透明效果

  另一种实现透明的方式是利用裁剪。这种方式的基本原理就是利用片段的透明度和我们给定的一个透明度做对比,当片段的透明度大于给定透明度时就表现为不透明,否则就是透明(其实是被丢掉)。因此这种方式作出的效果除了完全透明的部分就是完全不透明的部分。

  要使用AlphaTest,需要在#pragma后面添加alphatest:xxx,这个xxx就是我们给定的透明度。下面是一个简单的AlphaTest的例子:

Shader "Custom/AlphaTest" {
    Properties {
        _MainColor ("Main Color", Color) = (1, 1, 1, 1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _TestVal ("Test Value", Range(0, 1)) = 0.5
    }
    SubShader {
        Tags { "Queue" = "Transparent" "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert alphatest:_TestVal

        fixed4 _MainColor;
        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutput o) {
            half4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = _MainColor.rgb * c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    } 
    FallBack "Diffuse"
}

  为了体现效果我们需要一幅透明度渐变的贴图:

  这里写图片描述

  下面是_AlphaTest在不同值下的效果:

  这里写图片描述 这里写图片描述 这里写图片描述

  这种AlphaTest看上去比半透明的方式更有效率,事实上,大部分情况确实如此。一个例外是在移动设备上,AlphaTest在移动设备上效率非常低,甚至不如第一种半透明的方式。

  本次的工程文件在这里

结束语

  透明材质有非常好的表现效果,可以说在游戏开发过程中是必不可少的,恰当的使用透明也可以极大地节省模型的面数。但是,使用透明会有一定的性能开销,尤其是在移动设备上。可以说,开发移动平台的游戏,资源和效率都是锱铢必较的,开发一款效果又好效率又高的产品真的不容易。OK,下回再见~

没有更多推荐了,返回首页