何か好きな絵を生成してみなさい
前回作成した Vec class に加えて、 レンダリングを実行する GL class を作成しました。
gl.rb は、glsl などのシェーダのように画像処理が行えるようになります。
FragCoord は、 n 個の頂点に対して座標 vector x, y, z, s を指定します。
FragColor は、各pixelに対する 色 vector r, g, b, a を指定します。
デバッグのための gl.test 関数は、結果を 0: _と #: 1 のテキストで確認できます。
また、今回は以下のコードをひな形に、 gl.FragCoord のみを変更して画像を生成しました。
作成した GL class と Vec class は最後に記載しました。
これらから、a ~ f の演習のコードと実行結果は以下のようになりました。
require "./gl.rb"
require "./vec.rb"
gl = GL.new 8, 8, "out.ppm"
gl.FragCoord = ->_{[
0.5 / gl.w + (gl.i.to_f % gl.w) / gl.h,
0.5 / gl.h + (gl.i.to_f / gl.w).to_i.to_f / gl.h,
0.0, 1.0
]}
gl.FragCoord = ->_{[
# here !!!
]}
gl.test
平方和を利用して、原点からの距離に対する円の画像を作成しました。
gl.FragColor = ->_{
pos = gl.FragCoord.xyz
col = pos.x**2 + pos.y**2
[col, col, col, 1]
}
# # # # # # _ _
# # # # # _ _ _
# # # # # _ _ _
# # # # _ _ _ _
# # # _ _ _ _ _
# _ _ _ _ _ _ _
_ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _
sin 関数を利用した、チェック柄の画像を生成しました。
gl.FragColor = ->_{
pos = gl.FragCoord.xyz
col = Math::sin(pos.x * Math::PI * gl.w) / .5 + .5
col+= Math::sin(pos.y * Math::PI * gl.h) / .5 + .5
[col, col, col, 1]
}
_ _ _ _ _ _ _ _
_ # _ # _ # _ #
_ _ _ _ _ _ _ _
_ # _ # _ # _ #
_ _ _ _ _ _ _ _
_ # _ # _ # _ #
_ _ _ _ _ _ _ _
_ # _ # _ # _ #
青から黄色にグラデーションする画像を生成しました。
gl.FragColor = ->_{
pos = gl.FragCoord.xyz
a = vec(0, 0, 1) # blue
b = vec(1, 1, 0) # yellow
col = a * (1 - pos.y) + b * pos.y
[col.x, col.y, col.z, 1]
}
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # # # # # #
_ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _
c演習に対して、 alpha 値を原点からの距離にした画像を生成しました。
gl.FragColor = ->_{
pos = gl.FragCoord.xyz
len = pos.x ** 2 + pos.y ** 2
a = vec(0, 0, 1) # blue
b = vec(1, 1, 0) # yellow
col = a * (1 - pos.y) + b * pos.y
[col.x, col.y, col.z, len]
}
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # # # # # _
# # # # # # _ _
# # # # # _ _ _
# # # _ _ _ _ _
_ _ _ _ _ _ _ _
smoothstep 関数によって値の推移がなめらかになる画像を生成しました。
def smoothstep(a, b, x)
t = x < a? 0 : b < x ? 1 : (x - a) / (b - a)
t * t * (3 - 2 * t)
end
gl.FragColor <=> ->_{
pos = gl.FragCoord.xyz
col = Math::sin(pos.x * Math::PI * 8) / 0.5 + 0.5
col+= Math::sin(pos.y * Math::PI * 8) / 0.5 + 0.5
col = smoothstep(0, 1, col)
[col, col, col, 1]
}
sin 関数の小数点を利用した疑似乱数をつかった絵を作成しました。
x, y座標をシード値にしたランダムな白黒画像を生成します。
def fract(x) x = 1000 * Math::sin(x); x - x.to_i end
gl.FragColor <=> ->_{
pos = gl.FragCoord.xyz
col = fract(pos.x) + fract(pos.y)
[col, col, col, 1]
}
_ _ _ _ _ _ # _
_ _ _ _ _ _ # #
_ _ _ _ _ _ # _
_ _ _ _ _ _ _ _
_ _ _ _ _ _ # #
_ _ _ _ _ _ _ _
# # # _ # _ # #
_ # _ _ # _ # #
x, y座標から極座標に変換したあと、hsvからrgbに色を変換する画像を作成しました。
def mod (x, y) x - y.to_f * (x / y).floor end
def clamp (x, a, b) x < a ? a : b < x ? b : x end
def xy2st(xy) vec(Math::atan(xy.y / xy.x), xy.length) end
def hsv2rgb (c)
xyz = vec(0, 4, 2) + c.x * 6
r = clamp((mod(xyz.x, 6) - 3).abs, 0, 1)
g = clamp((mod(xyz.y, 6) - 3).abs, 0, 1)
b = clamp((mod(xyz.z, 6) - 3).abs, 0, 1)
(vec(1, 1, 1) * (1 - c.y) + vec(r, g, b) * c.y) * c.z
end
gl.FragColor = ->_{
pos = gl.FragCoord.xyz
st = xy2st(pos * 2 - 1)
hs = vec(st.x / Math::PI, st.y);
col = hsv2rgb(vec(hs.x, hs.y, 0) * 0.5 + 0.5);
[col.x, col.y, col.z, 1]
}
文字数が足りなくて記載できませんが、今後は Mat class と mvpMatric によって、
立体的な表現の glsl コードを Ruby から生成する予定です。
def isNum (target) target.is_a? Numeric end
def isArr (target) target.instance_of?(Array) end
def isVec (target) target.instance_of?(Vec) end
class GL
def initialize(w, h, filename = "")
@filename = filename
@w = w
@h = h
@i = 0
@FragCoord = vec 0, 0, 0, 0
@FragColor = vec 0, 0, 0, 0
end
def i() @i end
def w() @w end
def h() @h end
def w=(_) @w = _ end
def h=(_) @h = _ end
def FragCoord=(_) @FragCoord <=> _ end
def FragColor=(_) @FragColor <=> _ end
def FragCoord(i = @i) @i = i; @FragCoord end
def FragColor(i = @i) @i = i; @FragColor end
def drawArrays(count) gl_draw(self, count) end
def test() gl_test(self) end
end
def gl_draw(gl, count)
open(@filename, "wb") do |f|
f.puts("P6\n#{gl.w} #{gl.h}\n255")
(gl.w * gl.h).times do |i|
c = gl.FragColor(i) * 255
f.write([c.x.to_i % 255, c.y.to_i % 255, c.z.to_i % 255].pack("ccc"))
end
end
end
def gl_test (gl)
(gl.w * gl.h).times.inject("") do |acc, i|
col = gl.FragColor(i)
ret = acc + (col[3] * col.xyz.sum / 3 <= 0.5 ? " # " : " _ ")
if (i % w == w - 1) then puts ret; ret = "" end
ret
end
end
class Vec
def initialize(init = [1, 0, 0], args = 0)
@v = init
@n = args
@_ = ->(v = @v, n = @n){Vec.new(v, n)}
end
def to_a() isArr(@v) ? @v : @v[@n] end
def to_s() to_a.to_s end
def size() to_a.size end
def sum() to_a.sum end
def length() Math::sqrt(self * self) end
def sort() @_[->n{to_a.sort}] end
def map(_) @_[->n{to_a.map.with_index{|v, i| _[v, i]}}] end
def +(_) map(isNum(_) ? ->v, i{v + _} : ->v, i{v + _[i]}) end
def -(_) map(isNum(_) ? ->v, i{v - _} : ->v, i{v - _[i]}) end
def *(_) isNum(_) ? map(->v, i{v * _}) : map(->v, i{v * _[i]}).sum end
def /(_) isNum(_) ? map(->v, i{v / _}) : cross(_) end
def [](_) v = to_a; v[_] end
def <=>(_) @v = _ end
def >>(_) _ << @n end
def <<(n) @_[@v, n] end
def +@() map(->v, i{+v}) end
def -@() map(->v, i{-v}) end
def !@() self / length end
def cross(_) @_[->n{ v = _.to_a; [
@v[1] * v[2] - @v[2] * v[1],
@v[2] * v[0] - @v[0] * v[2],
@v[0] * v[1] - @v[1] * v[0],]
}] end
# utils
def x() self[0] end
def y() self[1] end
def z() self[2] end
def xyz() @_[->n{v = to_a; [v[0], v[1], v[2]]}] end
end
def vec(*init) Vec.new(init) end