这节课感觉代码细节很多,一些东西还是需要结合美术方面的直观感受来写代码,还是得感叹一句美术转技术美术真的很自然。
下面就简单放一下我的代码了,感觉也没什么好说的,一些比较重要的东西也都在代码里打上注释了,最主要的是下面这几张图:
-
- 贴图组成
- 贴图组成
-
- PS处理
- 需要注意的是有些图是歪的,像是武器的SpecularMask, RimMask, TintByBaseMask和SpecularExponent这四张图全都是歪了。需要在ps里面进行垂直翻转,然后再合成一张图。
- 最后三个fresnel的图是直接拉成了长宽大小的图,因为他们只是u坐标不一样,v坐标都是一样的。原本的长宽应该是这样的:
- PS处理
-
- 材质的shader实现思路图
- OldSchoolPro模型。为了防止和之前的课会有混淆,下面先放一下之前的shader思路图。跟之前的不一样的。之前的shader是将模型分为直接光照和环境光两个主要的部分,在光照和环境光下细分为漫反射和镜面反射去渲染。
- Dota2模型。在Dota2材质里是将模型分为漫反射和镜面反射两个主要部分去渲染,然后再在镜面反射和漫反射下分为直接光照和环境光进行渲染,然后再加上一些其他的细节,轮廓光自发光啥的。
下面就直接放上代码了:
Shader "shader forge/Dota_weapon"
{
Properties
{
[Header(Texture)] [Space]
_MainTex ("RGB With A(A:Cutout)", 2D) = "white" {}
_MaskTex("R:SpecInt G:RimInt B:TintMask A:SpecPow",2D) = "black"{}
_NormTex("RGB:Normal",2D) = "bump"{}
_MetalnessMask("Metalness Mask",2D) = "black"{}
_EmissionMask("Emission Mask",2D) = "black"{}
_DiffWrapTex("Diffuse Color Wrap Tex",2D) = "gray"{}
_FresWrapTex("Fresnel Wrap Tex",2D) = "gray"{}
_Cubemap("Cube Map",Cube) = "_Skybox"{}
[Header(Diffuse_Dir)] [Space]
_Light_Color("Light Color",Color) = (1.0,1.0,1.0,1.0)
[Header(Diffuse_Env)] [Space]
_EnvDiffCol ("Env Color",COLOR) = (1.0,1.0,1.0,1.0)
_EnvDiffInt ("Env Color Intensity", Range(0.0,10.0)) = 1.0
[Header(Specular_Dir)] [Space]
_mySpecInt ("Specular Intensity", Range(0.0,10.0)) = 5
_mySpecPow("Specular Power", Range(0.0,30.0)) = 5
[Header(Specular_Env)] [Space]
_EnvSpecInt ("Env Specular Intensity",Range(0.0,30.0)) = 1.0
[Header(RimLight)] [Space]
_RimCol ("Rim Color", Color) = (1.0,1.0,1.0,1.0)
_RimInt ("Rim Intensity", Range(0.0,20.0)) = 1.0
[Header(Emission)] [Space]
_EmitInt ("Emission Intensity",Range(0.0,20.0)) = 1.0
[HideInInspector]
_Cutoff ("Cutoff", Range(0.0,1.0)) = 0.05
[HideInInspector]
_Color ("Main Color",color)=(1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Name "FORWARD"
Tags{
"RenderType" = "ForwardBase"
}
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "AutoLight.cginc"
#include "Lighting.cginc"
#pragma multi_compile_fwdbase_fullshadows
#pragma target 3.0
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
float4 posWorld : TEXCOORD1;
float3 nDirWS : TEXCOORD2;
float3 tDirWS : TEXCOORD3;
float3 bDirWS : TEXCOORD4;
LIGHTING_COORDS(5,6)
};
//Texture
uniform sampler2D _MainTex;
uniform float4 _MainTex_ST;
uniform sampler2D _MaskTex;
uniform sampler2D _NormTex;
uniform sampler2D _MetalnessMask;
uniform sampler2D _EmissionMask;
uniform sampler2D _DiffWrapTex;
uniform sampler2D _FresWrapTex;
uniform samplerCUBE _Cubemap;
//Diffuse_Dir
uniform float4 _Light_Color;
//Diffuse_Env
uniform float4 _EnvDiffCol;
uniform float _EnvDiffInt;
//Specular_Dir
uniform half _mySpecInt;
uniform half _mySpecPow;
//Specular_Env
uniform half _EnvSpecInt;
//Rim
uniform half4 _RimCol;
uniform half _RimInt;
//Emission
uniform half _EmitInt;
//透切
uniform half _Cutoff;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
o.nDirWS = UnityObjectToWorldNormal(v.normal);
o.tDirWS = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz,0.0)).xyz);
o.bDirWS = normalize(cross(o.nDirWS,o.tDirWS) * v.tangent.w);
TRANSFER_VERTEX_TO_FRAGMENT(o)
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 nDirTS = UnpackNormal(tex2D(_NormTex,i.uv)).rgb;
//向量准备
float3x3 TBN_Matrix = float3x3(i.tDirWS,i.bDirWS,i.nDirWS);
//unity shader里的是列向量,列向量右乘矩阵时,结果是列向量
//但是法线本身有特殊性,这里反过来乘是为了乘以矩阵的逆转置矩阵
float3 nDirWS_FT = normalize(mul(nDirTS, TBN_Matrix));
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
float3 halfDir = normalize(lightDir + viewDir);
float3 vrDirWS = normalize(reflect(-viewDir,nDirWS_FT));
//中间量准备
half NoL = clamp(dot(nDirWS_FT,lightDir),0.0,1.0);
half NoV = clamp(dot(nDirWS_FT,viewDir),0.0,1.0);
half NoH = clamp(dot(nDirWS_FT,halfDir),0.0,1.0);
half VoR = clamp(dot(viewDir, vrDirWS),0.0,1.0);
//贴图采样
half4 var_MainTex = tex2D(_MainTex,i.uv);
half4 var_MaskTex = tex2D(_MaskTex,i.uv);
half var_Metalness = tex2D(_MetalnessMask,i.uv).r;
half var_Emission = tex2D(_EmissionMask,i.uv).r;
half3 var_Fresnel = tex2D(_FresWrapTex,half2(NoV,0.3)).rgb;
half3 var_Cubemap = texCUBElod(_Cubemap,float4(vrDirWS,lerp(8.0,0.0,var_MaskTex.a))).rgb;
//提取信息
half3 baseCol = var_MainTex.rgb;
half opacity = var_MainTex.a; //不透明度
half specInt = var_MaskTex.r;
half rimInt = var_MaskTex.g;
half specTint = var_MaskTex.b;
half specPow = var_MaskTex.a;
half metalness = var_Metalness;
half emitInt = var_Emission;
half3 envCube = var_Cubemap;
half3 shadow = LIGHT_ATTENUATION(i);
//光照模型
//漫反射和镜面反射,两个被不同的变量控制染色程度
half3 diff_col = lerp(baseCol,float3(0.0,0.0,0.0),metalness);
half3 spec_col = lerp(baseCol,float3(0.3,0.3,0.3),specTint); //0.3是经验值
//Fresnel
half3 fresnel = lerp(var_Fresnel,0.0,metalness);
half fres_col = fresnel.r; //罕见,这里不使用
half fres_rim = fresnel.g; //轮廓光用的Fresnel
half fres_spec = fresnel.b; //镜面反射光用的fresnel
//漫反射和镜面反射下都分为主光和环境光
// 1.0 漫反射的主光
half halfLambert = NoH * 0.5 + 0.5; //半兰伯特
half3 diffuseWrapTex = tex2D(_DiffWrapTex,half2(halfLambert,0.2)).rgb;
half3 dir_diffuseCol = diff_col * diffuseWrapTex * _Light_Color;
// 1.1 漫反射的环境光
half3 env_diffuseCol = diff_col * _EnvDiffCol * _EnvDiffInt;
// 2.0 镜面反射的主光
half phong = pow(max(VoR,0.0),specPow * _mySpecPow);
half spec = phong * max(0.0, NoL); //phong和lambert相乘,意思是在漫反射比较黑的地方,镜面反射也弱一些
spec = max(spec, fres_spec); //跟菲尼尔的高光进行混合,这里是使用取最大值
spec = spec * _mySpecInt;
half3 dir_specularCol = spec * spec_col * _Light_Color;
// 2.1 镜面反射的环境光
half envSpecInt = max(fres_spec,metalness) * specInt;
half3 env_specularCol = spec_col * envSpecInt * envCube * _EnvSpecInt;
//轮廓光rim
half3 rimCol = _RimCol * fres_rim * rimInt * max(0.0,nDirWS_FT.g) * _RimInt;
//half3 test = _RimCol * _RimInt * rimInt * max(0.0,nDirWS_FT.g);
//自发光Emission
half3 emitColor = diff_col * emitInt * _EmitInt;
//返回值
half3 finalColor = (dir_diffuseCol + dir_specularCol) * shadow + (env_diffuseCol + env_specularCol) + rimCol + emitColor;
//clip(opacity - _Cutoff); //透切,没大过阈值的都抛弃掉
return float4(finalColor,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
//FallBack "Legacy Shaders/Transparent/Cutout/VertexLit"
}
放一下效果图,上面的代码我注释掉了透切,也就是clip那一行,因为这个武器的Translucency贴图是全黑的,就中间一个白点,用了透切这行代码就啥也没有了。老师那个我也没明白咋回事,应该是自己画了贴图。
shader效果:
然后我试了一下模型主体,也就是食人魔模型,效果如下:
- 这里需要注意下,我们要从Game窗口去观察,因为这是从摄像机视角出发观察世界的,我们的shader的里的菲涅尔采样也是根据摄像机的观察方向采样的,从Game窗口看才是正常效果。
- 如果不是从Game,而是直接从Scene窗口看,会得到一个油光水亮的结果,这里让我疑惑了半天,想了下果然是观察视角的问题。油光水亮就像下面这样:
不完美的缺点
有个缺点,就像我代码最后写的FallBack,只有用Diffuse的时候,投影才正常。前面好几节课的作业都是投影问题,我查了一圈感觉可能是版本的问题。