概述
shaders/gpu_depth.glsl 是 Terrain3D 插件中的一个 GLSL 空间着色器,主要用于从屏幕深度纹理中读取绝对深度,并将其编码到 Albedo 的 RGB 通道中。它的核心流程如下:
- 读取深度纹理,获取当前像素的深度值。
- 根据渲染器类型(兼容模式)调整深度值。
- 将屏幕坐标和深度值转换为视空间坐标,得到线性深度。
- 将线性深度归一化到 0-1 范围。
- 用特定的编码方式将归一化深度分布到 RGB 三个通道,避免兼容性和移动端的精度损失。
- 最终输出编码后的深度到 ALBEDO,用于后续处理或特定渲染需求。
该文件常用于需要在着色器中获取和处理绝对深度信息的场景。
完整代码
shader_type spatial;
render_mode unshaded;
uniform highp sampler2D depth_texture : source_color, hint_depth_texture, filter_nearest, repeat_disable;
void fragment() {
float depth = textureLod(depth_texture, SCREEN_UV, 0.).x;
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
depth = depth * 2.0 - 1.0;
#endif
vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
view.xyz /= view.w;
float depth_linear = -view.z;
// Normalize depth to the range 0 - 1
highp float scaledDepth = clamp(depth_linear / 100000.0, 0.0, 1.0);
// Encode using 127 steps, which map to the 128 - 255 range.
// Avoids precision loss for compatability and mobile renderer
// 21bit depth value
highp float r = (floor(scaledDepth * 127.0) + 128.0) / 255.0;
highp float g = (floor(fract(scaledDepth * 127.0) * 127.0) + 128.0) / 255.0;
highp float b = (floor(fract(scaledDepth * 127.0 * 127.0) * 127.0) + 128.0) / 255.0;
ALBEDO = vec3(r, g, b); // Return encoded value, required for Mobile & Compatibility renderers
}逐行解读
下面是对 src/shaders/gpu_depth.glsl 文件第 7 行及其后主要内容的逐行解读:
-
shader_type spatial;声明该着色器类型为空间(spatial),用于 3D 场景渲染。 -
render_mode unshaded;设置渲染模式为无光照,着色器输出不会受光照影响。 -
uniform highp sampler2D depth_texture : source_color, hint_depth_texture, filter_nearest, repeat_disable;声明一个高精度的二维纹理采样器,用于读取屏幕深度纹理,且采用最近点采样和禁用重复。
Note
详细解释:
uniform:声明一个全局一致变量,表示该变量的值在所有片元/顶点中是相同的,由外部(如引擎或主程序)传入。highp:指定变量的精度为高精度(high precision),通常用于需要高精度计算的场景,比如深度值。sampler2D:表示一个二维纹理采样器,用于在着色器中读取 2D 纹理数据。depth_texture:变量名,表示这个采样器用于采样深度纹理。- 冒号后面的内容是 Godot 着色器的注解,用于指定纹理的用途和采样方式:
source_color:指定该纹理作为颜色源(在这里实际是深度源)。hint_depth_texture:提示该纹理是深度纹理,可能影响采样方式和优化。filter_nearest:使用最近点采样(Nearest Neighbor),不会做插值,适合深度等精度要求高的纹理。repeat_disable:禁用纹理重复(wrap),超出范围时不会重复采样。
注解是否可以理解为选项,使用与变量类型不相符的注解是否有问题?
可以理解为“选项”或“提示”,用于告诉引擎如何处理该变量。 如果注解与变量类型不符,可能导致着色器编译错误、运行异常或引擎忽略该注解。建议严格按照文档和变量类型使用对应的注解。
-
void fragment() { ... }片元着色器主函数,处理每个像素的渲染逻辑。float depth = textureLod(depth_texture, SCREEN_UV, 0.).x;读取当前像素的深度值。
Note
textureLod是 GLSL 的一个采样函数,用于从纹理中获取指定 LOD(细节层级)的像素值。depth_texture是传入的深度纹理采样器(sampler2D),通常存储了场景的深度信息。SCREEN_UV是当前像素在屏幕上的归一化坐标(范围 0~1),用于定位采样点。0.指定 LOD 层级为 0,表示使用最高分辨率的纹理数据。.x取采样结果的第一个分量(通常深度纹理只有一个分量,存储在 r 通道)。综合起来,这行代码的作用是: 在当前屏幕坐标下,从深度纹理的最高分辨率层级采样,获取该像素的深度值,并存储到
depth变量中。
Note
NDC(Normalized Device Coordinates,归一化设备坐标)是图形渲染管线中的一种坐标系。经过投影变换和齐次除法后,三维顶点会被映射到 NDC 空间,其范围通常是 x、y、z 都在[-1, 1] 之间。NDC 用于后续的视口变换,将场景内容映射到屏幕像素坐标。
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY ... #endif针对兼容渲染器调整深度值。
Note
这三行代码用于兼容不同渲染器的深度值范围:
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY:如果当前渲染器是兼容模式(如 OpenGL 等)。depth = depth * 2.0 - 1.0;:将深度值从[0, 1] 区间转换到[-1, 1] 区间,因为某些渲染器(如 OpenGL)NDC 深度范围是[-1, 1],而其他(如 DirectX)是[0, 1]。#endif:条件编译结束。这样可以保证后续的深度计算在不同渲染器下都能得到正确的结果。
vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);将屏幕坐标和深度值转换为归一化设备坐标。
Note
这行代码将屏幕空间的 UV 坐标(
SCREEN_UV,范围为 0 到 1)转换为 NDC(归一化设备坐标,范围为 -1 到 1),并将深度值作为 z 分量:
SCREEN_UV * 2.0 - 1.0:将 UV 从 [0,1] 映射到 [-1,1],得到 NDC 的 x 和 y 分量。depth:采样得到的深度值,作为 NDC 的 z 分量。vec3(ndc_x, ndc_y, depth):组合成三维向量,表示当前像素在 NDC 空间的位置。
vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);通过逆投影矩阵将 NDC 坐标转换为视空间坐标。
Note
INV_PROJECTION_MATRIX是逆投影矩阵(Inverse Projection Matrix),通常用于将标准化设备坐标(NDC)转换回视空间(View Space)坐标。这里不是做视口变换,而是将 NDC 坐标(
ndc)通过逆投影矩阵变换回视空间坐标。这样可以从深度纹理还原出场景中的实际深度值。这个过程常用于屏幕空间效果(如屏幕空间反射、深度重建等)。
Note
视空间(View Space)是指相机(观察者)为中心的三维坐标系,所有物体的位置都以相机为参考点。通常,经过模型变换和世界变换后,物体会被变换到视空间。
屏幕空间(Screen Space)是指最终渲染到屏幕上的二维像素坐标系,通常范围是[0, 0] 到[屏幕宽, 屏幕高]。
两者不是一个东西。视空间是三维的、以相机为中心;屏幕空间是二维的、以屏幕像素为单位。它们之间还会经过投影变换和归一化设备坐标(NDC)等步骤。
view.xyz /= view.w;齐次坐标归一化。float depth_linear = -view.z;得到线性深度值。
Note
depth_linear表示线性视空间(View Space)下的深度值。 具体来说,view是通过逆投影矩阵将屏幕空间的 NDC 坐标(包含深度)变换回视空间得到的四维向量。view.z是视空间下的 z 坐标,取负号后得到从摄像机到像素点的距离(通常为正值),即线性深度。
highp float scaledDepth = clamp(depth_linear / 100000.0, 0.0, 1.0);将线性深度归一化到 0-1 范围。highp float r = (floor(scaledDepth * 127.0) + 128.0) / 255.0;highp float g = (floor(fract(scaledDepth * 127.0) * 127.0) + 128.0) / 255.0;highp float b = (floor(fract(scaledDepth * 127.0 * 127.0) * 127.0) + 128.0) / 255.0;用特定方式将归一化深度编码到 RGB 三个通道,提升兼容性和精度。
Note
这几行代码的作用是将线性深度值归一化到 0 到 1 之间,并将其编码为 RGB 三个通道,以便在移动端和兼容性渲染器中以较高精度存储和传递深度信息。 具体过程如下:
scaledDepth:将线性深度除以最大深度(这里是 100000.0),并限制在 0 到 1 之间。r,g,b:将归一化后的深度分三步编码到 RGB,每个通道用 127 个等级,最终映射到 128-255 的范围,避免精度损失。(本质是转成 3 位变相的 128 进制数) 这样可以用颜色值间接表达高精度的深度信息。fract 是 GLSL 中的一个内置函数,用于返回一个数值的小数部分。 例如,fract(2.75) 返回 0.75。 常用于提取浮点数的小数部分,丢弃整数部分。
Note
这三行代码的作用是将归一化后的深度值
scaledDepth编码为 RGB 三个通道,每个通道用 7 位(0-127),最终拼成 21 位的深度精度。举个例子:假设
scaledDepth = 0.5,计算过程如下:
r 通道
r = (floor(0.5 * 127.0) + 128.0) / 255.00.5 * 127.0 = 63.5floor(63.5) = 6363 + 128 = 191191 / 255.0 ≈ 0.749g 通道
g = (floor(fract(0.5 * 127.0) * 127.0) + 128.0) / 255.00.5 * 127.0 = 63.5fract(63.5) = 0.50.5 * 127.0 = 63.5floor(63.5) = 6363 + 128 = 191191 / 255.0 ≈ 0.749b 通道
b = (floor(fract(0.5 * 127.0 * 127.0) * 127.0) + 128.0) / 255.00.5 * 127.0 = 63.563.5 * 127.0 = 8064.5fract(8064.5) = 0.50.5 * 127.0 = 63.5floor(63.5) = 6363 + 128 = 191191 / 255.0 ≈ 0.749所以,
scaledDepth = 0.5时,RGB 都大约是 0.749。 这样编码可以把一个 0~1 的浮点深度值拆成 3 个 7 位整数,分别存到 r、g、b,便于在 8 位通道中还原较高精度的深度。这里每个通道只用 7 位(0
127),是为了兼容移动端和部分兼容性渲染器。 因为有些平台在处理颜色时,最低位精度可能会丢失(比如只保证高 7 位有效),如果用满 8 位,最低位可能不可靠,导致还原深度时出现误差。 所以这里把编码范围限制在 128255(高 7 位),避免用到低位,保证跨平台精度一致。 可以这样还原出原始的scaledDepth值(21 位精度):// r, g, b 分别为 0~1 的 float,解码时先还原到 128~255 的整数 float r_int = r * 255.0 - 128.0; float g_int = g * 255.0 - 128.0; float b_int = b * 255.0 - 128.0; // 还原 scaledDepth float scaledDepth = (r_int + g_int / 127.0 + b_int / (127.0 * 127.0)) / 1.0;这样可以近似还原编码前的
scaledDepth,精度为 21 位。以编码值 r = g = b ≈ 0.749 为例,还原过程如下:
- 先还原整数值:
r_int = r * 255.0 - 128.0 ≈ 0.749 * 255.0 - 128.0 ≈ 191 - 128 = 63 g_int = g * 255.0 - 128.0 ≈ 63 b_int = b * 255.0 - 128.0 ≈ 63
- 还原 scaledDepth:
scaledDepth = (r_int + g_int / 127.0 + b_int / (127.0 * 127.0)) / 1.0 ≈ (63 + 63 / 127.0 + 63 / 16129.0) ≈ (63 + 0.496 + 0.0039) ≈ 63.4999
- 由于编码时是 scaledDepth * 127,解码时要除以 127:
scaledDepth = 63.4999 / 127.0 ≈ 0.5所以可以准确还原出原始的 0.5。
ALBEDO = vec3(r, g, b);输出编码后的深度到 Albedo 通道,用于后续处理或特定渲染需求。
Note
上述坐标转换的流程详细可以参考 坐标变换。