Photoshop "Motion Blur" 可以使用 GLSL 吗?
Is Photoshop "Motion Blur" possible with GLSL?
我正在使用 sfml 制作带有精灵的 2D 游戏并尝试获得这种效果:
我在示例中使用了 Photoshop 的 "Motion Blur"。可以看到,效果是有方向性的。
我的游戏使用了 paperdolled 精灵,因此将其作为 post 效果会更容易,而不是模糊每个精灵上的每个设备组合。
是否可以使用着色器来获得这种效果?一个例子将不胜感激。
在线上生成点
手动方面有点,但我可以建议一种方法。
让我们想象一个 圆 ,在确定的 角度 上被一条 线 交叉。在那条线上,我们放置了一些随机的 points.
像这样:
圆半径 100,角度 45º(PI / 4 弧度)和 100 个随机点
然后,无论这些点中的每一个在哪里,我们都会绘制一个带有一些 alpha(透明度)的纹理精灵。结果将如下所示:
改变圆的半径和点数,结果可能不同
进一步说明
在我的例子中,我使用了一个class,它代表了BlurredSprite
。它将保留这些点,然后 sf::Texture
进行绘制。
class BlurredSprite : public sf::Drawable, sf::Transformable {
public:
BlurredSprite(const sf::Texture &t, float radius, float angle, unsigned int points = 30) :
m_texture(t)
{
auto getPointOverLine = [=]{
auto angleOverX = angle - std::_Pi*0.5; // Angle 0 is a vertical line, so I rotate it 45 degrees clockwise (substract)
if (rand() % 2){
angleOverX += std::_Pi; // Half of the points will be on the "first quadrant", the other half over the "third", if you consider (0,0) the center of the circle
}
auto l = radius * ((rand() * 1.f) / RAND_MAX);
auto x = cos(angleOverX) * (l);
auto y = sin(angleOverX) * (l);
return sf::Vector2f(x, y);
};
while (m_points.size() < points){
m_points.push_back(getPointOverLine());
}
}
private:
std::vector<sf::Vector2f> m_points;
sf::Texture m_texture;
};
使用 getPointOverLine
lambda,我在线上创建了一个随机点,但可以使用其他选项来执行此操作。事实上,分配这些点的方式会影响最终结果。
我需要覆盖 draw
方法,以便在 window 上制作此 class Drawable。我还覆盖了 setPosition
方法(来自 sf::Transformable
),因为如果你移动它,你应该移动所有的点。
public:
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const{
auto it = m_points.begin();
while (it != m_points.end()){
sf::Sprite sp(m_texture);
auto col = sp.getColor();
auto rect = sp.getTextureRect();
auto orig = sp.getOrigin() + sf::Vector2f(rect.width, rect.height)*0.5f;
col.a = 25;
sp.setColor(col);
sp.setOrigin(orig);
sp.setPosition(*it);
target.draw(sp);
it++;
}
}
virtual void setPosition(sf::Vector2f pos){
sf::Transformable::setPosition(pos);
for (int i = 0; i < m_points.size(); ++i){
m_points[i] = pos + m_points[i];
}
}
在这里,我 post 我可以在一晚内完成最好的结果。我按照你的要求用着色器做了。我没有实现角度,但不是太难。
这是 cpp 文件的一部分:
...
if (!shader.loadFromFile("dMotionBlur_v05.frag", sf::Shader::Fragment)){
...
window.clear(sf::Color(120,120,120));
// Passing parameters to shader.
shader.setUniform("dir", dir); //direction of blur
shader.setUniform("nSamplesF", (float)std::atoi(argv[3])); // number of samples
shader.setUniform("radius", (float)std::atof(argv[4]) ); //radius of blur
window.draw(sprite, &shader);
window.display();
...
这是片段着色器:
uniform sampler2D u_texture;
uniform float nSamplesF;
uniform float radius;
uniform vec2 dir;
void main(){
vec2 tc = gl_TexCoord[0].xy;
float blur = radius;
float hstep = dir.x;
float vstep = dir.y;
float total = 0.0;
int nSamplesI = int(nSamplesF);
vec4 sum = vec4(0.0);
for (int i=1; i<=nSamplesI; i++){
float floatI = float(i);
float counter = nSamplesF-floatI+1.0;
float p = floatI/nSamplesF;
float tO = (p * 0.1783783784) + 0.0162162162;
total += tO;
sum += texture2D(u_texture, vec2(tc.x - counter*blur*hstep, tc.y - counter*blur*vstep)) * tO;
}
sum += texture2D(u_texture, vec2(tc.x, tc.y)) * 0.2270270270;
for (int i=nSamplesI; i>=1; i--){
float floatI = float(i);
float counter = nSamplesF-floatI+1.0;
float p = floatI/nSamplesF;
float tO = (p * 0.1783783784) + 0.0162162162;
total += tO;
sum += texture2D(u_texture, vec2(tc.x + counter*blur*hstep, tc.y + counter*blur*vstep)) * tO;
}
gl_FragColor = gl_Color * (sum/total);
}
我将 entire code 上传到我的存储库,您可以下载并试用。
您可以设置 x/Y 方向、样本和模糊半径。
X/Y 方向是从 0-1。
您可以使用样本数量。
模糊半径非常敏感,您可以从 0.01
开始尝试
我的例子是:
$ ./sfml-app [x(0-1)] [Y(0-1] [number of sample] [ radius blur]
部分图片:
我正在使用 sfml 制作带有精灵的 2D 游戏并尝试获得这种效果:
我在示例中使用了 Photoshop 的 "Motion Blur"。可以看到,效果是有方向性的。
我的游戏使用了 paperdolled 精灵,因此将其作为 post 效果会更容易,而不是模糊每个精灵上的每个设备组合。
是否可以使用着色器来获得这种效果?一个例子将不胜感激。
在线上生成点
手动方面有点,但我可以建议一种方法。
让我们想象一个 圆 ,在确定的 角度 上被一条 线 交叉。在那条线上,我们放置了一些随机的 points.
像这样:
圆半径 100,角度 45º(PI / 4 弧度)和 100 个随机点
然后,无论这些点中的每一个在哪里,我们都会绘制一个带有一些 alpha(透明度)的纹理精灵。结果将如下所示:
改变圆的半径和点数,结果可能不同
进一步说明
在我的例子中,我使用了一个class,它代表了BlurredSprite
。它将保留这些点,然后 sf::Texture
进行绘制。
class BlurredSprite : public sf::Drawable, sf::Transformable {
public:
BlurredSprite(const sf::Texture &t, float radius, float angle, unsigned int points = 30) :
m_texture(t)
{
auto getPointOverLine = [=]{
auto angleOverX = angle - std::_Pi*0.5; // Angle 0 is a vertical line, so I rotate it 45 degrees clockwise (substract)
if (rand() % 2){
angleOverX += std::_Pi; // Half of the points will be on the "first quadrant", the other half over the "third", if you consider (0,0) the center of the circle
}
auto l = radius * ((rand() * 1.f) / RAND_MAX);
auto x = cos(angleOverX) * (l);
auto y = sin(angleOverX) * (l);
return sf::Vector2f(x, y);
};
while (m_points.size() < points){
m_points.push_back(getPointOverLine());
}
}
private:
std::vector<sf::Vector2f> m_points;
sf::Texture m_texture;
};
使用 getPointOverLine
lambda,我在线上创建了一个随机点,但可以使用其他选项来执行此操作。事实上,分配这些点的方式会影响最终结果。
我需要覆盖 draw
方法,以便在 window 上制作此 class Drawable。我还覆盖了 setPosition
方法(来自 sf::Transformable
),因为如果你移动它,你应该移动所有的点。
public:
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const{
auto it = m_points.begin();
while (it != m_points.end()){
sf::Sprite sp(m_texture);
auto col = sp.getColor();
auto rect = sp.getTextureRect();
auto orig = sp.getOrigin() + sf::Vector2f(rect.width, rect.height)*0.5f;
col.a = 25;
sp.setColor(col);
sp.setOrigin(orig);
sp.setPosition(*it);
target.draw(sp);
it++;
}
}
virtual void setPosition(sf::Vector2f pos){
sf::Transformable::setPosition(pos);
for (int i = 0; i < m_points.size(); ++i){
m_points[i] = pos + m_points[i];
}
}
在这里,我 post 我可以在一晚内完成最好的结果。我按照你的要求用着色器做了。我没有实现角度,但不是太难。
这是 cpp 文件的一部分:
...
if (!shader.loadFromFile("dMotionBlur_v05.frag", sf::Shader::Fragment)){
...
window.clear(sf::Color(120,120,120));
// Passing parameters to shader.
shader.setUniform("dir", dir); //direction of blur
shader.setUniform("nSamplesF", (float)std::atoi(argv[3])); // number of samples
shader.setUniform("radius", (float)std::atof(argv[4]) ); //radius of blur
window.draw(sprite, &shader);
window.display();
...
这是片段着色器:
uniform sampler2D u_texture;
uniform float nSamplesF;
uniform float radius;
uniform vec2 dir;
void main(){
vec2 tc = gl_TexCoord[0].xy;
float blur = radius;
float hstep = dir.x;
float vstep = dir.y;
float total = 0.0;
int nSamplesI = int(nSamplesF);
vec4 sum = vec4(0.0);
for (int i=1; i<=nSamplesI; i++){
float floatI = float(i);
float counter = nSamplesF-floatI+1.0;
float p = floatI/nSamplesF;
float tO = (p * 0.1783783784) + 0.0162162162;
total += tO;
sum += texture2D(u_texture, vec2(tc.x - counter*blur*hstep, tc.y - counter*blur*vstep)) * tO;
}
sum += texture2D(u_texture, vec2(tc.x, tc.y)) * 0.2270270270;
for (int i=nSamplesI; i>=1; i--){
float floatI = float(i);
float counter = nSamplesF-floatI+1.0;
float p = floatI/nSamplesF;
float tO = (p * 0.1783783784) + 0.0162162162;
total += tO;
sum += texture2D(u_texture, vec2(tc.x + counter*blur*hstep, tc.y + counter*blur*vstep)) * tO;
}
gl_FragColor = gl_Color * (sum/total);
}
我将 entire code 上传到我的存储库,您可以下载并试用。 您可以设置 x/Y 方向、样本和模糊半径。 X/Y 方向是从 0-1。 您可以使用样本数量。 模糊半径非常敏感,您可以从 0.01
开始尝试我的例子是:
$ ./sfml-app [x(0-1)] [Y(0-1] [number of sample] [ radius blur]
部分图片: