UnityCG.cginc 部分结构体
struct appdata_base {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct appdata_tan {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
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
};
可视化
测试
Shader "Unlit/01TestShader"
{
Properties
{
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 position:SV_POSITION;
fixed3 color:COLOR0;
};
v2f vert(appdata_base i)
{
v2f o;
o.position = UnityObjectToClipPos(i.vertex); // 模型坐标转换到裁剪坐标
o.color = fixed3(0.5,1,1); // 每个顶点都是蓝色的
return o;
}
fixed4 frag(v2f i):SV_Target
{
return fixed4(i.color,1);
}
ENDCG
}
}
}
运行结果
UV 可视化
Shader "Unlit/01TestShader"
{
Properties
{
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 position:SV_POSITION;
fixed3 color:COLOR0;
};
v2f vert(appdata_base i)
{
v2f o;
o.position = UnityObjectToClipPos(i.vertex);
o.color = fixed3(i.texcoord.xy,0);
return o;
}
fixed4 frag(v2f i):SV_Target
{ // r,b 取 uv 坐标,如果超过了 [0,1],那么加蓝调。
fixed3 c = frac(i.color); // 取小数部分
if(any(saturate(i.color.xy)-i.color.xy)) // saturate,限制在 [0,1] 之间,any,全部为 0 ,返回 false
{
c.b = 0.5;
// return fixed4(0,0,1,1); // 超过了 [0,1],直接变成蓝色。
}
return fixed4(c,1);
}
ENDCG
}
}
}
运行结果
法线可视化
Shader "Unlit/01TestShader"
{
Properties
{
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 position:SV_POSITION;
fixed3 color:COLOR0;
};
v2f vert(appdata_full i)
{
v2f o;
o.position = UnityObjectToClipPos(i.vertex);
o.color = i.normal * 0.5 + 0.5; // 把法线映射到 [0,1] 的范围
return o;
}
fixed4 frag(v2f i):SV_Target
{
return fixed4(i.color,1);
}
ENDCG
}
}
}
运行效果
切线可视化
Shader "Unlit/01TestShader"
{
Properties
{
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 position:SV_POSITION;
fixed3 color:COLOR0;
};
v2f vert(appdata_full i)
{
v2f o;
o.position = UnityObjectToClipPos(i.vertex);
o.color = i.tangent * 0.5 + 0.5; // 把切线映射到 [0,1] 的范围
return o;
}
fixed4 frag(v2f i):SV_Target
{
return fixed4(i.color,1);
}
ENDCG
}
}
}
运行结果
副切线可视化
Shader "Unlit/01TestShader"
{
Properties
{
}
SubShader
{
Pass
{
Fog { Mode Off }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct v2f {
float4 pos : SV_POSITION;
float3 color : COLOR;
};
v2f vert(appdata_full i)
{
v2f o;
o.pos = UnityObjectToClipPos(i.vertex);
float3 bitangent = cross( i.normal, i.tangent.xyz ) * i.tangent.w; // w 为 1 或者 -1,用来确定副切线的方向
o.color.xyz = bitangent * 0.5 + 0.5; // 把副切线映射到 [0,1] 的范围
// o.color.xyz = fixed3(-1,-1,-1);
return o;
}
fixed4 frag(v2f i):SV_Target
{
return fixed4(i.color,1);
}
ENDCG
}
}
}
运行结果
漫反射模型
逐顶点
Shader "MyShader/MyDiffuseShader"
{
Properties
{
_Diffuse("Diffuse",Color)=(1,1,1,1)
}
SubShader
{
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityShaderVariables.cginc"
#include "Lighting.cginc"
fixed4 _Diffuse;
struct appdate
{
float4 position:POSITION;
float3 normal:NORMAL;
};
struct v2f
{
float4 position:SV_POSITION;
fixed3 color:COLOR0;
};
v2f vert (appdate i)
{
v2f o;
o.position = UnityObjectToClipPos(i.position);
// 环境光
fixed4 ambient = UNITY_LIGHTMODEL_AMBIENT;
// 漫反射光源
float3 normal = normalize(mul(i.normal,(float3x3)unity_WorldToObject)); // 计算法线,这种方式避免法线直接通过矩阵变换之后与表面不垂直的情况。
float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz); // 光照方向
o.color = ambient + _LightColor0 * _Diffuse * saturate(dot(normal,lightDirection)); // 颜色 * 就是对应位置相乘
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color,1);
}
ENDCG
}
}
}
运行结果
最后有个颜色相乘,其实就是对应位置相乘,比如说 Diffuse 为 (1,0,0),然后直射光颜色为 (0,1,0)。那么结果就是 (0,0,0),显示出来也就是黑色的。而且不能随着光源的远近有不同亮度的变化,并且应该是被完全遮挡的地方也可能会很亮,比如嘴巴那边,光是从背照过来的。
逐像素
Shader "MyShader/MyDiffuseShader"
{
Properties
{
_Diffuse("Diffuse",Color)=(1,1,1,1)
}
SubShader
{
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityShaderVariables.cginc"
#include "Lighting.cginc"
fixed4 _Diffuse;
struct appdate
{
float4 position:POSITION;
float3 normal:NORMAL;
};
struct v2f
{
float4 position:SV_POSITION;
fixed3 normal:COLOR0;
};
v2f vert (appdate i)
{
v2f o;
o.position = UnityObjectToClipPos(i.position);
o.normal = normalize(mul(i.normal,(float3x3)unity_WorldToObject)); // 计算法线
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz); // 光照的方向
fixed3 lightColor = _LightColor0.rgb; // 光源的颜色
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 计算环境光
fixed3 color = ambient + _Diffuse.rgb * lightColor * saturate(dot(i.normal,lightDirection)); // 计算最终的颜色
return fixed4(color,1);
}
ENDCG
}
}
}
运行结果
半兰伯特模型
Shader "MyShader/MyDiffuseShaderHalfLambert"
{
Properties
{
_Diffuse("Diffuse",Color)=(1,1,1,1)
_HalfLambert("HalfLambert",float) = 0
}
SubShader
{
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityShaderVariables.cginc"
#include "Lighting.cginc"
fixed4 _Diffuse;
float _HalfLambert;
struct appdate
{
float4 position:POSITION;
float3 normal:NORMAL;
};
struct v2f
{
float4 position:SV_POSITION;
fixed3 normal:COLOR0;
};
v2f vert (appdate i)
{
v2f o;
o.position = UnityObjectToClipPos(i.position);
o.normal = normalize(mul(i.normal,(float3x3)unity_WorldToObject)); // 计算法线
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz); // 光照的方向
fixed3 lightColor = _LightColor0.rgb; // 光源的颜色
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 计算环境光
fixed3 color = ambient + _Diffuse.rgb * lightColor *(_HalfLambert * dot(i.normal,lightDirection) + _HalfLambert); // 使用半兰伯特模型模型
return fixed4(color,1);
}
ENDCG
}
}
}
半波兰特模型没有物理依据,只是单纯的视觉增强效果,将原来光线方向的单位向量在法线上的投影小于零就使用 0,改成将 [-1,1] 投影到 [0,1] 的范围。
三种漫反射模型对比
- 最左:逐顶点,也就是计算每个顶点的值,然后在每个三角面进行插值,好处是顶点少的话,计算量少,坏处是顶点少的话过度比较生硬。
- 第二个:逐像素,计算每一个像素的颜色,相较于逐顶点,过度会更顺滑,代价是计算量更大一点。但如果模型足够细腻,顶点个数足够多,那从效果和计算量上来说,两者几乎一致。
- 第三个,两个系数都是 0.5 的半兰伯特模型,背部也能看到颜色了。
-
第四个,两个系数都是 0.25 的半兰伯特模型,要更亮了一些。
渲染结果对比
高光模型
高光逐顶点
Shader "Unlit/SpecularVertexLevelMat"
{
Properties
{
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
_Spacular("Spacular",Color) = (1,1,1,1)
_Gloss("Gloss",float) = 0
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityShaderVariables.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _Spacular;
float _Gloss;
struct appdata
{
float4 vertex : POSITION;
fixed3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed3 color:COLOR0;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
// 环境光
fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 漫反射光
float3 normal = normalize(mul(v.normal,(float3x3)unity_WorldToObject)); // 计算法线
fixed3 lightColor = _LightColor0.rgb; // 光源的颜色
float3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); // 光照的方向,是从模型出发的,也就是跟场景中看到的光照方向相反
fixed3 diffuseColor = _DiffuseColor * lightColor * saturate(dot(normal,worldLightDir));
// 计算高光
float3 reflectDir = normalize(reflect(-worldLightDir,normal)); // 计算光的反射方向
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz); // 计算观察的方向
fixed3 spacularColor = _Spacular * lightColor * pow(saturate(dot(reflectDir,viewDir)),_Gloss);
o.color = ambientColor + diffuseColor + spacularColor;
// o.color = reflectDir;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color,1);
}
ENDCG
}
}
}
运行结果
高光部分会跟随视角的变换改变强度和位置。_Gloss 越大,高光越小。观察点的位置不是摄像机的位置,而是我们在 Scene 人眼的位置。
高光逐像素
Shader "Unlit/SpecularVertexLevelMat"
{
Properties
{
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
_Spacular("Spacular",Color) = (1,1,1,1)
_Gloss("Gloss",float) = 0
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityShaderVariables.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _Spacular;
float _Gloss;
struct appdata
{
float4 vertex : POSITION;
fixed3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject)); // 计算法线
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 环境光
fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 漫反射光
float3 normal = i.worldNormal; // 计算法线
fixed3 lightColor = _LightColor0.rgb; // 光源的颜色
float3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); // 光照的方向,是从模型出发的,也就是跟场景中看到的光照方向相反
fixed3 diffuseColor = _DiffuseColor * lightColor * saturate(dot(normal,worldLightDir));
// 计算高光
float3 reflectDir = normalize(reflect(-worldLightDir,normal)); // 计算光的反射方向
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos); // 计算观察的方向
fixed3 spacularColor = _Spacular * lightColor * pow(saturate(dot(reflectDir,viewDir)),_Gloss);
fixed3 color = ambientColor + diffuseColor + spacularColor;
return fixed4(color,1);
}
ENDCG
}
}
}
运行结果
运行结果
逐像素 BlinnPhong 模型
Shader "MyShader/BlinnPhongMat"
{
Properties
{
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
_Spacular("Spacular",Color) = (1,1,1,1)
_Gloss("Gloss",float) = 0
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityShaderVariables.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _Spacular;
float _Gloss;
struct appdata
{
float4 vertex : POSITION;
fixed3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject)); // 计算法线
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 环境光
fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 漫反射光
float3 normal = i.worldNormal; // 计算法线
fixed3 lightColor = _LightColor0.rgb; // 光源的颜色
float3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); // 光照的方向,是从模型出发的,也就是跟场景中看到的光照方向相反
fixed3 diffuseColor = _DiffuseColor * lightColor * saturate(dot(normal,worldLightDir));
// 使用 BlinnPhone 模型,因为计算简单很多
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); // 计算观察的方向
float3 h_normal = normalize(viewDir.xyz + worldLightDir.xyz); // 计算视角方向和光源方向的中间方向,用它和法线的夹角来确定光强
fixed3 spacularColor = _Spacular * lightColor * pow(max(0,dot(normal,h_normal)),_Gloss);
fixed3 color = ambientColor + diffuseColor + spacularColor;
return fixed4(color,1);
}
ENDCG
}
}
}
BlinnPhong 模型
从下面的对比图可以看到相同的 _Gloss ,BlinnPhong 的光圈要更大一点,BlinnPhong 也是一种经验模型,因为可以简化求反射角的计算,而且可能在某些场景更加的真实。
左:BlinnPhong 右:基本光照模型使用 Unity 里面自带的函数
// File: UnityCG.cginc
// Transforms direction from object to world space
inline float3 UnityObjectToWorldDir( in float3 dir )
{
return normalize(mul((float3x3)unity_ObjectToWorld, dir));
}
// Transforms direction from world to object space
inline float3 UnityWorldToObjectDir( in float3 dir )
{
return normalize(mul((float3x3)unity_WorldToObject, dir));
}
// Transforms normal from object to world space
inline float3 UnityObjectToWorldNormal( in float3 norm )
{
#ifdef UNITY_ASSUME_UNIFORM_SCALING
return UnityObjectToWorldDir(norm);
#else
// mul(IT_M, norm) => mul(norm, I_M) => {dot(norm, I_M.col0), dot(norm, I_M.col1), dot(norm, I_M.col2)}
return normalize(mul(norm, (float3x3)unity_WorldToObject));
#endif
}
// Computes world space view direction, from object space position
inline float3 UnityWorldSpaceViewDir( in float3 worldPos )
{
return _WorldSpaceCameraPos.xyz - worldPos;
}
// Computes world space view direction, from object space position
// *Legacy* Please use UnityWorldSpaceViewDir instead
inline float3 WorldSpaceViewDir( in float4 localPos ) // 如果这里的 w 为 0,那么跟直接调用 UnityWorldSpaceViewDir 是一样的
{
float3 worldPos = mul(unity_ObjectToWorld, localPos).xyz;
return UnityWorldSpaceViewDir(worldPos);
}
修改之后的 BlinnPhone 模型:
Shader "MyShader/BlinnPhongMat"
{
Properties
{
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
_Spacular("Spacular",Color) = (1,1,1,1)
_Gloss("Gloss",float) = 0
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityShaderVariables.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _Spacular;
float _Gloss;
struct appdata
{
float4 vertex : POSITION;
fixed3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal); // 使用 UnityObjectToWorldNormal 计算法线
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 环境光
fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 漫反射光
float3 normal = i.worldNormal;
fixed3 lightColor = _LightColor0.rgb;
float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // 使用 UnityWorldSpaceLightDir获取光照的方向
fixed3 diffuseColor = _DiffuseColor * lightColor * saturate(dot(normal,worldLightDir));
// 使用 BlinnPhone 模型,因为计算简单很多
float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); // 使用 UnityWorldSpaceViewDir获取观察的方向
float3 h_normal = normalize(viewDir.xyz + worldLightDir.xyz);
fixed3 spacularColor = _Spacular * lightColor * pow(max(0,dot(normal,h_normal)),_Gloss);
fixed3 color = ambientColor + diffuseColor + spacularColor;
return fixed4(color,1);
}
ENDCG
}
}
}
网友评论