You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SFML中为各图形位置应用Shader?解决uniform数量限制

解决SFML鼠标轨迹发光的替代方案

针对你遇到的uniform数量限制问题,这里提供两种更高效且无uniform上限限制的实现方式:

方案一:后处理模糊实现发光(最推荐)

核心思路是先绘制鼠标轨迹到离屏缓冲,再对缓冲应用模糊滤镜,最后将模糊后的发光效果与原轨迹叠加,模拟发光效果。这种方法无需传递大量顶点位置到Shader,实现简单且性能稳定。

修改后的代码实现

#include <SFML/Graphics.hpp>
#include <boost/circular_buffer.hpp>

int main()
{
    const float winW = 1000;
    const float winH = 1000;
    const int tail_length = 100;
    sf::RenderWindow window(sf::VideoMode(winW, winH), "SFML Glow Trail");

    sf::VertexArray lines(sf::LinesStrip, tail_length);
    boost::circular_buffer<sf::Vector2i> tail(tail_length);

    // 创建离屏渲染纹理,用于绘制轨迹和模糊效果
    sf::RenderTexture trailTexture;
    trailTexture.create(winW, winH);
    trailTexture.clear(sf::Color::Transparent);

    // 高斯模糊Shader(片段Shader)
    sf::Shader blurShader;
    blurShader.loadFromFile("blur.glsl", sf::Shader::Fragment);
    blurShader.setUniform("texture", sf::Shader::CurrentTexture);
    blurShader.setUniform("resolution", sf::Vector2f(winW, winH));
    blurShader.setUniform("blurRadius", 8.0f); // 模糊半径,控制发光强度

    sf::Vector2i old_mouse_pos = sf::Vector2i(0, 0);

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        sf::Vector2i mousePos = sf::Mouse::getPosition(window);
        if (old_mouse_pos != mousePos)
            tail.push_back(mousePos);

        old_mouse_pos = mousePos;

        // 更新轨迹顶点数据
        for (int i = 0; i < tail.size(); i++)
        {
            lines[i].position = sf::Vector2f(tail[i].x, tail[i].y);
            lines[i].color = sf::Color(255, 0, 0, i * 255 / tail_length);
        }

        // 1. 绘制轨迹到离屏纹理
        trailTexture.clear(sf::Color::Transparent);
        trailTexture.draw(lines);
        trailTexture.display();

        // 2. 应用模糊Shader到轨迹纹理
        sf::Sprite blurSprite(trailTexture.getTexture());
        sf::RenderTexture blurTexture;
        blurTexture.create(winW, winH);
        blurTexture.clear(sf::Color::Transparent);
        blurTexture.draw(blurSprite, &blurShader);
        blurTexture.display();

        // 3. 绘制最终效果:先绘制模糊的发光层,再绘制原轨迹
        window.clear();
        window.draw(sf::Sprite(blurTexture.getTexture()));
        window.draw(lines);
        window.display();
    }
}

对应的模糊Shader(blur.glsl)

uniform sampler2D texture;
uniform vec2 resolution;
uniform float blurRadius;

void main() {
    vec2 uv = gl_FragCoord.xy / resolution.xy;
    vec4 color = vec4(0.0);
    float blurStep = 1.0 / resolution.y; // 垂直方向模糊步长,可改为双向模糊

    // 简单的高斯模糊近似(可优化为双向模糊提升效果)
    for (float i = -blurRadius; i <= blurRadius; i++) {
        color += texture2D(texture, uv + vec2(0.0, i * blurStep));
    }
    color /= (blurRadius * 2.0 + 1.0);

    // 增强发光亮度
    color *= 2.0;
    gl_FragColor = color;
}

方案二:用纹理存储轨迹点数据

将轨迹点的坐标编码到纹理的像素通道中,Shader通过采样纹理获取所有轨迹点,避免使用uniform数组。这种方法适合需要精确控制每个点发光强度的场景。

实现思路

  1. 创建一个纹理(比如100x1的大小,对应轨迹长度),每个像素的RG通道存储一个轨迹点的归一化坐标(x坐标存R通道,y坐标存G通道,范围0-1)。
  2. 每次更新轨迹时,将新的坐标归一化后写入纹理对应像素。
  3. 在片段Shader中遍历纹理的所有像素,读取轨迹点并计算发光效果。

关键代码片段

// 初始化轨迹纹理
sf::Texture trailPointTexture;
trailPointTexture.create(tail_length, 1);
sf::Uint8* pixels = new sf::Uint8[tail_length * 4]; // RGBA格式
memset(pixels, 0, tail_length * 4);
trailPointTexture.update(pixels);
delete[] pixels;

// 更新轨迹时写入纹理
sf::Uint8* updatePixels = new sf::Uint8[tail_length * 4];
for (int i = 0; i < tail.size(); i++) {
    // 归一化坐标到0-255范围
    updatePixels[i*4] = static_cast<sf::Uint8>(tail[i].x / winW * 255);
    updatePixels[i*4+1] = static_cast<sf::Uint8>(tail[i].y / winH * 255);
    updatePixels[i*4+2] = 0;
    updatePixels[i*4+3] = 255;
}
trailPointTexture.update(updatePixels);
delete[] updatePixels;

// 设置Shader参数
shader.setUniform("trailTexture", trailPointTexture);
shader.setUniform("trailLength", static_cast<float>(tail.size()));

对应的Shader修改

uniform sampler2D trailTexture;
uniform vec2 resolution;
uniform float trailLength;

void main() {
    vec2 p = gl_FragCoord.xy / resolution.xy;
    float d = 0.0;
    float textureWidth = trailLength;

    for (float i = 0.0; i < trailLength; i++) {
        // 采样纹理获取轨迹点坐标
        vec2 texUV = vec2((i + 0.5)/textureWidth, 0.5);
        vec2 point = texture2D(trailTexture, texUV).rg;
        point.x *= resolution.x;
        point.y *= resolution.y;

        vec2 q = p - point / resolution;
        float dist = pow(1.0/length(q) * 0.1, 1.5);
        d += dist;
    }

    vec3 col = d * vec3(1.0, 0.5, 0.25);
    gl_FragColor = vec4(1.0 - exp(-col), 1.0);
}

方案对比

  • 后处理模糊方案:实现简单,性能稳定,适合大多数发光效果需求,无需修改原有的轨迹绘制逻辑。
  • 纹理存储轨迹点方案:更灵活,能精确控制每个点的发光参数,但需要额外处理纹理数据的更新,代码复杂度稍高。

内容的提问来源于stack exchange,提问作者nowox

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 05:15:31