解读Unity中的CG编写Shader系列8——多光源漫反射

前文中完成最简单的漫反射shader只是单个光源下的漫反射,而往往场景中不仅仅只有一个光源,那么多个光源的情况下我们的物体表面的漫反射强度如何叠加在一起呢?前文打的tag "LightMode"="ForwardBase"又是什么意思呢?

Unity内置的DiffuseShader,也就是我们创建一个Material出来时默认的Shader也是多光源的,所以这篇文章完成的shader与默认的diffuse shader基本效果一致。

首先引入几个概念

渲染路径 Rendering Path

Unity在处理多光源的情况时为我们提供了三种模式:修改的地方在 Edit--Project Settings--Player--Other Settings--Rendering Path

1.顶点光 Vertex Lit

2.方向性 Forward (默认)

3.延迟照明 Deferred Lighting

我们的shader也使用默认的Forward

像素光 Pixel Light

Unity中将平行光称作为像素光,第一个像素光是基础平行光,以LightMode=ForwardBase标签修饰,每多一个像素光都以LightMode=ForwardAdd标签修饰。

并不是所有的光源在运行时都会反射到物体上,而是根据Project的Quality中设置的像素光数量来渲染的。

默认像素光的数量应该是2,我们有更多的平行光照在物体上,就需要在Edit > Project Settings > Quality中去调节像素光的数量Pixel Light Count,如下图:

当场景中的实际像素光数量超过这个设定值的时候,unity只会渲染最重要的光。至于如何将光源的渲染模式改为重要和不重要我们后面将继续讨论。

关于像素光的叠加原理

前面的系列6中已经讲过片段着色器是要将mesh组件传递的信息最终计算为颜色(或者深度)存储在帧缓存(Frame Buffer)中。

每个Pass之间输出的颜色通过一定的公式进行混合。

在这里我们简单实用一比一的模式进行颜色混合,即混合指令为:

Blend One One

第二个Pass的代码同样的也直接复制第一个Pass即可,相应的将Tags标签中的LightMode=ForwardBase修改为LightMode=ForwardAdd。

代码

Shader "Custom/Multi-Light Diffuse" {
	Properties {
		//材料颜色默认为黑色,可在inspector中调节
		_Color ("Material Color", Color) =  (1,1,1,1)
	}
	SubShader {

		Pass{

		//第一个像素光所在的反射通道标记为ForwardBase
		Tags {"LightMode" = "ForwardBase"} 

		CGPROGRAM
		// Upgrade NOTE: excluded shader from OpenGL ES 2.0 because it does not contain a surface program or both vertex and fragment programs.
		//#pragma exclude_renderers gles
		//定义顶点着色器与片段着色器入口
		#pragma vertex vert
		#pragma fragment frag
		//获取property中定义的材料颜色
		uniform float4 _Color; 

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

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

		//定义顶点着色器的输入参数结构体
		//我们只需要每个顶点的位置与对应的法向量
		struct vertexInput {
			float4 vertex : POSITION;
			float3 normal : NORMAL;
		};
		//定义顶点着色的输出结构体/片段着色的输入结构体
		//已经计算好的颜色
		struct vertexOutput {
			float4 pos : SV_POSITION;
			float4 col : COLOR;
		};

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

			//计算对象坐标系中的顶点法向量的单位向量
			//将mesh传递过来的顶点法向量与模型-->对象坐标系矩阵相乘得到对象坐标系中的法向量
			//然后单位化
			float3 normalDirection = normalize(float3(mul(float4(input.normal, 0.0), modelMatrixInverse)));

			//计算入射向量的单位向量
			float3 lightDirection = normalize(float3(_WorldSpaceLightPos0));

			//计算反射后的颜色
			//先将光源颜色与材料颜色向量相乘
			//再乘以上文提到的max(0,cos∠(N,L))
			float3 diffuseReflection=float3(_LightColor0) * float3(_Color)* max(0.0, dot(normalDirection, lightDirection));	

			//上面计算的是RGB颜色,差个A,补充一维就可以传递给片段着色器了
			output.col=float4(diffuseReflection,1);

			//国际惯例,顶点变化三步曲,这个例子中可写可不写
			output.pos = mul(UNITY_MATRIX_MVP, input.vertex);

			return output;
		}

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

		}

		ENDCG
	}

	Pass{

		//其余的像素光所在的反射通道标记为ForwardAdd
		Tags {"LightMode" = "ForwardAdd"} 

		//此通道的片段着色器输出颜色时帧缓存已经有了颜色,所以需要混合
		//混合模式为1比1

		Blend One One
		CGPROGRAM
		// Upgrade NOTE: excluded shader from OpenGL ES 2.0 because it does not contain a surface program or both vertex and fragment programs.
		//#pragma exclude_renderers gles
		//定义顶点着色器与片段着色器入口
		#pragma vertex vert
		#pragma fragment frag
		//获取property中定义的材料颜色
		uniform float4 _Color; 

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

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

		//定义顶点着色器的输入参数结构体
		//我们只需要每个顶点的位置与对应的法向量
		struct vertexInput {
			float4 vertex : POSITION;
			float3 normal : NORMAL;
		};
		//定义顶点着色的输出结构体/片段着色的输入结构体
		//已经计算好的颜色
		struct vertexOutput {
			float4 pos : SV_POSITION;
			float4 col : COLOR;
		};

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

			//计算对象坐标系中的顶点法向量的单位向量
			//将mesh传递过来的顶点法向量与模型-->对象坐标系矩阵相乘得到对象坐标系中的法向量
			//然后单位化
			float3 normalDirection = normalize(float3(mul(float4(input.normal, 0.0), modelMatrixInverse)));

			//计算入射向量的单位向量
			float3 lightDirection = normalize(float3(_WorldSpaceLightPos0));

			//计算反射后的颜色
			//先将光源颜色与材料颜色向量相乘
			//再乘以上文提到的max(0,cos∠(N,L))
			float3 diffuseReflection=float3(_LightColor0) * float3(_Color)* max(0.0, dot(normalDirection, lightDirection));	

			//上面计算的是RGB颜色,差个A,补充一维就可以传递给片段着色器了
			output.col=float4(diffuseReflection,1);

			//国际惯例,顶点变化三步曲,这个例子中可写可不写
			output.pos = mul(UNITY_MATRIX_MVP, input.vertex);

			return output;
		}

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

		}

		ENDCG
	}
}
}

这样我们就能在添加第二个光源之后看到两个光源的效果了,在这里为了对比效果我们保留上一个例子中的单光源漫反射shader,然后分别创建两个球体,在场景中设置2个光源,看一下对比的效果:

可以看到同一个场景中 两个平行光照在两个球体上,一个只体现了主光源的漫反射(ForwardBase),而另一个将两个平行光的漫反射混合了起来。

如果场景中再添加以个平行光,那么我们再添加一个Pass 标记为ForwardAdd,并且在Quality中将Pixcel Light Count 调节为3即可。

时间: 2024-08-12 09:38:46

解读Unity中的CG编写Shader系列8——多光源漫反射的相关文章

解读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/6117338.html 前文中完成最简单的漫反射shader只是单个光源下的漫反射,而往往场景中不仅仅只有一个光源,那么多个光源的情况下我们的物体表面的漫反射强度如何叠加在一起呢?前文打的tag "LightMode"="ForwardBase"又是什么意思呢? Unity内置的DiffuseShader,也就是我们创建一个Material出来时默认的Shader也是多光源的,所以这篇文章完成的shad

解读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) 上一篇文章中的管道环节中的“逐帧操作”环节中的一项操作就是混合操作,可见混合操作是不可编程的固定功能环节. 在