您当前的位置: 首页 >  unity

野奔在山外的猫

暂无认证

  • 5浏览

    0关注

    85博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【Shader笔记】Unity基础光照

野奔在山外的猫 发布时间:2021-05-13 14:27:51 ,浏览量:5

参考书籍:Unity Shader入门精要

一、认识光照 1.1 光源

实时渲染中,通常光源为一个没有体积的点,用 l {l} l表示其光照方向。

1.1.1 如何量化光

答:使用辐照度。

  • 对平行光,通过计算在垂直于 l {l} l的单位面积上单位时间内穿过的能量得到 默认方向的矢量为1,如下为理解图: 在这里插入图片描述
1.2 吸收和散射

光线由光源发射,与一些物体相交产生的结果:散射(scattering) 和 吸收(absorption)。

改变方向改变颜色改变密度散射√吸收√√ 1.2.1 散射

光线经物体表面散射后,有两种方向:

  • 散射到外部:折射(refraction)或 透射(transmission)
  • 散射到内部:反射(reflaction) 对于不透明物体,到内部的光线继续与内部颗粒相交。部分光线最后会重新发射出物体表面,另一部分被吸收。如下为理解图: 在这里插入图片描述
1.2.2 在Unity中区分散射方向

高光反射(specular):表示物体表面是如何反射光线的。 漫反射(diffuse):表示多少光线会被折射、吸收和三射出表面。

根据入射光线数量和方向,计算出射光线的数量和方向,使用出射度(exitnce)来描述。

1.3 着色

根据材质属性(如漫反射属性等)、光源信息(如光源方向、辐照度等),使用一个等式去计算沿某个观察方向的出射度的过程。这个等式也成为光照模型(Lighting Model)。

1.4 BRDF光照模型

BDRF(Bidirectional Reflectance Distribution Function):回答物体表面与光线是如何交互。

二、标准光照模型

只关心直接光照(direct light),即经过物体表面一次反射直接进入摄像机的光线。它将进入摄像机的光线分为 自发光、高光反射、漫反射、环境光 共四部分。

2.1 自发光(emissive)

解释:给定一方向后,表面本身向该方向发射多少辐射量。

注意:未使用全局光照(global illumination)情况下,自发光的表面不会照亮周围物体,仅本身显得更亮

2.2 高光反射 (specular)

解释:当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量

2.3 漫反射(diffuse)

解释:当光线从光源照射到模型表面,该表面会向每个方向散射多少辐射量

符合兰伯特定律:反射光线的强度与表面发现和光源方向之间的夹角的余弦值成正比。 c d i f f u s e = ( c l i g h t ∗ m d i f f u s e ) m a x ( 0 , n ∗ I ) {c_{diffuse} = (c_{light} *m_{diffuse})max(0, n*I)} cdiffuse​=(clight​∗mdiffuse​)max(0,n∗I)

  • n:表面法线
  • I {I} I:光源的单位矢量
  • m d i f f u s e {m_{diffuse}} mdiffuse​:材质的漫反射颜色
  • c l i g h t {c_{light}} clight​:光源颜色

注意:防止和避免法线和光源方向点乘的结果为负值。故使用 m a x ( 0 , n ∗ I ) {max(0,n*I)} max(0,n∗I)

2.4 环境光(ambient)

描述其他所有的间接光照。

间接光照:在多个物体之间反射,最后进入到摄像机。

2.5 逐像素与逐顶点

在片元着色器中计算:逐像素光照(per-pixel lighting) 在顶点着色器中计算:逐顶点光照(per-vertex lighting)

三、Unity Shader实现漫反射光照模型

漫反射计算公式 c d i f f u s e = ( c l i g h t ∗ m d i f f u s e ) m a x ( 0 , n ∗ I ) {c_{diffuse}=(c_{light}*m_{diffuse})max(0, n*I)} cdiffuse​=(clight​∗mdiffuse​)max(0,n∗I)

  • 入射光线的颜色和强度 c l i g h t {c_{light}} clight​
  • 材质的漫反射系数 m d i f f u s e {m_{diffuse}} mdiffuse​
  • 表面法线 n {n} n
  • 光源方向 I {I} I

函数:saturate( x x x) 参数 x x x:用于操作的标量或矢量,可以是float、float2、float3等类型 描述:把 x {x} x截取在[0, 1]范围内,如果 x {x} x是一个矢量,那么会对它的每一个分量进行这样的操作。

函数:reflect( i , n i, n i,n) 参数 i i i:入射方向,可以是float、float2、float3等类型 参数 n n n:法线方向,可以是float、float2、float3等类型 描述:当给定入射方向 i i i和法线方向 n n n时,reflect函数可以返回反射方向

3.1 逐顶点光照 实践
  • Window -> Lighting -> Skybox 设置天空盒材质为null
  • 新建材质并命名"DiffuseVertexLevelMat"
  • 创建 -> 着色器 -> 标准表面着色器,命名为"Chapter6-DiffuseVertextLevel"
Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level" {
	Properties {
		_Diffuse ("Diffuse", Color) = (1,1,1,1)
	}
	
	SubShader {
		Pass {
			Tags { "LightMode"="ForwardBase" }
	
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			
			fixed4 _Diffuse

			struct a2v {
				float4 vertex : POSITION;
				float4 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				fixed3 color : COLOR;
			};

			v2f vert(a2v v) {
				v2f o;
				//从模型空间转换到裁剪空间
				o.pos = UnityObjectToClipPos(v.vertex);
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				fixed3 worldNormal = normalize(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 {
				return fixed4(i.color, 1.0);
			}
			ENDCG
		}
	}
	FallBack "Diffuse"
}
  • 添加一个Direction Light,并观察效果图,如下所示: 在这里插入图片描述
3.1.1 属性 准备

Properties{}语义块中声明Color属性,用于控制漫反射颜色。

Properties {
	_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
3.1.2 标签 LightMode

顶点着色器需写在Pass{}语义块,指明该Pass{}的光照模式

Tags { "LightMode"="ForwardBase" }
  • LightMode标签:定义该Pass{}在Unity的光照流水线中的角色。 仅定义了正确的LightMode下,才能获取一些Unity的内置光照变量。
3.1.3 定义 顶点/片元着色器

分别定义 [vertex顶点着色器]/[fragment片元着色器] 为 [vert]/[frag]

#pragma vertex vert
#pragma fragment frag
3.1.4 Unity内置文件 Lighting.cginc

Unity提供的内置变量的文件,这里需要Lighting.cginc中的一些变量,故需要包含这文件

#include "Lighting.cginc"
3.1.5 存储 变量

fixed4 _Diffuse:定义一变量与Properties{}中的属性相匹配,由于颜色属性在[0, 1]范围内,故选择fixed4精度存储。

3.1.6 定义 结构体

定义顶点着色器输入结构体:

struct a2v {
	float4 vertex : POSITION;
	float4 normal : NORMAL;
};
  • NORMAL:访问模型顶点的法线变量

定义顶点着色器输出结构体(等同于片元着色器输入结构体):

struct v2f {
	float4 pos : SV_POSITION;
	fixed3 color : COLOR;
};
  • color:接受计算的颜色变量,并未实际使用COLOR语义,部分情况也有使用TEXCOORD0语义。
3.1.7 实现 逐顶点漫反射

为实现漫反射,参照 2.3漫反射,需要以下四个参数:

  • 表面法线 worldNormal
  • 光源方向 worldLight
  • 材质颜色 _Diffuse
  • 光源颜色 LightColor0

代码内容如下

v2f vert(a2v v) {
	v2f o;
	o.pos = UnityObjectToClipPos(v.vertex);
		
	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
	fixed3 worldNormal = normalize(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;
}

在Shader码中:

  • UnityObjectToClipPos():将模型空间坐标转换成裁剪空间坐标。
  • UNITY_LIGHTMODEL_AMBIENT:Unity内置变量,此处为获取环境光部分。
  • unity_WorldToObject:原为_World2Object[已替换],此处为从模型空间到世界空间中的变换矩阵的逆矩阵。
  • _WorldSpaceLightPos0:Unity内置变量,此处为获取光源方向。
  • _LightColor0:Unity内置变量,此处为访问该Pass{}处理的光源颜色和强度信息。 注意:需要定义合适的LightMode标签来获取正确的值
  • normalize():向量归一化(以单位向量表示,仅0或1)
  • _Diffuse:材质的颜色
  • saturate():防取值为0,限制值在[0, 1]
  • dot():矩阵点乘
3.1.8 输出 颜色
fixed4 frag(v2f i):SV_Target {
	return fixed4(i.color, 1.0);
}
  • i.color:颜色,返回的是float3类型,故1.0为透明度
3.1.9 回调(防该SubShader无法运行)
FallBack "Disffuse"
3.2 逐像素光照 实践
  • 新建材质,命名为 DiffusePixelLevelMat
  • 新建Shader,命名为 Chapter6-DiffusePixelLevel,并赋于给上述材质。
  • 添加下述内容:
Shader "Unity Shaders Book/Chapter 6/Diffuse Pixel-Level" {
	Properties {
		_Diffuse ("Diffuse", Color) = (1,1,1,1)
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}
	
	SubShader {
		Pass {
			Tags { "LightMode"="ForwardBase" }
	
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;

			struct a2v {
				float4 vertex : POSITION;
				float4 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
			};

			v2f vert(a2v v) {
				v2f o;
				
				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

				return o;
			}

			fixed4 frag(v2f i) : SV_Target {
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

				fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);

				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

				return fixed4(ambient + diffuse + specular, 1.0);
			}
			ENDCG
		}
	}
	FallBack "Diffuse"
}
  • 添加一Direction Light,观察效果,如下图所示: 在这里插入图片描述
3.2.1 属性 准备
Properties {
	_Diffuse ("Diffuse", Color) = (1,1,1,1)
	_Specular ("Specular", Color) = (1, 1, 1, 1)
	_Gloss ("Gloss", Range(8.0, 256)) = 20
}
  • Diffuse:漫反射颜色
  • Specular:光照颜色
  • Gloss:光滑程度
3.2.2 标签 LightMode

参考3.1.2描述

3.2.3 定义 顶点/片元着色器

参考3.1.3描述

3.2.4 Unity内置文件 Lighting.cginc

参考3.1.4描述

3.2.5 存储 变量
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;

_Diffuse_Specular[同为Color]:选择 fixed4 数据类型存储 _Gloss:选择 float 数据类型存储

3.2.6 定义 结构体

顶点着色器输入:

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

获取模型空间下的 顶点坐标 与 法线坐标。

片元着色器输入:

struct v2f {
	float4 pos : SV_POSITION;
	float3 worldNormal : TEXCOORD0;
	float3 worldPos : TEXCOORD1;
};

获取从顶点着色器转片元着色器的数据,如模型在模型空间下的坐标、法线坐标和纹理坐标。

3.2.7 实现 逐像素光照
v2f vert(a2v v) {
	v2f o;
				
	o.pos = UnityObjectToClipPos(v.vertex);
	o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
	o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

	return o;
}
  • UnityObjectToClipPos():将模型空间坐标 转换为 裁剪空间坐标
  • unity_WorldToObject:原为_World2Object[已替换],此处为从模型空间到世界空间中的变换矩阵的逆矩阵。
  • mul():计算矩阵的乘积
  • normalize():向量归一化(以单位向量表示,仅0或1)
3.2.8 输出 颜色
fixed4 frag(v2f i) : SV_Target {
	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
	fixed3 worldNormal = normalize(i.worldNormal);
	fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

	fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
	fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);

	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

	return fixed4(ambient + diffuse + specular, 1.0);
}
  • UNITY_LIGHTMODEL_AMBIENT:Unity内置变量,此处为获取环境光部分。
  • normalize():向量归一化(以单位向量表示,仅0或1)
  • _LightColor0:Unity内置变量,此处为访问该Pass{}处理的光源颜色和强度信息。 注意:需要定义合适的LightMode标签来获取正确的值
  • _WorldSpaceCameraPos:Unity内置变量,此处为屏幕相机的世界空间的坐标
  • pow(x, y):x的y次方
  • _WorldSpaceLightPos0:Unity内置变量,此处为获取光源方向。
  • saturate():防取值为0,限制值在[0, 1]
  • dot():矩阵点乘
3.2.9 回调

参考 3.1.9

3.3 Blinn-Phong 光照模型

Blinn模型没有使用反射方向,而是引入一个新的矢量 h h h。 计算高光公式如下: c s p e c u l a r = ( c l i g h t ∗ m s p e c u l a r m a x ( 0 , n ∗ h ) ) {c_{specular}=(c_{light}*m_{specular}max(0, n*h))} cspecular​=(clight​∗mspecular​max(0,n∗h))

  • 新建材质,命名 BlinnPhongMat
  • 新建Unity Shader,命名为 Chapter6-BlinnPhone,并赋予以上材质
  • 复制 Chapter6-specularPixelLevel 代码,仅修改以下代码:
fixed4 frag(v2f i) : SV_Target {
	...
	fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.world.xyz);
	fixed3 halfDir = normalize(worldLightDir + viewDir);
	fixed3 specular = _LightColor0.xyz * _Specular.xyz + pow(max(0, dot(worldNormal, halfDir)), _Gloss);
	
	return fixed4(ambient + diffuse + specular, 1.0);
} 
  • 效果如下图所示:

在这里插入图片描述

四、Unity内置函数 UnityCG.cginc 函数名描述float3 WorldSpaceViewDir(float4 v)输入一个模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向float3 UnityWorldSpaceViewDir(float4 v)输入一个世界空间中的顶点坐标,返回世界空间中从该点到摄像机的观察方向float3 ObjSpaceViewDir(float4 v)输入一个模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察方向float3 WorldSpaceLightDir(float4 v)仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向float3 UnityWorldSpaceLightDir(float4 v)仅可用于前向渲染中,输入一个世界空间中的顶点位置。返回世界空间中从该点到光源的光照方向float3 ObjSpaceLightDir(float4 v)仅可用于前向渲染中,输入一个模型空间中的顶点位置。返回模型空间中从该点到光源的光照方向float3 UnityObjectToWorldNormal(float3 norm)把法线方向从模型空间转换到世界空间中float3 UnityObjectToWorldDir(in float3 dir)把方向矢量从模型空间变换到世界空间中float3 UnityWorldToObjectDir(float3 dir)把方向矢量从世界空间变换到模型空间中
  • WorldSpaceViewDir()函数:返回值未被归一化,使用需要先归一。
关注
打赏
1659777066
查看更多评论
立即登录/注册

微信扫码登录

0.1339s