解读Unity中的CG编写Shader系列十 (光滑的镜面反射(冯氏着色))

前文完成了最基本的镜面反射着色器,单平行光源下的逐顶点着色(per-vertex lighting),又称为古罗着色(Gouraud shading)。这篇文章作为后续讨论更光滑的镜面反射方式,逐像素着色(per-pixcel lighting),又称为冯氏着色(Phong shading)

逐像素着色Per-Pixel Lighting (冯氏着色Phong Shading)

别把冯氏着色与冯氏反射模型搞混淆了,前问提到了冯氏反射模型,冯氏反射模型是为使计算机模拟接近真实的物体表面光泽提出的模型,即环境光(虚拟的)+漫反射光+镜面反射光=表面色彩。

逐顶点着色,故名思意跟顶点有关,也就是在我们的顶点着色器中根据每个顶点上的入射向量L、法向量N、观察向量V等直接计算出每个顶点该有的颜色,然后传递给后续环节进行着色,可想而知由于顶点是离散的,片段是连续的,所以引起着色效果的不光滑很容易理解。

那么逐像素着色与之对应的即是在片段着色器中,对法向量与坐标进行插值(如果不能理解插值,请百度一下),从而使离散的顶点计算出来的离散的颜色变得连续而光滑。这就是冯氏着色的要义。

将前文的古罗着色改为冯氏着色

说起来简单,做起来更简单了,我们直接把环境光、漫反射光、镜面反射光的计算拿到片段着色器中计算即可完成修改。为何不一开始就直接在片段着色器中写,是为了使看官对这两种不同的着色方式有一个印象。

在开始修改前我们只需要明白一个道理就行了:

我们之前在顶点着色器中获取到的法向量、观察向量、入射向量直接完成计算变成了颜色传递给片段着色器中,因此片段着色器的输入参数中基本上只有颜色。现在我们要把计算过程移到片段着色器中,那么法向量、观察向量、入射向量同理需要传递给片段着色器,而不再是直接传递一个颜色。

而观察向量与入射向量分别又是由世界相机坐标以及世界光源向量计算的,这两个信息并非meshRenderer传递给顶点着色器的,而是直接在cg中内置的uniform参数,因此在片段着色器中也可以直接获取,因此我们只需要传递顶点的坐标以及转换后的法向量。

因此我们的顶点着色器的输出结构体(又是片段着色器的输入结构体)应该修改为:

                //定义顶点着色的输出结构体/片段着色的输入结构体
		//去掉颜色  添加顶点的世界坐标以及法向量,这里的语义使用了TEXCOORD的两个集合,这里的TEXCOORD是我们自己使用的,与顶点着色器输入时使用该语义		已经有区别了

		struct vertexOutput {
			float4 pos : SV_POSITION;
			float4 posWorld : TEXCOORD0;
			float3 normalDir : TEXCOORD1;
		};

片段着色器接受到法向量后进行一定的插值,其过程是再次单位化,为什么两次单位化就能完成插值,我线性代数也不是很好,大家可以自己研究下。

然后把计算过程搬至片段着色中,系列9的代码修改后的代码为:

Shader "Custom/PhoneShadingSpecular" {
	Properties {
		_Color ("Diffuse Material Color", Color) = (1,1,1,1)
		_SpecColor ("Specular Material Color", Color) = (1,1,1,1)
		//材料表面的光泽程度,根据前文所述,此参数无穷大时,材料完全不会产生镜面反射
		_Shininess ("Shininess", Float) = 10
	}
	SubShader {
		Pass{
		Tags { "LightMode" = "ForwardBase" }

		CGPROGRAM

		//定义顶点着色器与片段着色器入口
		#pragma vertex vert
		#pragma fragment frag
		//获取property中定义的材料颜色
		uniform float4 _Color;
		uniform float4 _SpecColor;
		uniform float _Shininess;

		// 光源的位置或者方向
		//uniform float4 _WorldSpaceLightPos0;

		// 光源的颜色 (from "Lighting.cginc")
		uniform float4 _LightColor0;

		//定义顶点着色器的输入参数结构体
		//我们只需要每个顶点的位置与对应的法向量
		struct vertexInput {
			float4 vertex : POSITION;
			float3 normal : NORMAL;
		};
		//定义顶点着色的输出结构体/片段着色的输入结构体
		//去掉颜色  添加顶点的世界坐标以及法向量

		struct vertexOutput {
			float4 pos : SV_POSITION;
			float4 posWorld : TEXCOORD0;
			float3 normalDir : TEXCOORD1;
		};

		//顶点着色器
		vertexOutput vert (vertexInput input) {
			vertexOutput output;
			//对象坐标系到世界坐标系的变换矩阵
			//_Object2World与_World2Object均为unity提供的内置uniform参数
			float4x4 modelMatrix = _Object2World;
			//世界坐标系到对象坐标系的变换矩阵
			float4x4 modelMatrixInverse = _World2Object;

			//法向量N变化至对象坐标系
			output.normalDir = normalize(float3(mul(float4(input.normal, 0.0), modelMatrixInverse)));

			//将顶点坐标向世界坐标系变换
			output.posWorld=mul(modelMatrix,input.vertex);

			//国际惯例,顶点变化三步曲
			output.pos = mul(UNITY_MATRIX_MVP, input.vertex);

			return output;
		}

		//片段着色器,老规矩,把顶点着色器的输出参数作为片段着色器的输入参数
		float4 frag(vertexOutput input): COLOR
		{

			//接受顶点着色器传递的法向量与顶点世界坐标
			//这里必须将法向量再normalize一次
			//尽管在顶点着色器中已经normalize了一次
			float3 normalDirection=normalize(input.normalDir);

			float3 worldPosition=input.posWorld;

			//观察向量V由摄像机坐标与顶点坐标矢量相减
			//这里改顶点坐标为上面获取到的世界坐标
			float3 viewDirection = normalize(float3(float4(_WorldSpaceCameraPos, 1.0)
				- worldPosition));

			/*下面的部分直接招搬就好了*/

			//平行光源的入射向量L直接由uniform_WorldSpaceLightPos0给出
			float3 lightDirection =normalize(float3(_WorldSpaceLightPos0));

			//镜面反射光的计算
			float3 specularReflection=float3(_LightColor0)*float3(_SpecColor)*pow(max(0.0,dot(reflect(-lightDirection, normalDirection),viewDirection)),_Shininess);

			//前文计算好的漫反射光
			float3 diffuseReflection=float3(_LightColor0) * float3(_Color)* max(0.0, dot(normalDirection, lightDirection));	

			//环境光直接获取
			float3 ambientLighting = float3(UNITY_LIGHTMODEL_AMBIENT) * float3(_Color);

			//根据冯氏反射模型将上述3个RGB颜色向量相加,然后补充A:

			return float4(ambientLighting + diffuseReflection+ specularReflection, 1.0);;

		}

		ENDCG
		}
	}
	FallBack "Diffuse"
}

  

最后的效果图,我们与上一个例子中的球体进行对比:

图左为冯氏着色下,图右为上一例的古罗着色,可见冯氏还挺厉害的啊,又是反射模型又是逐像素着色都是他的名字

时间: 2024-12-26 16:57:05

解读Unity中的CG编写Shader系列十 (光滑的镜面反射(冯氏着色))的相关文章

解读Unity中的CG编写Shader系列10——光滑的镜面反射(冯氏着色)

前文完成了最基本的镜面反射着色器,单平行光源下的逐顶点着色(per-vertex lighting),又称为古罗着色(Gouraud shading).这篇文章作为后续讨论更光滑的镜面反射方式,逐像素着色(per-pixcel lighting),又称为冯氏着色(Phong shading) 逐像素着色Per-Pixel Lighting (冯氏着色Phong Shading) 别把冯氏着色与冯氏反射模型搞混淆了,前问提到了冯氏反射模型,冯氏反射模型是为使计算机模拟接近真实的物体表面光泽提出的模

解读Unity中的CG编写Shader系列一

CG=C for Graphics  用于计算机图形编程的C语言超集 前提知识点: 1.CG代码必须用 CGPROGRAM ... ENDCG括起来 2.顶点着色器与片段着色器的主函数名称可随意,但需要再#pragma vert 与#pragma fragment中声明并且与主函数名完全匹配,shader才会找到入口 3.float4是一种压缩数组,float4 vert与float vert[4]严格意义上讲不同,虽然都是存放4个float,但float4作为向量类型做点乘.内积等处理更快速

解读Unity中的CG编写Shader系列四——unity中的圆角矩形shader

上篇文章中我们掌握了表面剔除和剪裁模式 这篇文章将利用这些知识实现一个简单的,但是又很常用的例子:把一张图片做成圆角矩形 例3:圆角矩形Shader 好吧我承认在做这个例子的时候走了不少弯路,由于本人对矩阵的知识掌握已经悉数还给老师,所以一开始用了一些笨办法计算圆角矩形区域. 我们知道TEXTCOORD0是一个以对象为坐标系的坐标,并且范围在该坐标的第一象限,取值为(0,0)到(1,1) 那么我们把每一张图片都看做一张1X1大小的矩形 我们要在1X1大小的矩形中擦除4个角,应该是这样: 以左上角

解读Unity中的CG编写Shader系列9——镜面反射

讨论完漫反射之后,接下来肯定就是镜面反射了 在开始镜面反射shader的coding之前,要扩充一下前面提到的知识,加深理解镜面反射与漫反射的区别. 引用一下一位前人博文中的一些基础概念,特别是关于冯氏反射模型的: 平行光(directional light) 一种是从特定方向射入并只会照亮面对入射方向的物体,我们称之为平行光(directional light). 环境光(ambient light) 另一种光是来自所有方向并且会照亮所有物体,不管这些物体的朝向如何,我们称之为环境光(ambi

解读Unity中的CG编写Shader系列6——漫反射

如果前面几个系列文章的内容过于冗长缺乏趣味着实见谅,由于时间原因前面的混合部分还没有写完,等以后再补充,现在开始关于反射的内容了. 折射与反射 在物理世界中,光的反射与折射往往是同时存在的,光源由真空或者空气中射入一种材料,光在进入这种材料的同时就发生了折射,折射的程度与各个介质的折射率有关,使光的传播路线偏离原来的路线: 继而如果光在通过不同传播介质的表面时,会像乒乓球一样弹回来,我们人眼能够看到东西,都是因为东西会反射光源,如果一种物质无法反射光,或者没有光源,我们就看不到东西.同样对于不同

解读Unity中的CG编写Shader系列6——不透明度与混合

1.不透明度 当我们要将两个半透的纹理贴图到一个材质球上的时候就遇到混合的问题,由于前面的知识我们已经知道了片段着色器以及后面的环节的主要工作是输出颜色与深度到帧缓存中,所以两个纹理在每个像素上的颜色到底以怎样的形式混合在一起最后输出到帧缓存中是我们现在首要讨论的内容. 1.混合(blending) 上一篇文章中的管道环节中的"逐帧操作"环节中的一项操作就是混合操作,可见混合操作是不可编程的固定功能环节. 在混合操作中,我们将片段着色器中计算出来的颜色称之为 "源颜色&quo

解读Unity中的CG编写Shader系列3——表面剔除与剪裁模式

在上一个样例中,我们得到了由mesh组件传递的信息经过数学转换至合适的颜色区间以颜色的形式着色到物体上. 这篇文章将要在此基础上研究片段的擦除(discarding fragments)和前面剪裁.后面剪裁(front face culling and back face culling)来达到透明效果. 当一个mesh组件的信息被传递后,我们能够通过代码决定哪些部分渲染(render)出来.而哪些部分不要.这个过程就像把那些不要的部分剔除了,我们看不到他.尽管他的mesh信息还在.可是我们的G

解读Unity中的CG编写Shader系列三

转自http://www.itnose.net/detail/6096068.html 在上一个例子中,我们得到了由mesh组件传递的信息经过数学转换至合适的颜色区间以颜色的形式着色到物体上.这篇文章将要在此基础上研究片段的擦除(discarding fragments)和前面剪裁.后面剪裁(front face culling and back face culling)来达到透明效果. 当一个mesh组件的信息被传递后,我们可以通过代码决定哪些部分渲染(render)出来,而哪些部分不要,这

解读Unity中的CG编写Shader系列七(不透明度与混合)

转自http://www.itnose.net/detail/6098539.html 1.不透明度 当我们要将两个半透的纹理贴图到一个材质球上的时候就遇到混合的问题,由于前面的知识我们已经知道了片段着色器以及后面的环节的主要工作是输出颜色与深度到帧缓存中,所以两个纹理在每个像素上的颜色到底以怎样的形式混合在一起最后输出到帧缓存中是我们现在首要讨论的内容. 1.混合(blending) 上一篇文章中的管道环节中的“逐帧操作”环节中的一项操作就是混合操作,可见混合操作是不可编程的固定功能环节. 在