——内容源自唐老狮的shader课程
目录
1.概述
1.纹理就是图片或者图片集,我们要做的就是从这些纹理中取颜色,即纹理采样
2.纹理坐标(uv坐标):相当于将模型平铺成一张正方形图片,每一个顶点都有自己的位置,而且uv坐标的的横轴和纵轴是被归一化过的,在0~1的范围内
3.记录了顶点的uv坐标,并将其传入片元着色器之后,其会被转为片元(像素)的uv坐标(中间的转换靠的是插值计算),这个uv坐标可以在纹理中采集对应位置(像素)的颜色。
2.纹理颜色采样
首先要知道如何获取uv坐标:将a2v中用TEXCOORD修饰的的变量的xy分量进行变化然后赋给v2f中用TEXCOORD修饰的float2变量即可,这个float2变量就可以在片元着色器中进行纹理采样。
Shader "Study/Lesson29"
{
Properties
{
_MainTex("MainTex", 2D) = ""{}
}
SubShader
{
Tags {}
pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
//映射对应纹理属性的图片颜色相关数据
sampler2D _MainTex;
//映射对应纹理属性的 缩放平移(偏移)
float4 _MainTex_ST; //x,y代表缩放,zw代表平移
v2f_img vert(appdata_base data)
{
v2f_img v2fData;
v2fData.pos = UnityObjectToClipPos(data.vertex);
v2fData.uv = data.texcoord.xy;
//data.texcoord.xy; //代表uv坐标
//data.texcoord.zw; //代表一些额外信息
//如果没有进行缩放和平移,那么计算后的值是不会发生变化的
//先缩放,后平移;
//缩放用乘法计算,平移用加法计算
v2fData.uv = data.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; //固定写法
//或者用内置宏
//TRANSFORM_TEX(data.texcoord.xy, _MainTex);
return v2fData;
}
fixed4 frag(v2f_img data) : SV_TARGET
{
//这里传入的uv是经过插值运算后的,每一个片元都有自己的uv坐标
//这样能精准的在贴图中取出颜色
fixed4 color = tex2D(_MainTex, data.uv);
return color;
}
ENDCG
}
}
}
_MainTex_ST是_MainTex(2D类型)自带的,但需要将其声明出来才能用,其代表纹理的缩放与偏移,可以在外部改变

3.纹理结合光照
1.将纹理采样后得到的颜色与声明的漫反射颜色相乘得albedo,
2.然后用albedo作为漫反射计算中的漫反射颜色。
3.让环境光与albedo相乘得到新的环境光ambient
4.用新的环境光代替原来的环境光,漫反射颜色使用albedo计算所得的满反射颜色,进行光照模型的计算
Shader "Study/Lesson31"
{
Properties
{
//主要是将单张纹理Shader和Blinn-Phong光照模型逐片元Shader结合
_GunTexture("GunTexture", 2D) = ""{}
_DiffuseColor("DiffuseColor", Color) = (1, 1, 1, 1)
_HighlightColor("HighlightColor", Color) = (1, 1, 1, 1)
_Gloss("Gloss", Range(0, 20)) = 1
}
SubShader
{
Tags{"LightMode" = "ForwardBase"}
Pass
{
CGPROGRAM
//1.纹理颜色需要和漫反射颜色 进行乘法叠加,他们俩共同影响最终的颜色
//2.兰伯特光照模型计算时,漫反射材质颜色使用 1 中的叠加颜色计算
//3.最后使用的环境光叠加时,环境光变量UNITY_LIGHTMODEL_AMBIENT需要和 1 中颜色进行乘法叠加
// 为了避免最终的渲染效果偏灰
//
//其他的计算步骤同Blinn_Phong的逐片元光照实现
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _GunTexture;
float4 _GunTexture_ST;
fixed4 _DiffuseColor;
fixed4 _HighlightColor;
float _Gloss;
struct v2f
{
float4 cPos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 wPos : TEXCOORD1;
float3 wNormal : NORMAL;
};
fixed3 GetLambertColor(float4 wPos, float3 wNormal, fixed3 albedo)
{
float3 normalizedLightDir = normalize(_WorldSpaceLightPos0);
fixed3 color = _LightColor0.rgb * albedo * max(dot(wNormal, normalizedLightDir), 0);
return color;
}
fixed3 GetBlinn_PhongColor(float4 wPos, float3 wNormal)
{
float3 normalizedLightDir = normalize(_WorldSpaceLightPos0);
float3 normalizedViewDir = normalize(_WorldSpaceCameraPos - wPos);
float3 normalizedHalfAngleDir = normalize(normalizedLightDir + normalizedViewDir);
fixed3 color = _LightColor0.rgb * _HighlightColor.rgb * pow(max(dot(wNormal, normalizedHalfAngleDir), 0), _Gloss);
return color;
}
v2f vert(appdata_base v)
{
v2f v2fData;
v2fData.cPos = UnityObjectToClipPos(v.vertex);
v2fData.uv = v.texcoord.xy * _GunTexture_ST.xy + _GunTexture_ST.zw;
v2fData.wPos = mul(unity_ObjectToWorld, v.vertex);
v2fData.wNormal = UnityObjectToWorldNormal(v.normal);
return v2fData;
}
fixed4 frag(v2f i) : SV_TARGET
{
//
//漫反射颜色和纹理颜色叠加,共同决定最后的颜色
fixed3 albedo = tex2D(_GunTexture, i.uv).rgb * _DiffuseColor.rgb;
//
fixed3 lambertColor = GetLambertColor(i.wPos, i.wNormal, albedo);
fixed3 blinn_PhongColor = GetBlinn_PhongColor(i.wPos, i.wNormal);
fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
fixed3 finalColor = ambientColor + lambertColor + blinn_PhongColor;
return fixed4(finalColor, 1);
}
ENDCG
}
}
}

4.凹凸纹理
4.1.概念
使用一张法线纹理来代替模型本身的法线来参与计算,让我们无需增加顶点也能让模型看起来有凹凸效果。
4.2.高度纹理贴图
一般称为亮度图,存储了模型表面上每个点的高度信息,通常使用灰度图像,不同灰度值代表不同高度,较亮的区域使用较高的点。主要用于模拟物体表面的唯一
存储规则:图片的某一点的rgb值都是相同的,都表示高度值,a一般为1,高度值范围一般为0~1,0为最低,1为最高
好处是可以明确知道模型表面的凹凸情况;缺点是无法在shader中直接得到模型顶点的法线信息,需要额外计算,所以很少用
4.3.法线纹理贴图(一般用这个)
4.3.1.概述
存储了模型表面的每个点的法线方向
存储规则:图片的rgb值分别存储法线的xyz分量,w用于存储其他信息
优点是可以直接得到法线信息,缺点是不能直观地看到模型表面地凹凸情况
4.3.2.读取分量的规则
由于法线xyz分量在[-1, 1]之间,而像素rgb分量范围在[0, 1]之间,所以需要进行转换:
读取数据时:法线分量 = 像素分量 * 2 - 1
存储图片时进行逆运算
4.3.3.法线纹理贴图的两种存储方式
1.基于模型空间的法线纹理
模型空间中自带的法线数据,定义在模型空间中,其取出来直接参与shader计算即可
2.基于切线空间的法线纹理(这个最常用)
每个顶点都有自己切线空间:
原点为顶点本身,X轴为顶点切线,Y轴为法线方向,Z轴为XY的叉乘结果,也称副切线
而没有凹凸感的顶点的坐标为(0, 0, 1),映射到像素上就是蓝色,这就是为什么该纹理总是出现大片蓝色
这种纹理需要进行转换才能参与计算
优点:可以用于不同模型,方便处理模型变换,可以复用,可以压缩(只存储两个轴的分量),方便制作uv动画
4.4.计算
4.4.1.在切线空间下
需要把光照方向,视角方向变换到切线空间下参与计算,好处是计算效率高,计算量小,但对于一些全局效果的表现可能不准确
1.首先需要计算出由模型空间向切线空间转换的变换矩阵(父到子),该矩阵为3x3矩阵,因为进行的是矢量的变换,无需位移(这里的法线和切线用的模型本身的):
— 切线 —
— 副切线 —
— 法线 —
2.然后将光照方向和视角方向转换到切线空间参与计算
3.先对法线纹理进行采样,然后使用UnpackNormal对float4变量进行计算,所得即为切线空间下的法线tNormal。
4.然后将tNormal的xy分量乘以凹凸度(这个是自定义的),然后进行如下计算(也算是单位化)
tNormal.z = sqrt(1- saturate(dot(tNomral.xy, tNormal.xy)))
5.进行计算
Shader "Study/TangentNormal"
{
Properties
{
_DiffuseColor("DiffuseColor", Color) = (0, 0, 0, 0)
_SpecularColor("HighlightColor", Color) = (0, 0, 0, 0)
_Gloss("Gloss", Range(0, 20)) = 5
_MyTex("MyTex", 2D) = ""{}
//法线纹理
_BumpMap("BumpMap", 2D) = ""{}
_BumpScale("BumpScale", Range(-1, 1)) = 0
}
SubShader
{
Tags { "Lighting" = "ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _SpecularColor;
float _Gloss;
sampler2D _MyTex;
float4 _MyTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
struct v2f
{
float4 pos : SV_POSITION;
//纹理坐标
//float2 uvTex : TEXCOORD0;
//法线纹理
//float2 uvBump : TEXCOORD1;
//将上面俩合成一个,xy代表纹理坐标,zw代表法线纹理
float4 uv : TEXCOORD0;
float3 tangentSpaceViewDir : TEXCOORD2;
float3 tangentSpaceLightDir : TEXCOORD1;
};
v2f vert(appdata_full v)
{
v2f data;
data.pos = UnityObjectToClipPos(v.vertex);
data.uv.xy = v.texcoord.xy * _MyTex_ST.xy + _MyTex_ST.zw;
data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
float3 biTangent = cross(normalize(v.tangent), normalize(v.normal)) * v.tangent.w;;
float3x3 rotation = float3x3(v.tangent.xyz,
biTangent,
v.normal);
float3 viewDir = normalize(ObjSpaceViewDir(v.vertex));
float3 lightDir = normalize(ObjSpaceLightDir(v.vertex));
float3 tangentSpaceViewDir = mul(rotation, viewDir);
float3 tangentSpaceLightDir = mul(rotation, lightDir);
data.tangentSpaceViewDir = tangentSpaceViewDir;
data.tangentSpaceLightDir = tangentSpaceLightDir;
return data;
}
fixed4 frag(v2f f) : SV_TARGET
{
fixed3 texColor = tex2D(_MyTex, f.uv.xy);
float4 t_tangentNormal = tex2D(_BumpMap, f.uv.zw);
float3 tangentNormal = UnpackNormal(t_tangentNormal);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy))); //也算是一种单位化
fixed3 albedo = texColor * _DiffuseColor.rgb;
fixed3 lambertColor = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, f.tangentSpaceLightDir));
float3 halfAngle = normalize(f.tangentSpaceViewDir + f.tangentSpaceLightDir);
fixed3 blinn_PhongColor = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(tangentNormal, halfAngle)), _Gloss);
fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
fixed3 finalColor = ambientColor + lambertColor + blinn_PhongColor;
return fixed4(finalColor, 1.0);
}
ENDCG
}
}
}

4.4.2.在世界空间下
类比上面那个,将法线从切线空间变到世界空间,这时的变换矩阵为:
| | |
切线 副切线 法线
| | |

5.渐变纹理
将半兰伯特后半部分所得0~1的数作为 uv坐标,u和v都是该值。目的是让物体具有一定的卡通风格。

6.遮罩纹理
6.1.计算
作用是控制某些效果的显示范围,或者说,保护某些区域,使他们免于修改
1.从遮罩纹理中取出对应的遮罩掩码值(取出来的rgb值都可用,因为这仨都一样)
2.用该掩码值和我们定义的遮罩系数进行相乘得到遮罩值
3.用该遮罩值和高光反射计算出的颜色相乘,这样呈现出来的高光反射就会受到高光遮罩纹理和遮罩系数的影响

6.2.遮罩纹理的RGBA值
虽然有4个值,但我们只用1个,显而易见的浪费,所以我们可以用它们来存一些别的值,如透明遮罩系数(G),特效遮罩系数(B)等