(五)表面纹理
1.前言
模型表面颜色存储在纹理中,可以通过纹理采样来获取表面颜色,也通过表面纹理存储表面的其他信息来实现不同的效果,如这一篇中,通过其他纹理采用来控制消融的效果,后续会继续分析。
纹理采样通过tex2D内置方法来实现如tex2D(_MainTex,i.uv), _MainTex为主纹理,i.uv为uv坐标。在光照篇中,通过固定的颜色值来处理漫反射、高光反射等效果,实际中通过纹理采样的颜色值进行计算。当然有时候为了获得比较明显的高光反射效果会采用固定颜色计算高光或者固定颜色计算漫反射颜色。
在inspector面板上可以看到Till和Offset值,我们用tex2D进行采样的时并没有考虑缩放和偏移。
**获取缩放与偏移值:**定义float4类型变量,此变量名称为纹理属性名称+“_ST”,即可获取到缩放与偏移。如Properties中纹理名称为_MainTex,则float4 MainTex_ST。
计算新的UV:通过TRANSFORM_TEX来计算,以上述定义的_MainTex_ST为例,如下所示:
f.uv = TRANSFORM_TEX(v.uv, _MainTex),其方法实现如下:f.uv=v.uv * _MainTex_ST.xy + _MainTex_ST.zw;
有时候为了主纹理呈现某种颜色倾向,在进行处理时通常会给一个albedo(如unity standard材质中的albedo值),此值为一个固定的颜色值,所以通过像素相乘即可获取主纹理与albedo颜色值叠加的结果。如下所示:
_Color为颜色基调。
fixed3 albedo = tex2D(_MainTex,i.uv).rgb * _Color.rgb;
跟环境光叠加则为:
fixed3 albedo = tex2D(_MainTex,i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
在此篇中通过一个_DissolveTex纹理以及_DissolveCutoff变量,借助于clip方法可以实现将某些像素值discard掉。这样当从0-1调节_DissolveCutoff值时,就可以根据_DissolveTex纹理的rgb值逐渐消失。如果_DissolveTex采用unity内置的白色粒子纹理(即中间白色往周围逐渐变黑的纹理),则会看到模型从周围开始逐渐消失。如果是一个黑白相间的图片,则可以看到不同位置慢慢消隐,类似于消融效果。
float4 frag(v2f f):SV_TARGET
{
float4 mainTexColor=tex2D(_MainTex,f.uv);
float4 dissolveTexColor=tex2D(_DissolveTex,f.uv);
clip(dissolveTexColor.rgb-_DissolveCutoff);
return mainTexColor;
}
有时为了增加质感,会让模型表面产生凹凸不平的效果,表面凹凸不平意味着法线不同。如果单纯从模型角度处理,则过于复杂,所以通常通过更改显示来处理。所以正如前言所示,我们需要一张反应模型表面凹凸属性的纹理,然后通过此纹理获取法线,然后根据此法线计算不同光照下的颜色值进行叠加。如果不考虑光照问题,可以直接采用此纹理与主纹理相乘即可。凹凸纹理值一般是在切线空间(即法线,切线,副切换组成的坐标空间)值,所以进行计算时,所有的向量都要统一到一个空间下计算,此处统一到切线空间下计算。
我们已知顶点位置的法线和切线,可以计算得到副切线,得到三个坐标轴即可获取到从模型空间到切线空间的转换矩阵。可通过UnityCG中定义的代码块获取到。
#define TANGENT_SPACE_ROTATION \
float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; \
float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal )
在计算漫反射和高光反射时一般只需要如下几个向量:视角方向(viewDirect)、法线方向以及光线方向。由于在切线空间下,切线空间下的法线存储在纹理中,所以只需要视角方向和光线方向。
float3 binormal = cross(normalize(v.normal),normalize(v.tangent.xyz)) * v.tangent.w;
float3x3 rotation = float3x3(v.tangent.xyz,binormal,v.normal);
o.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex)).xyz;
可以采用UnpackNormal函数对法线进行转换,但是要把纹理TextureType改成NormalMap,如果图片是高度纹理还要勾选CreatedFromGrayscale。UnityCG中UnpackNormal方法如下所示:
不同版本的Unity定义不同。
inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(UNITY_NO_DXT5nm)
return packednormal.xyz * 2 - 1;
#else
return UnpackNormalmapRGorAG(packednormal);
#endif
}
这是由于不同的压缩方法,图片数据存储的rgb对应的存储位置不一样,所以获取方式不同。假如数据是一一对应的,即rgba对应float4类型的数据(x,y,z,w),则采用获取到的纹理坐标直接对应切线向量,所以此时可以直接使用,只需要将纹理坐标的(0-1)范围映射到(-1,1)范围内。
如下所示:
fixed4 packedNormal = tex2D(_BumpMap,i.uv);
fixed3 tangentNormal;
tangentNormal.xy = (packedNormal.xy*2-1)*_BumpScale;
tangentNormal.z = sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy)));
UnpackNormal使用如下:
fixed4 packedNormal = tex2D(_BumpMap,i.uv.zw);
fixed3 tangentNormal;
//tangentNormal.xy = (packedNormal.xy*2-1)*_BumpScale;
tangentNormal.xy = UnpackNormal(packedNormal).xy*_BumpScale;
tangentNormal.z = sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy)));
注:此shader抄自《Shader入门精要》(冯乐乐)一书。
Shader "LL/Tex/TexShader_OT_Tangent"
{
Properties
{
_Color("Color Tint",Color) = (1,1,1,1)
_MainTex("Main Tex",2D) = "white" {}
_Specular("Specular",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 20
_BumpMap("Normal Map",2D) = "bump"{}
_BumpScale("Bump Scale",float) = 1.0
}
SubShader
{
Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal :NORMAL;
float4 tangent : TANGENT;
float4 texcoord :TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float4 uv :TEXCOORD0;
float3 lightDir:TEXCOORD1;
float3 viewDir:TEXCOORD2;
};
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//TANGENT_SPACE_ROTATION
float3 binormal = cross(normalize(v.normal),normalize(v.tangent.xyz)) * v.tangent.w;
float3x3 rotation = float3x3(v.tangent.xyz,binormal,v.normal);
o.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex)).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);
fixed4 packedNormal = tex2D(_BumpMap,i.uv.zw);
fixed3 tangentNormal;
//tangentNormal.xy = (packedNormal.xy*2-1)*_BumpScale;
tangentNormal.xy = UnpackNormal(packedNormal