目录
一、本节介绍
1 上集回顾
2 本节介绍
二、什么是漫反射材质球
三、 漫反射进化史
1 三种算法结果的区别
2 具体算法
2.1 兰伯特逐顶点算法
a.本小节使用的unity自带结构体。
b.兰伯特逐顶点算法公式
c.代码实现——兰伯特逐顶点算法
2.2 代码实现——兰伯特逐像素算法
a.像素和顶点算法的区别
b.实现代码
2.3 代码实现——半兰伯特算法
a.为什么会出现半兰伯特
b.半兰伯特公式
c.代码实现
四、下集介绍
本集讲了如何让图片和外部颜色叠加显示。
如何做一个漫反射材质球。
1 之前的颜色材质球
我们目前只学过直接上色的材质球(如图1所示),还有上节课的颜色和图片叠加的材质球。
图1 材质球2 现实的光照下的球
现实光照下的大部分材质球并不是纯色且全亮的,而是(如图2所示)。
图2 现实中的球这种模拟大部分现实世界物体发光的状态,就是漫反射材质球。
备注:
反射有两种:镜面反射和漫反射。像镜子的反光,非常光滑的物体反光(比如金属),属于镜面反射,其他大部分是漫反射。具体区别详见初中物理~自己百度哦o(* ̄︶ ̄*)o
我们算到最后,对屏幕来说,仅仅想知道,我这个点应该用什么颜色。
所以,对这个颜色的计算出现了三种解法。
备注:兰伯特是个人,他和别人一起研究出来了以上三个定律。
兰伯特逐顶点算法(白色和黑色交界处有些方块块的感觉、照不到的地方全黑)
兰伯特逐像素算法(白色和黑色交界处平滑过渡、照不到的地方全黑)
半兰伯特算法(白色和黑色交界处平滑过渡、照不到的地方不是全黑)
内容参考(侵权立删):
Unity Shader 漫反射(Lambert、Half Lambert) - 知乎
图3 三种算法得到的效果struct appdata_full {
float4 vertex : POSITION; //顶点坐标
float4 tangent : TANGENT; //切线
float3 normal : NORMAL; //法线
float4 texcoord : TEXCOORD0; //第一纹理坐标
float4 texcoord1 : TEXCOORD1;//第二纹理坐标
float4 texcoord2 : TEXCOORD2;//第三纹理坐标
float4 texcoord3 : TEXCOORD3;//第四纹理坐标
fixed4 color : COLOR; //顶点颜色
UNITY_VERTEX_INPUT_INSTANCE_ID //ID信息
};
公式解释:
屏幕上对应点的颜色 = (光的颜色*物体的颜色)*max(0,该点的法向量*该点的光照方向)
备注(max函数解释):
max(a,b),如果这里面a大,答案就是a
如果b大,答案就是b。
例:
max(5,20)=20
max(8,-9)=8
此处的作用:
因为颜色没有负数,如果n*l算出来小于0的时候,就直接为0,其他时候就是n*l的值。
其实就是起一个“一刀切”掉负数的作用。
得出结论:我们想计算漫反射的时候屏幕显示什么颜色,我们需要光的颜色、物体的颜色、该点的法向量(单位向量)、该点的光照方向(单位向量)。
备注:公式里的字母上带^就是单位向量的意思。
计算注意事项:
在计算n*l时,注意:该点的法向量(往往直接获取的是物体本地坐标),该点的光照方向(往往获取的是世界坐标)
这样是不能乘的,所以需要把他们都换算到一个坐标系,这里换算到世界坐标下。
会用到的方法:
UnityObjectToWorldNormal() //把物体的法线坐标,换算到世界坐标下
normalize() //把任何一个向量变成单位向量
dot() //点乘
max() //上文讲过
_WorldSpaceLightPos0 //世界坐标下的光线坐标
//但是要引用#include "Lighting.cginc"才能找到
实现的代码:
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
//新的引用
#include "Lighting.cginc"
//返回结构体 //引用结构体
appdata_full vert (appdata_full v)
{
//模型顶点坐标转屏幕坐标
v.vertex = UnityObjectToClipPos(v.vertex);
//获取法线坐标并转换成世界坐标下的法线坐标
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
//世界坐标下的光线坐标 //单位化坐标 //获取世界坐标下的光线坐标
float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//上面的公式
float3 diffuse =_LightColor0.rgb * v.color.rgb * max(0,dot(worldNormal,worldLight));
//算出的值给颜色
v.color = float4(diffuse,1);
return v;
}
float4 frag (appdata_full v) : SV_Target
{
//输出颜色
return float4(v.color,1) ;
}
ENDCG
}
}
例:让你拿笔写一个字,你可能就写了,但是让你拿竹竿上面绑个中性笔写字,你就写不准了,肯定是离画出来的地方越近,画出来越是自己想要的。
结论:像素着色器离最后的显示比较近,所以出来的结果和我们想要的更一致。
Shader "Unlit/005_1"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
appdata_full vert (appdata_full v)
{
v.vertex = UnityObjectToClipPos(v.vertex);
//把法线转换成世界坐标,传进去
v.normal = UnityObjectToWorldNormal(v.normal);
return v;
}
float4 frag (appdata_full v) : SV_Target
{
//法线世界坐标
float3 worldNormal = v.normal;
//光线世界坐标
float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//计算颜色
float3 diffuse =_LightColor0.rgb * v.color.rgb * max(0,dot(worldNormal,worldLight));
//把颜色传进去
return float4(diffuse,1) ;
}
ENDCG
}
}
}
兰伯特的两个算法,得到的球,在没有光线照射的时候都是黑色的,但玩游戏的时候往往希望,虽然光线无法照到,但我们可以看见。
数学知识:公式中的n*l值的范围是【-1,1】之间,我们希望把这个区间改成【0,1】(前面的课学过),【-1,1】*0.5+0.5,就可以转成【0,1】,0的时候就是之前光照模型中黑色部分,越靠近1越亮。
因为我们实际上并不是需要它看不见,只是需要它要明暗变化,所以我们在环境光的基础上加上兰伯特公式计算出的值,就有了明暗变化。
于是就出现了第三种,半兰伯特。
在上图基础上:
最终颜色 = 环境光+Cdiffuse
这里其他代码都没有变,只更改了上图0.5的部分。最后输出前,再加入环境光。
备注:
获取环境光强度的方法:UNITY_LIGHTMODEL_AMBIENT.xyz
Shader "Unlit/005_2"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
appdata_full vert (appdata_full v)
{
v.vertex = UnityObjectToClipPos(v.vertex);
v.normal = UnityObjectToWorldNormal(v.normal);
return v;
}
float4 frag (appdata_full v) : SV_Target
{
float3 worldNormal = v.normal;
float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//本节变动
//获取环境光
float3 anbient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//计算范围
float halfLamient = dot(worldNormal,worldLight)*0.5+0.5;
//计算反射强度
float3 diffuse =_LightColor0.rgb * v.color.rgb *halfLamient;
//反射光加光照强度
float3 c = anbient + diffuse;
return float4(c,1) ;
}
ENDCG
}
}
}
本集讲了3种计算反射光的方法。
下集讲光照计算,高光反射。(最晚更新日期,1月7日)