UnityShader入门——光照 提示 所有的Shader效果图应该以Game窗口为准,Scene窗口很容易出现问题
光照 概述
技术点:BRDF光照模型:次时代渲染 ,SSS材质:和次表面相关
标准光照模型 自发光,高光反射,漫反射,环境光
自发光 物体在摄像机里看起来更亮,在Unity的常规使用中,不对周五其他物体产生影响
高光反射 差异 blinn——phong的 实现效果比较好
Phong模型 推导过程
高光反射公式
1 高光反射颜色 = 入射颜色 * 高光颜色 * max(0 ,(2 (n * l)n - l)v)^光泽度
字母代表向量
实现 知识:Unity的Reflect()方法可以计算入射光线的反射角度,但是使用的是常规物理画图的表达方式,入射光线方向朝着镜面,而Unity的光源方向和这个相反,因此要取一个 负值
注意:在之前的计算中,错误的使用了,该方法实际上返回的是一个向量,而在phong的计算过程中,需要用到的是点
1 2 3 4 5 6 inline float3 UnityObjectToWorldDir ( in float3 dir ) { return normalize(mul((float3x3)unity_ObjectToWorld, dir)); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 Shader "Unlit/Phong" { Properties { _MainTex ("Texture" , 2 D) = "white" {} _Diffse("Diff" ,color) = (1 ,1 ,1 ,1 ) _Specular("Specular" ,color) = (1 ,1 ,1 ,1 ) _Gloss("Gloss" ,Range(1 ,256 )) = 20 } SubShader { Tags { "RenderType" ="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "UnityLightingCommon.cginc " struct v2f { float4 vertex : SV_POSITION; float3 wordNormal: TEXCOORD0; float3 worldPos : TEXCOORD1; }; sampler2D _MainTex; float4 _MainTex_ST; float4 _Diffse; float4 _Specular; int _Gloss; v2f vert (appdata_base v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.wordNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { float3 environmentLightColor = UNITY_LIGHTMODEL_AMBIENT.xyz; float3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); float3 reflectDir = normalize(reflect(- lightDir,i.wordNormal)); float3 phong = _LightColor0.rgb * _Specular.rbg * pow (max(0 ,dot(viewDir,reflectDir)),_Gloss); float3 resColor = environmentLightColor + phong; float4 col = float4(resColor,1 ); return col; } ENDCG } } }
Blinn—phong模型 推导过程
在该光照模型中,引入 半角向量 这一概念,该向量是l 和v 的中间向量,(角度的一半或者 (l + v)/2;
实现 在实际中,使用的向量都进行了归一化操作,从而使得半角向量就是入射光线 + 视角
在片元着色器中操作
1 2 float3 halfView = normalize(_WorldSpaceCameraPos + _WorldSpaceLightPos0); float3 phong = _LightColor0.rgb * _Specular * pow (max(0 ,dot(i.wordNormal,halfView)),_Gloss);
替换方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 inline float3 UnityWorldSpaceViewDir ( in float3 worldPos ) { return _WorldSpaceCameraPos.xyz - worldPos; } inline float3 UnityWorldSpaceLightDir ( in float3 worldPos ) { #ifndef USING_LIGHT_MULTI_COMPILE return _WorldSpaceLightPos0.xyz - worldPos * _WorldSpaceLightPos0.w; #else #ifndef USING_DIRECTIONAL_LIGHT return _WorldSpaceLightPos0.xyz - worldPos; #else return _WorldSpaceLightPos0.xyz; #endif #endif }
漫反射 理论 根据入射光线角度的不用,可以分为三种情况
条件准备 入射光线和入射点法线的夹角为a,a不是钝角
a是锐角的情况:漫反射光从入射点法线相对入射光线的那一层射出
a是直角:从入射点向四周发散
a是平角:没有漫反射光
影响漫反射光的角度计算,向量点乘
相关链接
Unity——Shader实现边缘发光效果 | mao的博客 (mao1mao2mao3mao4.github.io)
公式 漫反射颜色计算
漫反射颜色 = (入射光线颜色 * 反射面光滑度 ) * max(0,dot (n ,l));
最终颜色计算
Lambert 光照模型公式: 最终颜色 = 直射光颜色 * 漫反射颜色 * max(0, dot(光源方向, 法线方向))
其中,直射光颜色,漫反射颜色,都是我们自定义的变量。
实现 知识:点乘需要放在同一个坐标系下才有效
顶点漫反射 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 Shader "Unlit/Lambert" { Properties { _MainTex ("Texture" , 2 D) = "white" {} _Diffse("Color" ,color) = (1 ,1 ,1 ,1 ) } SubShader { Tags { "RenderType" ="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "UnityLightingCommon.cginc " struct v2f { float4 vertex : SV_POSITION; float3 color : color; }; sampler2D _MainTex; float4 _MainTex_ST; float4 _Diffse; v2f vert (appdata_base v) { v2f o; o.vertex = UnityObjectToClipPos (v.vertex); float3 environmentLightColor = UNITY_LIGHTMODEL_AMBIENT.xyz; float3 wordNormal = UnityObjectToWorldNormal (v.normal); float3 lightDir = normalize (_WorldSpaceLightPos0. xyz); normalize (lightDir); o.color = _LightColor0. rgb * _Diffse.rgb * saturate (dot (wordNormal,lightDir)) + environmentLightColor; return o; } fixed4 frag (v2f i) : SV_Target { float4 col = float4 (i.color,1 ); return col; } ENDCG } } }
片元漫反射 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 Shader "Unlit/Lambert" { Properties { _MainTex ("Texture" , 2 D) = "white" {} _Diffse("Color" ,color) = (1 ,1 ,1 ,1 ) } SubShader { Tags { "RenderType" ="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "UnityLightingCommon.cginc " struct v2f { float4 vertex : SV_POSITION; float3 wordNormal: TEXCOORD0; }; sampler2D _MainTex; float4 _MainTex_ST; float4 _Diffse; v2f vert (appdata_base v) { v2f o; o.vertex = UnityObjectToClipPos (v.vertex); o.wordNormal = UnityObjectToWorldNormal (v.normal); return o; } fixed4 frag (v2f i) : SV_Target { float3 environmentLightColor = UNITY_LIGHTMODEL_AMBIENT.xyz; float3 lightDir = normalize (_WorldSpaceLightPos0. xyz); normalize (lightDir); float3 resColor = _LightColor0. rgb * _Diffse.rgb * saturate (dot (i.wordNormal,lightDir)) + environmentLightColor; float4 col = float4 (resColor,1 ); return col; } ENDCG } } }
半兰伯特漫反射 1 2 float3 resColor = _LightColor0.rgb * _Diffse.rgb * (dot(i.wordNormal,lightDir)*0.5 + 0.5 )+ environmentLightColor;
将原来的值大小限制直接通过计算归纳到0到1的范围内
环境光 例子:红苹果放在一张白纸旁边,白纸有点微红,描述的是间接光照
纹理采样 属性块中的属性
1 _MainTex ("Texture" , 2 D) = "white" {}
使用的时候需要设置这两个属性,第二个是第一个的从属,第一个有就默认有第二个,里面保存的是贴图的缩放和位移,分别可以用 xy 和 zw 来进行表示
1 2 sampler2D _MainTex; float4 _MainTex_ST;
实现后相较于常规的着色器添加的内容
1 2 3 4 5 6 7 8 9 float2 uv: TEXCOORD0; o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; float3 albedo = tex2D(_MainTex,i.uv).rbg;
注意:如果在片元着色器中存在光照模型,需要将 albedo 乘到 漫反射中。
图片设置 知乎上有一篇写的很好的,这里给出链接
纹理滤波,Mipmaps,Unity中图片的导入设置 - 知乎 (zhihu.com)