使用opengl着色器实现光源效果

⌚Time: 2024-12-22 23:09:00

👨‍💻Author: Jack Ge

有的2d地图要实现黑夜效果,在人物周围有光源实现部分可见。就需要借助glsl着色器语言。利用显卡绘制光源效果。

在片段着色器里,需要定义光源半径,中心位置,还有绘制的纹理。根据像素点偏离中心的距离,计算像素点的颜色亮度。

shader.txt

#version 330 core

// 定义常量
uniform float in_radius; // 半径
uniform vec2 in_lightcenter;//光亮中心位置
uniform sampler2D in_texture;//纹理

void main()
{
        float distance = length( gl_TexCoord[0].xy -in_lightcenter);
        //当前像素颜色
    vec4 color = texture2D(in_texture, gl_TexCoord[0].xy);
    // 根据距离判断是否在圆形区域内,并计算颜色
    if(distance<in_radius){
        //简单的计算偏离光源中心的位置,用于弱化亮度
    float a = distance/in_radius;
            //在圆形区域内,亮度是原来的1.0,但是随着偏离逐渐变暗
            gl_FragColor = vec4(1.0-0.9*a,1.0-0.9*a,1.0-0.9*a, 1.0)*color;
    }
    else
    {
        // 在圆形区域外,亮度减少到原来的0.08
        gl_FragColor = vec4(0.08,0.08,0.08, 1.0)*color;
    }
}

在glsl里,gl_TexCoord[0]就是片段的纹理坐标,gl_FragCoord.xy / in_resolution.xy,gl_FragCoord.xy是当前片段的窗口坐标,而 in_resolution.xy是外部输入的变量表示当前窗口的分辨率,通过除以窗口的分辨率,将当前片段坐标规范化到0到1的范围内,方便对纹理或屏幕空间进行采样或计算。

使用sfml实现程序,首先就定义了白色背景和两个三角形。从shader.txt加载片段着色器,设置输入的变量。

在主循环中进行绘制。不断更新光源位置,将图像绘制在sf::RenderTexture里,然后使用片段着色器绘制到窗口。


#include <SFML/Graphics.hpp>
#include <iostream>

int main()
{
    const int WIDTH = 800;
    const int HEIGHT = 600;

    sf::RenderWindow window(sf::VideoMode(WIDTH, HEIGHT), "a");
    window.setFramerateLimit(30);

    //时钟
    sf::Clock clock;

    //白色背景
    sf::VertexArray bg(sf::Quads,4);
    bg[0].position = sf::Vector2f(0.f,0.f);
    bg[1].position = sf::Vector2f(0.f,600.f);
    bg[2].position = sf::Vector2f(800.f,600.f);
    bg[3].position = sf::Vector2f(800.f,0.f);
    bg[0].color = sf::Color::White;
    bg[1].color = sf::Color::White;
    bg[2].color = sf::Color::White;
    bg[3].color = sf::Color::White;
    
    // 创建两个三角形
    sf::VertexArray target(sf::Triangles, 6);

    target[0].position = sf::Vector2f(100.f,100.f);
    target[1].position = sf::Vector2f(200.f,500.f);
    target[2].position = sf::Vector2f(300.f,150.f);

    target[3].position = sf::Vector2f(400.f,300.f);
    target[4].position = sf::Vector2f(500.f,200.f);
    target[5].position = sf::Vector2f(600.f,400.f);

    target[0].color = sf::Color::Red;
    target[1].color = sf::Color::Blue;
    target[2].color = sf::Color::Yellow;
    target[3].color = sf::Color::Red;
    target[4].color = sf::Color::Green;
    target[5].color = sf::Color::Blue;

    //创建RenderTexture
    sf::RenderTexture renderTexture;
    renderTexture.create(WIDTH, HEIGHT);
    // 创建一个着色器
    sf::Shader shader;
    if (!shader.loadFromFile("shader.txt", sf::Shader::Fragment))
    {
        std::cout << "Failed to load shader!" << std::endl;
        return -1;
    }
    //设置着色器输入变量
    shader.setUniform("in_texture", sf::Shader::CurrentTexture);//纹理
    shader.setUniform("in_radius", 0.15f);//半径
    
    // 主循环
    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
        }
        //控制光亮位置 简单的在水平方向移动
        if(clock.getElapsedTime().asSeconds()>10){
            clock.restart();
            shader.setUniform("in_lightcenter", sf::Vector2f(clock.getElapsedTime().asSeconds()/10,0.5));
        }else{
            shader.setUniform("in_lightcenter", sf::Vector2f(clock.getElapsedTime().asSeconds()/10,0.5));
        }
        //离屏绘制
        renderTexture.clear(sf::Color::Transparent);
        renderTexture.draw(bg);
        renderTexture.draw(target);
        //绘制到窗体
        window.clear();
        window.draw(sf::Sprite(renderTexture.getTexture()), &shader);
        window.display();
    }

    return 0;
}

其中的

shader.setUniform("in_texture", sf::Shader::CurrentTexture);

是使用了一个特殊的函数重载,这样不需要自己设置纹理,而是它会在绘制时自动设置绘制实体的纹理给目标变量。

效果