本文由@唐三十胖子出品,转载请注明出处。
文章链接:https://blog.csdn.net/iceSony/article/details/84349017
这篇文章将总结和提炼《Unity Shader入门精要》的第六章“Unity中的基础光照”的内容。
通过这篇文章,你可以知道
1)Phong公式介绍
2)高光反射的逐顶点光照模型
3)高光反射的逐像素光照模型
4)高光反射的Blinn-Phong光照模型
一.Phong公式介绍
Phong公式:高光反射颜色=光的颜色*高光反射系数*max(0,反射光在视角方向的投影)^反射度
(ShaderLab编写中,高光反射系数与反射度都是在属性中定义的)
那么反射光与视角方向怎么获取呢?
通过上一章节的学习我们知道NORMAL语义是模型空间法线,_WorldLightPos0是世界空间光照方向
可以通过内置函数reflect(法线,光照方向)
由于获取反射方向由于光线i是指向顶点的所以我们relect最后要加个负号:)
视角方向其实Unity中也有相关获取方式:_WorldSpaceCameraPos.xyz
好了有了这些参数我们开始实现吧。
二.高光反射的逐顶点光照
第一步设置属性
Properties { _Specular("反射系数",Color) = (1.0,1.0,1.0,1.0) _Gloss("高光区域",Range(8, 256)) = 20 }
第二步设置pass的调用与光照模式(结构体和漫反射光照没什么区别)
Pass { Tags{ "LightMode" = "ForwardBase"} CGPROGRAM #pragma vertex vert #pragma fragment frag #include"Lighting.cginc" float4 _Diffuse; float4 _Specular; float _Gloss; struct a2v { float4 pos : POSITION; float3 normal :NORMAL; }; struct v2f { float4 pos : SV_POSITION; float3 color :COLOR; };
重点来了顶点着色器编写
v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.pos); float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; float3 normal2 = (mul(v.normal, (float3x3)unity_WorldToObject)); float3 lightDir = (_WorldSpaceLightPos0.xyz); float3 reflectDir = -(reflect(lightDir, normal2)); float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - o.pos); float3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(reflectDir, viewDir)), _Gloss); o.color = ambient + specular; return o; }
lightDir 获取光照方向、reflectDir 通过法线与光照方向获取反射方向、_WorldSpaceCameraPos代表世界坐标下摄像机位向量,再减去世界坐标下的顶点向量,得到的就是视角方向viewDir
唯一需要注意的是视角方向viewDir必须加获取单位向量,很明显这里的pow代表的是颜色的深度,如果你viewDir不加限制会影响数据的!
最后效果
因为没有添加漫反射的原因,所以物体默认是黑色的
和漫反射类似的问题,高光部分不平滑,原因是逐顶点计算是非线性的。
完整代码如下
Shader"sony/Shader150" { Properties { _Specular("反射系数",Color) = (1.0,1.0,1.0,1.0) _Gloss("高光区域",Range(8, 256)) = 20 } SubShader { Pass { Tags{ "LightMode" = "ForwardBase"} CGPROGRAM #pragma vertex vert #pragma fragment frag #include"Lighting.cginc" float4 _Diffuse; float4 _Specular; float _Gloss; struct a2v { float4 pos : POSITION; float3 normal :NORMAL; }; struct v2f { float4 pos : SV_POSITION; float3 color :COLOR; }; v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.pos); float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; float3 normal2 = (mul(v.normal, (float3x3)unity_WorldToObject)); float3 lightDir = (_WorldSpaceLightPos0.xyz); float3 reflectDir = -(reflect(lightDir, normal2)); float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - o.pos);//必须加normalize float3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(viewDir,reflectDir)), _Gloss); o.color = ambient + specular; return o; } fixed4 frag(v2f i) : SV_Target { return fixed4(i.color,1.0); } ENDCG } } FallBack "Diffuse" }
三.逐像素光照
先看实现,右边逐像素光照看起来润滑很多
结构体的修改
struct v2f { float4 pos : SV_POSITION; float3 texcoord :TEXCOORD0; float3 texcoord1 :TEXCOORD1; };
函数的修改
v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.pos); o.texcoord = (mul(v.normal, (float3x3)unity_WorldToObject)); o.texcoord1 = mul(unity_ObjectToWorld, v.normal).xyz; return o; } fixed4 frag(v2f i) : SV_Target { float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; float3 lightDir = (_WorldSpaceLightPos0.xyz); float3 reflectDir = -(reflect(lightDir, i.texcoord)); float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.texcoord1);//必须加normalize float3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(viewDir,reflectDir)), _Gloss); return fixed4(ambient + specular,1.0); }
注意这里的结构体里面删掉了颜色,改变成TEXCOORD0
如果不知道什么是TEXCOORD的点击☞ https://blog.csdn.net/iceSony/article/details/84309811
这里其实也可以指定类型为NORMAL
但是如果texcoord:NORMAL ,texcoord1:NORMAL ,这样编辑器会报错
所以为了指定float3类型我们常用TEXTCOORD
完整代码
Shader"sony/Shader152" { Properties { _Specular("反射系数",Color) = (1.0,1.0,1.0,1.0) _Gloss("高光区域",Range(8, 256)) = 20 } SubShader { Pass { Tags{ "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include"Lighting.cginc" float4 _Diffuse; float4 _Specular; float _Gloss; struct a2v { float4 pos : POSITION; float3 normal :NORMAL; }; struct v2f { float4 pos : SV_POSITION; float3 texcoord :NORMAL; float3 texcoord1 :TEXCOORD1; }; v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.pos); o.texcoord = (mul(v.normal, (float3x3)unity_WorldToObject)); o.texcoord1 = mul(unity_ObjectToWorld, v.normal).xyz; return o; } fixed4 frag(v2f i) : SV_Target { float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; float3 lightDir = (_WorldSpaceLightPos0.xyz); float3 reflectDir = -(reflect(lightDir, i.texcoord)); //必须加normalize float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.texcoord1); float3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(viewDir,reflectDir)), _Gloss); return fixed4(ambient + specular,1.0); } ENDCG } } FallBack "Diffuse" }
四.Blinn-Phong光照模型
最后来介绍一种真实开发中使用的高光反射模型
逐顶点光照的改版frag函数,代码修改仅两行
float3 halfDir = normalize(lightDir + viewDir); float3 specular=_LightColor0.rgb*_Specular.rgb*pow(saturate(dot(halfDir,reflectDir)), _Gloss);
这种光照模型计算最贴近真实情况
代码如下
Shader"sony/Shader154" { Properties { _Specular("反射系数",Color) = (1.0,1.0,1.0,1.0) _Gloss("高光区域",Range(8, 256)) = 20 } SubShader { Pass { Tags{ "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include"Lighting.cginc" float4 _Diffuse; float4 _Specular; float _Gloss; struct a2v { float4 pos : POSITION; float3 normal :NORMAL; }; struct v2f { float4 pos : SV_POSITION; float3 texcoord :NORMAL; float3 texcoord1 :TEXCOORD1; }; v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.pos); o.texcoord = (mul(v.normal, (float3x3)unity_WorldToObject)); o.texcoord1 = mul(unity_ObjectToWorld, v.normal).xyz; return o; } fixed4 frag(v2f i) : SV_Target { float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; float3 lightDir = (_WorldSpaceLightPos0.xyz); float3 reflectDir = -(reflect(lightDir, i.texcoord)); float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.texcoord1);//必须加normalize float3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(viewDir,reflectDir)), _Gloss); return fixed4(ambient + specular,1.0); } ENDCG } } FallBack "Diffuse" }
事实上除了使用mul矩阵计算来转化向量空间,UNITY也提供一些CG函数帮助计算
下一章节我们将介绍Shader编程的纹理相关知识,毕竟好的效果离不开图片的支撑。
Excelsior!