如何在窗口式光线追踪器中实时渲染每条扫描线?
使用SDL3实现光线追踪器实时扫描线渲染
需要用到的SDL3核心功能
- SDL初始化与清理:
SDL_Init(SDL_INIT_VIDEO)初始化视频子系统,SDL_Quit()退出时释放资源 - 窗口与渲染器:
SDL_CreateWindow()创建显示窗口,SDL_CreateRenderer()创建硬件加速渲染器 - 纹理操作:
SDL_CreateTexture()创建可更新的纹理(存储像素数据),SDL_UpdateTexture()更新纹理的指定区域(单条扫描线) - 渲染与显示:
SDL_RenderCopy()将纹理绘制到渲染器,SDL_RenderPresent()刷新窗口显示 - 事件处理:
SDL_PollEvent()处理窗口关闭等事件,避免程序无响应
具体实现步骤与代码示例
1. 提前初始化SDL资源
在渲染前完成SDL的初始化、窗口和纹理创建,把这些逻辑整合到Camera类中:
#include <SDL3/SDL.h> #include <vector> #include <cstdio> #include <cstdlib> // 假设你的Color是vec3结构,分量为0~1的浮点数 struct Color { double r, g, b; Color& operator+=(const Color& other) { r += other.r; g += other.g; b += other.b; return *this; } }; // 假设Ray和相关函数已定义 struct Ray {}; Ray Camera::get_ray(uint32_t i, uint32_t j) { /* 原有实现 */ } Color ray_color(const Ray& r) { /* 原有实现 */ } class Camera { private: uint32_t _img_width; uint32_t _img_height; SDL_Window* _window = nullptr; SDL_Renderer* _renderer = nullptr; SDL_Texture* _texture = nullptr; // 将Color转换为SDL兼容的RGBA像素(RGBA8888格式) uint32_t color_to_rgba(const Color& c) { // 可选:添加gamma校正(比如sqrt),匹配原有write_color的逻辑 uint8_t r = static_cast<uint8_t>(255.999 * clamp(c.r, 0.0, 1.0)); uint8_t g = static_cast<uint8_t>(255.999 * clamp(c.g, 0.0, 1.0)); uint8_t b = static_cast<uint8_t>(255.999 * clamp(c.b, 0.0, 1.0)); uint8_t a = 255; // 不透明通道 return SDL_MapRGBA(SDL_PIXELFORMAT_RGBA8888, r, g, b, a); } // 颜色分量钳制函数 double clamp(double x, double min, double max) { if (x < min) return min; if (x > max) return max; return x; } public: Camera(uint32_t width, uint32_t height) : _img_width(width), _img_height(height) { // 初始化SDL视频子系统 if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "SDL初始化失败: %s\n", SDL_GetError()); exit(1); } // 创建窗口 _window = SDL_CreateWindow("光线追踪实时渲染", _img_width, _img_height, 0); if (!_window) { fprintf(stderr, "创建窗口失败: %s\n", SDL_GetError()); SDL_Quit(); exit(1); } // 创建硬件加速渲染器 _renderer = SDL_CreateRenderer(_window, nullptr, SDL_RENDERER_ACCELERATED); if (!_renderer) { fprintf(stderr, "创建渲染器失败: %s\n", SDL_GetError()); SDL_DestroyWindow(_window); SDL_Quit(); exit(1); } // 创建可流式更新的纹理 _texture = SDL_CreateTexture(_renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, _img_width, _img_height); if (!_texture) { fprintf(stderr, "创建纹理失败: %s\n", SDL_GetError()); SDL_DestroyRenderer(_renderer); SDL_DestroyWindow(_window); SDL_Quit(); exit(1); } } // 析构时自动清理资源 ~Camera() { SDL_DestroyTexture(_texture); SDL_DestroyRenderer(_renderer); SDL_DestroyWindow(_window); SDL_Quit(); }
2. 修改render函数为实时扫描线渲染
替换原有输出PPM的逻辑,改为每渲染完一行就更新纹理并刷新窗口:
void render() { SDL_Event event; bool running = true; for (uint32_t j = 0; j < _img_height && running; ++j) { // 存储当前行的RGBA像素数据 std::vector<uint32_t> row_pixels(_img_width); // 渲染当前行的所有像素 for (uint32_t i = 0; i < _img_width; ++i) { Color pixel_color; Ray r = get_ray(i, j); pixel_color += ray_color(r); row_pixels[i] = color_to_rgba(pixel_color); } // 更新纹理的第j行区域 SDL_UpdateTexture(_texture, &SDL_Rect{0, static_cast<int>(j), static_cast<int>(_img_width), 1}, row_pixels.data(), _img_width * sizeof(uint32_t)); // 将纹理渲染到窗口并刷新显示 SDL_RenderCopy(_renderer, _texture, nullptr, nullptr); SDL_RenderPresent(_renderer); // 处理窗口事件(比如关闭窗口) while (SDL_PollEvent(&event)) { if (event.type == SDL_EVENT_QUIT) { running = false; } } } } };
关键注意点
- 颜色转换:确保
Color的浮点数分量正确映射到0~255的uint8_t,如果原有ray_color返回高动态范围值,记得添加gamma校正(比如对分量开平方),避免画面过暗 - 纹理格式:示例中使用
RGBA8888是SDL通用格式,也可换成RGB888,但要同步修改SDL_MapRGBA和纹理创建的格式参数 - 性能优化:如果单线程渲染过慢,后续可扩展多线程渲染多行后批量更新纹理;
SDL_TEXTUREACCESS_STREAMING适合频繁更新的场景 - 事件处理:必须在循环中处理事件,否则操作系统会判定窗口无响应并强制关闭
内容的提问来源于stack exchange,提问作者Niccolò Tiezzi
相关产品推荐
相关产品推荐

