はじめに
冒頭の1行と言ってますが,正確には違うのでマジレスはご容赦
GLSLを書いているといるよく見かける冒頭の1行
がありますよね,これです↓
vec2 uv = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
GLSL初学者の人にとっては結構1行に情報が詰まっていて先頭行から難しいんじゃないかなと思ったので(少なくとも自分が勉強し始めた時はよくわからなかった)これについて説明していこうと思います.
そもそもの目的
そもそも上記のコードを使う目的なんですが,画面の縦横比を1:1に揃えるために使用します.
そうしたくなる理由を含め,順を追ってみていきましょう.
デフォルト状態
precision mediump float;
uniform float time;
uniform vec2 resolution;
void main() {
vec2 uv = (gl_FragCoord.xy) / resolution.xy;
gl_FragColor = vec4(uv.x, 0.0, uv.y , 1.0);
}
それぞれの組み込み変数について
-
gl_FragCoord.xy
: ピクセル座標.要するに何番目のピクセルにいるかっていうvec2型変数です.
そして左下が(0.0, 0.0)で右上へ向かって大きくなっていきます. -
resolution
: 解像度
なので,以下のコードでvec2(0.0, 0.0) ~ vec2(1.0, 1.0)までに正規化を行っているですね.
vec2 uv = (gl_FragCoord.xy) / resolution.xy;
正規化されているか検証
出力カラーを原点(左下)からの距離のマップにしてみます.
precision mediump float;
uniform float time;
uniform vec2 resolution;
void main() {
vec2 uv = (gl_FragCoord.xy) / resolution.xy;
float gray = length(uv);
gl_FragColor = vec4(vec3(gray) , 1.0);
}
正規化後の座標系は左下が(0.0, 0.0), 右上が(1.0, 1.0)なので原点(左下)からの距離が遠くになるにつれて白くなっているのでうまくいってそう
問題点
ここで1つ絵を作っていると問題点が上がってきます.それは縦横比です.上の画像のように横に引き伸ばされちゃってます..これをどうにか解決したい..そこで出てくるのが冒頭のプログラムです
本題
周りくどいようですが,この記事ではできるだけ丁寧に追っていきたいのでステップに分けて説明していきます.
ステップ1(0.0~1.0 -> -1.0 ~ 1.0へ)
今までの座標系ってvec2(0.0, 0.0)からvec2(1.0, 1.0)だったわけですが,これからはvec2(-1.0, -1.0)からvec2(1.0, 1.0)へ変換します.
precision mediump float;
uniform float time;
uniform vec2 resolution;
void main() {
vec2 uv = (gl_FragCoord.xy*2.0 - resolution) / resolution.xy;
float gray = length(uv);
gl_FragColor = vec4(vec3(gray) , 1.0);
}
gl_FragCoord.xy*2.0 - resolution / resolution.xy
: これで-1.0 ~ 1.0になります.
例えばresolution.xが1280だとします.gl_FragCoord.xが0.0の時上の式の結果は-1.0
gl_FragCoord.xが1280.0のとき1.0ですよね.
GLSL難しいんですけど結構具体的に数値入れて考えてみると理解出来ます.
これで原点(中心になった)vec2(0.0, 0.0)からの距離のマップを確認できました.
次で最終的な縦横比を解決していきます.
ステップ2(いよいよ本題)
いよいよ比率を合わせていく最終ステップです.
要するに今解決したいことってuvで考えるとx方向に1.0進む長さとy方向に1.0進む長さが違うから困っているんですよね.そしたらそれを合わせてあげるだけです.1歩引いて考えてみると超簡単なので安心してください
precision mediump float;
uniform float time;
uniform vec2 resolution;
void main() {
vec2 uv = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
float gray = length(uv);
gl_FragColor = vec4(vec3(gray) , 1.0);
}
min
: 小さい方を返す
今まではresolution.xyそれぞれの要素で割っていたんですけど,minを使いresolutionの小さい方を選択し割る数を統一してあげればvec2(0.0, 0.0)から同じ分だけのピクセル分を進めば同じ分だけuvが変化するってことです.
具体例で考えてみましょう.resolutionを(1280, 720)として中心からy軸に関して正方向に720ピクセル進んだpixel1と,x軸に関して正に720ピクセル進んだpixel2についてそれぞれuvを考えます.
今までのようにそれぞれのresolutionのxyで割るとpixel1のuvは(0.0, 1.0), pixel2は(720/1280, 0.0)となってしまいます.ですが小さい方のresolutionの要素(今回は720)で割ってあげればpixel1のuvは(0.0, 720/720), pixel2は(720/720, 0.0)これでどっちも原点から等しい距離になりましたね.
これで縦横1:1になりました.
注意しなければならないなのは小さい方のresolutionの値で割る部分です.大きい方で割ってしまうと1方の軸のマックスの値は1.0を下回ってしまいますよね.なので小さい方に合わせてあげます.
いまいちしっくりこない方
多分,下記のコード見たらもう少しすっきりするかなと思います.min(resolution.xy)
では上の場合resolution.y
が返り値として得られます.なのでx方向に関しては以前よりも小さいな値で割ることになるのでuv.xは1.0を超えます.なので以下のコードではuv.xが-1.0 もしくは1.0を超えた場合塗る色を黒にしています.これで縦横1:1をより意識できるのではないでしょうか?
precision mediump float;
uniform float time;
uniform vec2 resolution;
void main() {
vec2 uv = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
vec4 col = vec4(1.0);
if(abs(uv.x) > 1.0){
col = vec4(0.0, 0.0, 0.0, 1.0);
}
else{
col = vec4(uv.x, 0.0,uv.y, 1.0);
}
gl_FragColor = vec4(col);
}