之前在编写Phong与BlinnPhong的时候,我将光照计算的部分放在顶点程序vert当中
顶点程序是针对顶点的,比如一个模型有8个顶点,就会计算8次
而片段程序当中是针对像素的,有n个像素就会计算n次
所以在之前编写的着色器当中,由于在顶点当中进行,最后光照的效果,出现了斑块,效果不够平滑
在以前由于图像处理器性能不强,所以很多的计算放在顶点程序当中进行
但是随着现代图像处理器的发展,GPU以及帮我们优化好了这一部分的问题
所以现在很多光照的计算部分可以放在片元程序当中进行了
下面就是改写为片元程序实现的光照效果
基于片元程序的Phong冯氏光照模型
在计算Phong的光照过程中需要利用到模型的法线normal以及模型坐标系下的顶点vertex
所以就需要在v2f中传递模型坐标系下的vertex与normal
在v2f使用的参数符合类型且不占用语义即可,即TEXCOORD0与POSITION1
为了区分变换过后的vertex,所以在传递变换过后的顶点坐标时采用的语义为SV_POSITION
关于SV_POSITION
SV_POSITION:SV_前缀的变量代表system value,
在DX10以后的语义绑定中被使用代表特殊的意义,和POSITION用法并无不同。
唯一区别是 SV_POSTION一旦被作为vertex shader的输出语义,
那么这个最终的顶点位置就被固定了(不能tensellate,不能再被后续改变它的空间位置?),
已经成为了转换裁剪世界的坐标,是可以直接用来进入光栅化处理的坐标,
如果作为fragment shader的输入语义那么和POSITION是一样的,
代表着每个像素点在屏幕上的位置(这个说法其实并不准确,
事实是fragment 在 view space空间中的位置,但直观的感受是如括号之前所述一般)
其次:在DX10版本之前没有引入SV_的预定义语义,POSITION被用作vertex shader的输入,输出,
fragment shader的输入参数。但DX10之后就推荐使用SV_POSITION作为vertex shader的输出和fragment shader的输入了,
注意vertex shader的输入还是使用POSITION!切记。
但是DX10以后的代码依旧兼容POSITION作为全程表达,估计编译器会自动判断并替换
总结:
两个的用法大致相同,都是存储坐标信息,但是一个是转换前的,一个是转换后的。
POSITION:用来存储,模型在本地坐标下,模型空间中(objcet space)的顶点坐标,转换为剪裁空间坐标前的坐标,unity告诉我们的模型顶点坐标,没经过转换的。可用作定点着色器(vertex shader)的输入、输出;片元着色器(frag)的输入。
SV_POSITION:用来存储,模型在剪裁空间,投影空间中的位置信息,即把模型空间的定点坐标,转化为剪裁空间的坐标,可用作定点着色器(vertex shader)的输出;片元着色器(frag)的输入。
Shader "ShaderLearning/FragmentDiffuseShader/FrgmentPhongShader"
{
Properties
{
\_MainColor ("MainColor", Color) = (1,1,1,1)
\_SpecularColor ("SpecularColor", Color) = (0.5,0.5,0.5,1)
\_Shineness("Shineness",Range(1,32))=8
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma multi\_compile\_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct v2f
{
float4 pos : SV\_POSITION;
float3 normal : TEXCOORD0;
float4 vertex: POSITION1;
};
fixed4 \_MainColor;
fixed4 \_SpecularColor;
float \_Shineness;
v2f vert (appdata\_base v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.normal=v.normal;
o.vertex=v.vertex;
return o;
}
fixed4 frag (v2f i) : COLOR
{
//环境光颜色
fixed4 col=UNITY\_LIGHTMODEL\_AMBIENT;
//模型坐标系下的法线转位世界坐标系下
float3 N=UnityObjectToWorldNormal(i.normal);
//获取世界坐标系下的光照方向
float3 L=normalize(WorldSpaceLightDir(i.vertex));
//计算漫反射
float diffuse=saturate(dot(L,N));
//计算摄像机观察视角向量
float3 V=normalize(WorldSpaceViewDir(i.vertex));
//计算入射光反射向量,L取反就是入射光向量
float3 R=reflect(-L,N);
//计算镜面高光
float specular=pow(saturate(dot(V,R)),\_Shineness);
//叠加上漫反射与镜面高光的颜色乘上光照颜色
col+= \_LightColor0\*((\_MainColor\*diffuse)+(\_SpecularColor\*specular));
//计算世界坐标系下的顶点位置
float3 wpos=mul(unity\_ObjectToWorld,i.vertex).xyz;
//计算4个点光源颜色
col.rgb+=Shade4PointLights(unity\_4LightPosX0,
unity\_4LightPosY0,
unity\_4LightPosZ0,
unity\_LightColor\[0\].rgb,unity\_LightColor\[1\].rgb,
unity\_LightColor\[2\].rgb,unity\_LightColor\[3\].rgb,
unity\_4LightAtten0,wpos,N);
return col;
}
ENDCG
}
}
}
效果图⬇️
基于片元程序的BlinnPhong布林冯光照模型
更改为BlinnPhong只需要将计算镜面高光部分改为使用半角向量进行计算即可
//计算镜面高光,使用半角向量
float specular=pow(saturate(dot(N,normalize(L+V))),_Shineness);
完整代码
Shader "ShaderLearning/FragmentDiffuseShader/FrgmentBlinnPhongShader"
{
Properties
{
\_MainColor ("MainColor", Color) = (1,1,1,1)
\_SpecularColor ("SpecularColor", Color) = (0.5,0.5,0.5,1)
\_Shineness("Shineness",Range(1,32))=8
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma multi\_compile\_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct v2f
{
float4 pos : SV\_POSITION;
float3 normal : TEXCOORD0;
float4 vertex: POSITION1;
};
fixed4 \_MainColor;
fixed4 \_SpecularColor;
float \_Shineness;
v2f vert (appdata\_base v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.normal=v.normal;
o.vertex=v.vertex;
return o;
}
fixed4 frag (v2f i) : COLOR
{
//环境光颜色
fixed4 col=UNITY\_LIGHTMODEL\_AMBIENT;
//模型坐标系下的法线转位世界坐标系下
float3 N=UnityObjectToWorldNormal(i.normal);
//获取世界坐标系下的光照方向
float3 L=normalize(WorldSpaceLightDir(i.vertex));
//计算漫反射
float diffuse=saturate(dot(L,N));
//计算摄像机观察视角向量
float3 V=normalize(WorldSpaceViewDir(i.vertex));
//计算镜面高光,使用半角向量
float specular=pow(saturate(dot(N,normalize(L+V))),\_Shineness);
//叠加上漫反射与镜面高光的颜色乘上光照颜色
col+= \_LightColor0\*((\_MainColor\*diffuse)+(\_SpecularColor\*specular));
//计算世界坐标系下的顶点位置
float3 wpos=mul(unity\_ObjectToWorld,i.vertex).xyz;
//计算4个点光源颜色
col.rgb+=Shade4PointLights(unity\_4LightPosX0,
unity\_4LightPosY0,
unity\_4LightPosZ0,
unity\_LightColor\[0\].rgb,unity\_LightColor\[1\].rgb,
unity\_LightColor\[2\].rgb,unity\_LightColor\[3\].rgb,
unity\_4LightAtten0,wpos,N);
return col;
}
ENDCG
}
}
}
效果图(Phong与BlinnPhong对比)
左侧Phong,右侧BlinnPhong


...