[OpenGL] 纹理高级篇 - 视差贴图

        本文是贴图系统的续篇,上一篇为法线贴图 https://blog.csdn.net/ZJU_fish1996/article/details/83934059

        图源来自unreal的自带材质包里的new brick系列,可见https://api.unrealengine.com/CHN/Engine/Rendering/Materials/HowTo/BumpOffset/

        包含了三张纹理,包含底色纹理、法线纹理、遮罩纹理,其中底色纹理的alpha通道存储了高度信息。目前我们只使用了底色和法线。

        以下为效果对比图,即从法线贴图->视差贴图->陡峭视差贴图的转换对比。

效果对比

        法线贴图的主要原理是通过改变法线影响光照的计算,从而达到视觉欺骗的目的,使人觉得表面凹凸不平。但是对于使用法线贴图的物体而言,由于它并没有真正改变表面的高度,所以,如果有一处凸起的地方,我们依然可以看到凸起部位应当遮挡住的地方。为了解决这一问题,我们引入了视差贴图。

        简单来说,视差贴图就是根据观察视线,通过偏移纹理坐标,使得可见的高处纹理遮挡不可见的低处纹理,从而表现出凹凸不平的效果。类似的,我们也可以先制作一个高模的模型,将其高度信息记录在一张贴图里,然后使用低模来渲染模型,该高度h是相对于法线位置的偏移,即为切线空间中的坐标信息,它在切线空间中的坐标为(0,0,h)。如果希望计算更加简单,可以直接统一在切线空间中进行计算(上一篇法线贴图我们是在世界空间中计算的)

       为了能够模拟视差效果,我们需要一张额外的高度纹理,此例中,该信息被存储在表面颜色的alpha通道中,单独显示如下:

       

       其中,黑色表示凹部分,白色表示凸部分。

       

       对于一个凸起的砖块而言,当我们望向物体时,计算出来的落点为A,而实际的落点为B,这意味着我们需要将采样的uv坐标从A偏移到B。

       接下来我们需要计算的就是该纹理的偏移量。该偏移直接通过数学方式计算是比较复杂的,相当于求解视线与模型的交点,通过迭代逼近来完成。一般的简化方法是直接向视线方向进行偏移,可以得到一个和视线方向相关的近似结果。即:

      offsetUV += ViewDir.xy / ViewDir.z * height * scale

      该计算方法容易导致模型边缘失真,且难以模拟阴影。

       

      在此方法中,我们实际上对纹理做了暴力的偏移,实践后,会发现视线接近平行的时候,有些采样纹理不太合理。如上图红色部分,不仅采样了凹处的颜色,还采样了一部分砖块的颜色。

     因此,我们需要在近似的基础上,尽可能保证纹理偏移的合理性。我们按照类似于一开始提出迭代近似的方法,来计算近似的交点。

     我们自上到下分为n层,纹理偏移量也被分为n层,我们要做的就是在这n层中找到合适的一层,此处为视线的近似交点。上图中,横坐标为u或者v纹理伸展的方向,纵坐标为高度(或者说深度)。我们先从原始的uv处从高度图中采样一个高度,将其与最高的层进行对比,如果采样高度大于层高,则将纹理再沿着u/v方向偏移一个单位,采样新的高度,并与新的层高进行对比,直到采样高度小于层高。

       层数越多,结果也会越精确。

       上述的逻辑描述了计算细节,看起来比较绕,我们可以宏观描述一下:我们目前所做的事情是求视线与凹凸面的交点,取u,v方向两个横截面,可以进而抽象为计算曲线和直线的交点。我们沿着横坐标方向每次移动固定偏移量,再比较直线的纵坐标y(这里通过加上一个固定的delta y 得到)与曲线的纵坐标y(这里通过纹理采样得到),当前后两次y坐标差值符号相反的时候,就意味着出现了交点。

       

       但是靠近之后,能看出分层的现象。该方法中,只能通过增加迭代次数来避免这一问题。

       

Vertex Shader

uniform mat4 ModelMatrix;
uniform mat4 IT_ModelMatrix;
uniform mat4 ViewMatrix;
uniform mat4 ProjectMatrix;
uniform vec3 cameraPos;
uniform vec3 LightLocation;

attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec3 a_tangent;
attribute vec2 a_texcoord;

varying vec2 v_texcoord;
varying vec3 v_normal;
varying vec3 ViewDir;
varying vec3 lightDir;

void main()
{
    gl_Position = ModelMatrix * a_position;
    vec3 worldPos = vec3(gl_Position);
    gl_Position = ViewMatrix * gl_Position;
    gl_Position = ProjectMatrix * gl_Position;
    v_texcoord = a_texcoord;

    v_normal = mat3(IT_ModelMatrix) * a_normal;
    vec3 tangent = mat3(ModelMatrix) * a_tangent;

    vec3 N = normalize(v_normal);
    vec3 T = normalize(tangent - N * tangent * N);
    vec3 B = cross(N, T);
    mat3 TBN = mat3(T,B,N);

    ViewDir = normalize( (cameraPos - worldPos) * TBN);
    lightDir = normalize( (LightLocation - worldPos) * TBN);
}

Fragment Shader


uniform sampler2D brick_N;
uniform sampler2D brick_D;
uniform int type;

varying vec3 v_normal;
varying vec2 v_texcoord;
varying vec3 ViewDir;
varying vec3 lightDir;

vec3 UnpackNormal(vec3 normal)
{
    normal = normalize(2 * normal - 1);
    return normal;
}

vec2 Pallax()
{
    float h = texture2D(brick_D, v_texcoord).a;
    return v_texcoord + ViewDir.xy / ViewDir.z * (h * 0.008 - 0.004);
}

vec2 SteepPallax()
{
   float layer = mix(50, 5, abs(dot(vec3(0, 0, 1), ViewDir)));

   vec2 deltaUV = 0.02 * ViewDir.xy / ViewDir.z  / layer;

   vec2 uv = v_texcoord;
   float h = texture2D(brick_D, uv).a;
   float curH = 0.0;
   float deltaH= 1.0 / layer;

   while(h > curH)
   {
       curH += deltaH;
       uv += deltaUV ;
       h = texture2D(brick_D, uv).a;
   }

   return uv;
}

void main()
{

    vec2 uv;
    if(type == 0)
    {
        uv = v_texcoord;
    }
    else if(type == 1)
    {
        uv = Pallax();
    }
    else if(type == 2)
    {
        uv = SteepPallax();
    }
    vec3 normal = texture2D(brick_N, uv);
    normal = UnpackNormal(normal);

    float diffuse = 0.7 * clamp(dot(normal, lightDir), 0, 1);
    float ambient = 0.2;
    vec3 reflectDir = normalize(reflect(-lightDir,normal));
    float specular = pow(clamp(dot(reflectDir,ViewDir),0,1),1.0);

    vec3 color = texture2D(brick_D, uv);
    vec3 finalColor = color * ( specular +diffuse + ambient);

    gl_FragColor = vec4(finalColor,1);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值