Unity新手学习Shader之路---高光反射
其实对于一个shader需要什么参数我是这么理解的,在制作这个shader之前想一想影响这个shader可能的因素,比如漫反射就可以很自然的想到颜色肯定对漫反射有影响,然后我觉得大部分的属性都是在编写shader的过程中添加的,就是需要什么就加什么。在unity中摄像机就是我们的眼睛,我们应该通过摄像机的位置和模型的位置去计算视野方向。想象一束光射向某个点,然后反射出去,我们的眼睛同样看向那个点,
如有错误遗漏之处欢迎指正。
在学习高光反射之前我们首先应该知道漫反射的原理和公式,哪怕不理解原理也得先记住公式。
漫反射https://blog.csdn.net/weixin_48195035/article/details/150340178?spm=1011.2124.3001.6209让我们从头开始,一步一步来,先把代码看完然后在逐步解释
首先在漫反射那节我们知道Properties中应该是有个_Diffuse属性,但是对于高光反射来说我们还需要两个额外的属性,一个是_Specular,一个是_Gloss。其中_Diffuse代表的是代表材质的漫反射颜色,决定物体在光照下的基础颜色。然后_Specular代表材质的高光反射颜色,控制物体反射光的颜色。然后_Gloss用来控制材质的光滑度/高光锐利度。越接近 0
:表面粗糙 → 高光分散、模糊。越接近 1
:表面光滑 → 高光集中、尖锐。其实对于一个shader需要什么参数我是这么理解的,在制作这个shader之前想一想影响这个shader可能的因素,比如漫反射就可以很自然的想到颜色肯定对漫反射有影响,然后我觉得大部分的属性都是在编写shader的过程中添加的,就是需要什么就加什么。
Shader "Custom/Day10"
{
Properties
{
_Diffuse("Diffuse",Color)=(1,1,1,1)
_Specular("Specular",Color)=(1,1,1,1)
_Gloss("Gloss",Range(0,1))=0.5
}
}
补全结构
Shader "Custom/Day11"
{
Properties
{
_Diffuse("Diffuse",Color)=(1,1,1,1)
_Specular("Specular",Color)=(1,1,1,1)
_Gloss("Gloss",Range(0,1))=0.5
}
SubShader
{
Pass
{
CGPROGRAM
ENDCG
}
}
}
因为是光照,我们一定一定要给Shader声明标签Tags,之前本人在学习光照的时候因为漏了这行导致一个下午都在和一个黑色模型打交道。
Tags{"LightMode"="ForwardBase"}
然后还有就是要记得引用,引用之后我们才能使用光照变量
#include "Lighting.cginc"
然后剩下的基本都是固定的结构,如申明顶点片元函数申明变量,定义输入输出结构体等
Shader "Custom/Day11"
{
Properties
{
_Diffuse("Diffuse",Color)=(1,1,1,1)
_Specular("Specular",Color)=(1,1,1,1)
_Gloss("Gloss",Range(0,1))=0.5
}
SubShader
{
Pass
{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
float4 _Diffuse;
float4 _Specular;
float _Gloss;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
};
v2f vert(appdata v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.worldNormal=UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return fixed4(1,1,1,1);
}
ENDCG
}
}
}
可以看到我在顶点着色器中进行了两步操作,一步是用来将顶点从模型空间转换到裁剪空间,一步是将模型空间的法线转换到世界空间,因为根据光照公式我们是要通过法线和光线做点积。然后我们要保证计算是同空间计算,也就是要么都是模型空间,要么都是世界空间。放在片元着色器写光照效果会更好,会有渐变,如果全写在顶点着色器中他在光暗交接处的显示会比较棱角分明。
高光反射的实现也是建立在漫反射的基础上的,所以这边先实现一个基础的漫反射,在片元着色器中第一步我们将世界中的光线给归一化了,然后其实我在这有个疑问的,我当时想为什么不放在顶点着色器中进行归一化之后再传给片元着色器呢?首先我们先了解一下_WorldSpaceLightPos0
是 Unity 自动传给 Shader 的 光源在世界空间下的位置/方向。
-
如果是 平行光 那么
_WorldSpaceLightPos0.w = 0
,前 3 分量就是光的方向。 -
如果是 点光源那么
_WorldSpaceLightPos0.w = 1
,前 3 分量就是光源的世界坐标,需要用 光源位置 - 顶点位置 计算方向。
在片元着色器中归一化,是因为:
-
精度:顶点数目少,片元数目多。如果在顶点阶段就归一化并插值,插值后的向量不一定是归一化的,可能偏离(因为插值破坏单位长度)。这会导致光照计算不准确。
-
正确性:光照方向是和片元位置直接相关的(尤其点光源、聚光灯),所以一般选择在片元阶段再重新 normalize,保证每个片元的光照方向是精确的。
然后其实再顶点着色器中进行一次归一化然后再片元着色器再次进行归一化是比较好的。
一般用于方向计算的向量才进行两次归一化
-
两次归一化(顶点一次 + 片元一次)是最稳妥的做法。
-
顶点归一化是为了传递更合理的数据,片元归一化是为了确保最终计算精确。
然后对于ambient,它使用了环境光(UNITY_LIGHTMODEL_AMBIENT
)是场景里统一的全局光亮,通常是灰色或淡蓝色。在实际材质里,环境光也会受到物体材质颜色(Diffuse)的影响:
-
如果物体是红色的布料,那么环境光照上去,显示出来也应该带红。
-
如果物体是白色的纸,那么环境光就比较忠实地反映环境的颜色。
对于diffuse,我们用了_LightColor0.rgb去乘上_Diffuse.rgb和法线和光线的点积,如果这里不成_LightColor0.rgb会让你的模型看起来比较白,再unity中平行光是那种偏黄的,你不乘这个参数就会让你的模型看起来白白的,可以自行尝试一下。然后最后就是将两个光加起来就可以实现一个基础的漫反射了这种效果已经和unity创建出来的效果是一样的了,可以创建一个sphere去对比看看,只是因为我们还没添加高光反射和unity的差一个高光,接下来就可以添加高光去实现高光反射了。
Shader "Custom/Day11"
{
Properties
{
_Diffuse("Diffuse",Color)=(1,1,1,1)
_Specular("Specular",Color)=(1,1,1,1)
_Gloss("Gloss",Range(0,1))=0.5
}
SubShader
{
Pass
{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
float4 _Diffuse;
float4 _Specular;
float _Gloss;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
};
v2f vert(appdata v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.worldNormal=UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldNormal=normalize(i.worldNormal);
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse.rgb;
fixed3 diffuse=_LightColor0.rbg *_Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));
fixed3 color=diffuse+ambient;
return fixed4(color,1);
}
ENDCG
}
}
}
首先对于高光反射我们得先去了解一下原理
想象一束光射向某个点,然后反射出去,我们的眼睛同样看向那个点,当我们的眼睛看向那个点的方向,与光线反射的方向,越接近时,进入我们眼睛的反射光则越多,也就是更亮。
高光反射公式
Specular 光照模型公式: 最终颜色 = 直射光颜色 * 反射光颜色 * pow(max(0, dot(反射光方向, 视野方向)), 光泽度(gloss)) + 漫反射颜色 + 环境光颜色
从公式里能看到我们目前只需要能获取视野方向就可以了,那这个视野方向要怎么去算呢?在unity中摄像机就是我们的眼睛,我们应该通过摄像机的位置和模型的位置去计算视野方向。也就是模型到摄像机的光线。
Shader "Custom/Day11"
{
Properties
{
_Diffuse("Diffuse",Color)=(1,1,1,1)
_Specular("Specular",Color)=(1,1,1,1)
_Gloss("Gloss",Range(0,1))=0.5
}
SubShader
{
Pass
{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
float4 _Diffuse;
float4 _Specular;
float _Gloss;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float4 worldPos : TEXCOORD1;
};
v2f vert(appdata v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.worldNormal=normalize(UnityObjectToWorldNormal(v.normal));
o.worldPos=normalize(mul(unity_ObjectToWorld,v.vertex));
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldNormal=normalize(i.worldNormal);
fixed3 viewDir=normalize(_WorldSpaceCameraPos-i.worldPos);
fixed3 halfDir=normalize(viewDir+worldLightDir);
float shininess = _Gloss * 256.0;
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse.rgb;
fixed3 diffuse=_LightColor0.rgb*_Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));
fixed3 specular=_LightColor0.rgb*_Specular*pow(max(0,dot(worldNormal,halfDir)),shininess);
fixed3 color=diffuse+ambient+specular;
return fixed4(color,1);
}
ENDCG
}
}
}
上面的代码多了三个变量一个是viewDir,一个是halfDir,一个是specular。
其中viewDir是视线方向,因为我们要获得的是模型到摄像机的方向,根据向量计算就是用摄像机的位置减去模型的位置就可以得到模型朝摄像机的方向。
然后就是halfDir半程向量,他是两个向量进行相加得到的,由世界光线方向和视线方向相加得到他们中间位置的一个向量,这个位置就是我们处理高光的位置。
然后就是specular,根据公式代入即可。
有一个地方是需要注意的,因为我们的_Gloss参数设置的值比较小,直接通过幂运算会导致高光效果特别夸张,所以这边进行了一个处理,让shininess承载_Gloss变大。这样子就可以实现高光反射了。高光反射分两个模型有兴趣的读者可以自行理解。
更多推荐
所有评论(0)