トップ | puarts.com
ロゴ
「GLSL」に関連する記事一覧
0  

パーリンノイズで歪ませていただけの海シェーダを位相とスペクトルから逆フーリエ変換して波のディストーションマップ、ノーマルマップを生成して歪ませる海シェーダに改良しました。

以前よりは海感のある歪みが出せた気がします。地形のディスプレイメントマップ、ノーマルマップ生成ツールとしても応用できそうな気がしてきました。

実装にあたり、こちらのサイトが参考になりました。

http://david.li/waves/

最近作っていた GLSL 海シェーダに、静的な環境光の反射だけでなく、海に浮かんでいる物体の反射を表現できる機能を追加しました。

物体の反射表現に関しては、騙し騙しの方法なので、色々と試行錯誤が必要で苦労しました。やったことを書いておきます。

  • 海に浮かぶ物体を描画。
  • 海に浮かぶ物体をカメラのY座標、エイムのY座標を反転させて、本物を描画したFBOとは別のFBOに描画(元のFBOは後で深度値を利用するので汚さないため)。このとき、Y座標(高さ)に応じてアルファ値を下げておく(水面に映るときに物体との距離が離れるほど淡い反射になるようにするため)。
  • 反転させて描画した物体をスクリーン座標系でY軸反転。
  • 本物を描画したバッファと同じカラーバッファに海となる平面を描画。このときスクリーン座標系で物体を反転描画したカラーバッファをテクスチャとして、視点から投影マッピングでアルファブレンドではりつける。また、テクスチャをフェッチするときに、頂点シェーダで歪ませた法線を利用してX軸のテクスチャ座標のみ歪ませる(Yを歪ませると本物と物体のYの位置関係がずれて違和感があったため)。

恥ずかしながらシャドウマッピングのプログラムを自前で作ったことがなかったので、glsl で実装してみました。うまくフィルタが調整できていなくてジャギジャギしていますが..

実装にあたり、下記サイトが非常に参考になりました。

http://www.opengl-tutorial.org/jp/intermediate-tutorials/tutorial-16-shadow-mapping/

GLSL で海のシェーダ作りました。
パーリンノイズで平面を歪ませて、あとはキューブマップをいい加減なフレネル反射率ではっつけたらこんな感じになりました。

ちょっとソースコードは載せられるレベルじゃないので、またの機会に..

キューブマップはこちらのサイトからお借りしました。
http://www.humus.name

先ほど GLSL で作った波紋エフェクトの改良バージョンです。

波紋の数を1個から64個に増やして、波紋の生成から消滅までのアニメーションをもう少し現実の波紋に近づけました。
加えて、波紋の生成位置を、入力された音の振幅、高さに関連付けました。
上下が音程に関連し、左右が音量に関連しています。

ごちゃごちゃしてきた上に相変わらず無駄が多いですが、参考までにフラグメントシェーダのコードも載せておきます。


const int kNumVorteces = 64;

uniform sampler2D u_texture;
uniform float u_ripple_strength[kNumVorteces];
uniform float u_ripple_offset[kNumVorteces];
uniform float u_ripple_frequency;
uniform vec2 u_ripple_centers[kNumVorteces];
uniform float u_ripple_sine_disappear_distance[kNumVorteces];

uniform float u_horizontal_sine_distortion_frequency;
uniform float u_horizontal_sine_distortion_width;
uniform float u_horizontal_sine_distortion_offset;

uniform float u_vertical_sine_distortion_frequency;
uniform float u_vertical_sine_distortion_width;
uniform float u_vertical_sine_distortion_offset;

in highp vec2 a_fragment_uv_coord;
out vec4 o_diffuse;

const float PI = 3.1415926;

vec2 GetRippleDistortedUv(
    float ripple_strength,
    float ripple_offset,
    float sine_disappear_distance,
    float distance_source_u,
    float distance_source_v,
    float distort_source_u,
    float distort_source_v,
    float distance_center_x,
    float distance_center_y)
{
    float distance_square = (distance_source_u - distance_center_x) * (distance_source_u - distance_center_x)
        + (distance_source_v - distance_center_y) * (distance_source_v - distance_center_y);
    float distance = sqrt(distance_square);
    float normalized_distance = clamp(distance, 0.0, sine_disappear_distance) / sine_disappear_distance;
    float sine_strength = ripple_strength * (1.0 - normalized_distance) * (1.0 - normalized_distance);
    float theta = sin(ripple_offset + distance * u_ripple_frequency) * sine_strength;
    float u0 = distort_source_u - 0.5;
    float v0 = distort_source_v - 0.5;
    float u1 = u0 * cos(theta) - v0 * sin(theta);
    float v1 = u0 * sin(theta) + v0 * cos(theta);
    float u2 = u1 + 0.5;
    float v2 = v1 + 0.5;
    return vec2(u2, v2);
}

void main(void)
{
    float u = a_fragment_uv_coord.x + sin(a_fragment_uv_coord.y * PI * u_vertical_sine_distortion_frequency
        + u_vertical_sine_distortion_offset) * u_vertical_sine_distortion_width;
    float v = a_fragment_uv_coord.y + sin(a_fragment_uv_coord.x * PI * u_horizontal_sine_distortion_frequency
        + u_horizontal_sine_distortion_offset) * u_horizontal_sine_distortion_width;

    vec2 disorted_uv = vec2(u, v);
    for (int i = 0; i < kNumVorteces; ++i)
    {
        if (u_ripple_sine_disappear_distance[i] == 0.0)
        {
            continue;
        }

        disorted_uv = GetRippleDistortedUv(
            u_ripple_strength[i],
            u_ripple_offset[i],
            u_ripple_sine_disappear_distance[i],
            u, v,
            disorted_uv.x, disorted_uv.y,
            u_ripple_centers[i].x, u_ripple_centers[i].y);
    }
    o_diffuse = texture(u_texture, disorted_uv);
}

glsl で作った音に反応する波紋エフェクトです。

中心点から距離ベースで正弦波を作ると波紋っぽくなります。
入力された音の波形の振幅の大きさと周波数の高さによって、波紋エフェクトの振幅と時間軸のオフセットを変化させることでアニメーションさせています。

距離が中心から離れるほど波紋が減衰するように(1 - 正規化した距離)の2乗を乗算しています。

わかりやすさ重視なので処理に無駄が多いですが、フラグメントシェーダのコードを載せておきます。


uniform sampler2D u_texture;
uniform float u_ripple_strength;
uniform float u_ripple_offset;
uniform float u_ripple_frequency;
uniform float u_ripple_center_uv_x;
uniform float u_ripple_center_uv_y;
uniform float u_ripple_sine_disappear_distance;

in vec2 a_fragment_uv_coord;
out vec4 o_diffuse;

vec2 GetRippleDistortedUv(
    float distance_source_u,
    float distance_source_v,
    float distort_source_u,
    float distort_source_v,
    float distance_center_x,
    float distance_center_y)
{
    float distance_square = (distance_source_u - distance_center_x) * (distance_source_u - distance_center_x)
        + (distance_source_v - distance_center_y) * (distance_source_v - distance_center_y);
    float distance = sqrt(distance_square);
    float sine_disappear_distance = u_ripple_sine_disappear_distance;
    float normalized_distance = clamp(distance, 0.0, sine_disappear_distance) / sine_disappear_distance;
    float sine_strength = u_ripple_strength * (1.0 - normalized_distance) * (1.0 - normalized_distance);
    float theta = sin(u_ripple_offset + distance * u_ripple_frequency) * sine_strength
    float u0 = distort_source_u - 0.5;
    float v0 = distort_source_v - 0.5;
    float u1 = u0 * cos(theta) - v0 * sin(theta);
    float v1 = u0 * sin(theta) + v0 * cos(theta);
    float u2 = u1 + 0.5;
    float v2 = v1 + 0.5;
    return vec2(u2, v2);
}

void main(void)
{
    float u = a_fragment_uv_coord.x;
    float v = a_fragment_uv_coord.y;
    o_diffuse = texture(u_texture, GetRippleDistortedUv(u, v, u, v, u_ripple_center_uv_x, u_ripple_center_uv_y));
}

glsl-optimizer で手元の GLSL で書いたシェーダが高速になるか試してみました。

使うのはとても簡単でした。
ソースコードを落としてきて、コンパイルして、glsl_optimizer_lib-x64.libを作って、インクルードパスを通して、下記のような感じのコードを書いて、コンパイル時にglsl_optimizer_lib-x64.libをリンクするだけ。


#include <glsl/glsl_optimizer.h>

void LoadShader(const char* vertex_shader_path)
{
    std::string resolved_vertex_shader_text;
    
    resolved_vertex_shader_text = シェーダのソースコード読み込み;
    
    glslopt_target target_version = kGlslTargetOpenGLES30;
    glslopt_ctx* ctx = glslopt_initialize(target_version);
    uint options = 0;
    glslopt_shader* vertex_shader = glslopt_optimize(ctx, kGlslOptShaderVertex, resolved_vertex_shader_text.c_str(), options);
    if (!glslopt_get_status(vertex_shader))
    {
        printf("error: %s", glslopt_get_log(vertex_shader));
        glslopt_shader_delete(vertex_shader);
        glslopt_cleanup(ctx);
        return;
    }
    
    resolved_vertex_shader_text = glslopt_get_output(vertex_shader);
    glslopt_shader_delete(vertex_shader);
    
    resolved_vertex_shader_textからシェーダコンパイル;
    
    glslopt_cleanup(ctx);
}

手元にあるシェーダをひと通り最適化してみた感想。

  • Windowsでついていたテストコードのコンパイルは通らなかったが、使い方はとても簡単だった
  • サポートしているのがGLSL 1.2、GLES 2.0、GLES 3.0だけなのがきつい
  • switch が正しく変換されなくてコンパイルできないコードになるので、switchは使えなくなる
  • ユニフォームブロックがユニフォーム変数に変換されてしまうのでユニフォームブロックは使えなくなる
  • 一部のシェーダはglslopt_optimizeで不正アクセスになってしまい最適化できない
  • ちゃんと計測出来てないが、GPU時間はあまり速くなった感がなかった

という感じで、Unity で使われているという話から期待を膨らませていたのですが、残念ながら自分の期待していた結果は得られませんでした。

もともと、Unity で HLSL とかから GLSL に変換したコードが遅すぎるから、それをマシにするために使われている?とどこかに書いてあった気がします。 ある程度最適化を意識して自分で書いたソースコードをさらに最適化するような自分の目的とは違うので、あまり効果が目に見えなかったのかもしれません。

まぁ、簡単にオン、オフを切り替え出来るので、今後もシェーダが遅い場合はワンチャン glsl-optimizer で速くなることを期待して、計測しながら速くなったら有効にするような感じで使っていきたいと思います。

0  

0.0264 sec
にほんブログ村 ゲームブログ ファイアーエムブレムへ にほんブログ村 デザインブログ コンピュータグラフィックスへ

Copyright(C)2006-2018 wsp All Rights Reserved