标准Phong模型实现
Shader "Unlit/PhongJian"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Shininess ("Shininess", Range(0.01, 100)) = 1.0 // 高光亮度对比度
_SpecIntensity("SpecIntensity",Range(0.01,5)) = 1.0 // 高光亮度控制
_AmbientColor ("Ambient Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "AutoLight.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normal_dir : TEXCOORD1;
float3 pos_world : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _LightColor0;
float _Shininess;
float4 _AmbientColor;
float _SpecIntensity;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.normal_dir = normalize(mul(float4(v.normal, 0), unity_WorldToObject).xyz);
o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
half4 base_color = tex2D(_MainTex, i.uv);
half3 normal_dir = normalize(i.normal_dir);
half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);
half3 light_dir = normalize(_WorldSpaceLightPos0.xyz);
half NdotL = dot(normal_dir, light_dir);
half3 diffuse_color = max(0, NdotL) * _LightColor0.xyz * base_color;
half3 reflect_dir = reflect(-light_dir, normal_dir);
half RdotV = dot(reflect_dir, view_dir);
half3 spec_color = pow(max(0, RdotV), _Shininess) * _LightColor0.xyz * _SpecIntensity;
half3 final_color = diffuse_color + spec_color + _AmbientColor.xyz;
return fixed4(final_color,1);
}
ENDCG
}
}
Fallback "Diffuse"
}
AO贴图
Ambient Occlusion(环境遮挡贴图)简称AO贴图,模拟物体之间所产生的阴影,在不打光的时候增加体积感。也就是完全不考虑光线,单纯基于物体与其他物体越接近的区域,受到反射光线的照明越弱这一现象来模拟现实照明(的一部分)效果。
遮挡贴图用于提供关于模型哪些区域应接受高或低间接光照的信息。间接光照来自环境光照和反射,因此模型的深度凹陷部分(例如裂缝或折叠位置)实际上不会接收到太多的间接光照。
遮挡纹理贴图通常由 3D 应用程序使用建模器或第三方软件直接从 3D 模型进行计算。
遮挡贴图是灰度图像,其中以白色表示应接受完全间接光照的区域,以黑色表示没有间接光照。有时,对于简单的表面而言,这就像灰度高度贴图一样简单(例如前面高度贴图示例中显示的凸起石墙纹理)。
在其他情况下,生成正确的遮挡纹理稍微复杂一些。例如,如果场景中的角色穿着罩袍,则罩袍的内边缘应设置为非常低的间接光照,或者完全没有光照。在这些情况下,遮挡贴图通常将由美术师制作,使用 3D 应用程序基于模型自动生成遮挡贴图。
代码实现
half4 ao_color = tex2D(_AOMap, i.uv);
half3 final_color = (diffuse_color + spec_color + _AmbientColor.xyz) * ao_color;
左:无AO 右:有AO
点光源衰减公式计算
half3 light_dir = normalize(_WorldSpaceLightPos0.xyz - i.pos_world);
half distance = length(_WorldSpaceLightPos0.xyz - i.pos_world);
half range = 1.0 / unity_WorldToLight[0][0];
half attuenation = saturate((range - distance) / range);
法线贴图
每个顶点有法线和切线数据,其中切线的走向模型导入unity时根据UV坐标中的U来确定的(并不是模型自带),下图蓝色是法线,绿色是切线,红色是副切线
法线、切线、副切线
o.normal_dir = normalize(mul(float4(v.normal, 0), unity_WorldToObject).xyz);
o.tangent_dir = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0)).xyz);
o.binormal_dir = normalize(cross(o.normal_dir, o.tangent_dir)) * v.tangent.w; // tangent.w 是为了处理不同平台下的翻转问题,不是1就是-1
PC端上,采用DXT/BC对法线贴图压缩,通道信息会变更,所以要用UnpackNormal函数进行解码,顺便把法线图范围从0-1转为-1到1
解码后,会得到如下图所示的贴图,其中大量蓝色说明法线仍然是平面朝上的(0,0,1),红色是(0,1,0)代码沿着平面,这样在特定角度看,我们就能感受到它的凹凸不平,但平行于平面看还是会感觉很平,这是因为没有它的3D数据。
_NormalIntensity法线xy偏移强度,默认为1,下面会将将顶点法线改变为贴图上的位置
half3 normal_data = UnpackNormal(normalMap);
normal_data.xy = normal_data.xy * _NormalIntensity;
half3 normal_dir = normalize(i.normal_dir);
half3 tangent_dir = normalize(i.tangent_dir);
half3 binormal_dir = normalize(i.binormal_dir);
float3x3 TBN = float3x3(tangent_dir, binormal_dir, normal_dir);
normal_dir = normalize(mul(normal_data.xyz, TBN));
//normal_dir = normalize(tangent_dir * normal_data.x * _NormalIntensity + binormal_dir * normal_data.y * _NormalIntensity + normal_dir * normal_data.z);
右图:无法线贴图 左图:有法线贴图
完整代码提供
点击查看代码
Shader "Unlit/PhongJian"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_NormalMap("NormalMap",2D) = "bump"{}
_AOMap ("Texture", 2D) = "white" {} // AO贴图
_SpecMask ("Texture", 2D) = "white" {} // 粗糙度贴图反向得到
_Shininess ("Shininess", Range(0.01, 100)) = 1.0 // 高光亮度对比度
_SpecIntensity("SpecIntensity",Range(0.01,5)) = 1.0 // 高光亮度控制
_NormalIntensity("NormalIntensity", Range(0, 5)) = 1// 法线xy偏移强度
_AmbientColor ("Ambient Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "AutoLight.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normal_dir : TEXCOORD1;
float3 pos_world : TEXCOORD2;
float3 tangent_dir : TEXCOORD3;
float3 binormal_dir : TEXCOORD4;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _LightColor0;
float _Shininess;
float4 _AmbientColor;
float _SpecIntensity;
sampler2D _AOMap;
sampler2D _SpecMask;
sampler2D _NormalMap;
float _NormalIntensity;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.normal_dir = normalize(mul(float4(v.normal, 0), unity_WorldToObject).xyz);
o.tangent_dir = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0)).xyz);
o.binormal_dir = normalize(cross(o.normal_dir, o.tangent_dir)) * v.tangent.w; // tangent.w 是为了处理不同平台下的翻转问题,不是1就是-1
o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
half4 base_color = tex2D(_MainTex, i.uv);
half4 ao_color = tex2D(_AOMap, i.uv);
half4 spec_mask = tex2D(_SpecMask, i.uv);
half4 normalMap = tex2D(_NormalMap, i.uv);
half3 normal_data = UnpackNormal(normalMap);
normal_data.xy = normal_data.xy * _NormalIntensity;
half3 normal_dir = normalize(i.normal_dir);
half3 tangent_dir = normalize(i.tangent_dir);
half3 binormal_dir = normalize(i.binormal_dir);
float3x3 TBN = float3x3(tangent_dir, binormal_dir, normal_dir);
normal_dir = normalize(mul(normal_data.xyz, TBN));
//normal_dir = normalize(tangent_dir * normal_data.x * _NormalIntensity + binormal_dir * normal_data.y * _NormalIntensity + normal_dir * normal_data.z);
half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);
half3 light_dir = normalize(_WorldSpaceLightPos0.xyz);
half NdotL = dot(normal_dir, light_dir);
half3 diffuse_color = max(0, NdotL) * _LightColor0.xyz * base_color;
half3 reflect_dir = reflect(-light_dir, normal_dir);
half RdotV = dot(reflect_dir, view_dir);
half3 spec_color = pow(max(0, RdotV), _Shininess) * _LightColor0.xyz * _SpecIntensity * spec_mask.rgb;
half3 final_color = (diffuse_color + spec_color + _AmbientColor.xyz) * ao_color;
return fixed4(final_color,1);
}
ENDCG
}
Pass
{
Tags{"LightMode" = "ForwardAdd"}
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd
#include "UnityCG.cginc"
#include "AutoLight.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normal_dir : TEXCOORD1;
float3 pos_world : TEXCOORD2;
float3 tangent_dir : TEXCOORD3;
float3 binormal_dir : TEXCOORD4;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _LightColor0;
float _Shininess;
float4 _AmbientColor;
float _SpecIntensity;
sampler2D _AOMap;
sampler2D _SpecMask;
sampler2D _NormalMap;
float _NormalIntensity;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.normal_dir = normalize(mul(float4(v.normal, 0), unity_WorldToObject).xyz);
o.tangent_dir = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0)).xyz);
o.binormal_dir = normalize(cross(o.normal_dir, o.tangent_dir)) * v.tangent.w; // tangent.w 是为了处理不同平台下的翻转问题,不是1就是-1
o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
half4 base_color = tex2D(_MainTex, i.uv);
half4 ao_color = tex2D(_AOMap, i.uv);
half4 spec_mask = tex2D(_SpecMask, i.uv);
half4 normalMap = tex2D(_NormalMap, i.uv);
half3 normal_data = UnpackNormal(normalMap);
normal_data.xy = normal_data.xy * _NormalIntensity;
half3 normal_dir = normalize(i.normal_dir);
half3 tangent_dir = normalize(i.tangent_dir);
half3 binormal_dir = normalize(i.binormal_dir);
float3x3 TBN = float3x3(tangent_dir, binormal_dir, normal_dir);
normal_dir = normalize(mul(normal_data.xyz, TBN));
//normal_dir = normalize(tangent_dir * normal_data.x * _NormalIntensity + binormal_dir * normal_data.y * _NormalIntensity + normal_dir * normal_data.z);
half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);
#if defined (DIRECTIONAL)
half3 light_dir = normalize(_WorldSpaceLightPos0.xyz);
half attenuation = 1;
#elif defined (POINT)
half3 light_dir = normalize(_WorldSpaceLightPos0.xyz - i.pos_world);
half distance = length(light_dir);
half range = 1.0 / unity_WorldToLight[0][0];
half attenuation = saturate((range - distance) / range);
#endif
half NdotL = dot(normal_dir, light_dir);
half3 diffuse_color = max(0, NdotL) * _LightColor0.xyz * base_color * attenuation;
half3 reflect_dir = reflect(-light_dir, normal_dir);
half RdotV = dot(reflect_dir, view_dir);
half3 spec_color = pow(max(0, RdotV), _Shininess) * _LightColor0.xyz * _SpecIntensity * spec_mask.rgb * attenuation;
half3 final_color = (diffuse_color + spec_color) * ao_color; // add中不计算环境光
return fixed4(final_color,1);
}
ENDCG
}
}
Fallback "Diffuse"
}
参考文档
- https://mp.weixin.qq.com/s?__biz=MzU0MDcxOTc5MA==&mid=2247493155&idx=1&sn=856fc57a251058d31695f840c88c277e&chksm=fb3649e2cc41c0f4e479fc23d468ef3491f55b3e3ee6f109205b92a873b0d5776ae8474faf26&scene=27
- https://zhuanlan.zhihu.com/p/655471214