本文由@唐三十胖子出品,转载请注明出处。
文章链接:https://blog.csdn.net/iceSony/article/details/84591877这篇文章将总结和提炼《Unity Shader入门精要》的第七章“基础纹理”的内容。
通过这篇文章,你可以知道
1)法线贴图概念与使用
2)模型空间 vs 切线空间的法线纹理
3)切线空间下法线Shader实现
4)模型空间下法线Shader实现
一.法线贴图概念与使用
上图就是一张法线贴图,具体的应用如下
左边是4百万面的高模,右边是500面+法线贴图的效果
好处:将低模(几百顶点)显示效果增强为高模(上万顶点)
法线贴图修改的是法线坐标,通过前章节知识储备,法线反应的是顶点位置的光照信息。
同样一张纸,中间的法线旋转一些会得到凹陷下去的效果
PS:作为程序我们不需要过多了解法线贴图的制作过程,仅需知道两种方式
1.用颜色贴图转法线贴图
2.先制作一个高模一个低模,通过高模的烘焙到低模上就可以生成法线贴图
二.模型空间 vs 切线空间的法线纹理
切线空间:可复用,可以实现UV动画,可压缩(重点)
模型空间:简单直观,计算量少,边界平滑
对了文中的纹理贴图和法线贴图如下
三.切线空间下法线Shader实现
之前的所有法线用的都是顶点法线,这里我们使用的是切线法线。
其中没有变化的蓝色对应的rgb中的(0,0,1),也就是说明法线没有进行修改。
在片元着色器的漫反射计算中点乘变成切线空间即可_LightColor0.rgb * _Diffuse.rgb * max(0, dot(bump, lightDir)) * albedo;
属性除了漫反射颜色、主纹理还新增了两项
_BumpMap("法线纹理",2D) = "bump" {} _BumpScale("凹凸程度",Range(0,5.0)) = 1.0
为了给模型增加层次感,添加到CGPROGRAM中
sampler2D _BumpMap; float4 _BumpMap_ST; float _BumpScale;
接着在结构体a2v中添加切线信息,贴图坐标
虽然我们用不到NORMAL信息(之后计算得出),但是为了使用TANGENT_SPACE_ROTATION声明,编辑器会提示添加
float4 tangent:TANGENT; float4 texcoord:TEXCOORD0;
在结构体v2f中添加
float4 uv : TEXCOORD0; float3 lightDir : TEXCOORD1; float3 viewDir : TEXCOORD2;
这里为什么会多添加属性呢?因为我们需要在顶点着色器设置切线空间的光照与视角,再输出到v2f中通过这些变量保存。
顶点着色器
这里的uv坐标的xy zw分别用来存储材质纹理坐标、法线纹理坐标
这里用到了一个声明,为的是下面将光照与视角方向改变到切线空间下
v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.pos); 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; o.lightDir = mul(rotation, ObjSpaceLightDir(v.pos)).xyz; o.viewDir = mul(rotation, ObjSpaceViewDir(v.pos)).xyz; return o; }
片元着色器
之前修改过的法线纹理坐标转换到切线中
因为法线纹理坐标存储的为颜色RGB,需要进行一次转化
UnpackNormal进行的操作就是 packedNormal*2-1
(公式由来:RGB范围为0~1,法线范围为-1~1)
切记!!!所有获取到的packedNormal的数值需要进行转化
fixed4 frag(v2f i) : SV_Target { float3 lightDir = i.lightDir; float3 viewDir = i.viewDir; float4 packedNormal = tex2D(_BumpMap, i.uv.zw); float3 targentNormal = UnpackNormal(packedNormal); targentNormal.xy *= _BumpScale; targentNormal.z = sqrt(1.0 - saturate(dot(targentNormal.xy, targentNormal.xy))); float3 albedo = tex2D(_MainTex, i.uv.xy).rgb; float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo; float3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(targentNormal, lightDir)) * albedo; return fixed4(ambient + diffuse,1.0); }
这里对法线坐标进行了bumpscale的乘法,为的就是修改凹凸程度。
如果我们不进行乘法,那么最后的效果就是设置凹凸程度永远为1
进行修改以后,很明显凹凸程度增强了:
完整代码如下
Shader "sony/Shader168" { Properties { _Diffuse("漫反射系数",Color) = (1.0,1.0,1.0,1.0) _MainTex("主纹理",2D) = "white"{} _BumpMap("法线纹理",2D) = "bump" {} _BumpScale("凹凸程度",Range(0,5.0)) = 1.0 } SubShader { Pass { Tags{ "LightMode" = "ForwardBase" } CGPROGRAM #include "lighting.cginc" #pragma vertex vert #pragma fragment frag float4 _Diffuse; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _BumpMap; float4 _BumpMap_ST; float _BumpScale; struct a2v { float4 pos : 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.pos); 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; o.lightDir = mul(rotation, ObjSpaceLightDir(v.pos)).xyz; o.viewDir = mul(rotation, ObjSpaceViewDir(v.pos)).xyz; return o; } fixed4 frag(v2f i) : SV_Target { float3 lightDir = i.lightDir; float3 viewDir = i.viewDir; float3 targentNormal = UnpackNormal(tex2D(_BumpMap, i.uv.zw)); targentNormal.xy *= _BumpScale; targentNormal.z = sqrt(1.0 - saturate(dot(targentNormal.xy, targentNormal.xy))); float3 albedo = tex2D(_MainTex, i.uv.xy).rgb; float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo; float3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(targentNormal, lightDir)) * albedo; return fixed4(ambient + diffuse,1.0); } ENDCG } } }
四.模型空间下法线Shader实现
书中提供的其实是给你一个切线坐标下的法线贴图强行用模型空间来写
我们需要自己在顶点着色器中计算切线空间到世界空间的变换矩阵
是啊,unity都没有提供,所以完全不建议:)
修改的内容包括结构体v2f、两个着色器
struct v2f { float4 pos : SV_POSITION; float4 uv : TEXCOORD0; float3 TtoW0 : TEXCOORD1; float3 TtoW1 : TEXCOORD2; float3 TtoW2 : TEXCOORD3; };
顶点着色器
其中看起来很麻烦的TtoW0-2就是变换矩阵
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; float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); float3 worldNormal = UnityObjectToWorldNormal(v.normal); float3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; o.TtoW0 = float3(worldTangent.x, worldBinormal.x, worldNormal.x); o.TtoW1 = float3(worldTangent.y, worldBinormal.y, worldNormal.y); o.TtoW2 = float3(worldTangent.z, worldBinormal.z, worldNormal.z); return o; }
片元着色器
fixed4 frag(v2f i) : SV_Target { float3 worldPos = i.pos; float3 lightDir = UnityWorldSpaceLightDir(worldPos); float3 viewDir = UnityWorldSpaceViewDir(worldPos); float3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw)); bump.xy *= _BumpScale; bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy))); bump = normalize(half3(dot(i.TtoW0, bump), dot(i.TtoW1, bump), dot(i.TtoW2, bump))); float3 albedo = tex2D(_MainTex, i.uv).rgb; float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo; float3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(bump, lightDir)) * albedo; return fixed4(ambient + diffuse,1.0); }
完整代码如下
Shader "sony/Shader170" { Properties { _Diffuse("漫反射系数",Color) = (1.0,1.0,1.0,1.0) _MainTex("主纹理",2D) = "white"{} _BumpMap("法线纹理",2D) = "bump" {} _BumpScale("凹凸程度",Range(0,5.0)) = 1.0 } SubShader { Pass { Tags{ "LightMode" = "ForwardBase" } CGPROGRAM #include "lighting.cginc" #pragma vertex vert #pragma fragment frag float4 _Diffuse; sampler2D _MainTex; sampler2D _BumpMap; float4 _MainTex_ST; float4 _BumpMap_ST; float _BumpScale; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent:TANGENT; float4 texcoord:TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float4 uv : TEXCOORD0; float3 TtoW0 : TEXCOORD1; float3 TtoW1 : TEXCOORD2; float3 TtoW2 : TEXCOORD3; }; 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; float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); float3 worldNormal = UnityObjectToWorldNormal(v.normal); float3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; o.TtoW0 = float3(worldTangent.x, worldBinormal.x, worldNormal.x); o.TtoW1 = float3(worldTangent.y, worldBinormal.y, worldNormal.y); o.TtoW2 = float3(worldTangent.z, worldBinormal.z, worldNormal.z); return o; } fixed4 frag(v2f i) : SV_Target { float3 worldPos = i.pos; float3 lightDir = UnityWorldSpaceLightDir(worldPos); float3 viewDir = UnityWorldSpaceViewDir(worldPos); float3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw)); bump.xy *= _BumpScale; bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy))); bump = normalize(half3(dot(i.TtoW0, bump), dot(i.TtoW1, bump), dot(i.TtoW2, bump))); float3 albedo = tex2D(_MainTex, i.uv).rgb; float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo; float3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(bump, lightDir)) * albedo; return fixed4(ambient + diffuse,1.0); } ENDCG } } }
PS:有时候你会看到一些黑白的高度图,工作原理是越白的地方越高
在Unity贴图选项中选中Create from grayscale,这样黑白的图会变成蓝色的纹理图
当然了在设置之后有两个参数Bumpiness和Filtering,分别控制凹凸程度与光滑程度
在下一章节我们将介绍渐变纹理&遮罩纹理
下一章见:)