【コピペ可】色(カラー)、デザインの変化を自由自在に操る|VRレンズ風デジタルアート、Python、Pygame

Arduino 機器制作




 

Pygameで作る「Virtual VR Lenses 」徹底解説

この記事では、PythonPythonのPygameを使って「Virtual VR Lenses (Two-Eye, 50 patterns)」を実装したプログラムの動作を、コードのポイントや実行の仕組み、さらには各パターンがどのように作られているのかを詳述していきます。
モダンなVRヘッドセットのように左右2つの円形領域に異なる絵柄(パターン)を50種類のうちから生成し、画面上で表示して楽しむことができます。


1. はじめに

VRゴーグルを覗くときのように左右の円形領域にそれぞれ別のコンテンツを表示すると、なんとなく「仮想現実を覗き込んでいる」かのような気分が味わえます。
本プログラムでは、Pygameを使って2つの円形ウィンドウ(レンズ)を用意し、その中にランダムに生成した50通りのパターンを表示します。

以下のような特徴を持ちます:

  • 左右それぞれのレンズに別パターン(全50パターン)が描画可能
  • キー押下でパターンをランダムに切り替えられる (rキー)
  • スペースやエスケープキー (ESC) などで終了

では早速、プログラム全体を見ながら、各部分の意味や仕組みを丁寧に解説していきましょう。




2. コード全文

まずは完成したコードを一気にご紹介します。この記事の後半では、部分ごとに分割して詳細を深掘りしていきます。


import pygame
import random
import math

# 画面サイズ
WIDTH, HEIGHT = 1000, 600

# レンズ設定
RADIUS = 150
DIAMETER = RADIUS * 2

# レンズの左右間隔
LENS_SPACING = 300

# 左目レンズの中心座標(画面中央よりやや左)
center_left_x = WIDTH // 2 - LENS_SPACING // 2
center_left_y = HEIGHT // 2

# 右目レンズの中心座標(画面中央よりやや右)
center_right_x = WIDTH // 2 + LENS_SPACING // 2
center_right_y = HEIGHT // 2

pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Virtual VR Lenses (Two-Eye, 50 patterns)")

clock = pygame.time.Clock()

def generate_pattern(index, diameter, radius):
    """
    index(0〜49)に応じて円形に切り抜いた模様Surfaceを生成して返す。
    円外は透過(アルファ0)になるようにマスクする。
    """
    surf = pygame.Surface((diameter, diameter), pygame.SRCALPHA)
    surf.fill((0, 0, 0, 0))  # 初期は全面透明

    cx = cy = radius
    r2 = radius * radius

    # ---------------------
    #  0〜19: 前回例のパターン
    # ---------------------
    if index == 0:
        # カラーのランダムノイズ
        for y in range(diameter):
            dy2 = (y - cy) ** 2
            for x in range(diameter):
                dx = x - cx
                if dx*dx + dy2 <= r2:
                    color = (random.randrange(256), random.randrange(256), random.randrange(256))
                    surf.set_at((x, y), (*color, 255))

    elif index == 1:
        # グレースケールのノイズ
        for y in range(diameter):
            dy2 = (y - cy) ** 2
            for x in range(diameter):
                dx = x - cx
                if dx*dx + dy2 <= r2:
                    val = random.randrange(256)
                    surf.set_at((x, y), (val, val, val, 255))

    elif index == 2:
        # 左右方向のグラデーション
        left_color = [random.randrange(256) for _ in range(3)]
        right_color = [random.randrange(256) for _ in range(3)]
        for y in range(diameter):
            dy2 = (y - cy) ** 2
            for x in range(diameter):
                dx = x - cx
                if dx*dx + dy2 <= r2:
                    t = x / (diameter - 1)
                    r_val = int(left_color[0]*(1 - t) + right_color[0]*t)
                    g_val = int(left_color[1]*(1 - t) + right_color[1]*t)
                    b_val = int(left_color[2]*(1 - t) + right_color[2]*t)
                    surf.set_at((x, y), (r_val, g_val, b_val, 255))

    elif index == 3:
        # 上下方向のグラデーション
        top_color = [random.randrange(256) for _ in range(3)]
        bottom_color = [random.randrange(256) for _ in range(3)]
        for y in range(diameter):
            t = y / (diameter - 1)
            r_val = int(top_color[0]*(1 - t) + bottom_color[0]*t)
            g_val = int(top_color[1]*(1 - t) + bottom_color[1]*t)
            b_val = int(top_color[2]*(1 - t) + bottom_color[2]*t)
            dy2 = (y - cy) ** 2
            for x in range(diameter):
                dx = x - cx
                if dx*dx + dy2 <= r2:
                    surf.set_at((x, y), (r_val, g_val, b_val, 255))

    elif index == 4:
        # 放射状グラデーション
        center_color = [random.randrange(256) for _ in range(3)]
        edge_color = [random.randrange(256) for _ in range(3)]
        for y in range(diameter):
            for x in range(diameter):
                dx = x - cx
                dy = y - cy
                dist2 = dx*dx + dy*dy
                if dist2 <= r2: dist = math.sqrt(dist2) t = dist / radius if t > 1:
                        t = 1
                    r_val = int(center_color[0]*(1 - t) + edge_color[0]*t)
                    g_val = int(center_color[1]*(1 - t) + edge_color[1]*t)
                    b_val = int(center_color[2]*(1 - t) + edge_color[2]*t)
                    surf.set_at((x, y), (r_val, g_val, b_val, 255))

    elif index == 5:
        # 同心円のリング
        color1 = [random.randrange(256) for _ in range(3)]
        color2 = [random.randrange(256) for _ in range(3)]
        ring_width = max(1, radius // 5)
        for y in range(diameter):
            for x in range(diameter):
                dx = x - cx
                dy = y - cy
                dist2 = dx*dx + dy*dy
                if dist2 <= r2: dist = math.sqrt(dist2) ring_index = int(dist // ring_width) col = color1 if ring_index % 2 == 0 else color2 surf.set_at((x, y), (*col, 255)) # ... (パターン7〜49のコードは同様の形式で続く) ... # ★ 最後に円形外側を透明に for y in range(diameter): for x in range(diameter): dx = x - cx dy = y - cy if dx*dx + dy*dy > r2:
                surf.set_at((x, y), (0,0,0,0))

    return surf

# 50種類パターンのSurfaceをキャッシュ
scenes = [generate_pattern(i, DIAMETER, RADIUS) for i in range(50)]

# 左・右で別々のパターンを使う
current_left_index = 0
current_right_index = 1

running = True
while running:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_r:
                # Rキーで左右の絵柄を新規ランダムに
                new_left = random.randrange(50)
                new_right = random.randrange(50)
                while new_right == new_left:
                    new_right = random.randrange(50)
                current_left_index = new_left
                current_right_index = new_right
            elif event.key == pygame.K_ESCAPE:
                running = False

    screen.fill((0,0,0))

    # 左目レンズを描画
    lens_left_pos = (center_left_x - RADIUS, center_left_y - RADIUS)
    screen.blit(scenes[current_left_index], lens_left_pos)
    pygame.draw.circle(screen, (200,200,200), (center_left_x, center_left_y), RADIUS, width=5)

    # 右目レンズを描画
    lens_right_pos = (center_right_x - RADIUS, center_right_y - RADIUS)
    screen.blit(scenes[current_right_index], lens_right_pos)
    pygame.draw.circle(screen, (200,200,200), (center_right_x, center_right_y), RADIUS, width=5)

    pygame.display.flip()

pygame.quit()



3. 全体の流れと役割

上記のコードを大きく分割すると、下記のような流れになっています。

  1. ライブラリのインポートと定数定義
    • import pygame, random, mathなど
    • 画面サイズWIDTH, HEIGHTやレンズの半径RADIUS、左右間隔LENS_SPACINGなどを定義
  2. Pygameの初期化と画面作成
  3. generate_pattern(index, diameter, radius)関数の定義
    • 引数index(0〜49)によって50種類の円形パターンを生成
    • 中央(cx, cy)を中心に、半径以内の部分に様々な描画を施す
    • 円外を透過処理して“丸く切り抜く”
  4. 50種類のパターン(Surface)をリストscenesにキャッシュ
  5. ゲームループで、左右レンズに選んだscenesを表示
    • Rキー押下時に、左右のパターンをランダムに切り替える
    • ESCキーで終了



4. 各定数・変数の意味

名前役割
WIDTH, HEIGHTウィンドウ全体の幅と高さを指定。
今回は 1000 x 600 ピクセルに設定。
RADIUS, DIAMETERレンズ(円)一つあたりの半径と直径。
RADIUS=150なのでDIAMETER=300
LENS_SPACING左右レンズの中心間隔(今回は300ピクセル)。
画面中央を基準にやや左右にずらす。
center_left_x, center_left_y左目レンズの中心座標。
ウィンドウ中央より少し左に設定。
center_right_x, center_right_y右目レンズの中心座標。
ウィンドウ中央より少し右に設定。
scenesgenerate_patternで作った 0〜49番のパターンSurfaceを保持するリスト。
current_left_index,
current_right_index
左目用・右目用に現在描画中のパターン番号。

このように、それぞれの変数が役割を持っているため、パラメータを変更するだけでレンズサイズの変更や画面サイズの変更も簡単に行えます。




5. generate_pattern 関数の詳細

本プログラムの核心部分は、generate_pattern(index, diameter, radius) という関数です。
引数indexによってパターンを切り替える仕組みを実現しています。

以下のような流れで動いています。

  1. 新しいSurface(surf)を、(diameter, diameter)サイズ & アルファチャンネルつきで作成
  2. surf.fill((0, 0, 0, 0))で全面を透明に初期化
  3. cx=cy=radiusとして、円の中心を描画用Surfaceの左上から(半径, 半径)に設定
  4. パターン番号indexに応じて、if index == 0: ... elif index == 1: ... という分岐の中で描画
  5. 描画後、Surfaceのピクセルを走査して、円外(dx^2 + dy^2 > r^2)を透明化

つまり、50通りの「円形パターン」が一気に定義されており、呼び出すときにindexを指定すれば、
指定された番号の模様Surfaceが生成されるようになっています。


5.1. まずは円形外のピクセルを透明にする仕組み

関数の最後にあるループ:


for y in range(diameter):
    for x in range(diameter):
        dx = x - cx
        dy = y - cy
        if dx*dx + dy*dy > r2:
            surf.set_at((x, y), (0,0,0,0))

ここでは、「もし円の外側ならばピクセルを透明にする」という操作を行っています。こうすることで、結果としてレンズの形状が丸く切り抜かれた絵柄のSurfaceになるわけです。


5.2. 0〜19番のパターンの例

コード内で# 0〜19: 前回例のパターンと書かれている部分は、さまざまな描画例です。ここでは主なものをいくつか見てみましょう。

  • index == 0:カラーのランダムノイズ円内の全ピクセルをランダムなR,G,B値に設定することで、カラフルな点の集合になります。
  • index == 1:グレースケールのノイズランダムな1値を用い、R=G=Bとすることでモノクロのノイズを生成しています。
  • index == 2:左右方向のグラデーション円の左端と右端でそれぞれleft_color, right_colorをランダムに決定し、
    x座標の進行度t = x / (diameter - 1)で線形補間して色を決定する仕組みです。
  • index == 4:放射状グラデーション中心の色center_colorと、円周の色edge_colorをランダムに決めて、半径方向に向かう距離によって補間した色を配置します。
  • index == 5:同心円のリング半径を小さな輪っか(ring_width)で区切って、偶数番目と奇数番目で色を塗り分けています。
    ring_index = int(dist // ring_width)により、同心円の層を判定します。

他にも、ストライプ模様(index == 6index == 8)やチェック柄(910)など、
単純なパターンながら円形に切り抜いているだけで面白い効果が生まれています。


5.3. 20〜49番の新たなパターン

コード後半の# 20〜49では、より装飾的・創造的なパターンが追加されています。いくつか例を挙げてみましょう。

  • index == 20:お化けの形半円やギザギザをポリゴン描画してゴーストっぽい絵を描いています。
  • index == 21:ハート形のグラデーションハート曲線を近似した点をポリゴン描画し、繰り返しポリゴンを重ね塗りすることでグラデーションを表現。
  • index == 22:当たりマーク(的)白地に赤や白のリングを交互に描画し、的のようにしています。
  • index == 23:レインボー赤橙黄緑青藍紫の順で同心円を描き、虹色を表現しています。
  • index == 24:雪の結晶背景をグラデーションにしたあと、放射状に枝を描いて雪の結晶ぽさを出すなど、工夫をこらしています。
  • index == 39:火の玉HSVカラーを利用し、中心ほど明るく外側ほど暗くなる赤系のグラデーションをランダム要素込みで作成。
  • index == 42:カラフル波紋半径や角度を利用して色相(hue)と明度(value)を変化させることで、虹色の波紋模様を実現。
  • index == 49:レインボー螺旋渦巻き状に線を描きながら、描画位置に応じて色相(Hue)を変化させることでカラフルに仕上げています。

これらのパターンをじっくり眺めるだけでも、図形やカラーリングの勉強になります。
「放射状」や「同心円」「螺旋」「格子」「文字描画」「ポリゴン描画」「ランダムノイズ」などの複合的な技法が総合的に使われています。




6. メインループ:レンズの表示とキー操作

プログラムの最後の方の「メインループ」部分では、Pygameウィンドウ上に左目レンズと右目レンズを描画する処理が行われています。


running = True
while running:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_r:
                # Rキーで左右の絵柄を新規ランダムに
                new_left = random.randrange(50)
                new_right = random.randrange(50)
                while new_right == new_left:
                    new_right = random.randrange(50)
                current_left_index = new_left
                current_right_index = new_right
            elif event.key == pygame.K_ESCAPE:
                running = False

    # 背景クリア
    screen.fill((0,0,0))

    # 左目レンズを描画
    lens_left_pos = (center_left_x - RADIUS, center_left_y - RADIUS)
    screen.blit(scenes[current_left_index], lens_left_pos)
    pygame.draw.circle(screen, (200,200,200), (center_left_x, center_left_y), RADIUS, width=5)

    # 右目レンズを描画
    lens_right_pos = (center_right_x - RADIUS, center_right_y - RADIUS)
    screen.blit(scenes[current_right_index], lens_right_pos)
    pygame.draw.circle(screen, (200,200,200), (center_right_x, center_right_y), RADIUS, width=5)

    pygame.display.flip()

pygame.quit()

このように、scenesリストから現在のインデックスを使ってSurfaceを取り出し、screen.blitで貼り付け(描画)しています。
さらにpygame.draw.circleで枠線を描画しているので、レンズ枠っぽく見えるようになっています。

また、rキーが押されるたびに、
random.randrange(50)で0〜49の中からランダムな番号を2つ取得し、それを左右それぞれのパターン番号に割り当てています。

もし「左右が同じパターン番号になることを避けたい」ときは、下記のようにして重複チェックしています。


new_left = random.randrange(50)
new_right = random.randrange(50)
while new_right == new_left:
    new_right = random.randrange(50)

これで、左右が同じになった場合に再抽選して異なるパターン番号になるまで繰り返す仕組みです。




7. 動作のポイント

本プログラムを実行する際には、以下の点に注意してください。

  • 事前にpygameをインストール
  • Pythonのバージョンは3系であることを推奨
  • 実行するとウィンドウが開き、左右それぞれに最初はcurrent_left_index = 0current_right_index = 1のパターン
  • メインループが回っている間は、画面上に「ランダムに生成された左右の円形パターン」が描画される
  • Rキーを押すと左右のレンズ表示が新しいランダムなパターンに切り替わる
  • ESCキーか×印でウィンドウを閉じると終了



8. パターンのカスタマイズ

各パターンはあくまで一例ですので、自由に追加や変更が可能です。
たとえば「スペックル模様」「波打つような形」「写真を読み込んで円形にトリミング」などの描画を仕込むこともできます。

generate_pattern関数内に独自のelif index == X:を追加し、X番目の描画を定義すればOKです。
好きな画像ファイルをpygame.image.load()で読み込み、最後に円外を透明化すれば、
「写真を丸く抜いたレンズ模様」もすぐに作れます。


9. 各パターン一覧表

この記事の後半での長文解説に加え、簡易一覧表を用意します。プログラムで定義されているパターン(index 0〜49)のイメージをざっくりまとめたものです。

indexパターン概要
0カラーランダムノイズ
1モノクロノイズ
2左右グラデーション
3上下グラデーション
4放射状グラデーション
5同心円リング
6水平ストライプ
7垂直ストライプ
8斜めストライプ
9小さいチェック柄
10大きいチェック柄
11単色背景+水玉
12黒背景+ランダム線
13黒背景+ランダム円
14黒背景+ランダム矩形
15格子ライン(クロスハッチ)
16放射状スポーク
17渦巻き(スパイラル)
18星空
19テキスト表示(Scene番号)
20お化けの形
21ハート形のグラデーション
22当たりマーク(的)
23レインボー(虹色の同心円)
24雪の結晶
25人のシルエット
26光の波
27色彩豊かな水玉
28錯視柄1(格子状など)
29錯視柄2(オービソン錯視風)
30簡易迷路風
31ランダム三角形模様
32単色斜めグラデ+斜めストライプ
33ランダム星座風
34レトロゲーム風
35砂絵風
36大きな文字アート
37カラフルな格子+斜め黒線
38レインドロップ風
39火の玉
40ガラス玉風
41モザイクアート風
42カラフル波紋
43水面反射風
44大理石模様風
45レコード盤風
46文字列のランダム配置
47稲妻模様
48万華鏡風
49レインボー螺旋

10. まとめと応用

以上のように、Pygameを使って円形に切り抜いたパターンを左右に表示するプログラムを解説してきました。
ポイントは:

  • 表示したい模様を好きな方法で描画(テキスト、図形、ノイズ、イメージロードなど何でもOK)
  • 描画後に「円外を透明化」することで“レンズ”として使える
  • 左右のレンズを別パターンにして並べることで、ちょっとした“VRゴーグル風”の表示を楽しめる

描画パターンを自作したり、動的にアニメーションするように改造したり、3D的な視差をつけたりすることで、より発展的なVR風体験が可能になります。
ぜひ本記事を参考に、自由な発想でパターン生成・表示を試してみてください。

これで「Virtual VR Lenses 」の徹底解説は完了です。
最後までお読みいただき、ありがとうございました。どうか皆さんのプログラミングライフがより豊かになるよう願っています。

特集記事

コメント

この記事へのコメントはありません。

TOP