从尺度上来描述物体的细节,我们可以把它分为三种,宏观尺度,中观尺度和微观尺度。从宏观尺度上来看,它的特征可能覆盖多个像素。中观尺度覆盖几个像素,而微观尺度可能小于一个像素。宏观尺度是由顶点或者是三角形或者是其他几何图元来表示的,当我们创建三维角色的时候,四肢或者头部,这种通常就是在宏观尺度上建模的,微观尺度工作在着色模型中,着色模型通常在像素着色器里实现并且使用纹理贴图作为参数,着色模型模拟了物体表面微观几何形体下的相互作用。比如,有光泽的物体物体在微观尺度下是光滑的,而漫反射的物体在微观尺度下是粗糙的,角色的皮肤或者衣服看起来也有不同的材质,因为它们使用了不同的着色模型。而中观尺度就描述了宏观尺度和微观尺度之间的特征,它包含的细节比较复杂,没办法使用单个三角形进行渲染,但这些细节又足够大,能让观察者看出几个像素以上的表面曲率变化,比如人脸上的皱纹,肌肉组织的细节,衣服的褶皱,砖头之间的缝隙。凹凸映射就是模拟中观尺度的常用方法之一,它能够让观察者感知到比几何模型尺度更小的细节。它的基本思想是在纹理中把尺度细节相关的信息给编码进去,在着色过程中用稍微收到干扰的表面来代替真实的表面,就让表面看起来具有小尺度的细节。 Bump Mapping分类Bump Mapping的种类:法线映射、视差映射、浮雕映射。这几种方法或者贴图。都是广泛的被使用于增加模型的细节效果,或者用来做特殊的画面表现效果。最常见的是法线映射,一般的增加法线贴图后,会对局部的物体表面进行法线扰动,进而改变明暗关系,从而达到增加表面细节的效果,其中三种映射都用到的法线贴图也是本次介绍的重点内容。 Normal Mapping法线映射是一张存有物体局部表面信息的一张贴图 ,在计算程序的时候,程序会去读取法线图并且读到当前像素点的法线信息,结合光照进行计算。使用法线贴图计算光照,就能让物体表现出更丰富的细节,并且随着光照方向的变换实时变换,这是普通的纹理贴图所不能表现出来的,法线贴图一般由高模映射到低模上来生成,但像金属、木头这些细节丰富的物体就可以借助一些程序化生成的软件比如PS。 法线映射的实现切线空间法线的存储,一般会放到模型的切线空间中切线空间:以物体表面的切线,副切线和法线组成的几何空间每个顶点都有属于自己的切线空间,这个空间的原点是顶点本身,z轴是顶点的法线方向(n),x轴是顶点的切线方向(t),y轴有前边两个轴叉乘而来,被称为副切线(b)或者副法线。//可以参考入门精要第七章部分 在计算光照时,需要把相关的向量放在统一的坐标系下进行运算。此时就需要不同空间坐标的转换矩阵(世界空间转切线空间/切线空间转世界空间) 世界空间和切线空间的转换将世界坐标系下顶点的法线(Normal)、切线(Tangent)、副切线(Bitangent)作为切线空间坐标系的正交基。用这三个向量的标准正交基构建转换矩阵。对应关系为:法线方向作为z轴,切线方向作为x轴,副切线方向作为y轴转换矩阵:切线空间到世界空间的转换矩阵为一个3×3的旋转矩阵,一般称为TBN矩阵世界空间到切线空间的转换矩阵为上述TBN矩阵的逆矩阵,因为是正交矩阵,所以逆矩阵就是它的转置矩阵 ////TBN矩阵参考资料:Tutorial 13 : Normal MappingGLSL-TBN矩阵_沉默的舞台剧的博客-CSDN博客_tbn矩阵//旋转矩阵的逆=转置可以参考《games101第四课》开头部分转换矩阵完成之后,接下来就是光照计算将光照计算中需要的数据,例如光照方向、观察方向、法线方向等参数,带入到光照模型中计算切线空间的优点法线存在各个空间里都可以,但关键不只是存在哪里,还有后续的光照计算切线空间的好处自由度高。 模型空间下是绝对法线信息(仅可以用在创建它时的那个模型) 而切线空间下的是相对法线信息,是对当前物体法线的扰动。(可以复用) 可进行uv动画。 比如:移动uv坐标来实现凹凸移动效果 可以重用法线纹理。 比如:一个立方体,6个面可以用一张法线贴图 可压缩。 由于切线空间下贴图中法线的Z方向总是正方向(模型空间下可以是负的),那么我们只存XY(切线和副切线)就能推出Z(法线)了,可以少存一个。 在Unity中,非移动平台上,Unity会把法线贴图转换成DXRT5nm格式,这个格式只有两个有效GA通道,这样可以节省空间,而移动平台Unity使用传统的RGB通道。在DXRT5nm格式中,GA储存UI应法线x、y分量,z分量需要通过一个简单运算求得。 Parallax Mapping法线映射虽然能展示较为通真的模型表面细节,但毕竟法线映射只能改变法线进而改变光照,并不能让模型表面产生让人信服的遮挡效果。所以我们引入视差映射。Parallax Mapping中文为视差映射,是—种类似于法线映射的技术,但是原理不同,类似法线贴图,它是用以提高模型表面细节并赋予其遮挡关系的技术。并可以和法线贴图—起使用提供令人信服的通真的效果。视差映射同样引进—张新的贴图,高度图,高度图—般是作为顶点位移来使用的,但模型要包含大量的三角形才能获得比较不错的效果,否则看起来会成块状。所以如何在有限的三角面上表示通真的令人信服的效果?这就是视差映射技术。视差映射的核心是改变纹理坐标,但如何改变?怎么改变?这时需要—张存储模型信息的高度图,利用模型表面高度信息来对纹理进行偏移。 ![](data:image/svg+xml;utf8,svg%20xmlns='http://www.w3.org/2000/svg'%20width='385'%20height='305'/svg) 视差映射主要是让平面看起来立体,和法线贴图—样是欺骗眼睛的做法。我们制作的模型在—个三角面即切线空间下,所有点都位于切线和副切线组成的平面内(右图水平0.0点),但实际物体要有更多的丰富细节。如右图当计算我们当前视角的片元A时,真正应该计算的点是视线与物体表面的实际交点,即B点。如何计算B点,计算B点,我们就需要知道A、B两点在平面上uv偏差。这个其实是不太好计算的。不过可以近似计算,根据高度图以及切线空间下的视角方向,近似的求解偏移量。视角方向(v) ,A点的高度值来近似的求解,并可以通过—个缩放值(scale)来控制。 d = v.xy * ha * scale l v.z Steep Parallax Mapping视差映射往往是近似值,所以计算结果并不是准确的。在此基础上想获得更加准确的结果就需要陡峭视差映射,陡峭视差也是一个近似的解,但相比于普通视差映射要精确的多,效果表现上也更好。并且会对纹理坐标偏移进行合理性检查。陡峭视差映射的基本思想是将深度分为等距的若干层,然后从最顶端开始采样,并且每次沿着视角方向偏移一定的值,若当前层的深度大于采样出的深度,则停止检查并返回最后的结果。 Relief Mapping浮雕映射,对比与视差映射.想要有更好更准确的表现效果,视差映射是不够的,使用更大的uv偏移,视差映射就会导致失真。于是引入浮雕映射,浮雕映射更容易提供更多的深度,还可以做自阴影以及闭塞效果。 ![](data:image/svg+xml;utf8,svg%20xmlns='http://www.w3.org/2000/svg'%20width='663'%20height='525'/svg) 浮雕映射—般采用射线步进,和二分查找来决定uv偏移量。第—种使用射线步进来查找可能的交点,为什么不直接用二分查找。因为直接用二分查找可能会漏掉较薄的区域导致结果不准确。所以第—步,使用射线步进来确定交点位于那个步进内。之后在该步进内使用二分查找,快速确定交点位置。最后返回结果,偏移贴图。 ![](data:image/svg+xml;utf8,svg%20xmlns='http://www.w3.org/2000/svg'%20width='413'%20height='300'/svg) Shader "Unlit/ReliefMapping"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_NormalMap("法线贴图",2D) = "bump"{}
_NormalScale("法线强度",Float) = 1
[Toggle]_HEIGHTMAP("高度映射",Float) = 0.0
_HeightMap("高度图",2D) = "white"{}
_HeightScale("高度图强度",Range(0,0.5)) = 0.005
_MainColor("主颜色",Color) = (1,1,1,1)
_CubeMap("环境贴图",CUBE) = "gray"{}
_Metal("金属度",range(0,1)) = 0.5
}
SubShader
{
Tags { "RenderPipeline"="UniversalRenderPipeline""RenderType"="Opaque" }
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
float4 _NormalMap_ST;
float4 _HeightMap_ST;
float4 _MainColor;
float _NormalScale;
float _HeightScale;
float _Metal;
bool _HEIGHTMAP;
CBUFFER_END
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
TEXTURE2D(_NormalMap);
SAMPLER(sampler_NormalMap);
TEXTURE2D(_HeightMap);
SAMPLER(sampler_HeightMap);
TEXTURE2D(_CubeMap);
SAMPLER(sampler_CubeMap);
ENDHLSL
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma shader_feature _HEIGHTMAP_ON
struct appdata
{
float3 normal : NORMAL;
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 tangent:TANGENT;
};
struct v2f
{
float4 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 nDirWS : TEXCOORD1;
float3 lDirWS : TEXCOORD2;
float3 posWS : TEXCOORD3;
float3 TDirWS : TEXCOORD4;
float4 uv2 : TEXCOORD5;
float3 BDirWS : TEXCOORD6;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
o.nDirWS = TransformObjectToWorldNormal(v.normal.xyz,true);
o.posWS = TransformObjectToWorld(v.vertex);
o.TDirWS = normalize(TransformObjectToWorld(v.tangent.xyz));
o.BDirWS = normalize(cross(o.nDirWS,o.TDirWS)*v.tangent.w);
o.uv.zw = TRANSFORM_TEX(v.uv,_NormalMap);
#ifdef _HEIGHTMAP_ON
o.uv2.xy = TRANSFORM_TEX(v.uv,_HeightMap);
#endif
return o;
}
//视差映射
float2 ParallaxMapping(float2 Height_HV,float3 V)
{
float height = SAMPLE_TEXTURE2D(_HeightMap,sampler_HeightMap,Height_HV);
float2 offsetuv = V.xy/V.z*height*_HeightScale;
return offsetuv;
}
//陡峭视差映射
float2 SteepParallaxMapping(float2 uv,float3 V)
{
float layerNum = 40;//迭代层数
float layerHeight = 1/layerNum;//每层步进距离
float currentLayerHeight = 0;//当前高度
float2 offsetLayerUV = V.xy/V.z*_HeightScale;//最大偏移距离
float2 stepOffset = offsetLayerUV/layerNum;//每步偏移量
float2 offsetUV = float2(0,0);
float2 currentUV = uv;//当前采样高度UV
float currentHeight = SAMPLE_TEXTURE2D(_HeightMap,sampler_HeightMap,currentUV + offsetUV).r;//当前高度
for(int i=0;icurrentHeight)
{
return offsetUV;//当前采样的层数高度,大于当前高度
}
offsetUV+=stepOffset;
currentHeight = SAMPLE_TEXTURE2D(_HeightMap,sampler_HeightMap,currentUV + offsetUV).r;//采样偏移
currentLayerHeight+=layerHeight;
}
return offsetUV;
}
//浮雕映射
float2 ReliefMapping(float2 uv, real3 V)
{
float2 offlayerUV = V.xy / V.z * _HeightScale;//依然是最大偏移量
float RayNumber = 40;//步进算法
float layerHeight = 1 / RayNumber;//每层高度
float2 SteppingUV = offlayerUV / RayNumber;//每步偏移量
float currentLayerHeight = 0;//当前高度
float offlayerUVL = length(offlayerUV);//长度
float2 offUV = float2(0,0);
for(int i = 0;i < RayNumber; i++)
{
offUV += SteppingUV;
float currentHeight = SAMPLE_TEXTURE2D(_HeightMap,sampler_HeightMap,uv + offUV).r;
currentLayerHeight += layerHeight;//层数增加
if (currentHeight < currentLayerHeight)
{
break;
}
}
float2 T0 = uv + offUV;//当前UV
float2 T1 = uv + offUV - SteppingUV;//上一个UV
//二分查找
for (int j = 0; j < 40; j++)
{
float2 P0 = (T1 + T0) * 0.5;
float P0Height = SAMPLE_TEXTURE2D(_HeightMap,sampler_HeightMap,P0).r;//当前采样高度
float P0LayerHeight = length(P0) / offlayerUVL;//当前高度(公式中还应乘上总高度1)
if (P0Height < P0LayerHeight)
{
T0 = P0;
}
else
{
T1= P0;
}
}
return (T0 + T1) / 2 - uv;
}
half4 frag (v2f i) : SV_Target
{
// sample the texture
//-------HalfLambert--------
float2 MainTex_UV = i.uv.xy;
float2 Normal_UV = i.uv.zw;
half3x3 TBN = transpose(half3x3(i.TDirWS,i.BDirWS,i.nDirWS));//TBN矩阵
float3 VDirWS = normalize(GetWorldSpaceViewDir(i.posWS));
#ifdef _HEIGHTMAP_ON
half3 VDirTS = normalize(mul(-VDirWS,TBN));
float2 offuv = float2(0,0);
offuv = ReliefMapping(i.uv2.xy,VDirTS);
MainTex_UV +=offuv;
Normal_UV+=offuv;
#endif
float3 normalTS = UnpackNormal(SAMPLE_TEXTURE2D(_NormalMap,sampler_NormalMap,Normal_UV)).xyz;
normalTS.xy*=_NormalScale;
normalTS.z = sqrt(1-saturate(dot(normalTS.xy,normalTS.xy)));
Light light = GetMainLight();
float3 NDirWS = normalize(mul(TBN,normalTS));
float3 lDirWS = normalize(light.direction);
float3 RDirWS = normalize(reflect(-VDirWS,NDirWS));
float3 HDirWS = normalize(VDirWS+lDirWS);
float NdotL = max(0,dot(NDirWS,lDirWS));
float NDotH = max(0,dot(NDirWS,HDirWS));
float4 TexCol = SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex, MainTex_UV);
float Glossiness = lerp(0,100,_Metal);
float MipMapLevel = lerp(0.00001,8,1-_Metal);
float3 Diffuse = light.color.rgb*TexCol*_MainColor*NdotL;
float3 Specular = _Metal*pow(NDotH,Glossiness);
float3 ambient = SAMPLE_TEXTURECUBE_LOD(_CubeMap,sampler_CubeMap,RDirWS,MipMapLevel);
float3 result = Diffuse+Specular;
return float4(result,1);
}
ENDHLSL
}
}
}
|