欢迎光临散文网 会员登陆 & 注册

Godot Shader 地形材质原理与着色器编程

2023-02-18 08:07 作者:紧果呗  | 我要投稿

🟡 ShaderMaterial Programming 着色器材质编程

有个好消息,Godot 4.0 RC2 放出来了,正式版本奋斗在这 3 年之间终于快完成了!

Release candidate: Godot 4.0 RC 2

The Godot 4.0 release is mere days away!

https://godotengine.org/article/release-candidate-godot-4-0-rc-2/

虽然,这小节内容是讲材质与着色器编程,但本质上,是在讲数学的应用。编程只是使用数学工具的手段,作品效果就是最终产品。这中间要经历的内容很多,根据不同数学基础的人自然有不同的理解或不理解,或者难以理解。到目前为止,傅里叶级数及其应用是给我最大触动的数学工具,3Blue1Brown 制作的一个图形化傅里叶级数教学视频以一种难以想象的直观手段,将极度抽象的数学工具,用形象的动画形式展示在观众面前。这真的太棒了,尽管,数学的意义远不止如此,但通过动画形象地说明了数学在音乐、图像、电子、通信等等领域的共通性。


Godot 材质有三类,它们都可以使用节点编辑界面方式、或者使用着色器方式进行编辑,与 4 个资源类型关联:

  • **spatial**     - for 3D rendering.

  • **canvas_item** - for 2D rendering.

  • **particles**   - for particle systems.

Spatial Material 材质基于物理的渲染 PBR - Physically Based Rendering 技术,自迪士尼在 SIGGRAPH 2012 上提出了著名的迪士尼原则的 BRDF(Disney Principled BRDF)之后,由于其高度的易用性以及方便的工作流,已经被电影和游戏业界广泛使用。

🟠🔵 Displacemnet & Terrain 转换贴图与地形

置换贴图是地形制作的基本工具,在 GPU 渲染管线上,顶点着色器是处理几何体原始信息的工具,通过修改顶点坐标,使用原先的平面几何体产生任何希望的效果。结合噪声纹理,根据其像素的灰度值来修改顶点的高度坐标,就可以使用平面按照噪声纹理隆起,或者凹陷,形成逼真的地形。


材质中 Height 用于启用视差高度贴图,和 Normal Mapping 类似,是用于改变表面光线细节的贴图。Godot 3.5 中使用 Depth 属性表示视差贴图。

另一种用于地形制作的置换贴图,Displacement Mapping,Godot 没有直接在材质中提供设置,而只能通过着色器来实现,即需要创建着色器材质,对几何体的顶点进行直接处理。

创建 PlaneMesh 几何体

给场景添加 MeshInstance3D 节点,并为其创建一个 PlaneMesh 平面几何体,然后为其创建着色器材质,点击 ShaderMaterial 打开着色器设置,在 Shader 属性中创建一个 `Shader` 即着色器程序。或者,可以使用 `VisualShader` 进行可视化着色器编程。


Godot 提供了 GLSL 编程接口,与源始的 OpenGL 着色器编程不同的是,直接在单个着色器程序中编写:

  • Vertex Shader 顶点着色器:`void vertex() { ... }`

  •  Fragment Shader 片段着色器:`void fragment() { ... }`

  •  Lighting 光照处理函数;:`void light() { ... }`

使用可视化着色器编程的一个好处是更直观,直接通过图形界面就可以生成着色器代码,通过节点连接实现和各种着色器数据的赋值。通过选择要编写的着色器,所有可用的输出变量就直接显示在 Output 节点中。


节点化编程和代码编辑方式有思路层面的差异,比如代码中使用 uniform 来定义全局变量来输出纹理资源,在可视编程中,只需要放置一个 Texture 节点,就会自动创建相应的 uniform 变量。但是资源数据的使用方式是有很大差别的。可以使用 `Expression` 或者 `GlobalExpression` 节点编写 GLSL 代码,它们分别会在着色器全局作用域、函数作用域中生成相应的代码。


最新推出的 compute shaders 则需要使用底层的 `RenderingDevice` 访问 Vulkan API 来实现。

Godot 可视化编程 - Vertex Shader‍

注意:Godot 使用 xz 表示地平面,获取顶点坐标时可以转换为 VERTEX.xzy。


Godot 光照函数


在创建材质时,有两个选择,可以给 MeshInstance3D 几何节点或者 Mesh 资源附加材质,两者都通用。

创建 ShaderMaterial

然后点击着色器打开它,在其属性面板中,可以通过工具菜单 Save 将着色器保存到文件中进行编辑。

Display Wireframe 线框视图

为了让 PlaneMesh 可以表现凹凸曲面,需要对平面进行细分,让它拥有更多的顶点,而不是只有 4 个顶点。Subdivision Width/Depth 设置合适的值,比如 256。为何查看细分的效果,可以打开线框视图功能,Perspective -> Display Wireframe,这样就会将细分的边线显示出来。


以下要实现这个简单的地形着色器包含内容:

  •  一个噪声纹理,通过 NoiseTexture 加载 OpenSimplexNoise 或者新版本的 FastNoiseLite

  •  一个顶点着色器处理函数 `void vertex()`;

  •  使用一个 GLSL `texture()` 纹理采样函数;

  •  定义一些全局的着色器变量,用于地形的海拔高度偏移、地形纹理缩放,高度差放大等等;

着色器中需要使用 GLSL `texture()` 纹理采样函数,其返回值包含指定坐标上的像素值 RGBA。对于灰度纹理来说,RGB 三分量是等值的,而透明度分量 Alpha = 1。


需要注意:

  • 新创建的 PlaneMesh 顶点坐标值为 [-1,1] 范围,平面中心为零点,大小为 2x2,而 UV 坐标范围总是是 [0,1]。

  • Godot 中的 XYZ 三轴,红绿蓝三色对应,Y 是垂直方向,XZ 才是地平面。

3D 环境中,平移、Size 等使用米制单位,1 表示 1 米,创建噪声纹理使用的是像素。可以使用三角函数去观测 PlaneMesh 细分后的顶点坐标变化。三角函数的周期是 2PI,对于一个 20x20 大小的平面,它在两个维度上就有 3 个多的三角函数周期的变化。

使用三角函数观测顶点坐标数据

在着色器中定义的 uniform 全局变量,Godot 会进行处理,并且在 ShaderMaterial 面板中提供设置操作,分组在 Shader Params。注意,如果着色器程序有语法错误时,参数就不会显示在发展面板中。

为着色器定义的 Height Map 属性创建噪声纹理:


然后,再调整一下着色器提供的参数,Shader Params:Scale/Translation/Offset/Multiply。

地形效果参考:

Displacement Mappng Principle

🟠🔵 法向量修正


经过以上操作得到的地形还只是停留在曲面的阶段,对于一个能在项目中使用的地形,还需要更多的功能。绿植装饰、光照效果等等基础材质属性,还要提供碰撞检测等等功能。


光照处理涉及到法线,中学的物理定义,光线是直线传播的,反射光则与表面法线向量形成和入射角相等的夹角。当前的地形虽然经过基本的顶点置换处理,得到了与地面一样起伏的曲面。但是,其法线还是和原来新创建的PlaneMesh 一样,即所有细分出来的片面都拥有一样的法线。当光线照射到这个曲面时,光照效果和照在平面上没有两样的!

法线错误与正确状态下的光照对比

在场景上添加一个灯光节点,散光灯或直射光都可以,DirectionalLight,OmniLight。然后打开法线视图 Perspective -> Display Normal,就可以看到这个问题,面没有明暗差异。如果添加散光灯,曲面的明暗变化只跟光源的距离有关,而与曲面的朝向无关。


法线数据保存在 Mesh 资源中,前面的着色器只是修改了顶点坐标,而没有处理法线信息,使得法线错误。修复法线朝向有两个方法:

  •  修改着色器内置的 `NORMAL` 或者 `NORMAL_MAP` 向量;

  •  使用向量贴图修正法线;

在噪声程序纹理工具中,可以按同样的方式生成法线贴图,即和纹理贴图一致的法线信息,Noise Texture面板中勾选 As Normal Map 选项即生成法线贴图。


在渲染流水线上,顶点处理和法线处理使用不同的着色器,顶点着色器一般专用于顶点处理。法线处理可以在片段着色器中处理。如果使用 OpenGL API,则需要加载两个着色器程序。在 Godot 中已经处理好着色器的加载,只需要在同一相着色器程序中编写 `void fragment()` 函数即可。


OpenGL Shading Language 手册简化 GPU 渲染流水线如下,开发者主参与两个阶段:

  • 1. **Vertex Processor**

  • 2. Tessellation Control Processor

  • 3. Tessellation Evaluation Processor

  • 4. Geometry Processor

  • 5. **Fragment Processor**

  • 6. Compute Processor

GLSL 着色器语言从语法上讲,和 C 语言无异,基本数据类似也一样。但是作为 GPU 着色器,它提供了大量向量、矩阵、纹理等数据类型,并且基于它们提供了丰富的 API。GLSL 实际上是几种密切相关的语言,这些语言用于为 API 处理管道中包含的每个可编程处理器创建着色器。因此,编程模型完全与 C 语言编程不一样。官方文档仓库中包含一个单页面的语言手册 GLSLangSpec.4.60.html。


另一个问题是,**顶点着色器**与**片断着色器**是两个 GPU 程序,虽然在 Godot 中使用同一个着色器代码文件。这两个着色器会在 GPU 渲染流水线上依工序执行处理,为了将顶点着色器的数据传递给片断着色器,GPU 编程中提供了 varying 变量,


GLSL 的一大特色就是有各种修饰符号来决定变量的使用规则,参考修饰符说明。uniformvarying 很大不同在于,前者在处理一个基本体的过程中是不会改变的,用来链接 CPU 运行的程序与 GPU 运行的着色器。后者则更像是一般的 C 语言变量,可以在着色器程序中改变它的值,并实现不同着色阶段中的数据传递。


存储修饰符 Storage Qualifiers [4.3]

  •  `none` (Default) local read/write memory, or input parameter.

  •  `const` Compile-time constant, or read-only function parameter.

  •  `attribute` Linkage between a vertex shader and OpenGL ES for per-vertex data.

  •  `uniform` Value does not change across the primitive being processed, uniforms form the linkage between a shader, OpenGL ES, and the application.

  •  `varying` Linkage between a vertex shader and fragment shader for interpolated data.

参数修饰符 Parameter Qualifiers [4.4]

  •  `none` (Default) same as `in`.

  •  `in` For function parameters passed into a function.

  •  `out` For function parameters passed back out of a function, but not initialized for use when passed in.

  •  `inout` For function parameters passed both into and out of a function.

输入参数在调用函数时复制传入,输出参数在函数返回时复制输出值。其它修饰符参考官方手册。


朗伯照明理想镜面散射光模型,表面不吸收任何入射光,Lambertian lighting model 实现如下,Godot着色器中提供了光照函数定义,通过修改这个函数可以实现自己的光照效果:

在主光源光强度为 1.0 的条件下,ALBEDO 纹理会按以上算法与法线、入射光方向,衰减系数等因素下生成模型表面的漫射光效果,通过调整基本色输入来调整最终效果。

修正法线后的正确光照

从输出效果可以看到,由于纹理使用的是 8-bit 颜色深度,精度不足导致“等高线现象”,可以降低 Bump Strength 属性减缓,更好的办法是使用 EXR、TTF、TGA 等支持 16-bit 以上的图像格式。

顶点着色器与法线处理代码参考:


🟠🔵 参考

  • 本文代码请自取 https://github.com/Jeangowhy/Godot-Tour/tree/main/Particles

  • [The OpenGL® Shading Language, v4.60.7] https://github.com/KhronosGroup/OpenGL-Registry/tree/main/specs/gl/GLSLangSpec.4.60.html

  • [Inigo Quilez - 2D distance functions] https://iquilezles.org/articles/distfunctions2d/

  • [Your 1st 3D shader] https://docs.godotengine.org/en/latest/tutorials/shaders/your_first_shader/your_first_3d_shader.html

  • [Your 2nd 3D shader] https://docs.godotengine.org/en/latest/tutorials/shaders/your_first_shader/your_second_3d_shader.html

  • [Multiple resolutions] https://docs.godotengine.org/en/latest/tutorials/rendering/multiple_resolutions.html

  • [Setting up the game area] https://docs.godotengine.org/en/stable/getting_started/first_3d_game/01.game_setup.html

  • [Matlab Help - Mathematical Functions] https://ww2.mathworks.cn/help/symbolic/mathematical-functions.html

  • [The OpenGL Shading Language] https://www.khronos.org/opengl/wiki/OpenGL_Shading_Language

  • [The Book of Shaders by Patricio Gonzalez Vivo & Jen Lowe] https://thebookofshaders.com/?lan=ch

  • [Shaders Programming] https://docs.godotengine.org/en/3.5/tutorials/shaders/index.html

  • [Shading language] https://docs.godotengine.org/en/3.5/tutorials/shaders/shader_reference/shading_language.html

  • [Godot Shaders] https://docs.godotengine.org/en/3.5/tutorials/shaders/index.html

  • [Particle shaders] https://docs.godotengine.org/en/3.5/tutorials/shaders/shader_reference/particle_shader.html

  • [Converting GLSL to Godot shaders] https://docs.godotengine.org/en/3.5/tutorials/shaders/converting_glsl_to_godot_shaders.html

  • [Sprite Shaders] https://github.com/godotengine/godot-demo-projects/tree/master/2d/sprite_shaders

  • [Screen Space Shaders] https://github.com/godotengine/godot-demo-projects/tree/master/2d/screen_space_shaders

  • [Voronoi and Worley  cellular) noise - Godot Shaders] https://godotshaders.com/snippet/voronoi/

  • [smoothstep desmos] http://www.desmos.com/calculator/309w4rkmpe

  • [双曲函数基础知识 by Coston] https://zhuanlan.zhihu.com/p/363616604

  • [Inigo Quilez - 3D distance functions] https://iquilezles.org/articles/distfunctions

  • [Ray Marching and Signed Distance Functions] https://jamie-wong.com/2016/07/15/ray-marching-signed-distance-functions/

  • [傅里叶级数、傅里叶变换与频谱 by Eugene Khutoryansky] https://www.bilibili.com/video/BV1sS4y1G7WF

  • [Spatial Material] https://docs.godotengine.org/en/3.5/tutorials/3d/spatial_material.html

  • [Principled BSDF] https://docs.blender.org/manual/en/latest/render/shader_nodes/shader/principled.html

  • [Physically Based Rendering: From Theory to Implementation, Third Edition] http://www.pbr-book.org/3ed-2018/contents.html

  • [基于物理的渲染(PBR)白皮书 - 毛星云] https://zhuanlan.zhihu.com/p/53086060


Godot Shader 地形材质原理与着色器编程的评论 (共 条)

分享到微博请遵守国家法律