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

141 篇文章 35 订阅

此文是基于之前的一篇: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
在这里插入图片描述

关于叉乘可以参考这篇:Basis Orientations in 3ds Max, Unity 3D and Unreal Engine

下面引用博主的两张图:
Basis orientations in 3ds Max, Unity 3D and Unreal Engine (right and left hand rules are shown)
Basis orientations in 3ds Max, Unity 3D and Unreal Engine (right and left hand rules are shown) - 在3ds Max, Unity 3D 和 Unreal 引擎 的基本朝向(左右手坐标的规则显示)

  • 3ds Max – right handed, z-up - 右手坐标,z 向上
  • Unity 3D – left handed, y-up - 左手坐标,y 向上
  • Unreal Engine – left handed, z-up - 左手坐标,y 向上

Right hand rules
右手坐标的 叉乘规则


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;
            }
        }
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值