(四)Shader中基本光照模型

1.前言

所谓的光照效果,反映到屏幕上就是一个个像素问题,所以光照的计算公式就是计算的一个一个颜色值。这些公式基本属于经验公式范畴,只是让人看起来像真正的光效。
光分为环境光、自发光、漫反射以及高光反射,由于反映到像素上就是颜色的叠加。即最终在片元着色器中返回的颜色值=环境光颜色+自发光颜色+漫反射颜色+高光反射颜色。下面依次对这些光进行说明。

2.环境光Ambient

环境光在Unity中可以设置,即windows-lighting中即可看到,在2018中则是window-rendering-lightingsettings中设置。在shader中通过如下变量可以获取到:UNITY_LIGHTMODEL_AMBIENT。但是一般计算中我们只需要颜色的rgb值。

3.自发光Emissive

自发光是最简单的,直接简单粗暴的暴露一个颜色值,包此值当作自发光颜色。

4.漫反射diffuse

漫反射计算参考一下光照模型进行计算。

4.1 兰伯特定律LambertLaw

兰伯特定律认为反射光线跟光源方向与法线夹角有关系(夹角余弦值),所以漫反射计算公式如下:
漫反射颜色 = 光源颜色 x 材质的漫反射颜色 x Max(0,Dot(法线,指向光源的方向)
其中:
光源颜色:变量_LightColor0代表光源颜色。
材质漫反射颜色:自定义的颜色值。
法线:可以获取到每个顶点的法线,计算式采用单位向量。
光源方向:指从当前位置到光源的方向,计算式采用单位向量。
由于实际中当法线与光源方向夹角大于90°时,已经不会有反射效果,此时两者余弦值为负值,所以此时应该取0。

4.1.1 逐顶点计算

逐顶点计算漫反射时,在顶点着色器中计算,此时计算量小,但是效果差。在计算世界坐标系下的法线时,采用如下所示:

    //获取法线(统一到世界坐标系下),不做归一化效果要优于归一化。
    //fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
    fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);

上述注释掉的方法也是UnityObjectToWorldNormal方法,当然UnityCG种方法比注释掉的方法复杂一点。完整代码如下:

Shader "LL/Light/DiffuseVertex_Lambert"
{
	Properties
	{
		_Diffuse ("Diffuse", Color) = (1,1,1,1)
	}
	SubShader
	{
		Tags { "LightMode"="ForwardBase" }

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

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

			struct a2v
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float4 position : SV_POSITION;
				float3 color : COLOR;
			};

			fixed4 _Diffuse;

			v2f vert (a2v v)
			{
				v2f o;
				o.position = UnityObjectToClipPos(v.vertex);
                //获取环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				//获取法线(统一到世界坐标系下)。
				//fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
				fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
				//fixed3 worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
				//fixed3 worldNormal = mul((float3x3)unity_ObjectToWorld, v.normal);
				//获取环境光方向
				fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
				//计算漫反射强度
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
				//总强度叠加效果
				o.color = ambient + diffuse;
				return o;
			}

			fixed4 frag (v2f i) : SV_Target
			{
				return fixed4(i.color,1);
			}
			ENDCG
		}
	}
	Fallback "Diffuse"
}

注:在计算光源方向时,应该是光源方向应该是光源位置减顶点位置,但是在真正计算时考虑不同情况,计算方式不同,UnityCG中也存在计算方法,直接用光源位置是一种情况,本文是示例,所以只采用最简单的方法。

4.1.2 逐片元计算

逐片元计算方法与顶点计算方法一致,只是在片元着色器中进行颜色计算,在顶点着色器中将法线传递过来即可,如下所示:

Shader "LL/Light/DiffuseFragment_Lambert"
{
	Properties
	{
		_Diffuse ("Diffuse", Color) = (1,1,1,1)
	}
	SubShader
	{
		Tags { "LightMode"="ForwardBase" }

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

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

			struct a2v
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float4 position : SV_POSITION;
				//float3 color : COLOR;
				float3 worldNormal : NORMAL;
			};

			fixed4 _Diffuse;

			v2f vert (a2v v)
			{
				v2f o;
				o.position = UnityObjectToClipPos(v.vertex);
				//获取法线(统一到世界坐标系下)
				o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
				//获取环境光方向
				//fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
				//计算漫反射强度
				//fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
				//总强度叠加效果
				//o.color = ambient + diffuse;
				return o;
			}

			fixed4 frag (v2f i) : SV_Target
			{
				//获取环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				//获取归一化的法线
				fixed3 worldNormal = normalize(i.worldNormal);
			    //fixed3 worldNormal = i.worldNormal;
			    //获取环境光的方向
			    fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
				//计算光照强度
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
				//叠加
				fixed3 color = ambient + diffuse;
				return fixed4(color,1.0);
			}
			ENDCG
		}
	}
	Fallback "Diffuse"
}

4.2 半兰伯特光照

半兰伯特光照其实还是根据兰伯特定律进行的另外一种光照经验公式。
漫反射颜色 = 光源颜色 x 材质的漫反射颜色 x( A*Dot(法线,指向光源的方向) + B)
其他参数见4.1,A和B为两个参数值,当其均为0.5时,则相当于将法线和光源方向的点积结果映射到0-1范围内。

4.2.1 半兰伯特光照计算

此方法只给出在片元着色器中的计算,跟4.1差别在于计算漫反射颜色方式有差别。

Shader "LL/Light/DiffuseFragment_HalfLambert"
{
	Properties
	{
		_Diffuse ("Diffuse", Color) = (1,1,1,1)
	}
	SubShader
	{
		Tags { "LightMode"="ForwardBase" }

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

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

			struct a2v
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float4 position : SV_POSITION;
				float3 worldNormal : NORMAL;
			};

			fixed4 _Diffuse;

			v2f vert (a2v v)
			{
				v2f o;
				o.position = UnityObjectToClipPos(v.vertex);
				//获取法线(统一到世界坐标系下)
				o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);

				return o;
			}

			fixed4 frag (v2f i) : SV_Target
			{
				//获取环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				//获取归一化的法线
				fixed3 worldNormal = normalize(i.worldNormal);
				//fixed3 worldNormal = i.worldNormal;
			    //获取环境光的方向
			    fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
				//计算半lambert值
				fixed halfLambert = dot(worldNormal, worldLight) * 0.5 + 0.5;
				//计算光照强度
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
				//叠加
				fixed3 color = ambient + diffuse;
				return fixed4(color,1.0);
			}
			ENDCG
		}
	}
	Fallback "Diffuse"
}

5.高光反射Specular

高光反射本质上还是计算一个颜色值,其计算公式如下所示:
高光反射颜色 = 入射光颜色 X 高光反射颜色 X max(0,Dot(视角方向,反射方向))^反射系数

其中:
视角方向可以通过摄像机与当前顶点位置计算,如下所示:

_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz

反射方向通过入射光方向与法线方向即可计算,如下所示:

				fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
				//fixed3 worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
				//fixed3 worldNormal = mul((float3x3)unity_ObjectToWorld, v.normal);
				//获取环境光方向
				fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));

5.1 逐顶点计算

Shader "LL/Light/SpecularVertex"
{
	Properties
	{
		_Diffuse ("Diffuse", Color) = (1,1,1,1)
		_Specular ("Specular", Color) = (1,1,1,1)
		_Gloss ("Gloss",Range(8.0,256)) = 20
	}
	SubShader
	{
		Tags { "LightMode"="ForwardBase" }

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

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

			struct a2v
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float4 position : SV_POSITION;
				float3 color : COLOR;
			};

			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;

			v2f vert (a2v v)
			{
				v2f o;
				o.position = UnityObjectToClipPos(v.vertex);
                //获取环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				//获取法线(统一到世界坐标系下),不做归一化效果要优于归一化。
				fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
				//fixed3 worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
				//fixed3 worldNormal = mul((float3x3)unity_ObjectToWorld, v.normal);
				//获取环境光方向
				fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
				//计算漫反射强度
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));

				//计算反射方向
				fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));
				//获取视线方向
				fixed3 view = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
				//计算高光反射
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,view)),_Gloss);
				//总强度叠加效果
				o.color = ambient + diffuse + specular;
				return o;
			}

			fixed4 frag (v2f i) : SV_Target
			{
				return fixed4(i.color,1);
			}
			ENDCG
		}
	}
	Fallback "Specular"
}

5.2 逐像素计算

Shader "LL/Light/SpecularFragment"
{
	Properties
	{
		_Diffuse("Diffuse", Color) = (1,1,1,1)
		_Specular("Specular", Color) = (1,1,1,1)
		_Gloss("Gloss",Range(8.0,256)) = 20
	}
		SubShader
	{
		Tags { "LightMode" = "ForwardBase" }

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

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

			struct a2v
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float4 position : SV_POSITION;
				float3 normal : NORMAL;
				float3 worldPos : TEXCOORD0;
			};

			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;

			v2f vert(a2v v)
			{
				v2f o;
				o.position = UnityObjectToClipPos(v.vertex);
				o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
				o.normal = mul(v.normal,unity_WorldToObject);
				//获取环境光
				//fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				//获取法线(统一到世界坐标系下)
				//fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
				//fixed3 worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
				//fixed3 worldNormal = mul((float3x3)unity_ObjectToWorld, v.normal);
				//获取环境光方向
				//fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
				//计算漫反射强度
				//fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));

				//计算反射方向
				//fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));
				//获取视线方向
				//fixed3 view = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
				//计算高光反射
				//fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,view)),_Gloss);
				//总强度叠加效果
				//o.color = ambient + diffuse + specular;
				return o;
			}

			fixed4 frag(v2f i) : SV_Target
			{
				//获取环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                //获取法线(统一到世界坐标系下)
                fixed3 worldNormal = normalize(i.normal);
                //获取环境光方向
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                //计算漫反射强度
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
                //计算反射方向
                fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));
                //获取视线方向
                fixed3 view = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
                //计算高光反射
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,view)),_Gloss);
                return fixed4(ambient + diffuse + specular,1);
		    }
		    ENDCG
	    }
	}
	Fallback "Specular"
}

5.3 Blinn-Phong光照模型

高光反射颜色值还可以采用如下方法计算,其计算公式如下所示:
高光反射颜色 = 入射光颜色 X 高光反射颜色 X max(0,Dot(法向量,视角光照向量))^反射系数

其中:
视角光照向量是视角方向与光照方向相加归一后的法向量。

                //获取法线(统一到世界坐标系下)
                fixed3 worldNormal = normalize(i.normal);
                //获取环境光方向
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                //获取视线方向
                fixed3 view = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
				fixed halfDir = normalize(worldLight + view);
				//计算BlinnPhong高光反射
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal, halfDir)), _Gloss);

6.结语

以上为基本的光照模型,且计算均为单一情况测试,考虑不充分,只是用来学习。

原文地址:https://www.cnblogs.com/llstart-new0201/p/11965525.html

时间: 2024-11-14 13:04:20

(四)Shader中基本光照模型的相关文章

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

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

火云开发课堂 - 《Shader从入门到精通》系列 第四节:在Shader中使用纹理

<Shader从入门到精通>系列在线课程 第四节:在Shader中使用纹理 视频地址:http://edu.csdn.net/course/detail/1441/22668?auto_start=1 交流论坛:http://www.firestonegames.com/bbs/forum.php 工程下载地址:请成为正式学员获取工程 课程截图: 项目实例: 版权声明:本文为博主原创文章,未经博主允许不得转载.

【Unity Shaders】Shader中的光照

写在前面 自己写过Vertex & Fragment Shader的童鞋,大概都会对Unity的光照痛恨不已.当然,我相信这是因为我们写得少...不过这也是由于官方文档对这方面介绍很少的缘故,导致我们无法自如地处理很多常见的光照变量.这篇我们就来讨论下Unity内置的一些光照变量和函数到底怎么用. 以下内容均建立在Forward Rendering Path的基础上. 自己总结的,如果有硬伤一定要告诉我啊!感激不尽~ 主要参考: http://en.wikibooks.org/wiki/Cg_P

Unity3d中BlinnPhong光照模型注解

[狗刨学习网] /** 版本:0.1 最后修改:2012-08-10 撰写:李现民 Unity3D中主要用到的shader(着色器)有三种,除了常规的vertex shader与fragment shader外,还有一类称为surface shader.Unity3D自带文档中关于surface shader的介绍非常少,除了有一些示例外,surface shader的结构定义,调用时机,参数的含义都被略了,对我等初学者而言,这无疑加大了学习难度. 前两天在网上查相关资料时,偶然发现了一篇文章,

内存四区中全局区见解

今天我把自己对于内存四区中全局区见解写出来,希望可以帮到大家,同时也希望指出我的不正! 可能有些人不解,我不是想通过,内存四区引入指针么?怎么上来就用指针? 这一点我要说明一下,我用指针是为了更清楚的表明我对于内存四区的见解,是想让大家明白,内存四区是什么?栈区,堆区和全局区之间的关系! 正如同在栈区和在堆区分配的内存空间是不同的!指针里存的是地址,地址是哪里的地址?是内存上的地址!而如果我们分不清我们的指针到底指向的是哪一个区域的地址,调用起来岂不是很麻烦.比如我们在子函数上分配了一个变量(栈

火云开发课堂 - 《Shader从入门到精通》系列 第八节:在Shader中实现黑白滤镜

<Shader从入门到精通>系列在线课程 第七节:在Shader中实现黑白滤镜 视频地址: http://edu.csdn.net/course/detail/1441/22672?auto_start=1 交流论坛:http://www.firestonegames.com/bbs/forum.php 工程下载地址:请成为正式学员获取工程 课程截图: 版权声明:本文为博主原创文章,未经博主允许不得转载.

java编程思想第四版中net.mindview.util包下载,及源码简单导入使用

在java编程思想第四版中需要使用net.mindview.util包,大家可以直接到http://www.mindviewinc.com/TIJ4/CodeInstructions.html 去下载,并按照所在页面的操作进行操作.当然也可以直接我下载下面的链接,下载的直接是JAR包,可以直接导入并使用: net.mindview.util包:百度网盘:点击下载  密码: ggpi java编程思想第四版源码:百度网盘:点击下载  密码: ur3e 下面我简单的介绍一下源码在Eclipse中的导

unity, multi pass shader中的surface pass

今天尝试写一个multi pass shader,但其中有一个Pass是surface pass,总是莫名奇妙地报错.后来看到下面帖子: http://forum.unity3d.com/threads/achieving-a-multi-pass-effect-with-a-surface-shader.96393/ 此帖3楼中写道:“ You can add arbitrary passes while using a surface shader. You just stick every

Unity Shaders and Effects Cookbook (7-2) Surface Shader 中实现 顶点动画

上一节中说了,在 Surface Shader 中,添加顶点函数,我们可以在 顶点函数中获取到 顶点数据,比如顶点颜色.顶点坐标等. 这一节学习获取顶点坐标,并且修改顶点坐标,来实现顶点动画. 简单介绍原理: 在顶点函数中,获取到顶点坐标 vertex,然后,求float offsetY = sin(vertex.x) ,然后将 offsetY 加到 vertex.y 上,这样就把原来的平面 ,变成了 正弦 波浪. 然后再使用之前学过的 内置变量 _Time ,算式变为 float offset