Unity Shader - 简单山脉 - 顶点着色器重构法线

unity 同时被 2 个专栏收录
154 篇文章 3 订阅
85 篇文章 10 订阅

此文是基于之前的一篇:Unity Shader - 使用Noise噪点图生成简单山脉(使用tex2Dlod控制顶点高度)写的,这篇使用tex2Dlod的,虽然将控制顶点的高度逻辑放到了vertex shader(顶点着色器)中读取高度图来控制了,但是却丢失了实时的法线控制,因为之前在CSharp脚本控制山脉顶点高度时有调用:Mesh.RecalculateNormals();,所以运行后,可以看到山脉可以与灯光有正常的交互。

那么这儿重新写的这篇就是既能在vertex shader中控制高度,也能保留实时调整顶点法线,让山脉与灯光能正常交互。

效果

材质有个:Rebuild Normal 的下拉列表项:On就是开启重构法线,Off就是关闭。
可以看到Off时,整个山脉就如同一个Quad的光照一下,没有立体感。On之后,可以看到山脉可以与方向光有交互了。
在这里插入图片描述

Shader

先大概看看代码,后面会将到思路。
主要在vertex shader处理就好了。
其他代码不变:

请详细查看注释,该说明的都说了。

			// jave.lin 2020.03.14 - vertex shader重构法线核心逻辑
            v2f vert (appdata v) {
                v2f o;
                ...

                #if _REBUILD_NORMAL_ON // 开始重构法线

                // reconstruct normals
                // 这个4x4数据也可以通过外部传入,可以节省顶点着色器ALU的计算量与L1 caches缓存量。
                const float4x4 offset_xz = {
                    {+1,+0, /* gap **/ +1,-1},  // 右下
                    {+0,-1, /* gap **/ -1,-1},  // 左下
                    {-1,+0, /* gap **/ -1,+1},  // 左上
                    {+0,+1, /* gap **/ +1,+1}   // 右上
                };
                // gridGap是没个顶点上下左右的距离(在CSharp端生成时设置,可以传入shader也行,这里我偷懒,就写死了)
                const float gridGap = 5;
                // 默认向量也可以外部传入,因为上面的默认法线是可以调整的
                // 下面我讲默认法线初始化为:up
                float3 sumNormal = float3(0, 1, 0);
                for (int i = 0; i < 4; i++) {
                    // 获取偏移数据
                    float4 uvs_offset = offset_xz[i];
                    // 获取偏移数据分别的高度
                    half h1 = tex2Dlod(_NoiseTex, float4(v.uv + uvs_offset.xy * _NoiseTex_TexelSize.xy, 0, 0)).r * _MountainHeight;
                    half h2 = tex2Dlod(_NoiseTex, float4(v.uv + uvs_offset.zw * _NoiseTex_TexelSize.xy, 0, 0)).r * _MountainHeight;
                    // 根据偏移方向,高度重构从当前顶点,指向附近偏移点的高度两个向量
                    float3 dir1 = float3(uvs_offset.x * gridGap, h1 - centerH, uvs_offset.y * gridGap);
                    float3 dir2 = float3(uvs_offset.z * gridGap, h2 - centerH, uvs_offset.w * gridGap);
                    // 根据平面两个向量(两个向量可以确定一个平面,如同:TBN当中的TB两个切向量)
                    // 叉乘得到平面的法线向量
                    float3 newNormal = (cross(dir1, dir2));
                    // 累加到混合向量中
                    sumNormal += newNormal;
                }
                // 均值混合
                sumNormal /= 5; // 改为 sumNormal *= 0.2; 效果更高,乘法比除法效率要高
                o.normal = UnityObjectToWorldNormal(sumNormal);

                #else // _REBUILD_NORMAL_OFF
                o.normal = UnityObjectToWorldNormal(v.normal);
                #endif
                ...
                return o;
            }

思路

  • 每个顶点的高度,都可是通过vertex shader中,tex2Dlod来读取纹理中的高度中。
  • 当前点的高度得到后,取附近两个点的高度。
  • 通过高度gridGrap与生成:从当前点,指向两个附近点的向量:dir1dir2
  • 通过dir1dir2叉乘计算出法线,并累加到混合法线:sumNormal
  • 最后sumNormal混合法线均值混合为该顶点的法线。

为了方便理解,我在Excel与Win10 Paint.exe绘制了一些图解:

下面为图1
在这里插入图片描述
下面为图2
在这里插入图片描述

Project

backup : UnityShader_NoiseMountain_RebuildNormals_2018.3.0f2


2021/09/01 的今天,偶然在反编译某个插件中的代码,法线 c sharp 上的脚本的重构法线,也可以这么写,思路差不多:

        private static void GenerateNormals(Vector3[] _vertices, int[] _trinagles, out Vector3[] _normals)
        {
            _normals = new Vector3[_vertices.Length];
            List<List<Vector3>> list = new List<List<Vector3>>();
            for (int i = 0; i < _vertices.Length; i++)
            {
                list.Add(new List<Vector3>());
            }

            for (int j = 0; j < _trinagles.Length; j += 3)
            {
                Vector3 b = _vertices[_trinagles[j]];
                Vector3 a = _vertices[_trinagles[j + 1]];
                Vector3 a2 = _vertices[_trinagles[j + 2]];
                Vector3 lhs = a - b;
                Vector3 rhs = a2 - b;
                Vector3 item = Vector3.Cross(lhs, rhs);
                item.Normalize();
                list[_trinagles[j]].Add(item);
                list[_trinagles[j + 1]].Add(item);
                list[_trinagles[j + 2]].Add(item);
            }

            for (int k = 0; k < _vertices.Length; k++)
            {
                Vector3 zero = Vector3.zero;
                for (int l = 0; l < list[k].Count; l++)
                {
                    zero += list[k][l];
                }

                _normals[k] = zero / list[k].Count;
            }
        }
  • 0
    点赞
  • 1
    评论
  • 7
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值