光照模型:用数学的方法模拟现实世界中的光照效果。
场景中模型身上的光反射到相机中的光线:
1.漫反射:产生明暗效果
2.高光反射:产生镜面反射,物体中有最亮且比较耀眼的一部分
3.自发光:
4.环境光:
漫反射
光线被粗糙的表面无规则的向各个方向反射的现象。
漫反射的计算公式(兰伯特光照模型)模拟漫反射
漫反射光 = 光照颜色和强度 * 漫反射颜色 * max(0, dot(法线方向, 光照方向))
光照颜色和强度:Unity引擎提供的,CGIncludes/UnityLightingCommon.cginc,需要引用
漫反射颜色:从属性面板获取的
法线方向:世界空间下的:需要把模型空间下的法线转换到世界空间下
光照方向:从顶点指向光源的CGIncludes/UnityCG.cginc

逐顶点漫反射光照:是把光照计算放在顶点函数里,效果差(过渡区域),效率高
Shader "Lesson/VFVertDiffuse" { Properties{ _DiffuseColor("漫反射颜色", Color) = (1,1,1,1) } SubShader{ Pass { //设置正确的光照渲染模式,前项渲染里的基础 Tags { "LightMode" = "ForwardBase" } //定义Cg的开始和结束 CGPROGRAM #pragma vertex vert//定义顶点函数名字 #pragma fragment frag//定义片元函数名字 #include "Lighting.cginc"//如果想要使用提供一些灯光变量和方法,需要引入文件 //漫反射光 = 光照颜色和强度 * 漫反射颜色 * max(0, dot(法线方向, 光照方向)) //漫反射颜色:从属性面板获取 _DiffuseColor //光照颜色:系统提供的,_LightColor0 //法线方向:世界空间下的:需要把模型空间下的法线转换到世界空间下 worldNormal fixed4 _DiffuseColor;//重定义属性变量 struct m2v { //需要获取顶点坐标和法线向量 float4 vex : POSITION; float3 normal : NORMAL; }; struct v2f { //把光照计算放在顶点函数中 //所以需要把计算之后的漫反射光Color传递到片元函数中 float4 position : SV_POSITION; fixed4 color : COLOR0; }; //实现定点和片元函数 v2f vert(m2v v) { v2f f; f.position = UnityObjectToClipPos(v.vex);//转换顶点坐标 float3 worldNormal = normalize(mul(UNITY_MATRIX_M, v.normal));//normalize归一化向量 float3 worldPosition = mul(UNITY_MATRIX_M, v.vex);//先获取世界空间下的顶点坐标 float3 lightDir = normalize(UnityWorldSpaceLightDir(worldPosition));//UnityWorldSpaceLightDir,获取光照方向 fixed3 color = _LightColor0.xyz * _DiffuseColor.xyz * max(0, dot(worldNormal, lightDir));//漫反射颜色,光照不影响透明度,所以是.xyz没有w f.color = fixed4(color, 1); return f; } fixed4 frag(v2f f) : SV_Target { return f.color; } ENDCG } } FallBack "Diffuse" }
逐像素漫反射光照:是把光照计算放在片元函数里,效果好,效率低
Shader "Lesson/VFFragDiffuse" { Properties{ _DiffuseColor("漫反射颜色", Color) = (1,1,1,1) } SubShader{ Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" fixed4 _DiffuseColor; struct m2v { float4 vex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 position : SV_POSITION; float3 worldNormal : COLOR0;//把世界空间下的法线传递过去 float3 worldPosition : COLOR1;//世界空间下的顶点坐标 }; v2f vert(m2v v) { v2f f; f.position = UnityObjectToClipPos(v.vex); f.worldNormal = normalize(mul(UNITY_MATRIX_M, v.normal));//转换法线向量,变成世界空间下的 f.worldPosition = mul(UNITY_MATRIX_M, v.vex); return f; } fixed4 frag(v2f f) : SV_Target { float3 lightDir = normalize(UnityWorldSpaceLightDir(f.worldPosition)); fixed3 color = _LightColor0.xyz * _DiffuseColor.xyz * max(0, dot(f.worldNormal, lightDir)); return fixed4(color, 1); } ENDCG } } FallBack "Diffuse" }
Pass { //设置正确的光照渲染模式,前项渲染里的基础 Tags { "LightMode" = "ForwardBase" } }
半兰伯特光照模型,解决兰伯特背光面毫无细节的问题。
漫反射光 = 光照颜色和强度 * 漫反射颜色 * (dot(法线方向, 光照方向) * 0.5 + 0.5)
实现半兰伯特光照的逐像素渲染和逐顶点渲染
fixed3 color = _LightColor0.xyz * _DiffuseColor.xyz * (dot(f.worldNormal, lightDir) * 0.5 + 0.5);
高光反射
高光:物体上最亮的部分。
镜面反射:当平行的入射光线射到物体光滑的表面,仍会平行的向一个方向反射出来。
高光(Specular) = 光照颜色和强度 * 高光颜色 * pow(max(0, dot(v, r)), 高光系数)
V:顶点到相机的方向
R:反射光的方向
Shader "Lesson/VFVertSpecular" { Properties{ _DiffuseColor("漫反射颜色", Color) = (1,1,1,1) _SpecularColor("高光颜色", Color) = (1,1,1,1) _Gloss("高光系数", Range(0, 20)) = 0.2 } SubShader{ Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" fixed4 _DiffuseColor; fixed4 _SpecularColor; float _Gloss; struct m2v { float4 vex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 position : SV_POSITION; fixed4 color : COLOR0; }; v2f vert(m2v v) { v2f f; f.position = UnityObjectToClipPos(v.vex); //高光(Specular) = 光照颜色和强度 * 高光颜色 * pow(max(0, dot(v, r)), 高光系数) //V:顶点到相机的方向 UnityWorldSpaceViewDir(worldPosition) //R:反射光的方向 reflect(-lightDir, worldNormal) //光照颜色和强度 _LightColor0 //高光颜色 _SpecularColor //高光系数 _Gloss //世界空间下的法线 float3 worldNormal = normalize(mul(UNITY_MATRIX_M, v.normal)); //世界空间下的顶点坐标 float3 worldPosition = mul(UNITY_MATRIX_M, v.vex); //顶点到光源的方向 float3 lightDir = normalize(UnityWorldSpaceLightDir(worldPosition)); //顶点到相机的方向 float3 cameraDir = normalize(UnityWorldSpaceViewDir(worldPosition)); //反射光的方向 float3 refDir = reflect(-lightDir, worldNormal); //漫反射 半兰伯特 fixed3 diffuse = _LightColor0.xyz * _DiffuseColor.xyz * (dot(worldNormal, lightDir) * 0.5 + 0.5); //高光 fixed3 specular = _LightColor0.xyz * _SpecularColor.xyz * pow(max(0, dot(cameraDir, refDir)), _Gloss); f.color = fixed4(diffuse + specular, 1); return f; } fixed4 frag(v2f f) : SV_Target { return f.color; } ENDCG } } FallBack "Diffuse" }
实现漫反射,高光,自发光、贴图纹理混合影响的Shader
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' Shader "Lesson/VFFragSpecular" { Properties{ _MainTex("纹理", 2D) = "white"{} _DiffuseColor("漫反射颜色", Color) = (1,1,1,1) _SpecularColor("高光颜色", Color) = (1,1,1,1) _Emission("自发光颜色", Color) = (0.05,0.05,0.05,1) _Gloss("高光系数", Range(1, 100)) = 1.05 } SubShader{ Pass { Tags{ "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" //重定义 fixed4 _DiffuseColor; fixed4 _SpecularColor; fixed4 _Emission; float _Gloss; sampler2D _MainTex; struct m2v { float4 vex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { //转换之后的坐标 float4 position : SV_POSITION; //世界空间下法线向量 float3 worldNormal : COLOR0; //世界空间下的顶点坐标 float3 worldPosition : COLOR1; //uv float2 uv : TEXCOORD0; }; v2f vert(m2v v) { v2f f; f.position = UnityObjectToClipPos(v.vex); f.worldNormal = normalize(mul(UNITY_MATRIX_M, v.normal)); f.worldPosition = mul(UNITY_MATRIX_M, v.vex); f.uv = v.uv; return f; } fixed4 frag(v2f f) : SV_Target { /*高光(Specular) = 光照颜色和强度 * 高光颜色 * pow(max(0, dot(v, r)), 高光系数) V: 顶点到相机的方向 UnityWorldSpaceViewDir(世界空间下的顶点坐标) R:反射光的方向 reflect(从光源指向顶点的方向, 世界空间下的法线方向) */ //顶点到相机的方法 float3 cameraDir = normalize(UnityWorldSpaceViewDir(f.worldPosition)); //光照方向 从顶点指向光源方法 float3 lightDir = normalize(UnityWorldSpaceLightDir(f.worldPosition)); //反射光方向 -lightDir 从光源指向顶点方法 float3 refDir = reflect(-lightDir, f.worldNormal); //漫反射 半兰伯特 fixed3 diffuse = _LightColor0.xyz * _DiffuseColor.xyz * (dot(f.worldNormal,lightDir) * 0.5 + 0.5); //高光 fixed3 specular = _LightColor0.xyz * _SpecularColor.xyz * pow(max(0, dot(cameraDir, refDir)), _Gloss); //自发光 _Emission //环境光 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; //光的颜色 fixed3 lightColor = diffuse + specular + _Emission + ambient; //根据UV获取纹理的颜色 fixed4 texColor = tex2D(_MainTex, f.uv); texColor.xyz = texColor.xyz * lightColor; return texColor; } ENDCG } } FallBack "Diffuse" }
多光情况
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' Shader "Lesson/VFMoreLightDiffuse" { Properties{ _DiffuseColor("漫反射颜色", Color) = (1,1,1,1) } SubShader{ Pass { Tags{ "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" //重定义 fixed4 _DiffuseColor; struct m2v { float4 vex : POSITION; float3 normal: NORMAL; }; struct v2f { float4 position : SV_POSITION; fixed4 color : COLOR0; }; v2f vert(m2v v) { v2f f; f.position = UnityObjectToClipPos(v.vex); //世界空间下的法线 float3 worldNormal = normalize(mul(UNITY_MATRIX_M, v.normal)); //光照方向 float3 lightDir = normalize(UnityWorldSpaceLightDir(mul(UNITY_MATRIX_M, v.vex))); fixed3 diffuse = _LightColor0.xyz * _DiffuseColor.xyz * max(0, dot(worldNormal, lightDir)); f.color = fixed4(diffuse, 1); return f; } fixed4 frag(v2f f) : SV_Target { return f.color; } ENDCG } Pass { Blend One One//把点光源(新)和太阳光(旧)混合 Tags{ "LightMode" = "ForwardAdd" }//最多添加4盏光源 CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" //重定义 fixed4 _DiffuseColor; struct m2v { float4 vex : POSITION; float3 normal: NORMAL; }; struct v2f { float4 position : SV_POSITION; fixed4 color : COLOR0; }; v2f vert(m2v v) { v2f f; f.position = UnityObjectToClipPos(v.vex); //世界空间下的法线 float3 worldNormal = normalize(mul(UNITY_MATRIX_M, v.normal)); //光照方向 float3 lightDir = normalize(UnityWorldSpaceLightDir(mul(UNITY_MATRIX_M, v.vex))); fixed3 diffuse = _LightColor0.xyz * _DiffuseColor.xyz * max(0, dot(worldNormal, lightDir)); f.color = fixed4(diffuse, 1); return f; } fixed4 frag(v2f f) : SV_Target { return f.color; } ENDCG } } FallBack "Diffuse" }
补充内容
补充:在障碍物后面显示轮廓阴影
Shader "Lesson/PlayerBody" { Properties { _Color ("Color", Color) = (1,1,1,1) _OcculusionColor ("被挡住以后的颜色", Color) = (1,1,1,1) } SubShader { Tags { "Queue"="Geometry+200" } Pass { ZTest Greater Color[_OcculusionColor] } Pass { ZTest Less Color[_Color] } } FallBack "Diffuse" }
补充:物体多个材质球渲染,unity mobile shader:解决眼镜和身体的遮挡关系
Shader "Lesson/Glass" { Properties { _Color ("Color", Color) = (1,1,1,1) _OcculusionColor ("被挡住以后的颜色", Color) = (1,1,1,1) } SubShader { Tags { "Queue"="Geometry+100" } Pass { ZTest Greater ZWrite off Color[_OcculusionColor] } Pass { ZTest Less Color[_Color] } } FallBack "Diffuse" }
边缘发光
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject' // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' Shader"Lesson/OutLine1" { Properties { _MainTex("main tex",2D) = "black"{} _RimColor("rim color",Color) = (1,1,1,1)//边缘颜色 _RimPower("rim power",range(1,10)) = 2//边缘强度 } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include"UnityCG.cginc" struct v2f { float4 vertex:POSITION; float4 uv:TEXCOORD0; float4 NdotV:COLOR; }; sampler2D _MainTex; float4 _RimColor; float _RimPower; v2f vert(appdata_base v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord; float3 V = WorldSpaceViewDir(v.vertex); V = mul(unity_WorldToObject,V);//视方向从世界到模型坐标系的转换 o.NdotV.x = saturate(dot(v.normal,normalize(V)));//必须在同一坐标系才能正确做点乘运算 return o; } half4 frag(v2f IN) :COLOR { half4 c = tex2D(_MainTex,IN.uv); //用视方向和法线方向做点乘,越边缘的地方,法线和视方向越接近90度,点乘越接近0. //用(1- 上面点乘的结果)*颜色,来反映边缘颜色情况 c.rgb += pow((1 - IN.NdotV.x) ,_RimPower)* _RimColor.rgb; return c; } ENDCG } } FallBack"Diffuse" }
轮廓
思路:两次渲染,第一次渲染背面,剔除正面,把模型顶点沿法线方向扩伸一定距离(用来表现描边的粗细)
第二次渲染,渲染正面,剔除背面
Shader "Lesson/OutLine2" { Properties { _MainTex("main tex",2D) = ""{} _Factor("factor",Range(0,0.1)) = 0.01//描边粗细因子 _OutLineColor("outline color",Color) = (0,0,0,1)//描边颜色 } SubShader { Pass { Cull Front //剔除前面 CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct v2f { float4 vertex :POSITION; }; float _Factor; half4 _OutLineColor; v2f vert(appdata_full v) { v2f o; //v.vertex.xyz += v.normal * _Factor; //o.vertex = mul(UNITY_MATRIX_MVP,v.vertex); //变换到视坐标空间下,再对顶点沿法线方向进行扩展 float4 view_vertex = mul(UNITY_MATRIX_MV,v.vertex); float3 view_normal = mul(UNITY_MATRIX_IT_MV,v.normal); view_vertex.xyz += normalize(view_normal) * _Factor; //记得normalize o.vertex = mul(UNITY_MATRIX_P,view_vertex); return o; } half4 frag(v2f IN) :COLOR { //return half4(0,0,0,1); return _OutLineColor; } ENDCG } Pass { Cull Back //剔除后面 CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct v2f { float4 vertex :POSITION; float4 uv:TEXCOORD0; }; sampler2D _MainTex; v2f vert(appdata_full v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP,v.vertex); o.uv = v.texcoord; return o; } half4 frag(v2f IN) :COLOR { //return half4(1,1,1,1); half4 c = tex2D(_MainTex,IN.uv); return c; } ENDCG } } FallBack "Diffuse" }
描边
思路:两次渲染。第一次渲染背面,剔除正面。利用Offset指令,向离摄像机更近的方式偏移
第二次正常渲染物体(不剔除),(或者可以渲染正面,剔除背面)。
Offset:深度偏移
Offset Factor,Units
Factor参数表示 Z缩放的最大斜率的值。
Units参数表示可分辨的最小深度缓冲区的值。
我的理解:深度偏移,Unity是左手坐标系,Z的正方向是朝屏幕里面。
沿Z的负方向偏移就是离摄像机更近,
沿Z的正方向偏移就是离摄像机更远。
作用:可以强制使位于同一位置上的两个几合体中的一个几何体绘制在另一个的上层
比如:几何体A的Z值比几何体B更远,此时B是在A的上层。
但是,给A设置了Offset 往Z的负方向偏移,此时A比B更近了,A就在B上层了。
在深度测试(Z test)的时候,会去比较Z值,近的会绘制在远的上面。
所以上面的思路,要么第一次渲染背面,往Z的负方向偏移
要么第二次渲染的,往Z的正方向偏移,都是可以的。
Shader "Lesson/OutLine3" { Properties { _MainTex("main tex", 2D) = ""{ } _OutLineColor("outline color",Color) = (0,0,0,1)//描边颜色 } SubShader { //描边 pass { Cull Front Offset -5,-1 //深度偏移 CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex; half4 _OutLineColor; struct v2f { float4 pos : SV_POSITION; }; v2f vert(appdata_base v) { v2f o; o.pos = mul(UNITY_MATRIX_MVP,v.vertex); return o; } float4 frag(v2f i) : COLOR { return _OutLineColor; } ENDCG } //正常渲染物体 pass { //Cull Back //Offset 5,-1 CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex; float4 _MainTex_ST; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert(appdata_base v) { v2f o; o.pos = mul(UNITY_MATRIX_MVP,v.vertex); o.uv = TRANSFORM_TEX(v.texcoord,_MainTex); return o; } float4 frag(v2f i) : COLOR { float4 c = tex2D(_MainTex,i.uv); return c; } ENDCG } } }