高度贴图转法线贴图HeightMap to NormalMap

最近在做大世界地形,用几张噪声图生成高度图,再用高度图渲染地形,光照用的最基本的albedo

添加图片注释,不超过 140 字(可选)

可以看到由于是用一个平面的4x4网格组合而成,光照信息是不对的

于是想着用高度图生成法线贴图

添加图片注释,不超过 140 字(可选)

computer shader生成,大致原理就是取3x3像素,根据不同算子,做一个差异取得法线贴图

float _dz;
float _invertR;
float _invertG;
float _invertH;
int _type;
int _heightOffset;
Texture2D<float3> _HeightTex;
RWTexture2D<float3> _NormalTex;

[numthreads(8, 8, 1)]
void HeightToNormal(uint3 id : SV_DispatchThreadID)
{
    int w, h;
    _HeightTex.GetDimensions(w, h);
    float2 size = float2(w, h);
    float2 vUv = id.xy;
    float2 _step = float2(-1, -1);

    float2 tlv = float2(vUv.x - _step.x, vUv.y + _step.y);
    float2 lv = float2(vUv.x - _step.x, vUv.y);
    float2 blv = float2(vUv.x - _step.x, vUv.y - _step.y);
    float2 tv = float2(vUv.x, vUv.y + _step.y);
    float2 bv = float2(vUv.x, vUv.y - _step.y);
    float2 trv = float2(vUv.x + _step.x, vUv.y + _step.y);
    float2 rv = float2(vUv.x + _step.x, vUv.y);
    float2 brv = float2(vUv.x + _step.x, vUv.y - _step.y);

    tlv = float2(tlv.x >= 0.0 ? tlv.x : (size.x + tlv.x), tlv.y >= 0.0 ? tlv.y : (size.y + tlv.y));
    tlv = float2(tlv.x < size.x ? tlv.x : (tlv.x - size.x), tlv.y < size.y ? tlv.y : (tlv.y - size.y));
    lv = float2(lv.x >= 0.0 ? lv.x : (size.x + lv.x), lv.y >= 0.0 ? lv.y : (size.y + lv.y));
    lv = float2(lv.x < size.x ? lv.x : (lv.x - size.x), lv.y < size.y ? lv.y : (lv.y - size.y));
    blv = float2(blv.x >= 0.0 ? blv.x : (size.x + blv.x), blv.y >= 0.0 ? blv.y : (size.y + blv.y));
    blv = float2(blv.x < size.x ? blv.x : (blv.x - size.x), blv.y < size.y ? blv.y : (blv.y - size.y));
    tv = float2(tv.x >= 0.0 ? tv.x : (size.x + tv.x), tv.y >= 0.0 ? tv.y : (size.y + tv.y));
    tv = float2(tv.x < size.x ? tv.x : (tv.x - size.x), tv.y < size.y ? tv.y : (tv.y - size.y));
    bv = float2(bv.x >= 0.0 ? bv.x : (size.x + bv.x), bv.y >= 0.0 ? bv.y : (size.y + bv.y));
    bv = float2(bv.x < size.x ? bv.x : (bv.x - size.x), bv.y < size.y ? bv.y : (bv.y - size.y));
    trv = float2(trv.x >= 0.0 ? trv.x : (size.x + trv.x), trv.y >= 0.0 ? trv.y : (size.y + trv.y));
    trv = float2(trv.x < size.x ? trv.x : (trv.x - size.x), trv.y < size.y ? trv.y : (trv.y - size.y));
    rv = float2(rv.x >= 0.0 ? rv.x : (size.x + rv.x), rv.y >= 0.0 ? rv.y : (size.y + rv.y));
    rv = float2(rv.x < size.x ? rv.x : (rv.x - size.x), rv.y < size.y ? rv.y : (rv.y - size.y));
    brv = float2(brv.x >= 0.0 ? brv.x : (size.x + brv.x), brv.y >= 0.0 ? brv.y : (size.y + brv.y));
    brv = float2(brv.x < size.x ? brv.x : (brv.x - size.x), brv.y < size.y ? brv.y : (brv.y - size.y));

    float tl = abs(_HeightTex[tlv].r);
    float l = abs(_HeightTex[lv].r);
    float bl = abs(_HeightTex[blv].r);
    float t = abs(_HeightTex[tv].r);
    float b = abs(_HeightTex[bv].r);
    float tr = abs(_HeightTex[trv].r);
    float r = abs(_HeightTex[rv].r);
    float br = abs(_HeightTex[brv].r);

    float dx = 0.0, dy = 0.0;
    if (_type == 0) // Sobel
    {
        dx = tl + l * 2.0 + bl - tr - r * 2.0 - br;
        dy = tl + t * 2.0 + tr - bl - b * 2.0 - br;
    }
    else
    {
        // Scharr
        dx = tl * 3.0 + l * 10.0 + bl * 3.0 - tr * 3.0 - r * 10.0 - br * 3.0;
        dy = tl * 3.0 + t * 10.0 + tr * 3.0 - bl * 3.0 - b * 10.0 - br * 3.0;
    }

    float3 normal = normalize(float3(dx * _invertR * _invertH * 255.0, dy * _invertG * _invertH * 255.0, _dz));
    _NormalTex[id.xy] = (_heightOffset == 0) ? (normal.xy * 0.5 + 0.5, normal.z) : (normal.xyz * 0.5 + 0.5);
}

其中几个参数

        float strength = 2.5f;
        float level = 7;
        cs.SetFloat("_dz", (float)(1.0 / strength * (1.0 + Mathf.Pow(2f, level))));
        cs.SetFloat("_invertR", 1);
        cs.SetFloat("_invertG", 1);
        cs.SetFloat("_invertH", 1);
        cs.SetInt("_type", 0);
        cs.SetInt("_heightOffset", 1);

得到有法线的地形

添加图片注释,不超过 140 字(可选)

可能由于精度,或者法线贴图参数等,看起来有点怪怪的,看来要另寻他法

三角形计算法线

其实只要能拿到三角形3个点的坐标,就是可以算出各个点法线,经过谷歌大法寻找,让我找到可以用

geometry shader,只需要在shader中申明一个#pragma geometry geom

            [maxvertexcount(3)]
            void geom(triangle v2f p[3], inout TriangleStream<v2f> triangleStream)
            {
                for (int i = 0; i < 3; i++)
                {
                    v2f A = p[i];
                    float3 AB = p[(i + 1) % 3].pos - A.pos;
                    float3 AC = p[(i + 2) % 3].pos - A.pos;

                    A.normal = normalize(cross(AB, AC));
                    triangleStream.Append(A);
                }
            }

再来看看效果

添加图片注释,不超过 140 字(可选)

非常不错,达到了想要的效果

对切线空间的法线的一些疑问

在做的过程中发现对TBN和UV的关系有点迷糊

TBN大家都知道,T切线,B副切线,N法线,3个互相垂直的向量组成一个坐标系

TBN矩阵,可以把切线空间的坐标转对象(世界)空间坐标,这些都很简单很好理解

那么TBN和UV有什么关系?

我们都知道模型的所有点xyz在uv上都能一一对应,也就是三维转二维

其中切线的方向和UV展开方向是一样的,为么是这样?问就是约定俗成的规定

举个例子,一个平面,在0,0,0,朝向0,1,0

2个三角形完全覆盖了UV,那么T(1,0,0) B(0,1,0) N(0,0,1)

添加图片注释,不超过 140 字(可选)

T就是U的朝向,B就是V的朝向

不是所有三角形在uv中是这样的,比如如下图这样

添加图片注释,不超过 140 字(可选)

我们就需要计算TB,有一个固定公式

添加图片注释,不超过 140 字(可选)

我们也可以写成这样

添加图片注释,不超过 140 字(可选)

写成矩阵

添加图片注释,不超过 140 字(可选)

变换下,求出TB

添加图片注释,不超过 140 字(可选)

代码表示

float3 edge1 = pos2 - pos1;
float3 edge2 = pos3 - pos1;
float3 deltaUV1 = uv2 - uv1;
loat3 deltaUV2 = uv3 - uv1;

float3 tangent = normalize(deltaUV1 * edge1 - deltaUV2 * edge1) / (deltaUV1 * deltaUV2 - deltaUV2 * deltaUV1);
float3 bitangent = normalize(-deltaUV1 * edge2 + deltaUV2 * edge2) / (deltaUV1 * deltaUV2 - deltaUV2 * deltaUV1);

这种计算出的T,B不一定互相垂直,必须用施密特正交化

float3 edge1 = pos2 - pos1;
float3 edge2 = pos3 - pos1;
float3 deltaUV1 = uv2 - uv1;
loat3 deltaUV2 = uv3 - uv1;

float3 tangent = normalize(deltaUV1 * edge1 - deltaUV2 * edge1) / (deltaUV1 * deltaUV2 - deltaUV2 * deltaUV1);
tangent = tangent - dot(tangent, normal) * normal;
float3 bitangent =  normalize(cross(normal, tangent));

切线空间法线为什么是蓝紫色的?

在unity中,在shader中我们可以拿到模型的法线和切线

为什么我们还要采样法线贴图?

我们需要更加细腻的光照,让各个三角形的法线有区别,于是我们就需要采样法线贴图

添加图片注释,不超过 140 字(可选)

如果和模型的的法线一致,那么就是0,0,1一般法线的范围在-1和1之间,我们需要*0.5并且+0.5

变成0.5,0.5,1这个就是蓝紫色

添加图片注释,不超过 140 字(可选)

其中有变化的,就是它的法线在TBN空间相对于法线0,0,1的扰动

总结:

学的越多,不知道的就更多,永远学不完哈

这次就当是自己工作中的一些小记录,下次出个如何LOD渲染地形

参考:

NormalMap-Online工具

https://learnopengl-cn.github.io

  • 24
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值