http://blog.csdn.net/lzhq1982/article/details/75032752
纹理最基础的目的就是用一张图片控制模型的外观,使用纹理映射技术,把一张图附在模型表面,逐纹素的控制模型的颜色。
美术人员会在建模软件中利用纹理展开技术把纹理映射坐标存储在每个顶点上。纹理映射坐标定义了该顶点在纹理中对应的2D坐标。我们用二维变量(u, v)来表示这些坐标,所以也被称为UV坐标。
不管纹理大小如何,UV坐标会被归一化到[0, 1]范围内。当然,纹理采样使用的纹理坐标不一定是[0, 1]范围内。这取决于纹理的平铺模式。
我们前面说过OpenGL和DirectX在二维纹理空间的坐标差异。OpenGL原点位于左下角,DirectX位于左上角,Unity默认OpenGL的左下角为原点,当然如果跨平台我们也需要手动判断。如下图所示:
我们通常会用一张纹理代替物体的漫反射颜色。下面我们实现一个最简单的纹理。
先上代码:
Shader "CustomShader/Texture/SingleTexture"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_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"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD1;
float3 worldPos : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed4 _Specular;
float _Gloss;
v2f vert (appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
// o.uv = v.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, normalize(viewDir + worldLightDir))), _Gloss);
fixed3 color = ambient + diffuse + specular;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
看着挺长,其实是加了漫反射和高光反射的部分,这在前面都介绍过了,除去这些代码,有关纹理采样的代码很少,我们剥离一下:
首先Properties属性部分,我们加入了_MainTex,可以在面板上设置,注意写法:
_MainTex ("Texture", 2D) = "white" {}
在Pass中,我们在顶点的输入结构体中传入了uv坐标,靠TEXCOORD0语义传入的。在输出结构体中,我们也输出了uv,当然是经过转化后的uv。在声明变量那里,除了属性里声明的,我们奇怪的多了一个_MainTex_ST,这个是要重点介绍的,首先,这个名字不是随便起的,我们要用纹理名_ST的方式来声明它,ST是缩放(scale)和平移(translation)的缩写。_MainTex_ST可以让我们得到该纹理的缩放和平移(偏移)值。这些值可以在材质面板的纹理属性中调节,如下图所示:
Tiling对应缩放,Offset对应偏移。
然后我们看看用到它的地方,在顶点着色器中,我注释掉了一行代码,先看看这行代码:
o.uv = v.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw;
这是我们唯一用到 _MainTex_ST的地方,从代码中看出,我们用_MainTex_ST的xy属性对顶点纹理坐标进行缩放,用zw进行偏移。而Unity提供了一个内置宏TRANSFORM_TEX来帮我们计算上述过程,我们看看它在UnityCG.cginc中的定义:#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)
第一个参数是纹理坐标,第二个参数是纹理名,回到上面代码看看:
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
这就是我为什么注释掉上面那行的原因,当然,如果你的纹理不需要缩放和偏移,你完全可以不用定义这个变量,直接用顶点着色器输入的uv就行。
然后看片元着色器部分
我们对纹理进行了采样:
tex2D(_MainTex, i.uv)
第一个参数是纹理,第二个参数是uv坐标,返回对应的纹素值,也就是该坐标对应纹理的颜色。这里和颜色属性_Color相乘作为材质最终反射颜色,并将结果参与到漫反射中,其他没啥好说的,看效果:
我们可以换别的图片看看效果。
本篇我们实现了一个最基础的纹理,但纹理其实没这么简单,读者可以在Unity中选择一个纹理,在属性面板看到一堆数据,这堆数据是什么,下一篇我们将做详细介绍。