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

  

讨论完漫反射以后,接下来确定就是镜面反射了
在开始镜面反射shader的coding以前,要扩充一下前面提到的知识,加深理解镜面反射与漫反射的区别。
注:这篇文章实现的镜面反射是逐顶点着色(per-vertex lighting),最后效果图能够看到高亮区域并不光滑,更光滑的着色方式请看系列10逐像素着色(per-pixcel lighting),又称冯氏着色
引用一下一位前人博文中的一些基础概念,特别是关于冯氏反射模型的:
平行光(directional light)
一种是从特定方向射入并只会照亮面对入射方向的物体,咱们称之为平行光(directional light)。函数

环境光(ambient light)
另外一种光是来自全部方向而且会照亮全部物体,无论这些物体的朝向如何,咱们称之为环境光(ambient light)。固然在真实世界里,这只是平行光照到其余物体上,好比空气、灰尘等等,而后反射出来的散射而已。可是在这里,咱们须要把它单独做为一个光照模型列出来。
漫反射(Diffuse)
不管光的入射角度如何,都会向全部方向发生反射。反射光的亮度只和光线的入射角度有关,与观察角度无关。光线越平行于物体表面,则反射光越弱,表面越暗;光线越垂直于表面,反射光越强,表面越亮。漫反射是咱们一般想到一个物体受到光照时须要首先想到的。
镜面反射(Specular)
这就像镜子同样,反射光将按照和入射角相同的角度反射出来。这种状况下,你看到的物体反射出来的光的亮度,取决于你的眼睛和光反射的方向是否在同一直线上;也就是说,反射光的亮度不只与光线的入射角有关,还与你的视线和物体表面之间的角度有关。镜面反射一般会形成物体表面上的“闪烁”和“高光”现象,镜面反射的强度也与物体的材质有关,无光泽的木材不多会有镜面反射发生,而高光泽的金属则会有大量镜面反射。orm

P.S.:能够看出除了环境光是咱们计算机图形学中抽象出来的虚拟现象,其余3种现象都是真实存在的
冯氏反射模型(Phong Rlection Model)
冯氏反射模型引伸了这个四步走的光照系统,首先全部的光线都有如下两个属性:对象

发生漫反射光的RBG值。
发生镜面反射光的RGB值。blog

其次全部材质都有如下四个属性input

反射的环境光RGB值
反射的漫反射光RGB值
反射的镜面反射光RGB值
物体的反光度,它决定了镜面反射的细节数学

每条光线咱们都须要知道两个属性,每一个物体表面上的点都须要4个属性it

因此在咱们讨论镜面反射光的时候要明白镜面反射光的渲染效果跟观察者的观察角度已经有了直接联系。
在Unity中,环境光能够在菜单Edit > Render Settings中开启,而untiy中的Cg函数已经提早内置了环境光uniform参数UNITY_LIGHTMODEL_AMBIENTio

镜面反射与观察视角的联系form

系列6中我已经说明了材料表面的平整程度决定了镜面反射的明显与否,现实生活中找不到绝对平的物体表面,因此咱们引入一个概念,每一种材料的表面的平整程度为Nshininess, n越大越平整,越小越粗糙,理想状态下n无穷大的时候是绝对的镜面反射,也就是前面引用的文字中所说的你想看到光源,则必须从光线的反射角彻底重合去看。基础

 

 

结合上图,也就是说咱们的材料表面越平整,系数n趋近于无穷大的时候,想要看到光源,则必须从射线R所在的方向去看。
当材料表面的粗糙程度更大时,即便咱们在R附近,也能看到部分光源。
现实世界中的材料每每是这样的,也就是从R附近的观察这个物体时,都能看到光源的影像,至于这个附近的范围有多大,取决于材料的平整程度。
反射向量R与法向量N和入射向量L的数学关系为:

R=2N(N·L)-L (ps:N与L是点乘关系)

在Unity中,Cg内置了2个函数来计算这个关系:
float3 reflect(float3 I, float3 N)
float4 reflect(float4 I, float4 N)
其中I即为入射向量

前面废话了一大堆,这里终于能够说出我想说的了:
观察向量V越接近R,则镜面反射越明显;
观察向量V越远离R,镜面反射越不明显;
缘由就在于世界上没有绝对平整的材料。
因此咱们终于以数学公式给出冯氏反射模型:

 

 

其中I specular即为镜面反射的强度,I incoming为入射光线的颜色向量 k specular为材料的镜面反射光颜色,一般是白色的

至于max(0,R·V)又于系列中的漫反射同理,当R与V的夹角超过90°的时候余弦值为负数,物理意义为观察者从物体的内表面去看外表面了,因此咱们仍是与0取最大值再去作n次方
可见这个数学公式中镜面反射的强度与反射向量和观察向量的夹角呈指数关系

编写Shader

前面已经提到Cg中已经为咱们提供了环境光参数,因此咱们只须要将这个参数与光源的颜色向量作矢量相乘获得环境光的颜色向量:

float3 ambientLighting = float3(UNITY_LIGHTMODEL_AMBIENT) * float3(_Color);

漫反射光 diffuseReflection 上文中已经计算好。

接下来咱们要计算V,没有V的话咱们的shader就没法由观察者的视角不一样产生镜面反射的效果变化:

观察向量的单位向量计算过程为 MainCamer在世界坐标系中的3维坐标(由内置uniform参数_WorldSpaceCameraPos)与 顶点的世界坐标系中的坐标作矢量相减,最后单位化。
step1:将顶点坐标从对象坐标系变换至世界坐标系:

float 4 vertexPos=mul(_Object2World,input.vertex);

step2:将两个坐标矢量相减(ps,_Object2World是4X4矩阵,因此变换后的顶点坐标是4维的,mianCamera的坐标须要补充一维,减完后再去掉无心义的那一维)

float4 direction=float4(_WorldCameraPos,1.0)-vertexPos;

stpe3:减去无心义的第四维,而后单位长度化:

float3 viewDirection=normalize(float3(direction));

而后是入射向量L与法向量N:
//平行光源传递过来的_WorldSpaceLightPos0参数是直接就是方向,而点光源传递过来的是位置,关于点光源咱们后面再深刻

float3 lightDirection=normalize(float3(_WorldSpaceLightPos0));

//将法向量变换至对象坐标系获得N

float3 normalDirection=normalize(float3(mul(float4(input.normal, 0.0), _World2Object)));

至此咱们计算好了观察向量V,入射向量L,法向量N,根据上面的公式I=I*k*max(0,R·V)^n,R由内置函数reflect(-L,N)计算出,咱们能够计算镜面反射强度了(指数n _Shininess咱们经过shader的property定义,以便在inspector中调节不一样材料的光泽程度,同理还有镜面反射光颜色_SpecColor):

float3 specularReflection=float3(_LightColor0)*float3(_SpecColor)*pow(max(0.0,dot(reflect(-lightDirection, normalDirection),viewDirection)),_Shininess);

有了环境光、漫反射光、镜面反射光3个颜色向量以后,根据冯氏镜面反射模型进行相加获得接近真实的物体表面反射效果颜色:
//alpha仍是设置为1不透明度
output.col = float4(ambientLighting + diffuseReflection+ specularReflection, 1.0);

最终代码为:

Shader "Custom/CustomSpecular" {
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 col : COLOR;
};

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

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

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

//观察向量V由摄像机坐标与顶点坐标矢量相减
float3 viewDirection = normalize(float3(float4(_WorldSpaceCameraPos, 1.0)
- mul(modelMatrix, input.vertex)));

//镜面反射光的计算
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:

output.col = float4(ambientLighting + diffuseReflection+ specularReflection, 1.0);
//国际惯例,顶点变化三步曲
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);

return output;
}

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

}

ENDCG
}
}
FallBack "Diffuse"
}

用新的shader再建立一个材质球,再建立一个球体,与以前的2个球体进行比较。(本例的shader仍是只有一个ForwardBase的单光源Shader,可是相信看到这里您应该会把这个例子也作成多光源了)

 

 

 

 

 

 

可见咱们的新shader 会根据观察者的视角表面的颜色随之变化了

相关文章
相关标签/搜索