基础纹理

纹理,即使用纹理映射(texture mapping)技术来控制模型的外观,将一张图片黏在模型表面,逐纹素(用于和像素区分)地控制模型模型颜色。通常会在建模软件中利用纹理展开技术把纹理**映射坐标(texture-mapping coordinate)**存储在每个顶点上,纹理映射坐标定义了该顶点在纹理中对应的2D坐标,利用二维变量(u, v)来表示,其中u为横向坐标,v为纵向坐标,也被称为UV坐标。

顶点UV坐标的范围一般被归一化在[0, 1]范围内,但同时,纹理坐标不一定在[0, 1]范围内,不在[0, 1]范围内地纹理特别有用,将关系到纹理的平铺模式。

  • OpenGL:纹理原点位于左下角
  • DirectX:纹理原点位于左上角
  • Unity:符合OpenGL传统,纹理原点位于左下角

1 单张纹理

1.1 实践

Shader "MyShader/Texture/SingleTexture"
{Properties{_Diffuse("Diffuse", Color) = (1, 1, 1, 1)      // 控制漫反射颜色_Specular("Specular", Color) = (1, 1, 1, 1)    // 控制高光反射颜色_Gloss("Gloss", Range(8.0, 256)) = 20          // 控制高光区域大小_Color("Color Tint", Color) = (1, 1, 1, 1)     // 控制纹理总体色调_MainTex("Main Tex", 2D) = "white" {}          // 控制纹理图片}SubShader{Pass{Tags { "LightMode" = "UniversalForward" }CGPROGRAM#pragma vertex vert#pragma fragment frag#include "Lighting.cginc"#include "UnityCG.cginc"// 获取Properties语句中定义的变量fixed4 _Diffuse;fixed4 _Specular;float _Gloss;fixed4  _Color;sampler2D _MainTex;float4 _MainTex_ST;   // Unity中需要使用 纹理名_ST 的方式声明某个纹理的属性// 其中S是Scale表示缩放,T为Tanslation表示平移,_MainTex_ST可以让我们获取该纹理的缩放和偏移值,其中_MainTex_ST.xy存储缩放值,_MainTex_ST.zw存储偏移值struct a2v {float4 vertex : POSITION;     // 模型空间中顶点的位置float3 normal : NORMAL;       // 模型空间中顶点的法线float4 texcoord : TEXCOORD;   // 模型的第一组纹理坐标};struct v2f {float4 pos : SV_POSITION;          // 裁剪空间中顶点的位置float3 worldNormal : TEXCOORD0;    // 顶点着色器计算得到的颜色float3 worldPos : TEXCOORD1;       // 顶点着色器计算得到的颜色float2 uv : TEXCOORD2;             // 在片元着色器中使用该坐标进行纹理采样};v2f vert (a2v v){v2f f;f.pos = UnityObjectToClipPos(v.vertex);                         // 将顶点坐标从模型空间变化到裁剪空间f.worldNormal = UnityObjectToWorldNormal(v.normal) ;            // 将顶点法线从模型空间变换到世界空间// f.worldPos = mul(unity_ObjectToWorld, v.vertex);f.worldPos = UnityObjectToWorldDir(v.vertex);                   // 将顶点坐标从模型空间变换到世界空间f.uv = TRANSFORM_TEX(v.texcoord, _MainTex);                     // 也可以之间使用内置函数计算uv坐标,在UnityCG.cginc中// f.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;      // 首先使用缩放属性_MainTex_ST.xy对顶点纹理坐标进行缩放,再使用偏移属性_MainTex.zw进行偏移return f;}fixed4 frag (v2f f) : SV_Target{fixed3 worldNormal = normalize(f.worldNormal);                                                           // 获取顶点法线在世界空间的坐标fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(f.worldPos));                                   // 获取光源的方向fixed3 viewDir = normalize(UnityWorldSpaceViewDir(f.worldPos));                                          // 获取视线方向fixed3 halfDir = normalize(worldLightDir + viewDir);                                                     // 获取矢量hfixed3 albedo = tex2D(_MainTex, f.uv).rgb * _Color.rgb;                                                  // 使用tex2D函数对纹理进行采样,使用采样结果和颜色属性的乘积作为材质反射率fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;                                                  // 获取环境光的同时乘以材质反射率fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));   // 计算漫反射的同时乘以材质反射率fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, halfDir)), _Gloss);   // 计算高光反射return fixed4(ambient + diffuse + specular, 1);}ENDCG}}FallBack "Specular"
}

1.2 纹理的属性

  • 纹理类型(Texture Type):默认为Texture类型,后面的章节中会用到Normal Map和类型等,需要为导入的纹理传递合适的类型才能为Unity Shder传递正确的纹理,并在一些情况下可以让Unity对该纹理进行优化
  • 平铺模式(Wrap Mode):决定了当纹理坐标超过[0, 1]时会如何被平铺,想要纹理得到平铺的效果,Shader中须对顶点纹理进行相应变换
f.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
f.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
  • 纹理滤波(Filter Mode):决定纹理由于变换而产生拉伸时将会采用哪种滤波模式,支持Point,Bilinear,Trilinear,得到的图片滤波效果依次提升,但性能也会增大,纹理滤波会影响放大或缩小纹理时得到的图片质量

    • Point:使用最近邻滤波(nearest neighbor),在放大或缩小时。它的采样像素数目通常只有一个,因此图片风格看起来有种像素风格的效果
    • Bilinear:线性滤波,对于每个目标像素,会找到4个邻近像素,然后对它们进行线性插值混合后得到最终像素,因此图像看起来被模糊了
    • Trilinear:几乎和Bilinear一样,但是还会在多级渐远纹理之间混合
  • 多级渐远纹理(MipMapping):将原纹理提前用滤波处理来得到很多更小的图像,形成一个金字塔,每一层都是对上一层图像降采样的结果,这样实际运行时,就可以快速得到结果像素。在Unity中Advanced-Generate Mip Maps即可开启多级渐远纹理技术

更多关于纹理的细节可以参考Unity官方文档:

2 凹凸映射

**凹凸映射(bump mapping)**的目的是使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节,凹凸映射不会改变顶点的位置,只是模型看起来好像”凹凸不平“,但从模型的轮廓处可以看出”破绽“

两种主流方法:

  • 高度映射(height mapping):使用一张高度纹理(height map)来模拟表面位移(displacement),然后得到一个修改后的法线值
  • 法线映射(normal mapping):使用一张**法线纹理(normal map)**来直接存储表面法线

2.1 高度纹理

高度图中存储的时强度值(intensity),用于表示模型表面局部的海拔高度,颜色越浅表示该位置的表面越向外凸起,颜色越深表示该位置更向里凹

优点:直观,可以从高度图中明确地知道一个模型表面的凹凸情况

缺点:计算复杂,在实时计算不能直接得到表面法线,而是需要由像素灰度值计算而得

2.2 法线纹理

法线纹理中存储的就是表面的法线方向,由于法线方向分量范围为[-1, 1],而像素的分量范围为[0, 1],因此我们需要做一个映射:
pixel=normal+12pixel = \frac{normal + 1}{2} pixel=2normal+1​
即将顶点的法线大小映射到像素值大小上,利用像素值大小标识法线的方向

这就要求我们在Shader中对法线纹理进行纹理采样后,还需要对结果进行一次反映射过程,以得到原先的法线方向
normal=pixel×2−1normal = pixel \times 2 - 1 normal=pixel×2−1

  • 模型空间的法线纹理(object-space normal map):将修改后的模型空间中的表面法线存储在一张纹理中

    • 实现简单,更加直观,所有的法线所在的坐标空间是同一个坐标空间
    • 可以提供平滑的边界,在纹理坐标缝合处和尖锐的边角部分,可见的突隙较少
  • 切线空间的法线纹理(tangent-space normal map):对于模型的每个顶点,它都有一个属于自己的切线空间,这个切线空间的原点就是顶点本身,而z轴时顶点的法线方向,x轴是顶点切线方向,y可由法线和切线叉乘获得,也被称为副切线
    • 自由度很高,模型空间法线纹理仅可用于创建它时的模型。应用到其他模型上效果会完全出错,而切线空间法线纹理可以重用
    • 可以进行UV动画
    • 可压缩

切线空间在很多情况下优于模型空间,也可以节省美术的工作,本书中使用的也是切线空间下的法线纹理

2.3 实践

1.在切线空间下计算

本质上就是通过改变法线方向达到有凹凸纹理的效果

Shader "MyShader/Texture/NormalMapTangentSpace"
{Properties {// 光照_Diffuse("Diffuse", Color) = (1, 1, 1, 1)      // 控制漫反射颜色_Specular("Specular", Color) = (1, 1, 1, 1)    // 控制高光反射颜色_Gloss("Gloss", Range(8.0, 256)) = 20          // 控制高光区域大小// 基础纹理_Color("Color Tint", Color) = (1, 1, 1, 1)     // 控制纹理总体色调_MainTex("Main Tex", 2D) = "white" {}          // 控制纹理图片// 凹凸纹理_BumpMap("Normal Map", 2D) = "bump" {}         // "bump"为Unity内置的法线纹理,当没有提供任何法线时,"bump"就对应了模型自带的法线信息_BumpScale("Bump Scale", Float) = 1.0          // 控制凹凸程度,当为0时,意味该法线纹理不会对光线产生任何影响}SubShader {Pass {Tags { "LightMode" = "UniversalForward" }CGPROGRAM#pragma vertex vert#pragma fragment frag#include "Lighting.cginc"#include "UnityCG.cginc"// 获取Properties语句中定义的变量// 光照fixed4 _Diffuse;fixed4 _Specular;float _Gloss;// 基础纹理fixed4  _Color;sampler2D _MainTex;float4 _MainTex_ST;   // Unity中需要使用 纹理名_ST 的方式声明某个纹理的属性// 凹凸纹理sampler2D _BumpMap;float4 _BumpMap_ST;float _BumpScale;struct a2v {// 光照float4 vertex : POSITION;      // 模型空间中顶点的位置float3 normal : NORMAL;        // 模型空间中顶点的法线// 基础纹理float4 texcoord : TEXCOORD0;   // 顶点的第一组纹理坐标,通过该纹理坐标,经过缩放和偏移后可以计算得到顶点的uv坐标// 凹凸纹理float4 tangent : TANGENT;      // 把顶点的切线填充到tangent变量中};struct v2f {// 光照float4 pos : SV_POSITION;          // 裁剪空间中顶点的位置float3 lightDir : TEXCOORD1;       // 顶点着色器计算得到的切线空间下的光照方向float3 viewDir : TEXCOORD2;        // 顶点着色器计算得到的切线空间下的视线方向// 纹理float4 uv : TEXCOORD0;             // 在片元着色器中使用该坐标进行纹理采样,其中xy分量存储_MainTex的纹理坐标,zw分量存储_BumpMap的纹理坐标};// 顶点着色器在这里主要负责计算_MainTex和_BumpMap的纹理坐标,同时计算切线空间下的视角和光照方向,传递给片元着色器v2f vert (a2v v) {v2f f;f.pos = UnityObjectToClipPos(v.vertex);                         // 将顶点坐标从模型空间变化到裁剪空间// 将v2f的uv定义为float4类型,其中xy分量存储_MainTex的纹理坐标,zw分量存储_BumpMap的纹理坐标f.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;      f.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;      // float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;  // 计算副切线方向// 由于和切线与法线均垂直的方向有两个,所以在计算副切线时我们使用v.tangent.w和叉积结果相乘来决定选择的方向// float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);                       // 得到从模型空间到切线空间的变换矩阵TANGENT_SPACE_ROTATION;  // 是UnityCG.cginc中提供的rotation变换矩阵,写入该行后后面就可以直接使用rotation矩阵f.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;   // 获取模型空间下的光照方向,利用rotation变换矩阵把它从模型空间变换到切线空间f.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;     // 获取模型空间下的视角方向,利用rotation变换矩阵把它从模型空间变换到切线空间return f;}fixed4 frag (v2f f) : SV_Target {// 获取切线空间下光照和视角方向并归一化fixed3 tangentLightDir = normalize(f.lightDir);   fixed3 tangentViewDir = normalize(f.viewDir);fixed4 packedNormal = tex2D(_BumpMap, f.uv.zw);   // 利用tex2D对法线纹理_BumpMap进行采样,得到法线经过映射后的像素值// 由于法线纹理中存储的是把法线经过映射后得到的像素值,所以下面需要把法线映射回来(如果Unity中把该法线的纹理类型设置为Normal map,就需要在代码中手动映射)fixed3 tangentNormal;   // 存储切线空间中的法线方向// tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;                         // 将xy分量按照法线纹理映射公式映射回法线方向,同时乘以凹凸程度// tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));   // z分量可由x分量计算获得tangentNormal = UnpackNormal(packedNormal);   // 使用内置函数直接映射,可以无视平台的差异  --  推荐使用该方法tangentNormal.xy *= _BumpScale;tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));      // x^2 + y^2 + z^2 = 1fixed3 albedo = tex2D(_MainTex, f.uv.xy).rgb * _Color.rgb;   // 使用tex2D函数对纹理_MainTex进行采样// 这里也可以省去.xy,会将f.uv强制转换为float2类型fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;                                                      // 获取环境光的同时乘以材质反射率fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * saturate(dot(tangentNormal, tangentLightDir));   // 计算漫反射的同时乘以材质反射率fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);                                                // 获取矢量hfixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(tangentNormal, halfDir)), _Gloss);     // 计算高光反射return fixed4(ambient + diffuse + specular, 1);}ENDCG}}FallBack "Specular"
}

2 在世界空间下计算

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'Shader "MyShader/Texture/NormalMapWorldSpace"
{Properties {// 光照_Diffuse("Diffuse", Color) = (1, 1, 1, 1)      // 控制漫反射颜色_Specular("Specular", Color) = (1, 1, 1, 1)    // 控制高光反射颜色_Gloss("Gloss", Range(8.0, 256)) = 20          // 控制高光区域大小// 基础纹理_Color("Color Tint", Color) = (1, 1, 1, 1)     // 控制纹理总体色调_MainTex("Main Tex", 2D) = "white" {}          // 控制纹理图片// 凹凸纹理_BumpMap("Normal Map", 2D) = "bump" {}         // "bump"为Unity内置的法线纹理,当没有提供任何法线时,"bump"就对应了模型自带的法线信息_BumpScale("Bump Scale", Float) = 1.0          // 控制凹凸程度,当为0时,意味该法线纹理不会对光线产生任何影响}SubShader {Pass {Tags { "LightMode" = "UniversalForward" }CGPROGRAM#pragma vertex vert#pragma fragment frag#include "Lighting.cginc"#include "UnityCG.cginc"// 获取Properties语句中定义的变量// 光照fixed4 _Diffuse;fixed4 _Specular;float _Gloss;// 基础纹理fixed4  _Color;sampler2D _MainTex;float4 _MainTex_ST;   // Unity中需要使用 纹理名_ST 的方式声明某个纹理的属性// 凹凸纹理sampler2D _BumpMap;float4 _BumpMap_ST;float _BumpScale;struct a2v {// 光照float4 vertex : POSITION;      // 模型空间中顶点的位置float3 normal : NORMAL;        // 模型空间中顶点的法线// 基础纹理float4 texcoord : TEXCOORD0;   // 顶点的第一组纹理坐标,通过该纹理坐标,经过缩放和偏移后可以计算得到顶点的uv坐标// 凹凸纹理float4 tangent : TANGENT;      // 把顶点的切线填充到tangent变量中};struct v2f {// 光照float4 pos : SV_POSITION;          // 裁剪空间中顶点的位置// 纹理float4 uv : TEXCOORD0;             // 在片元着色器中使用该坐标进行纹理采样,其中xy分量存储_MainTex的纹理坐标,zw分量存储_BumpMap的纹理坐标// 用3行分别存储从切线空间到世界空间变换矩阵的每一行,把世界空间下的顶点位置存储在这些变量的w分量中float4 TtoW0 : TEXCOORD1;float4 TtoW1 : TEXCOORD2;float4 TtoW2 : TEXCOORD3;};// 顶点着色器在这里主要负责计算_MainTex和_BumpMap的纹理坐标,同时计算切线空间下的视角和光照方向,传递给片元着色器v2f vert (a2v v) {v2f f;f.pos = UnityObjectToClipPos(v.vertex);                         // 将顶点坐标从模型空间变化到裁剪空间// 将v2f的uv定义为float4类型,其中xy分量存储_MainTex的纹理坐标,zw分量存储_BumpMap的纹理坐标f.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;      f.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;   float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;                // 将顶点坐标从模型空间转换到世界空间fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);                 // 将法线方向从模型空间转换到世界空间fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);              // 将切线方向从模型空间转换到世界空间fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;   // 计算世界空间的副切线方向f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);return f;}fixed4 frag (v2f f) : SV_Target {float3 worldPos = float3(f.TtoW0.w, f.TtoW1.w, f.TtoW2.w);float3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));// 获取切线空间下的法线方向fixed3 bump = UnpackNormal(tex2D(_BumpMap, f.uv.zw));   // 使用内置函数直接映射,可以无视平台的差异  --  推荐使用该方法bump.xy *= _BumpScale;bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));   // x^2 + y^2 + z^2 = 1// 将从纹理中映射回来的法线方向从切线空间转换到世界空间fixed3 bumpNormal = normalize(half3(dot(f.TtoW0.xyz, bump), dot(f.TtoW1.xyz, bump), dot(f.TtoW2.xyz, bump)));fixed3 albedo = tex2D(_MainTex, f.uv.xy).rgb * _Color.rgb;   // 使用tex2D函数对纹理_MainTex进行采样// 这里也可以省去.xy,会将f.uv强制转换为float2类型fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;                                            // 获取环境光的同时乘以材质反射率fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * saturate(dot(bumpNormal, lightDir));   // 计算漫反射的同时乘以材质反射率fixed3 halfDir = normalize(lightDir + viewDir);                                                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(bumpNormal, halfDir)), _Gloss);     // 计算高光反射return fixed4(ambient + diffuse + specular, 1);}ENDCG}}FallBack "Specular"
}

在视觉表现下,在世界空间和在切线空间下计算光照没有任何区别

2.4 Unity中的法线纹理类型

Unity根据不同的平台对纹理进行压缩,再通过UnpackNormal函数来针对不同的压缩格式对法线纹理进行正确的采样

Create from Grayscale:把纹理类型设置为Normal Map后,选中Create from Grayscale复选框,Unity将会使用高度图,从高度图中生成法线纹理。当我们把一张高度图导入Unity后,除了需要把它的纹理设置为Normal Map外,还需要勾选Create from Grayscale,这样就可以将其和法线纹理同等对待了

  • Bumpiness:控制凹凸程序
  • Filtering:决定使用哪种方式计算凹凸程度
    • Smooth:是的生成的法线比较平滑
    • Sharp:使用Sobel滤线生成法线

3 渐变纹理

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'Shader "MyShader/Texture/RampTexture"
{Properties{_Color ("Color Tint", Color) = (1, 1, 1, 1)_RampTex ("Ramp Tex", 2D) = "white" {}_Specular ("Specular", Color) = (1, 1, 1, 1)_Gloss("Gloss", Range(8.0, 256)) = 20}SubShader{Tags { "LightMode"="UniversalForward" }LOD 100Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"#include "Lighting.cginc"fixed4 _Color;sampler2D _RampTex;float4 _RampTex_ST;fixed4 _Specular;float _Gloss;struct a2v {float4 vertex : POSITION;float3 normal : NORMAL;fixed4 texcoord : TEXCOORD0;};struct v2f {float4 pos : SV_POSITION;float3 worldNormal : TEXCOORD0;float3 worldPos : TEXCOORD1;float2 uv : TEXCOORD2;};  v2f vert (a2v v) {v2f f;f.pos = UnityObjectToClipPos(v.vertex);f.worldNormal = UnityObjectToWorldNormal(v.normal);f.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;f.uv = TRANSFORM_TEX(v.texcoord, _RampTex);return f;}fixed4 frag (v2f f) : SV_Target {fixed3 worldNormal = normalize(f.worldNormal);fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(f.worldPos));fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;// 使用半兰伯特光照fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;// 使用halfLambert构建一个纹理坐标,并用这个纹理坐标对渐变纹理_RampTex进行采样// 由于_RampTex实际上是一个一维纹理,因此纹理坐标的u和v方向都使用了halfLambertfixed3 diffuse = _LightColor0.rgb * diffuseColor;fixed3 viewDir = normalize(UnityWorldSpaceViewDir(f.worldPos));fixed3 halfDir = normalize(viewDir + worldLightDir);fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, halfDir)), _Gloss);return fixed4(ambient + diffuse + specular, 0);}ENDCG}}FallBack "Specular"
}

需要注意的是,我们需要把Wrap Mode设置为Clamp模式,以防止纹理进行采样时由于浮点数精度而造成的问题

4 遮罩纹理

遮罩允许我们保护某些区域,使其免于被修改

例如之前,我们都是把高光反射应用到模型表面的所有的地方,即所有的像素都使用同样大小的高光强度和高光指数,但有时,我们希望模型表面某些区域的反光强烈一些,而某些区域弱一些。为了得到更细腻的效果,我们使用一张遮罩纹理来控制光照,

使用遮罩纹理的一般流程是:通过采样得到遮罩纹理的纹素值,然后使用其中几个通道的值来与某种表面属性进行相乘,这样,当该通道的值为0时,可以保护表面不受该属性影响

Shader "MyShader/Texture/MaskTexture"
{Properties{_Color ("Color Tint", Color) = (1, 1, 1, 1)_MainTex ("Main Tex", 2D) = "white" {}_BumpMap("Normal Map", 2D) = "bump" {}         _BumpScale("Bump Scale", Float) = 1.0      _SpecularMask ("Specular Mask", 2D) = "white" {}_SpecularScale ("Specular Scale", Float) = 1.0_Specular ("Specular", Color) = (1, 1, 1, 1)_Gloss ("Gloss", Range(8.0, 256)) = 20}SubShader{Tags { "LightMode"="UniversalForward" }LOD 100Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "Lighting.cginc"#include "UnityCG.cginc"fixed4 _Color; sampler2D _MainTex;float4 _MainTex_ST;sampler2D _BumpMap;float _BumpScale;sampler2D _SpecularMask; float _SpecularScale;fixed4 _Specular;float _Gloss;struct a2v{float4 vertex : POSITION;float3 normal : NORMAL;float4 tangent : TANGENT;float4 texcoord: TEXCOORD0;};struct v2f{float4 pos : SV_POSITION;float2 uv : TEXCOORD0;float3 lightDir : TEXCOORD1;float3 viewDir : TEXCOORD2;};v2f vert (a2v v){v2f f;f.pos = UnityObjectToClipPos(v.vertex);f.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);TANGENT_SPACE_ROTATION;f.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;f.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;return f;}fixed4 frag (v2f f) : SV_Target{fixed3 tangentLightDir = normalize(f.lightDir);fixed3 tangentViewDir = normalize(f.viewDir);fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, f.uv));tangentNormal.xy *= _BumpScale;tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));fixed3 albedo = tex2D(_MainTex, f.uv).rgb * _Color.rgb;fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(tangentNormal, tangentLightDir));// 利用r分量计算掩码值fixed specularMask = tex2D(_SpecularMask, f.uv).r * _SpecularScale;fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(tangentNormal, halfDir)), _Gloss) * specularMask;return fixed4(ambient + diffuse + specular, 1.0);}ENDCG}}FallBack "Specular"
}

基础纹理 --Shader入门精要学习(6)相关推荐

  1. Unity Shader入门精要学习笔记 - 第14章 非真实感渲染

    Unity Shader入门精要学习笔记 - 第14章 非真实感渲染 本系列为UnityShader入门精要读书笔记总结, 原作者博客链接:http://blog.csdn.net/candycat1 ...

  2. Unity Shader入门精要学习笔记 - 第7章 基础纹理

    转自 冯乐乐的 <Unity Shader 入门精要> 纹理最初的目的就是使用一张图片来控制模型的外观.使用纹理映射技术,我们可以把一张图"黏"在模型表面,逐纹素地控制 ...

  3. Unity Shader入门精要学习笔记 - 第6章 开始 Unity 中的基础光照

    转自冯乐乐的<Unity Shader入门精要> 通常来讲,我们要模拟真实的光照环境来生成一张图像,需要考虑3种物理现象. 首先,光线从光源中被发射出来. 然后,光线和场景中的一些物体相交 ...

  4. Unity Shader入门精要学习笔记 - 第14章非真实感渲染

    转载自 冯乐乐的 <Unity Shader 入门精要> 尽管游戏渲染一般都是以照相写实主义作为主要目标,但也有许多游戏使用了非真实感渲染(NPR)的方法来渲染游戏画面.非真实感渲染的一个 ...

  5. Games101结合Unity Shader入门精要学习笔记(个人向)

    第四章 3D旋转 绕X轴旋转: 绕Y轴旋转: 绕Z轴旋转: 旋转变换(一)旋转矩阵_csxiaoshui的博客-CSDN博客_旋转矩阵 重点:MVP变换!!! model transformation ...

  6. Unity shader入门精要学习笔记-代码篇6(序列帧动画/滚动背景/流动河流/广告牌/顶点动画的阴影)

    一.序列帧动画 建立一个四边形对着摄像机. 我们需要一张序列帧图像,这里用到8x8的爆炸图. 给四边形上材质和shader,代码如下: Shader "Custom/NewSurfaceSh ...

  7. 透明效果 -- Shader入门精要学习(7)

    透明效果 对于不透明的物体,不考虑其渲染顺序也能得到正确的结果,这是由于强大的深度缓冲的存在 几个概念: 深度缓冲(z-buffer):用于解决可见性问题,它可以决定哪个物体的哪些部分会被渲染在前面, ...

  8. unity shader入门精要_Unity Shader 入门(二):shader 基础

    一.参考与说明(需要写在开始东西): 1.1 Unity Shader 入门精要学习 https://github.com/candycat1992/Unity_Shaders_Book/tree/u ...

  9. Unity Shader入门精要第七章 基础纹理之遮罩纹理

    Unity系列文章目录 文章目录 Unity系列文章目录 前言 一.实践 参考 前言 遮罩纹理(mask texture)是本章要介绍的最后一种纹理,它非常有用,在很多商业游戏中 都可以见到它的身影. ...

最新文章

  1. 【实战】OpenCV钢管计数分析与方法比较
  2. 如何用php向wsdl服务器发请求,知道服务器端Wsdl,不写服务端代码,仅写客户端代码能调用服务端的方法吗?...
  3. 删除vue打包大小限制_如何优化 Vue 祖传代码
  4. 动窗口的制作暨CSizingControlBar类的使用说明
  5. ActiveMQ的Transport Connectors配置(六)
  6. 互联网环境下分布式事务处理系统现状与趋势
  7. 【Java】基于IDE的JUnit软件测试入门
  8. [译] Architecture Components 之 Adding Components to your Project
  9. 浅谈win10修复系统文件的方法
  10. sqlserver中分区函数 partition by的用法
  11. 头号英雄 答题助手助力通关赢大奖
  12. STC15单片机学习笔记1——STC15W4K56S4引脚功能整理说明
  13. matlab 转换为相对湿度
  14. 安卓按键精灵手机助手(功能:安卓版抓抓、命令库、制作电脑UI界面)
  15. FineReport根据查询参数显示和隐藏列
  16. 朴素贝叶斯、贝叶斯网络分类器
  17. TO_DATS() AS ABAP_DATE
  18. 同花顺_知识_看盘技法
  19. vue单个表单的校验清空
  20. R语言爬虫:当当图书畅销榜(近7日)

热门文章

  1. windows10计算机管理中没有账户,win10系统没有管理员帐户的解决方法
  2. 广西来宾中考计算机考试考什么,2018来宾中考科目及分值
  3. 水位测量是计算机在( )方面的应用,哈工大化工原理实验思考题答案及哈工大仪器分析实验思考题答案...
  4. linux排查进程退出原因面试,linux面试中经常会遇到的问题
  5. Windows10 打开SQL Server 配置管理器的方法
  6. 计算机xp系统自带录制视频文件,怎样使用WinXp系统自带录音机录制视频对白【图文教程】...
  7. 数码软文营销文案写作的12套公式模板
  8. java不失精度的小数计算工具类
  9. ORA-28112: 无法执行策略函数 问题解决
  10. 1、测试诊断软件工具大集合!