05

About

Exercise 4

Exercise 5

何か好きな絵を生成してみなさい

Result

前回作成した 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

a. XY 軸に沿った 円

平方和を利用して、原点からの距離に対する円の画像を作成しました。

gl.FragColor = ->_{ pos = gl.FragCoord.xyz col = pos.x**2 + pos.y**2 [col, col, col, 1] }
# # # # # # _ _ # # # # # _ _ _ # # # # # _ _ _ # # # # _ _ _ _ # # # _ _ _ _ _ # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

b. ストライプ、ボーダー、チェック

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] }
_ _ _ _ _ _ _ _ _ # _ # _ # _ # _ _ _ _ _ _ _ _ _ # _ # _ # _ # _ _ _ _ _ _ _ _ _ # _ # _ # _ # _ _ _ _ _ _ _ _ _ # _ # _ # _ #

c. 徐々に色調が変わっていく

青から黄色にグラデーションする画像を生成しました。

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] }
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

d. 重ね塗り

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] }
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # _ # # # # # # _ _ # # # # # _ _ _ # # # _ _ _ _ _ _ _ _ _ _ _ _ _

e. ぼやけた形、ふわっとした形などを表現

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] }

f. その他、美しい絵を描くのにあるとよい機能

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] }
_ _ _ _ _ _ # _ _ _ _ _ _ _ # # _ _ _ _ _ _ # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ # # _ _ _ _ _ _ _ _ # # # _ # _ # # _ # _ _ # _ # #

Exercise 5

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] }

Consideration

文字数が足りなくて記載できませんが、今後は Mat class と mvpMatric によって、 立体的な表現の glsl コードを Ruby から生成する予定です。

helper

def isNum (target) target.is_a? Numeric end def isArr (target) target.instance_of?(Array) end def isVec (target) target.instance_of?(Vec) end

GL

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

Vec

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