参考 《unity shader 入门精要》
之前学过了,这几天回顾发现忘了一干二净,现在整理出来
高光反射光照模型分为Phong模型和Blinn-Phong模型,而光照实现方式又分为逐像素光照和逐顶点光照
Phong和Blinn-Phong是计算镜面反射光的两种光照模型,两者仅仅有很小的不同之处。
0X01 Phong光照模型(高光反射) |
Phone模型计算中的一个关键步骤就是反射向量R的计算:
上图中的位于表面“下面”的向量 ‘I’ 是原始 ‘I’ 向量的拷贝,并且二者是一样的,现在我们的目标计算出向量 ‘R’ 。根据向量相加原则,向量 ‘R’ 等于 ‘I’ + ‘V’,‘I’ 是已知的,所以我们需要做的就是找出向量 ‘V’。注意法向量 ‘N’ 的负方向就是 ‘-N’,我们可以在 ‘I’ 和 ‘-N’ 之间使用一个点乘运算就能得到 ‘I’ 在 ‘-N’ 上面的投影的模。这个模正好是 ‘V’ 的模的一半,由于 ‘V’ 与 ‘N’ 有相同的方向,我们可以将这个模乘上 ‘N’ (其模为 1 )再乘上 2 即可得到 ‘V’。总结一下就是下面的公式:
根据公式实现自己的myReflect函数,和unity shader自带的reflect一样
//反射 float3 myReflect(float3 I,float3 normal) { //return I - 2*normal*dot(normal,I); return I - 2*normal*mul(I,float3x1( normal.x,normal.y,normal.z)); }
关键代码:
fixed3 reflectDir = normalize(myReflect(-worldLightDir, worldNormal)); // Get the view direction in world space fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz); // Compute specular term fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(reflectDir, viewDir)), _Gloss);
unity shader实现:
//Phong 高光光照模型 逐顶点 Shader "Book/06.SpecularPhongVert" { 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; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; fixed3 color : COLOR; }; //反射 float3 myReflect(float3 I,float3 normal) { //return I - 2*normal*dot(normal,I); return I - 2*normal*mul(I,float3x1( normal.x,normal.y,normal.z)); } v2f vert(a2v v) { v2f o; // Transform the vertex from object space to projection space o.pos = UnityObjectToClipPos(v.vertex); // Get ambient term fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // Transform the normal from object space to world space fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject)); // Get the light direction in world space fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); // Compute diffuse term fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal, worldLightDir)); // Get the reflect direction in world space //fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal)); fixed3 reflectDir = normalize(myReflect(-worldLightDir, worldNormal)); // Get the view direction in world space fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz); // Compute specular term fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(reflectDir, viewDir)), _Gloss); o.color = ambient + diffuse + specular; return o; } fixed4 frag(v2f i) : SV_Target { return fixed4(i.color, 1.0); } ENDCG } } FallBack "Specular" }
//Phong 高光光照模型 逐像素 Shader "Book/06.SpecularPhongPixel" { 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; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 worldPos : TEXCOORD1; }; //反射 float3 myReflect(float3 I,float3 normal) { //return I - 2*normal*dot(normal,I); return I - 2*normal*mul(I,float3x1( normal.x,normal.y,normal.z)); } v2f vert(a2v v) { v2f o; // Transform the vertex from object space to projection space o.pos = UnityObjectToClipPos(v.vertex); // Transform the normal from object space to world space o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject); // Transform the vertex from object spacet to world space o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; return o; } fixed4 frag(v2f i) : SV_Target { // Get ambient term fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); // Compute diffuse term fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal, worldLightDir)); // Get the reflect direction in world space //fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal)); fixed3 reflectDir = normalize(myReflect(-worldLightDir, worldNormal)); // Get the view direction in world space fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); // Compute specular term fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(reflectDir, viewDir)), _Gloss); return fixed4(ambient + diffuse + specular, 1.0); } ENDCG } } FallBack "Specular" }
0X02 Blinn-Phong光照模型(高光反射) |
Phong模型中计算反射光线的向量是一件相对比较耗时的任务,因此Blinn-Phong对这一点进行了改进。
Ks:物体对于反射光线的衰减系数
N:表面法向量
H:光入射方向L和视点方向V的中间向量
Shininess:高光系数
可见,通过该式计算镜面反射光是符合基本规律的,当视点方向和反射光线方向一致时,计算得到的H与N平行,dot(N,H)取得最大;当视点方向V偏离反射方向时,H也偏离N。
同时H的计算比起反射向量R的计算简单的多,R向量的计算需要若干次的向量乘法与加法,而H的计算仅仅需要一次加法。
关键代码:
fixed3 halfDir = normalize(worldLightDir + viewDir); // Compute specular term fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
unity shader基本实现:
//Blinn-Phong 高光光照模型 Shader "Book/06.SpecularBlinnPhong" { 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; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 worldPos : TEXCOORD1; }; v2f vert(a2v v) { v2f o; // Transform the vertex from object space to projection space o.pos = UnityObjectToClipPos(v.vertex); // Transform the normal from object space to world space o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject); // Transform the vertex from object spacet to world space o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; return o; } fixed4 frag(v2f i) : SV_Target { // Get ambient term fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); // Compute diffuse term fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); // Get the view direction in world space fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); // Get the half direction in world space fixed3 halfDir = normalize(worldLightDir + viewDir); // Compute specular term fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss); return fixed4(ambient + diffuse + specular, 1.0); } ENDCG } } FallBack "Specular" }
所有效果:左边Phong逐顶点光照,中间Phong逐像素光照,右边Blinn-Phong光照
关于法线从模型空间转换到世界空间o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);后面补充为什么这么计算
参考文章:
Phong和Blinn-Phong光照模型