2017-11-05 16:51:54 s10141303 阅读数 705

光照模型

光照模型就是一个公式,使用这个公式来计算在某个点的光照效果。
在标准光照模型里面,我们把进入摄像机的光分为下面四个部分:
* 自发光

类似生活中的萤火虫等自己能够发光

* 高光反射

类似生活中的镜子,近似认为百分百反射出去

* 漫反射

类似生活中的光照射到墙壁上、桌子上的反光不会百分百反射出去,各个方向都会反射。

* 环境光

类似生活中的光照照射在某个物体上,物体漫反射然后反射到其他物体上这些过程的光是环境光。

以上都是模拟生活中的光照效果,并不等同于实际生活中的光照计算,实际生活中的光照会更加复杂。

光照模型中关于漫反射的计算

漫反射计算放在顶点函数里面就是逐顶点光照

Disffuse = 直射光颜色*max(0,cosθ) (θ是法线跟光照的夹角) 如果夹角小于0的话就是背光面就直接取黑色

理论知识:

  • 向量点乘(结果值)

    a*b=|a||b|cosθ

  • 向量叉乘(结果是向量)

    a*b=c c的一个向量,值是|a||b|sinθ

编写漫反射Shader

在Pass块里添加Tags{“LightMode”=”ForwardBase”}和#include “Lighting.cginc” //引用一些写好的程序块 会包含一些获取光照的信息

只有定义了正确的LightMode才能得到一些Unity的内置光照变量。

  • normalize() 将一个向量单位化
  • max()用来取得函数中最大的一个
  • dot()用来取得两个向量的点积
  • _WorldSpaceLightPos0取得平行光的位置
  • _LightColor0取得平行光的颜色
    UNITY_MATRIX_MVP 这个矩阵是将模型空间转换到剪裁空间
  • _World2Object这个矩阵用来将一个方向从时间空间转换到模型空间
  • UNITY_LIGHTMODEL_AMBIENT 用来获取环境光
Shader "AladdinShader/04 Diffuse Tertex Shader"
{
    Properties
    {

    }
    SubShader {
        Pass 
        {
            Tags{"LightMode"="ForwardBase"}

        CGPROGRAM
#include "Lighting.cginc" //引用一些写好的程序块 会包含一些获取光照的信息  
//_LightColor0 取得第一个直射光的颜色
//_WorldSpaceLightPos0 

#pragma vertex vert
#pragma fragment frag 

        struct a2v 
        {
            float4 vertex:POSITION;
            float3 normal:NORMAL; //模型空间下法线
        };

        struct v2f
        {
            float4 position:SV_POSITION;
            fixed3 color:COLOR;
        };
        v2f vert(a2v v)
        {
            v2f f;
            f.position = mul(UNITY_MATRIX_MVP, v.vertex);

            //获得世界空间的单位法线向量
            fixed3 normalDir = normalize(mul(v.normal, (float3x3)unity_WorldToObject)); //从模型空间转到世界空间 float3x3 将一个4*4的矩阵强转成3*3的矩阵

            //世界空间下的光照位置
            fixed3  lightDir = _WorldSpaceLightPos0.xyz; //对于每一个点来说每一个光的位置就是光的方向(针对平行光)

            //取得漫反射的颜色
            fixed3 diffuse = _LightColor0.rgb * max(0, dot(normalDir, lightDir));//取得第一个直射光的颜色
            f.color = diffuse;
            return f;
        }

        fixed4 frag(v2f f):SV_Target
        {
            return fixed4(f.color,1);
        }

        ENDCG
        }
    }
    FallBack  "VertexLit"
}

效果:
没有写反射光照之前的效果,红色是均匀的
这里写图片描述
写了光照效果之后,会看到正对光照地方很亮,背对光照的地方就很暗甚至黑色。
这里写图片描述
现在是逐顶点光照,顶点的数量是有限的,如果将计算放在片元函数里面计算效果会更好,但计算量也会更大,那就叫逐片元光照。

给物体自身添加颜色和环境光共同控制

上面物体的颜色只是受环境光的颜色影响,但实际生活中我们看到的物体的颜色是环境光和自身颜色混合的效果。我们在上面的例子的基础上,添加上物体本身的颜色:
给物体添加颜色属性然后*环境光

Shader "AladdinShader/04 Diffuse Tertex Shader"
{
    Properties
    {
        _Diffuse("Diffuse",Color)=(1,1,1,1) //添加自身的颜色
    }
    SubShader {
        Pass 
        {
            Tags{"LightMode"="ForwardBase"}

        CGPROGRAM
#include "Lighting.cginc" //引用一些写好的程序块 会包含一些获取光照的信息  
//_LightColor0 取得第一个直射光的颜色
//_WorldSpaceLightPos0 

#pragma vertex vert
#pragma fragment frag 

        fixed4 _Diffuse;

        struct a2v 
        {
            float4 vertex:POSITION;
            float3 normal:NORMAL; //模型空间下法线
        };

        struct v2f
        {
            float4 position:SV_POSITION;
            fixed3 color:COLOR;
        };
        v2f vert(a2v v)
        {
            v2f f;
            f.position = mul(UNITY_MATRIX_MVP, v.vertex);

            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;//获得系统内置的环境光

            //获得世界空间的单位法线向量
            fixed3 normalDir = normalize(mul(v.normal, (float3x3)unity_WorldToObject)); //从模型空间转到世界空间 float3x3 将一个4*4的矩阵强转成3*3的矩阵

            //世界空间下的光照位置
            fixed3  lightDir = _WorldSpaceLightPos0.xyz; //对于每一个点来说每一个光的位置就是光的方向(针对平行光)

            //取得漫反射的颜色
            fixed3 diffuse = _LightColor0.rgb * max(0, dot(normalDir, lightDir)) * _Diffuse.rgb;//取得第一个直射光的颜色 颜色融合
            f.color = diffuse + ambient;  //颜色增加 增强
            return f;
        }

        fixed4 frag(v2f f):SV_Target
        {
            return fixed4(f.color,1);
        }

        ENDCG
        }
    }
    FallBack  "VertexLit"
}

效果:
这里写图片描述
这样直射光设置成白色,颜色本身设置成绿色,并且在面板中调整颜色,物体就显示成这种颜色。颜色融合一般是颜色的改变,颜色相加是强度增强。

环境光设置:Window->Lighting->Ambient Source->Color 一般不建议很强的环境光

逐像素光照

Shader "AladdinShader/05 Diffuse fragment Shader"
{
    Properties
    {
        _Diffuse("Diffuse",Color)=(1,1,1,1) //添加自身的颜色
    }
    SubShader {
        Pass 
        {
            Tags{"LightMode"="ForwardBase"}

        CGPROGRAM
#include "Lighting.cginc" //引用一些写好的程序块 会包含一些获取光照的信息  
//_LightColor0 取得第一个直射光的颜色
//_WorldSpaceLightPos0 

#pragma vertex vert
#pragma fragment frag 

        fixed4 _Diffuse;

        struct a2v 
        {
            float4 vertex:POSITION;
            float3 normal:NORMAL; //模型空间下法线
        };

        struct v2f
        {
            float4 position:SV_POSITION;
            fixed3 worldNormalDir:COLOR0;
        };
        v2f vert(a2v v)
        {
            v2f f;
            f.position = mul(UNITY_MATRIX_MVP, v.vertex);
            f.worldNormalDir = mul(v.normal, (float3x3)unity_WorldToObject);
            return f;
        }

        fixed4 frag(v2f f):SV_Target
        {
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;//获得系统内置的环境光

            //获得世界空间的单位法线向量
            fixed3 normalDir = normalize(f.worldNormalDir); //从模型空间转到世界空间 float3x3 将一个4*4的矩阵强转成3*3的矩阵

            //世界空间下的光照位置
            fixed3  lightDir = _WorldSpaceLightPos0.xyz; //对于每一个点来说每一个光的位置就是光的方向(针对平行光)

            //取得漫反射的颜色
            fixed3 diffuse = _LightColor0.rgb * max(0, dot(normalDir, lightDir)) * _Diffuse.rgb;//取得第一个直射光的颜色 颜色融合
            fixed3 tempColor = diffuse + ambient;  //颜色增加 增强
            return fixed4(tempColor,1);
        }

        ENDCG
        }
    }
    FallBack  "VertexLit"
}

效果图:
这里写图片描述
一个是逐顶点光照,一个是逐像素光照,自己看还是能看出区别的。逐像素光照会更消耗性能一些,如果不需要很高的精度的话就可以用逐顶点光照代替。

半兰伯特光照模型

模型表面的明亮度直接取决于光线向量(light vector)和表面法线(normal)两个向量将夹角的余弦值。

如果漫反射光强设置为Diffuse,入射光光强为I,光方向和法线夹角为θ,那么兰伯特光照模型可以用下面的公式表示:Diffuse = I * cosθ。
兰伯特光照模型 模型背面全都是黑色都看不清楚
这里写图片描述

为了解决这个问题,改造一些兰伯特光照模型的公式,让背光部分也随着光照强度改变而改变。
半兰伯特光照模型:
为了解决兰伯特模型背光面全都是黑的看不清楚的问题。
Diffuse=直射光颜色 * (cosθ * 0.5 + 0.5)
这里写图片描述

Shader修改

Shader "AladdinShader/06 Diffuse fragment Half Lanbote Shader"
{
    Properties
    {
        _Diffuse("Diffuse",Color)=(1,1,1,1) //添加自身的颜色
    }
    SubShader {
        Pass 
        {
            Tags{"LightMode"="ForwardBase"}

        CGPROGRAM
#include "Lighting.cginc" //引用一些写好的程序块 会包含一些获取光照的信息  
//_LightColor0 取得第一个直射光的颜色
//_WorldSpaceLightPos0 

#pragma vertex vert
#pragma fragment frag 

        fixed4 _Diffuse;

        struct a2v 
        {
            float4 vertex:POSITION;
            float3 normal:NORMAL; //模型空间下法线
        };

        struct v2f
        {
            float4 position:SV_POSITION;
            fixed3 worldNormalDir:COLOR0;
        };
        v2f vert(a2v v)
        {
            v2f f;
            f.position = mul(UNITY_MATRIX_MVP, v.vertex);
            f.worldNormalDir = mul(v.normal, (float3x3)unity_WorldToObject);
            return f;
        }

        fixed4 frag(v2f f):SV_Target
        {
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;//获得系统内置的环境光

            //获得世界空间的单位法线向量
            fixed3 normalDir = normalize(f.worldNormalDir); //从模型空间转到世界空间 float3x3 将一个4*4的矩阵强转成3*3的矩阵

            //世界空间下的光照位置
            fixed3  lightDir = _WorldSpaceLightPos0.xyz; //对于每一个点来说每一个光的位置就是光的方向(针对平行光)

            float halfLambert = dot(normalDir, lightDir) * 0.5 + 0.5; //半兰伯特光照模型
            //取得漫反射的颜色
            fixed3 diffuse = _LightColor0.rgb * halfLambert * _Diffuse.rgb;//取得第一个直射光的颜色 颜色融合
            fixed3 tempColor = diffuse + ambient;  //颜色增加 增强
            return fixed4(tempColor,1);
        }

        ENDCG
        }
    }
    FallBack  "VertexLit"
}

这里写图片描述
会明显的感觉模型亮度变亮了,而且背面也不会纯黑了也会渐变,而且正面颜色也会鲜明一些,这样整体效果会更好一些。

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




2017-08-12 23:46:59 niuge8905 阅读数 783

shader问题解答:扣扣 2540,33230
要想实现边缘泛光,只需要将普通的diffuse反射的亮度反过来即可。所以将1减去法向量和视角点乘的值即可。

Shader "Custom/FragmentShader" {
properties{
_SpecularColor("SpecularColor",color)=(1,1,1,1)
_Shininess("Shininess",range(1,8))=4

}
    SubShader {
        PASS{
        tags{"LightMode"="ForwardBase"}     
        CGPROGRAM
        #pragma vertex vert  
        #pragma fragment frag

        #include "UnityCG.cginc"
        #include "Lighting.cginc"


        float4 _SpecularColor;
        float _Shininess;


        struct v2f{
        float4 pos:POSITION;
        float3 normal1:TEXCOORD0;
        float4 vertex1:TEXCOORD1;
        };  

        v2f vert(appdata_base v){
        v2f o;
        o.pos=UnityObjectToClipPos(v.vertex);
        o.normal1=v.normal;
        o.vertex1=v.vertex;

        return o;
        }

        float4 frag(v2f IN):COLOR{

        float4 col= UNITY_LIGHTMODEL_AMBIENT;
        float3 N=normalize(UnityObjectToWorldNormal(IN.normal1));
        float3 worldPos=mul(unity_ObjectToWorld,IN.vertex1).xyz;
        float3 V=normalize(_WorldSpaceCameraPos.xyz-worldPos);
        float diffuseScale=pow(1.0-saturate(dot(V,N)),_Shininess);
        col+=_LightColor0*diffuseScale;
        return col;
        }
        ENDCG
        }
    }
    FallBack "Diffuse"

}

这里写图片描述

如果想实现透明的状况,而且被遮挡的物体颜色也实现变化,那么就需要再将上面的代码再稍作修改。

Shader "Custom/FragmentShader" {
properties{
_SpecularColor("SpecularColor",color)=(1,1,1,1)
_Shininess("Shininess",range(1,8))=4

}
    SubShader {
    Tags { "Queue" = "Transparent" }
        PASS{
            Blend SrcAlpha OneMinusSrcAlpha
            zwrite off  
        CGPROGRAM
        #pragma vertex vert  
        #pragma fragment frag

        #include "UnityCG.cginc"
        #include "Lighting.cginc"


        float4 _SpecularColor;
        float _Shininess;


        struct v2f{
        float4 pos:POSITION;
        float3 normal1:TEXCOORD0;
        float4 vertex1:TEXCOORD1;
        };  

        v2f vert(appdata_base v){
        v2f o;
        o.pos=UnityObjectToClipPos(v.vertex);
        o.normal1=v.normal;
        o.vertex1=v.vertex;

        return o;
        }

        float4 frag(v2f IN):COLOR{

        //float4 col= UNITY_LIGHTMODEL_AMBIENT;
        float3 N=normalize(UnityObjectToWorldNormal(IN.normal1));
        float3 worldPos=mul(unity_ObjectToWorld,IN.vertex1).xyz;
        float3 V=normalize(_WorldSpaceCameraPos.xyz-worldPos);
        float diffuseScale=pow(1.0-saturate(dot(V,N)),_Shininess);
        float4 col=_LightColor0*diffuseScale;
        return col;
        }
        ENDCG
        }
    }
    FallBack "Diffuse"

}

这里写图片描述

2017-11-29 17:04:25 ji2570489271 阅读数 333

1、Unity四种标准的光照模型是什么,以及高光模型的两个公式是什么

  环境光、自发光、高光、漫反射    Lambert:C(diffuse)=C(light)*m*max(0,N*L)  Phong: 


2、一个游戏对象最终渲染到屏幕上,经过那几个坐标变换

   本地坐标系--世界--》视野----》裁剪--》屏幕DirectX windows屏幕坐标系 左下角(0,0) OpenGL(右下角)


3、如何实现磁铁效果?比如酷跑游戏中,获得磁铁道具后,自动吸附附近金币的效果。

获得磁铁效果后,主要流程可以分为3步:

首先判断金币是否进入磁铁有效范围(圆半径,距离,矩形,collider触发器等方式都可以)接着可以使用差值运算方法,Lerp方法或ITween等插件或者类似方法对金币做一个缓动效果吸附的动画,最后判断动画到达目标点后(碰撞器,触发器,计时等)进行销毁加分等处理即可。

①在玩家身上相同的坐标位置创建一个空的gameobject作为自物体,添加圆形或者球形碰撞器(根据实际要求2D或者3D)来选择,勾选istrigger作为触发器,触发器的范围即为磁铁吸附范围,添加刚体,取消勾选useGravity,gameobject.setActive(false);

②当玩家获得磁铁道具后,控制该gameobject.setActive(true);

③在金币上挂载脚本:


4、向量的点积,叉积,在游戏中的应用(分别,举一个例子说明)

 通过点积Vector3.Dot,可以计算两个向量的夹角的。Vector3.Cross 可以求两个向量的叉积。Vector3.Dot(transform.forward, target.position)返回值为正时,目标在自己的前方,反之在自己的后方

判断目标在自己的左右方位可以使用下面的方法:

Vector3.Cross(transform.forward, target.position).y

返回值为正时,目标在自己的右方,反之在自己的左方通过点积可以计算两个向量的夹角,叉积可以判断目标在自己的方位。向量单位化后可以在游戏中用来计算怪物视野张角;玩家视野张角;NPC视野张角等,完成巡逻索敌等一些功能。


5、请写出ShaderLab中的固定着色器的程序结构


6、Mesh,Sphere,Box,Capsule,四种碰撞器。请按照性能开销由小到大进行排序。

答:collider的性能和效率大概的顺序是:Sphere Collider > Capsule Collider> Box Collider > Mesh Collider。取决于碰撞的定点数量和计算复杂度



7、静态成员与实例成员的区别

静态成员:static关键字、类名调用、可以访问静态成员、不可以直接访问实例成员、调用前初始化

实例方法:不需要static、使用实例对象调用、可以访问静态成员、可以直接访问实例成员、实例化对象初始化


8、什么是渲染管道?

是指在显示器上为了显示出图像而经过的一系列必要操作。 渲染管道中的很多步骤,都要将几何物体从一个坐标系中变换到另一个坐标系中去。

主要步骤有:
本地坐标->视图坐标->背面裁剪->光照->裁剪->投影->视图变换->光栅化。


9、NGUI中八个内置事件函数

    OnClick、OnValueChange.........

    

10、Unity Shader分为哪几种着色器。

    表面着色器,顶点片源着色器,固定功能着色器。

2015-04-11 21:58:26 qp120291570 阅读数 2662

自定义光照模型

在这之前首先来了解一下SurfaceOutput这个结构体,它是一个包含大多数描述一个物体表面渲染特征的结构,具体结构如下:

struct SurfaceOutput {
	half3 Albedo;//纹理颜色
	half3 Normal;//法线
	half3 Emission;//自发光,不受照明的影响
	half Specular;//高光指数
	half Gloss;//光泽度
	half Alpha;//Alpha通道
};

基本上所有的Shader函数要处理的就是这个结构体。


Unity自带的光照实现都定义在一些*.cginc文件中,要自定义光照模型,只要不用Unity自带的光照模型就可以了。

将下面这一行的语句的最后替换成对应的光照计算函数。

#pragma surface surf  NoLight


无光照的材质Shader

Shader "Custom/NoLight" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		#pragma surface surf NoLight

		sampler2D _MainTex;
		inline float4 LightingNoLight(SurfaceOutput s, fixed3 lightDir, fixed3 atten)
		{
			float4 col;
			col.rgb = s.Albedo;
			col.a = s.Alpha;
			return col;
		}


		struct Input {
			float2 uv_MainTex;
		};

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


		ENDCG
	} 
	FallBack "Diffuse"
}


自己实现一个diffuse


inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten)
{
	float difLight = max(0, dot (s.Normal, lightDir));
	float4 col;
	col.rgb = s.Albedo * _LightColor0.rgb * (difLight * atten * 2);
	col.a = s.Alpha;
	return col;
}





上图中左边是NoLight,右边是SimpleDiffuse。


同理可以自定义实现各种光照模型了,Lambert,Blinning,,,,,


补充一下光照函数的几种写法

half4 LightingName (SurfaceOutput s, half3 lightDir,half atten){}

这个函数用于不需要视角方向的情况下的Forward rendering。

half4 LightingName (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten){}

这个函数用于需要视角方向的情况下的Forward rendering。

half4 LightingName_PrePass (SurfaceOutput s, half4 light){}

这个函数用于Deferred rendering。


用代码控制shader动态修改材质

对SimpleDiffuse稍微做一下修改,添加一个叠加的颜色。

Shader "Custom/SimpleDiffuse" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_Color ("Main Color", Color) = (0,1,0,1)
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		#pragma surface surf Lambert

		sampler2D _MainTex;
		float4 _Color;

		struct Input {
			float2 uv_MainTex;
		};

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

在模型(有MeshRenderer的)上面挂一个脚本,实现如下

MeshChanger

using UnityEngine;
using System.Collections;

public class MaterialChanger : MonoBehaviour {
	float time = 0.0f;
	float changeSpeed1 = 2.0f;
	float changeSpeed2 = 5.0f;
	Renderer render;

	// Use this for initialization
	void Start () {
		render = transform.GetComponent<Renderer>();
	}
	
	// Update is called once per frame
	void Update () {
		float v1 = (Mathf.Cos(time * changeSpeed1) + 1.0f) * 0.5f;
		float v2 = (Mathf.Sin(time * changeSpeed2)+ 1.0f) * 0.5f;

		render.materials[0].SetColor("_Color", new Color(v1, v2, v2));
		time += Time.deltaTime;
	}
}

根据时间动态修改0号材质的_Color选项,结果就像这样





自行脑补中间过程。


使用渐变纹理来处理光照

首先要准备一张渐变纹理,原理就是通过计算当前位置的光照与法线的点积,索引到渐变图片上的像素值,最后将其和diffuse叠加。


Shader "Custom/RampTexture" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_RampTex ("Ramp Tex (RGB)", 2D) = "white" {}

	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		#pragma surface surf RampLight

		sampler2D _MainTex;
		sampler2D _RampTex;

		half4 LightingRampLight (SurfaceOutput s, half3 lightDir, half atten) 
		{
			half NdotL = dot(s.Normal, lightDir);
			float diff = NdotL * 0.5 + 0.5;
			half3 ramp = tex2D(_RampTex, float2(diff, diff)).rgb;
			half4 c;
			c.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2);
			c.a = s.Alpha;
			return c;
		}

		struct Input {
			float2 uv_MainTex;
		};

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

渲染结果



没有纹理的情况(是大象不是小猪)




换一下索引贴图

得到结果如下(Toon Shading)



参考

Unity Shaders and Effects CookBook


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