这是个人的学习笔记。
一、一个简单的顶点/片段着色器 (Unity2017)
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Custom/Chapter5-MyTest"
{
Properties {
//_MainTex ("Texture", 2D) = "white" {}
}
SubShader {
//Tags { "RenderType"="Opaque" }
//LOD 100
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert (float4 v : POSITION) : SV_POSITION {
return UnityObjectToClipPos(v);
}
fixed4 frag () : SV_Target {
return fixed4(1.0, 1.0, 1.0, 1.0);
}
ENDCG
}
}
}
二、通过在片段着色器的参数进行自定义,可以将法线,颜色,坐标等信息传入在Frag函数中进行处理。这些信息是经过顶点着色器处理之后得来的。
三、顶点着色器是逐顶点调用的,而片段着色器是逐片段调用的。片段着色器中的输入实际上是把顶点着色器的输出进行插值后得到的结果。
顶点着色器最基本的任务:
把顶点位置从模型空间转换到裁剪空间中,因此需要使用Unity的内置方法 -- 模型*世界*投影矩阵 UNITY_MATRIX_MVP来完成这样的坐标变换。这个方法:mul(UNI
四、调试:VS提供了
Graphics Debugger。不仅可以查看每个像素的最终颜色、位置等信息。还可以对顶点着色器和片段着色器进行单步高度。
Unity5之后提供的
Frame Debugger
五、性能优化:尽量少用分支和循环。尽量将计算放到顶点着色器中(尽量将计算放到流程的上层),或者CPU计算后再传给GPU。
六、
ForwardBase和ForwardAdd的区别及表现效果
七、基础光照,漫反射。(第6章
基于顶点的光照--漫反射实现)
1、计算漫反射需要4个参数:材质的反射颜色_Diffuse,顶点法线v.normal,光源的颜色及强度,光源的方向。
2、Unity提供内置变量_LightColor0来访问该Pass处理的光源的颜色及强度。(想要得到正确的值需要定义正确的LightMode标签)
3、Unity提供_WorldSpaceLightPos0来得到光源方向。(这不是通用方法,只有在场景中只有一个光源且该光源是平行光。如果场景中有多个光源且是点光源时,不能使用此方法)
4、需要将法线(顶点的法线此时是模型空间的坐标)和光源方向转换到同一坐标系中。(比如转换到世界坐标系中)
我们知道可以使用顶点变换矩阵的逆转置矩阵对法线进行相同的变换,因此我们首先得到模型空间到世界空间的变换矩阵的逆矩阵unity_WorldToObject,然后通过调换它在mul函数中的位置,得到和转置矩阵相同的矩阵乘法。由于法线是一个三维矢量,困此只需要截取unity_WorldToObject的前三行前三列即可。
模型空间的向量转换成世界空间的向量
:
//模型空间的顶点法向量,转换成世界空间的 向量,并归一化
fixed3 worldNormal = normalize(
mul(v.normal, (float3x3)unity_WorldToObject));
5、得到世界空间中的法线和光源方向后,需要归一化。
6、在得到上述点积结果后,为保证结果不为负值,需要使用saturate函数。(将参数截取到[0,1]范围内)
7、最后如上点积结果再与光源颜色和强度以及材质的漫反射颜色相乘即可得到最终的漫反射光照部分。最后,对环境光与漫反射光部分相加,得到最终的光照结果。
{
Properties
{
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : POSITION;
float3 color : COLOR;
};
fixed4 _Diffuse;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//环境光的颜色
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//模型空间的顶点法向量,转换成世界空间的 向量,并归一化
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
//光照方向 归一化
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//顶点法向量 与光照方向的点积。一个向量在另一个向量方向上的投影长度。
fixed worldLightNormalProject = dot(worldNormal, worldLight);
//漫反射计算 : 材质的漫反射颜色 顶点法向量 光照颜色和强度 光照方向
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(worldLightNormalProject);
//环境光 + 漫反射光
o.color = ambient + diffuse;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color, 1.0);
}
ENDCG
}
}
}
八、基础光照,漫反射。逐像素光照。(第6章
基于像素的光照--漫反射实现)
对于一些细分程度较低的模型,逐顶点光照会出现一些视觉问题。如看到背光面与向光面交界处有一些锯齿。为了解决这个问题,可以使用逐像素的漫反射光照。
基本流程与基于顶点的光照一致。只是将光照计算部分移到fragment函数中。
Shader "MyTest/DiffusePixelLevel-5-Test"
{
Properties
{
_Diffuse("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : POSITION;
float3 worldNormal : NORMAL;
};
float4 _Diffuse;
v2f vert (appdata v)
{
v2f o;
//MVP
o.vertex = UnityObjectToClipPos(v.vertex);
//模型空间的向量转换成世界空间的向量
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//取得环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//材质反射颜色 材质顶点法向量 光照颜色及强度 光照方向
fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * saturate(dot(worldNormal, lightDir));
fixed3 color = diffuse + ambient;
return fixed4(color,1);
}
ENDCG
}
}
}
九、半兰伯特光照模型。
解决的问题是,比如漫反射时,背光面是全黑的问题。
方法是 在法向量 与 光照方向点积时,取值范围由原来 0-1 变成现在的0.5-1。这样背光的地方也不会太暗。
fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5
其他计算方式与漫反射的计算方式一致。
十、高光反射光照模型:逐顶点光照。
计算高光反射需要知道四个参数:入射光线的颜色和强度,材质的高光反射系数,视角方向,反射方向。
其中反射方向可由顶点法向量和光源方向计算得到。提供了计算反射方向的函数reflect(i,n)。参数分别是光照方向(入射方向),顶点法向量。
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
高光反射计算公式(经验模型):
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
视角方向:
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
Shader "MyTest/SpecularVertexLevel-5-Test"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1,1,1,1)
_Specular ("Specular", Color) = (1,1,1,1)
_Gloss ("Gloss", Range(6, 256)) = 20
}
SubShader
{
Pass
{
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1,1,1,1)
_Specular ("Specular", Color) = (1,1,1,1)
_Gloss ("Gloss", Range(6, 256)) = 20
}
SubShader
{
Pass
{
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
v2f vert (appdata v)
{
v2f o;
//顶点坐标从模型空间转换成投影空间
o.pos = UnityObjectToClipPos(v.vertex);
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//顶点法向量由模型空间转换成世界空间的向量
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
//光照方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射
fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * saturate(dot(worldNormal, worldLightDir));
//世界空间的反射方向
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
//视角方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
//计算高光
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
o.color = ambient + diffuse + specular;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
十一、高光反射模型:逐像素光照
顶点函数fragment中 1、计算顶点到屏幕的投影。 o.pos = UnityObjectToClipPos(v.vertex);
2、顶点法向量从模型空间转换到世界空间。 o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
3、顶点的位置,从模型空间转换到世界空间。 o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
其他计算都放到片段函数中处理。
Shader "MyTest/SpecularPixelLevel-6-Test"
{
Properties
{
_Diffuse("Diffuse", Color) = (1, 1, 1, 1)
_Specular("Specular", Color) = (1, 1, 1, 1)
_Gloss("Gloss", Range(8, 256)) = 20
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
//需要先取得世界坐标
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//light dir in world space
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//diffuse
fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * saturate(dot(i.worldNormal, worldLightDir));
//视角方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
//反射
fixed3 reflectDir = normalize(reflect(-worldLightDir, i.worldNormal));
//specular
fixed3 specular = _Specular.rgb * _LightColor0.rgb * pow(saturate(mul(viewDir, reflectDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1);
}
ENDCG
}
}
}