如何在Rust Nannou中为阴影投射的可见区域实现径向渐变
我已经实现了一套阴影投射算法,效果如下:
现在我想给可见区域(非阴影区域)添加径向渐变效果,模拟真实光源的衰减特性:靠近光标(光源位置)的区域更亮,远离的可见区域则更暗。
我能获取到的输入有两部分:
- 可见区域:由
Vec<Point2>表示,是按从光源到顶点的角度递增排序的有序顶点向量 - 光源位置:由
Point2表示
作为OpenGL和Rust Nannou的新手,我尝试过用Nannou的points_colored函数,但效果异常;也看过nannou示例里的draw/draw_mesh.rs,感觉相关但搞不懂三角形剖分的概念。想请教:
- 怎么在Nannou里实现径向渐变?为什么需要创建三角形(或四边形)?
一、为什么需要三角形/四边形?
OpenGL(包括Nannou底层依赖的渲染管线)本质是基于三角形图元渲染复杂形状的。所有2D/3D图形最终都会被拆解成三角形网格——因为三角形是最稳定的平面图形,能保证渲染时的一致性和正确性。你的可见区域是一个多边形,要给它填充渐变颜色,必须先把这个多边形剖分成多个三角形(四边形最终也会拆成两个三角形),这样才能给每个顶点赋予颜色,让GPU自动插值出平滑的渐变效果。
二、Nannou中实现径向渐变的步骤
1. 把可见多边形剖分成三角形扇
你的顶点已经按从光源出发的角度递增排序,刚好可以直接构建三角形扇:以光源为中心顶点,依次连接相邻的两个可见区域顶点,形成一个个三角形。比如顶点序列是P0, P1, P2, ..., Pn,那么三角形就是[光源, P0, P1]、[光源, P1, P2]……直到[光源, Pn-1, Pn]。
2. 给每个顶点计算渐变颜色
径向渐变的核心是:顶点离光源越近,颜色越亮;越远越暗。你可以按以下逻辑计算每个顶点的颜色:
- 定义基础亮色(比如白色
rgb(1.0,1.0,1.0))和暗色(比如深灰rgb(0.2,0.2,0.2)) - 计算每个可见顶点到光源的距离
distance = point.distance(light_pos) - 设定一个最大衰减距离
max_distance(比如屏幕对角线长度,根据你的窗口大小调整) - 计算衰减系数
alpha = 1.0 - (distance / max_distance).clamp(0.0, 1.0)(确保系数在0-1之间) - 最终顶点颜色 = 亮色 * alpha + 暗色 * (1.0 - alpha)
- 光源本身的顶点直接设为最亮的颜色
3. 用Nannou的Mesh绘制带颜色的三角形扇
直接用MeshBuilder构建带颜色的网格,示例代码如下:
use nannou::prelude::*; fn draw_gradient_visible_area(draw: &Draw, light_pos: Point2, visible_points: &[Point2]) { // 可根据窗口大小调整最大衰减距离 let max_distance = 500.0; let light_color = WHITE; let dark_color = rgb(0.2, 0.2, 0.2); let mut mesh = Mesh::new(); // 添加光源顶点,颜色设为最亮 mesh.add_vertex(Vertex { position: light_pos, color: light_color, tex_coords: Default::default(), }); for (i, &point) in visible_points.iter().enumerate() { let next_point = visible_points[(i + 1) % visible_points.len()]; // 计算当前顶点的颜色 let dist = point.distance(light_pos); let alpha = 1.0 - (dist / max_distance).clamp(0.0, 1.0); let point_color = light_color * alpha + dark_color * (1.0 - alpha); // 计算下一个顶点的颜色 let next_dist = next_point.distance(light_pos); let next_alpha = 1.0 - (next_dist / max_distance).clamp(0.0, 1.0); let next_point_color = light_color * next_alpha + dark_color * (1.0 - next_alpha); // 添加当前点和下一个点的顶点到网格 let p1_idx = mesh.add_vertex(Vertex { position: point, color: point_color, tex_coords: Default::default(), }); let p2_idx = mesh.add_vertex(Vertex { position: next_point, color: next_point_color, tex_coords: Default::default(), }); // 添加三角形:光源 -> 当前点 -> 下一个点 mesh.add_triangle(0, p1_idx, p2_idx); } draw.mesh(&mesh); }
4. 替代方案:自定义着色器
如果觉得三角形剖分麻烦,也可以用自定义片段着色器:先绘制可见区域的蒙版,然后在片段着色器中根据每个像素到光源的距离直接计算颜色。不过对于新手来说,三角形扇的方法更直观,不需要编写GLSL代码。
内容的提问来源于stack exchange,提问作者Enigmatisms

