基础概念
- 纹理映射技术(texture mapping)
- 纹理映射坐标(texture-maping coordinates,uv坐标)
- 纹素(texel)
单张纹理
_MainTex_ST
:Unity中使用{纹理名}_ST
的方式来声明某个纹理的属性,其中ST
是缩放(scale)和平移(translation)的缩写,通过_MainTex_ST.xy
来获取缩放值,通过_MainTex_ST.zw
获取偏移值,可通过面板进行调节
albedo
:材质的反射率,作用于环境光照和漫反射光照中tex2D
:传入纹理和uv坐标进行采样获取颜色
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader "Unity Shaders Book/Chapter 7/Single Texture"{
Properties {
_Color ("Color", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader{
Tags {
"LightMode" = "ForwardBase"
}
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex: POSITION;
fixed3 normal: NORMAL;
float4 texcoord: TEXCOORD0;
};
struct v2f {
float4 pos: SV_POSITION;
float3 worldNormal: TEXCOORD0;
float3 worldPos: TEXCOORD1;
float2 uv: TEXCOORD2;
};
v2f vert(a2v i){
v2f o;
o.pos = UnityObjectToClipPos(i.vertex);
//o.worldNormal = normalize(mul(i.normal, (float3x3)unity_WorldToObject));
o.worldNormal = normalize(UnityObjectToWorldNormal(i.normal));
o.worldPos = mul(unity_ObjectToWorld, i.vertex).xyz;
//错误写法
//o.uv = i.texcoord;
//o.uv = i.texcoord * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv = TRANSFORM_TEX(i.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i): SV_Target {
float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 h = normalize(viewDir + worldLightDir);
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
// 环境光也需要 * albedo
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
// _Diffuse.rgb改为albedo
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldLightDir, i.worldNormal));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(i.worldNormal, h)), _Gloss);
fixed3 color = ambient + diffuse + specular;
return fixed4(color, 1.0);
}
ENDCG
}
}
}
纹理的属性
- Texture Type(纹理类型):
- Alpha Source(透明度来源):
Wrap Mode(平铺模式):
- 选择Per-Axis相当于可以单独对UV定制化平铺模式
- 效果对比:
- 通过调整纹理偏移查看效果
- 注意:若shader中没有考虑纹理_ST的作用则调整偏移则无效果,例如
Filter Mode(纹理过滤)
- 资源导入设置
- 三种模式的效果对比:用64 x 64的图片放大成256 x 256进行渲染
Mipmap:多级渐远纹理
- 环境搭建:
- Scene中添加Plane对象
- 新增材质文件TextureFilterMat,同时新增Unity Shader文件TextureFilter
- Plane对象的MeshRender组件设置材质TextureFilterMat
- TextureFilterMat材质属性中,纹理的缩放设置(30,30)
- No Mipmap效果:摩尔纹(走样)
- 带Mipmap效果,其中Filter Mode
- Point
- Bilinear:
- Trilinear:
纹理其他信息
-
最大尺寸:如果纹理大小超过最大尺寸,则Unity会将该纹理进行缩放至满足最大尺寸范围下
-
纹理格式:
-
NPOT纹理:占用更多内存空间,而且GPU读取纹理的速度也下降;
凹凸映射(bump mapping)
- 目的:使用一张纹理来修改模型的表面法线,来为模型提供更多细节(看起来好像是凹凸不平,实际在特定角度上能看出破绽)
- 方案:
- 高度映射(height mapping):高度纹理(height map)模拟表面位移得到一个修改后的法线值,可以理解为增量改动原法线;
- 法线映射(normal mapping):法线纹理(normal map)直接存储表面法线,可以理解为直接替换原法线;
- 法线纹理
- 模型空间的法线纹理:
- 为何颜色看起来五颜六色:主要是模型空间下存储的法线跟当前顶点强相关,每个顶点的法线在模型空间下差异较大,因此对应的模型空间下的法线纹理也差异较大;
- 切线空间的法线纹理:
- 为何看起来都是偏蓝色:由于在切线空间下z轴为顶点法线方向,而法线纹理存储的则是每个点的法线扰动方向,如果一个点的发现方向是(0,0,1),则对应的颜色值为(0.5,0.5,1)为浅蓝色,因此看上去一大片都是浅蓝说明大部分顶点的法线是没有变化;
- 为何看起来都是偏蓝色:由于在切线空间下z轴为顶点法线方向,而法线纹理存储的则是每个点的法线扰动方向,如果一个点的发现方向是(0,0,1),则对应的颜色值为(0.5,0.5,1)为浅蓝色,因此看上去一大片都是浅蓝说明大部分顶点的法线是没有变化;
- 模型空间的法线纹理:
- 为何选择使用切线空间的法线纹理?
- 讲道理每太看明白书中介绍的优点(P147)
- 自由度高
- 在切线空间下的法线纹理更容易复用,而模型空间下的法线纹理是跟特定模型绑定(复用性较差)
- 兼容UV动画
- 可压缩
实践演练(切线空间的法线纹理)
- 选择哪个坐标系进行光照计算:切线空间,还是世界空间
- 回答:效率上看是切线空间,通用性(其他计算需要用到世界空间的情况下)上看是世界空间
在切线空间下计算
float4 tangent: TANGENT;
告诉Unity将顶点的切线方向填充到tangent变量中,而为什么最终结果是float4(normal变量是float3)是因为分量w决定副切线的方向性(讲道理需要认真学习下切线空间下的相关知识,没太理解这块)- 坑点
- 通过内建函数
TRANSFORM_TEX
根据顶点的uv坐标换算(考虑纹理 平铺 和 偏移 参数)为最终传入fragment shader的uv坐标时,如果在Pass中没有提前声明_MainTex_ST
则会出现编译异常,说明内建函数最终还是会依赖Pass中声明的变量;
- 使用
UnpackNormal(tex2D("_BumpMap", i.uv.zw))
的原因?看上去应该是要将法线转换到切线方向进行计算 - 使用
TANGENT_SPACE_ROTATION
宏时发现编译错误提示变量v不存在
- 查看UnityCG.cginc中宏事实上是对顶点着色器中的输入变量名字是有限制(蛋疼)
- 解决方案:
v2f vert(a2v i)
中调整输入变量名为v,猜测其他宏也有类似的依赖,可以养成习惯默认在顶点着色器中的输入变量名字为v更合适;
- 查看UnityCG.cginc中宏事实上是对顶点着色器中的输入变量名字是有限制(蛋疼)
- 将
albedo
的变量类型误写成float
(正常应该是float3
)导致最终表现效果异常,类似变成一个灰度图(读取单分量后跟向量进行乘积相当于缩放)
- 通过内建函数
- 最终效果
- 代码实现
Shader "Unity Shaders Book/Chapter 7/Normal Map In Tangent Space" {
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {} // 法线纹理,默认值"bump"是内置的法线纹理
_BumpScale ("Bump Scale", Float) = 1.0 // 控制凹凸程度,0则代表法线纹理不会对光照产生任何影响
_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" // 感觉涉及光照就基本要导入下这个
#include "UnityCG.cginc" // TANGENT_SPACE_ROTATION内建变量需要导入
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 = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
// float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w;
// float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
return o;
}
fixed4 frag(v2f i): SV_Target{
float3 tangentLightDir = normalize(i.lightDir);
float3 tangentViewDir = normalize(i.viewDir);
// 从normal map中获取法线向量
fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
fixed3 tangentNormal;
tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
float3 tangentHalfDir = normalize(tangentViewDir + tangentLightDir);
float3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentLightDir, tangentNormal));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentHalfDir, tangentNormal)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
- 不同BumpScale的对比
世界空间下计算
- 注意点:
- 法线贴图从切线空间转换世界空间,这里的矩阵计算有点复杂
- fragment shader中的法线方向是直接通过法线贴图获取;
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader "Unity Shaders Book/Chapter 7/Normal Map In World Space" {
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {} // 法线纹理,默认值"bump"是内置的法线纹理
_BumpScale ("Bump Scale", Float) = 1.0 // 控制凹凸程度,0则代表法线纹理不会对光照产生任何影响
_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" // 感觉涉及光照就基本要导入下这个
#include "UnityCG.cginc" // TANGENT_SPACE_ROTATION内建变量需要导入
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;
float4 TtoW0: TEXCOORD1;
float4 TtoW1: TEXCOORD2;
float4 TtoW2: TEXCOORD3;
};
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float3 worldNormal = UnityObjectToWorldNormal(v.normal).xyz;
float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
float3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
return o;
}
fixed4 frag(v2f i): SV_Target{
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
float3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float3 halfDir = normalize(lightDir + viewDir);
// Get the normal in tangent space
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
bump.xy *= _BumpScale;
bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
// Transform the narmal from tangent space to world space
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
float3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(lightDir, bump));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(halfDir, bump)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
法线纹理类型
- 用到UnpackNormal内建函数时需要将纹理类型设置为Normal map
- 高度图设置
- 勾选:Create from Grayscale
- Bumpiness:控制凹凸程度
- Filtering:控制计算凹凸程度的方式
- Sharp:使用Sobel滤波生成法线
- Smooth:生成的法线纹理相对光滑
渐变纹理
- 冷到暖色调(cool-to-warm tones)着色技术:插画风格的渲染效果,很多卡通风格的渲染都使用这种技术
- 代码实现
- 半兰伯特光照模型:表现增强技术,在这里将计算结果作为渐变纹理的uv坐标
- 注意点
- 用到
TRANSFORM_TEX
函数时需要提前声明_RampTex_ST
- 用到
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader "Unity Shaders Book/Chapter 7/Ramp Texture"{
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{
Pass{
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
float4 _Color;
sampler2D _RampTex;
float4 _RampTex_ST;
float4 _Specular;
float _Gloss;
struct a2v{
float4 vertex: POSITION;
float3 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 = TRANSFORM_TEX(v.texcoord, _RampTex);
return o;
}
fixed4 frag(v2f i): SV_TARGET{
float3 worldNormal = normalize(i.worldNormal);
float3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
float3 halfDir = normalize(lightDir + viewDir);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed halfLambert = 0.5 + 0.5 * dot(worldNormal, lightDir);
fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;
fixed3 diffuse = _LightColor0.rgb * diffuseColor;
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
}
- 效果展示
不同渐变纹理的效果
- 效果1:
- 效果2:
- 效果3
渐变纹理的平铺模式影响
- Repeat模式:halfLambert计算结果可能存在超过1.0的情况,如果用repeat模式则采样渐变纹理会存在突变的情况最终导致异常的情况
- 解决方案:将渐变纹理的Wrap Mode改为Clamp模式,以防止对纹理采样时由于浮点数精度而造成的问题;
遮罩纹理(mask texture)
- 应用场景:像素级别的控制表面属性,利用N张遮罩纹理的不同通道(1张有4个通道,N张则有N * 4个通道来存储信息)来分别存储,例如:高光反射的强度、边缘光照的强度、高光反射的指数等信息,使得材质的自由度较高;
- 书中提到了《Dota2》中的人物模型使用了4张纹理,官方也提供了不同模型的下载链接;
- 注意点
a2v.normal
是float3,而a2v.tangent
是float4TRANSFORM_TEX
中第一个参数是v.texcoord,第二个参数是_MainTex
- 代码实现
Shader "Unity Shaders Book/Chapter 7/Mask Texture"{
Properties{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "dump" {}
_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{
Pass{
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "UnityCG.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
sampler2D _SpecularMask;
float4 _SpecularMask_ST;
float _SpecularScale;
float4 _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 o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
TANGENT_SPACE_ROTATION; // 获得rotation
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 tangentVieDir = normalize(i.viewDir);
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
float3 halfDir = normalize(tangentLightDir + tangentVieDir);
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentLightDir, tangentNormal));
fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(halfDir, tangentNormal)), _Gloss) * specularMask;
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}
- 效果展示