本章节分为代码部分和概念部分,概念部分明天再写,今天先开摆!
在最初的游戏行业,游戏中的物体都是使用一张图片来控制模型外观的,将“纹理映射”直接“黏”在 模型的表面,再通过“逐纹素(texel)”来控制模型的颜色。这里的“纹素”其实就是为了区分“像素”而诞生的名词,在纹理中的每个单位,即为纹素。
根据上文,可以轻松的得知,我们需要如何将贴图(也就是我们的纹理图片)映射到模型上——通过纹理展开技术,将纹理映射坐标存储在每一个顶点上。
还有一个重要的便是unity中的“纹理贴图”的坐标,没搞懂坐标,可能在之后会发现贴图的效果与自己预想的恰好相反。在纹理贴图中,一般默认使用二维变量(u,v)来表示二维坐标,而不是xy。
Unity的shader纹理贴图与OpenGL的贴图是类似的——都是以左下角为原点,u的正方向向右,v的正方向向上,如下图所示。(而对于DirectX来说,恰恰相反——原点在左上角,v的正方向向下,u的正方向向右)
Unity会自动为我们调整纹理贴图的方向
。这样就可以确保Unity在OpenGL和DirectX的平台都适配。(这里的自动调整,会根据玩家自己的电脑平台,来调整是否旋转以达到正确预期)Graphics.Blit()函数
继续帮我们调整贴图方向。(开启抗锯齿后,在DirectX平台中,_MainTexelSize.y的值将会小于0。但是对于装饰性的纹理贴图来说,不必太在意是否翻转)
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y < 0)
uv.y = 1 - uv.y;
#endif
纹理的大小可能是不尽相同的(256×256、1024×1024),但最终都会进行归一化处理成 [0,1] 的范围内。虽然在纹理采样的时候可能不会严格按照 [0,1] 的范围进行采样(这个稍后谈到)。
在Unity中,会自动为我们调整纹理坐标(情况就是上述所讲的三种抗锯齿情况)。
Mat_Texture
。Sha_Texture
并将该Shader赋值给Mat_Texture
。Mat_Texture
赋予该物体。①删除第3步新建的Shader里的默认代码。
②我们以Blinn-Phong光照模型来计算光照。
③为了使用纹理,我们需要在Properties语义块中添加一个纹理属性:
Properties{
_Color ("Base Color",Color) = (1,1,1,1)
_MainTex("Main Texture",2D) = "white"{ }
_Specular("Specular",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 20
}
④光照继续使用ForwardBase
⑤设置顶点着色器vert和片元着色器frag,并包含Lighting.cginc
⑥初始化上述四个变量
float4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Specular;
half _Gloss;
这里的_MainTex_ST
的名字并不是任意起的。在unity中,我们需要使用 纹理名_ST 的方式来声明某个纹理的属性。其中,ST分别是缩放Scale 和 平移Translation 的缩写。使用方法是:_MainTex_ST.xy 访问纹理缩放,_MainTex_ST.zw 访问纹理偏移。
⑦接下来,我们定义顶点着色器的输入和输出结构体:
struct a2v {
float4 vertex : POSITION;
float4 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
其中,TEXCOORD0会把第一组纹理坐标传递给texcoord,方便顶点着色器访问。且在v2f中,定义了uv方便我们在片元着色器使用该坐标进行纹理采样。
⑧然后,我们来定义顶点着色器:
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
//_ST.xy -- 缩放 _ST.zw -- 位移
return o;
}
上述代码首先计算了_MainTex_ST.xy的缩放,然后再使用_MainTex_ST.zw对结果进行偏移。(TRANSFORM_TEX可以帮我们计算上述过程。)
⑨实现片元着色器,计算漫反射时使用的纹理中的纹素值。
fixed4 frag(v2f i) : SV_Target{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
上述代码首先根据Blinn-Phong公式计算了法线方向和光照方向。然后CG的tex2D函数对纹理进行采样。第一个是需要采样的纹理,第二个是计算返回的纹素值,再使用采样结果和颜色属性_Color的成绩来作为材质的 反射率albedo。
整体代码:
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader"LeonShader/Shader_7_1_Texture"{
Properties{
_Color ("Base Color",Color) = (1,1,1,1)
_MainTex("Main Texture",2D) = "white"{ }
_Specular("Specular",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 20
}
SubShader{
Pass{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
float4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Specular;
half _Gloss;
struct a2v {
float4 vertex : POSITION;
float4 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
//_ST.xy -- 缩放 _ST.zw -- 位移
return o;
}
fixed4 frag(v2f i) : SV_Target{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
}