——内容源自唐老狮的shader课程
目录
1.概述
2.纹理颜色采样
3.纹理结合光照
4.凹凸纹理
4.1.概念
4.2.高度纹理贴图
4.3.法线纹理贴图(一般用这个)
4.3.1.概述
4.3.2.读取分量的规则
4.3.3.法线纹理贴图的两种存储方式
1.基于模型空间的法线纹理
2.基于切线空间的法线纹理(这个最常用)
4.4.计算
4.4.1.在切线空间下
4.4.2.在世界空间下
5.渐变纹理
6.遮罩纹理
6.1.计算
6.2.遮罩纹理的RGBA值
7.如有疏漏,还请指出
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类型)自带的,但需要将其声明出来才能用,其代表纹理的缩放与偏移,可以在外部改变
![](https://i-blog.csdnimg.cn/direct/1a3d723afa1041cb923d14f4da464297.jpeg)
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}}
}
![](https://i-blog.csdnimg.cn/direct/a3499038ab4a41db93ab8f7de66b977d.jpeg)
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}}
}
![](https://i-blog.csdnimg.cn/direct/9325901c27c148f28babfb185226c352.jpeg)
4.4.2.在世界空间下
类比上面那个,将法线从切线空间变到世界空间,这时的变换矩阵为:
| | |
切线 副切线 法线
| | |
![](https://i-blog.csdnimg.cn/direct/9c5ad314e61b40509e79219d2dd02390.jpeg)
5.渐变纹理
将半兰伯特后半部分所得0~1的数作为 uv坐标,u和v都是该值。目的是让物体具有一定的卡通风格。
![](https://i-blog.csdnimg.cn/direct/256aae1e06a24b9ca08f25debca51342.jpeg)
6.遮罩纹理
6.1.计算
作用是控制某些效果的显示范围,或者说,保护某些区域,使他们免于修改
1.从遮罩纹理中取出对应的遮罩掩码值(取出来的rgb值都可用,因为这仨都一样)
2.用该掩码值和我们定义的遮罩系数进行相乘得到遮罩值
3.用该遮罩值和高光反射计算出的颜色相乘,这样呈现出来的高光反射就会受到高光遮罩纹理和遮罩系数的影响
![](https://i-blog.csdnimg.cn/direct/414e4cd876654374a7baf08dadeb6cbc.png)
6.2.遮罩纹理的RGBA值
虽然有4个值,但我们只用1个,显而易见的浪费,所以我们可以用它们来存一些别的值,如透明遮罩系数(G),特效遮罩系数(B)等