画像マテリアルを作る(その1)


今まで「挿絵画家になろう」として、MB-Labを用いたアニメ調3D人体モデルに関して検討してきました。
簡単ではないですけれど、服も用意できるようになってきました。
服を作れるようになってくると今度は柄に拘りたくなってきますよね?
ネットには様々な画像素材があって、中には無料で提供頂けるものもあります。
ありがたいですね、便利な世の中になったものです。
ただ今回は二次元画像マテリアルを自分で生成しようという試みです。
お付き合いいただけると幸いです。

諸兄姉は「#つぶやきglsl」というtwitterのハッシュタグをご存じでしょうか?
https://twitter.com/hashtag/つぶやきglsl

画像や動画を表示するためのglslコードを280文字以下でtwitterに投稿するものです。
投稿されたglslコードはtwigl.appというサイトで実行・表示できます。
「#つぶやきglsl」の投稿には、投稿コードにより表示されるであろう動画や画像が添付されているのですが、それがまたとても美麗なものばかりです。
このような複雑で美しい表現がたった280文字で実現できるのです。
glslとはOpenGLのシェーダー言語なのですが、その表現力の豊かさに驚かされます。

とはいえ、いきなりこのような匠の技が使えるものではありません。
技術は一つ一つ、目的意識をもって習得していかなければならないでしょう。
そして習得するべき技術は、簡単で役に立つものを選ぶべきです。
本投稿の目的はglslのフラグメントシェーダーを用いて伝統的紋様を表現することです。

glslに限らず、フラグメントシェーダープログラムは難解で複雑な言語です。
世の中にプログラム言語は数多あれど、これほど初心者キラーな言語も珍しいと思います。
しかし求めるならば道は開かれます。
非常に良い入門サイトがあるのです。
The Book of Shaders

全ての入門者はこのサイトで学ぶことができます。
このサイトの良いところは、実際のglslのコードをWEBブラウザ上で確認できるサイトを用意してくれているところです。
以下のWEBページを開いてください。
http://editor.thebookofshaders.com/

このWEBページの左側のコードを書き換えると、変更が右側の画像に反映されます。
生成された画像は右クリックでダウンロードすることができます。
まさに至れり尽くせり。
以下、伝統紋様のglslコードを晒しますので、コピペして画像を確認してください。
そしてパラメータを変えてみて、お好みの画像に変更してください。

では一番簡単な模様「市松」から。

色違いの正方形を交互に並べてゆく紋様です。
洋の東西を問わず古くから人気のある模様です。
glslコードによる実装例は以下。

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

void main (void) {
    vec2 st = gl_FragCoord.xy/u_resolution.xy;

    st *= 5.;
    st = fract(st);
    st *= 2.;
    float index = 0.;
    index += step(1., mod(st.x,2.));
    index += step(1., mod(st.y,2.))*2.;
    st = fract(st);

    // c is color vector (r, g, b)
    // white is (1., 1., 1.) and black is (0., 0., 0.)
    vec3 c = vec3(1., .75, .12); // r, g, b 

    // index
    // 0.:BL 1.:BR 2.:TL 3.:TR  
    if(index == 1. || index == 2.){
       c = vec3(1., .9, 0.);
    }

    gl_FragColor = vec4(c ,1.);
}

市松模様を斜めにしたものもあります。

glslコードによる実装例は以下。

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

float lengthN(vec2 v, float n){
  vec2 tmp = pow(abs(v), vec2(n));
  return pow(tmp.x+tmp.y, 1.0/n);
}

void main (void) {
    vec2 st = gl_FragCoord.xy/u_resolution.xy;

    st *= 5.;
    st = fract(st);
    st *= 2.;
    float index = 0.;
    index += step(1., mod(st.x,2.));
    index += step(1., mod(st.y,2.))*2.;
    st = fract(st);

    // index
    // 0.:BL 1.:BR 2.:TL 3.:TR  
    if(index == 1.){
        st.x = 1.-st.x;
    }
    if(index == 2.){
        st.y = 1.-st.y;
    }
    if(index == 3.){
        st.x = 1.-st.x;
        st.y = 1.-st.y;
    }
    st *= 0.5;

    // c is color vector (r, g, b)
    // white is (1., 1., 1.) and black is (0., 0., 0.)
    vec3 c = vec3(1., 0., 0.);
    float l=lengthN(st,1.);
    if (lengthN(st,1.) > 0.5){
       c = vec3(1., 1., 0.);
    }
    gl_FragColor = vec4(c ,1.);
}

次は「七宝」。
円を斜めにずらしながら重ねていく紋様です。
円と円の重複部分で色を変えることにより星が現れます。

glslコードによる実装例は以下。

#ifdef GL_ES
precision mediump float;
#endif

#define PI 3.14159265358979323846

uniform vec2 u_resolution;
uniform float u_time;

void main (void) {
    vec2 st = gl_FragCoord.xy/u_resolution.xy;

    st *= 5.;
    st = fract(st);
    st *= 2.;
    float index = 0.;
    index += step(1., mod(st.x,2.));
    index += step(1., mod(st.y,2.))*2.;
    st = fract(st);

    // index
    // 0.:BL 1.:BR 2.:TL 3.:TR  
    if(index == 1. || index == 2.){
        st.x = 1.-st.x;
    }

    // c is color vector (r, g, b)
    // white is (1., 1., 1.) and black is (0., 0., 0.)
    vec3 c = vec3(1., 0., 0.);
    float d;
    if ((distance(vec2(0., 0.), st)) < 1. && (distance(vec2(1., 1.), st)) < 1.){
       c = vec3(1., 1., 0.);
    }
    gl_FragColor = vec4(c ,1.);
}

次は「鹿子」です。
鹿の背中の模様の意匠です。
絞り染めの代表的な紋様ですね。

glslコードによる実装例は以下。

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

float lengthN(vec2 v, float n){
  vec2 tmp = pow(abs(v), vec2(n));
  return pow(tmp.x+tmp.y, 1.0/n);
}

void main (void) {
    vec2 st = gl_FragCoord.xy/u_resolution.xy;

    st *= 5.;
    st = fract(st);
    st *= 2.;
    float index = 0.;
    index += step(1., mod(st.x,2.));
    index += step(1., mod(st.y,2.))*2.;
    st = fract(st);

    // index
    // 0.:BL 1.:BR 2.:TL 3.:TR  
    if(index == 1.){
        st.x = 1.-st.x;
    }
    if(index == 2.){
        st.y = 1.-st.y;
    }
    if(index == 3.){
        st.x = 1.-st.x;
        st.y = 1.-st.y;
    }
    st *= 0.5;

    // c is color vector (r, g, b)
    // white is (1., 1., 1.) and black is (0., 0., 0.)
    vec3 c = vec3(1., 0., 0.);
    if (   (lengthN(st, .7) > 1.0 && lengthN(st, .7) < 1.2)
        || (lengthN(st-.5, .7) > 1.0 && lengthN(st-.5, .7) < 1.2)){
       c = vec3(1., 1., 0.);
    }
    gl_FragColor = vec4(c ,1.);
}

次は「鱗」。
鱗紋様には様々なパターンがあるのですけれど、代表的なのは色違いの二等辺三角形を並べてゆくものです。

glslコードによる実装例は以下。

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

float cross(vec2 v1, vec2 v2) {
    return v1.x*v2.y - v1.y*v2.x;
}

void main (void) {
    vec2 st = gl_FragCoord.xy/u_resolution.xy;

    st.x *= 10.;
    st.y *= 6.;
    st = fract(st);
    st *= 2.;
    float index = 0.;
    index += step(1., mod(st.x,2.));
    index += step(1., mod(st.y,2.))*2.;
    st = fract(st);

    // index
    // 0.:BL 1.:BR 2.:TL 3.:TR  
    if(index == 1. || index == 2.){
        st.x = 1.-st.x;
    }

    // c is color vector (r, g, b)
    // white is (1., 1., 1.) and black is (0., 0., 0.)
    vec3 c = vec3(1., 0., 0.);
    if (cross(st,normalize(vec2(1.,1.))) > 0.0){
       c = vec3(1., 1., 0.);
    }
    gl_FragColor = vec4(c ,1.);
}

次は「青海波」です。
鱗紋様の特殊なパターンで重なり合う扇が波のように見えます。

glslコードによる実装例は以下。

#ifdef GL_ES
precision mediump float;
#endif

#define PI 3.14159265358979323846

uniform vec2 u_resolution;
uniform float u_time;

void main (void) {
    vec2 st = gl_FragCoord.xy/u_resolution.xy;

    st *= 5.;
    st = fract(st);
    st *= 2.;
    float index = 0.;
    index += step(1., mod(st.x,2.));
    index += step(1., mod(st.y,2.))*2.;
    st = fract(st);

    // index
    // 0.:BL 1.:BR 2.:TL 3.:TR  
    if(index == 1. || index == 3.){
        st.x = 1.-st.x;
    }

    float t;
    float d;
    if((d = distance( vec2(1., -.5), st)) < 1.) {
        t = abs(cos(d*2.65*PI));
    }
    else if ((d = distance( vec2(0., 0.), st)) < 1.){
        t = abs(cos(d*2.65*PI));
    }
    else{
        t = abs(cos(distance(vec2(1., .5), st)*2.65*PI));
    }

    // c is color vector (r, g, b)
    // white is (1., 1., 1.) and black is (0., 0., 0.)
    vec3 c = vec3(1., 1., 1.);
    if (t > 0.5){
        c = vec3(0., .5, 1.);
    }
    gl_FragColor = vec4(c ,1.);
}

もう一つ「鱗」紋様の変種。
竜の鱗をイメージしたものです。

glslコードによる実装例は以下。

#ifdef GL_ES
precision mediump float;
#endif

#define PI 3.14159265358979323846

uniform vec2 u_resolution;
uniform float u_time;

void main (void) {
    vec2 st = gl_FragCoord.xy/u_resolution.xy;

    st *= 5.;
    st = fract(st);
    st *= 2.;
    float index = 0.;
    index += step(1., mod(st.x,2.));
    index += step(1., mod(st.y,2.))*2.;
    st = fract(st);

    // index
    // 0.:BL 1.:BR 2.:TL 3.:TR  
    if(index == 1. || index == 2.){
        st.x = 1.-st.x;
    }

    float t;
    float d;
    if ((d = distance(vec2(0., 0.), st)) < 1.){
        t = abs(sin(d*3.15*PI));
   }
    else{
        t = abs(sin(distance(vec2(1., st.y), vec2(st.x, 1.))*3.15*PI));
    }

    // c is color vector (r, g, b)
    // white is (1., 1., 1.) and black is (0., 0., 0.)
    vec3 c = vec3(1., 0., 0.);
    if (t > 0.5){
        c = vec3(0., 0., 0.);
    }
    gl_FragColor = vec4(c ,1.);
}

最後は「籠目」紋様。
編んだ籠の目の意匠です。
六芒星(ダビデの星)が無限に続くので魔除けとしての側面もあります。

glslコードによる実装例は以下。

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

float cross(vec2 v1, vec2 v2) {
    return v1.x*v2.y - v1.y*v2.x;
}

void main (void) {
    vec2 st = gl_FragCoord.xy/u_resolution.xy;

    st.x *= 10.;
    st.y *= 6.;
    st = fract(st);
    st *= 2.;
    float index = 0.;
    index += step(1., mod(st.x,2.));
    index += step(1., mod(st.y,2.))*2.;
    st = fract(st);
    st -= 0.5;

    // index
    // 0.:BL 1.:BR 2.:TL 3.:TR  
    if(index == 1. || index == 2.){
        st.x = -st.x;
    }

    float l1=abs(cross(st,normalize(vec2(1.,1.))));
    float l2=length(vec2(0., st.y));

    // c is color vector (r, g, b)
    // white is (1., 1., 1.) and black is (0., 0., 0.)
    vec3 c = vec3(1., .25, .25);
    if (l1 < .12 || l2 < .07){
       c = vec3(1., 1., .5);
    }
    gl_FragColor = vec4(c ,1.);
}

いかがでしょうか。
伝統的な紋様が比較的簡単なglslコードで作成できました。

最近、鬼滅の刃の影響で日本伝統の柄も良いなあと思っています。
同士たる諸兄姉も登場人物のイラストに和テイストの服を着せてみてはいかがでしょうか?
晒したglslコードでは変数 vec3 c がカラーコードです。
お好みの色に変更すると良いかと。


SunnySideUp UnityChan !!


この七月七日、七夕にUnityChanの新アセットがリリースされました。
SunnySideUp UnityChan !!

HDRP用とURP用にそれぞれのアセットパッケージが用意されているようです。
少々気づくのが遅れましたがURP版のサンプルを実行してみました。
良い感じに踊ってくれます。
せっかくなのでWebGLでビルドしたものを以下に設置しておきます。
少なくともWindows10のChromeでは動作しました。
※私のAndroidスマホではメモリが足りず、動作しないようです。

インナーの胸当たりの電飾が非常に良いです。

UNITY+WebAssembly+WebGLの組み合わせって可能性を感じます。
夢を叶えられる気がしますよね?
このクラスのコンテンツを作るのは至難なんですけれど。

この作品はユニティちゃんライセンス条項の元に提供されています。


UNITYのWebGLコンテンツからパノラマ画像をダウンロードする


UNITYのWebGLコンテンツで3DコンテンツをWEBブラウザで稼働させ、そのコンテンツからパノラマ画像を生成し、ローカルPCに画像を保存する方法についてです。

目的はUNITYで作成したWebGLコンテンツをWEB上で公開するにあたり、ユーザーが自由にゲーム中のシーンのパノラマ画像を取得できるインターフェースを提供することです。

記事でのUNITYバージョンは2019.1.6f1、WebGL Build Supportとします。
また、環境はWindows10、ブラウザはChromeとします。

[File:Build Settings]を開きます。
[Platform]の項目からWebGLを選択します。
右下にある[Switch Platform]をクリックし切り替えます。

これでWebGLコンテンツのビルドができるようになります。
試しに素の状態でbuildができるか試してみます。
※結構時間がかかりますが、工程を進めてbuildできないということになるとダメージ大なので試してみることをお勧めします。

[File:Build and Run]を開きます。
現在のプロジェクトのフォルダが開きますので、例えば[build]というディレクトリを作成します。

[build]のディレクトリを選択し[フォルダーの選択]をクリックするとビルドが開始します。
PCのグレードによりますが、かなり待つとChromeでWebGLコンテンツが開きます。

素の状態なのでSkyBoxとダイレクトライト、Main Cameraしかありません。
したがって上記のような画像が表示されるだけです。

[Build and Run]でWebGLビルドを行うと初回だけなぜかChromeで実行できるのですが、二回目以降ではローカルのビルド成果物をChromeで実行できません。
動作確認を行うにはWebサーバーにアップロードしてChromeで確認しています。

パノラマ画像の作成ですが、「360-screenshot-capture」という無料アセットを使わせていただきます。

https://assetstore.unity.com/packages/tools/camera/360-screenshot-capture-112864

インポートします。

空のゲームオブジェクトを作成します。

適当に分かりやすい名前に変更します。
ここではEmptyGameObjectとします。

適当なアクセス可能なフォルダに”Panorama.cs”というスクリプトファイルを生成します。
今回はAssets/Plagin/Simple360Render配下に生成します。

“Panorama.cs” を以下のように編集します。

using System.IO;
using UnityEngine;
#if UNITY_WEBGL
using System.Runtime.InteropServices;
#endif

public class Panorama : MonoBehaviour
{
#if UNITY_WEBGL
    [DllImport("__Internal")]
    private static extern void FileDownLoad(byte[] bytes, int size, string filename);
#endif

    public int imageWidth = 4096;
    public string filename = "skyonsea_panorama.png";
    private bool saveAsJPEG = false;

    // LateUpdate is called once per frame
    void LateUpdate()
    {
        if (Input.GetKeyDown(KeyCode.P))
        {
            byte[] bytes = I360Render.Capture(imageWidth, saveAsJPEG);
            if (bytes != null)
            {
#if UNITY_EDITOR
                string path = Path.Combine(Application.persistentDataPath, filename);
                File.WriteAllBytes(path, bytes);
                Debug.Log(filename + " has been saved into " + path);
#elif UNITY_WEBGL
                FileDownLoad(bytes, bytes.Length, filename);
#endif
            }
        }
    }
}

Assets/Plugins/WebGL というディレクトリを作成し、以下のコードを “FileDownload.jslib” というファイルネームで保存します。

var FileDownloadPlugin = {
    FileDownLoad: function(pImage , size, pStr) {
        var mimeType = 'data:image/png;base64';
        var filename = Pointer_stringify(pStr);
        var binary = new ArrayBuffer(size);
        var dv = new DataView(binary);

        for (var i = 0; i < size; i++)
            dv.setUint8(i, HEAPU8[pImage + i]);
        var blob = new Blob([binary], {type : mimeType});
        const a = document.createElement('a');
        a.href = window.URL.createObjectURL(blob);
        a.download = filename;
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
    }
}
mergeInto(LibraryManager.library, FileDownloadPlugin);

Panorama.csをEmpty EmptyGameObjectにアタッチします。

この状態でUnity Editorを再生すると[p]キーでパノラマキャプチャすることができます。

キャプチャされた画像の格納場所はUnity Editorの最下段にデバッグ情報として表示されます。
設定にもよりますが、以下のディレクトリにskyonsea_panorama.pngとして保存されると思います。

C:/Users/%USERNAME%/AppData/LocalLow/DefaultCompany/%PROJECTNAME%

画像は例えば以下のようなものです。

この画像はシーンのパノラマ画像です。
背後に有ったSkyBoxの太陽が見えます。
WebGLとしてビルドしたものをChromeで実行しても、同様にパノラマ画像をダウンロードすることができます。

左下をご確認ください。
ダウンロードディレクトリにパノラマ画像がダウンロードされていると思います。

さてこれでパノラマ画像をダウンロードする仕組みはできました。
後はシーンを作り上げていけば良いことになります。

以下は作例です。

この作例では以下の有料アセットを使っています。

aquas-water-river-set
massive-clouds-screen-space-volumetric-clouds

このWebGLコンテンツをクリック/マウスオーバーしてから[p]キーを押下してください。
Windows10のChromeならパノラマ画像がダウンロードできるかと思います。

如何でしょうか?
ゲーム等にパノラマ画像をダウンロードさせるインターフェースを簡単に設置できることがお分かり頂けたかと存じます。

ただし注意事項があります。
なんでもがパノラマ画像としてキャプチャされるわけではないようなのです。
WebGLとしてビルド可能であることは必須なのですが、例えWebGLとして表示できていたとしてもパノラマ画像としてキャプチャできない例に遭遇します。

例えば私はAQUASのアセットを持っていて、無料アップグレード特典で入手したAQUAS 2020も持っているわけです。
当然AQUAS 2020の方が高機能なのでAQUAS 2020を使ったWebGLコンテンツでパノラマ画像を取得したいのです。
しかしなぜかAQUAS2020のエフェクトがパノラマ画像に反映されません。
理由が分かる方、お教え頂けると助かります。

UNITYはひと時も同じ状態で留まりません。
バージョン毎の差分、アセットの対応バージョン、組み合わせなどが多すぎて上手くいかないことが多いと痛感します。

前はこれでできたから、と思って新作アセットと組み合わせようと思うと、意味不明のエラーメッセージが出力される。
やっとの思いで解決しても、最新のバージョンではまた別の問題に直面する。
その度にググり、ログを見て原因を探り、解決したり諦めたり。

最近では諦めることが多いです。
実を言うと今回の記事、Unity-Chan “Candy Rock Star”でやろうかなと思っていたのですが、 UNITY 2019のWebGLでBuildできず諦めています。

とは言え、簡単に背景を作ることとだけに着目するとそれほど大きな変化が無いので背景作成ツールとしては長いこと使い続けています。

この記事がだれかのお役に立てば幸いです。


挿絵画家になろう(その11)


「挿絵画家になろう(その11)」です。
これまでBlenderのアドオン、MB-Labの啓蒙として記事を連ねてきました。
MB-Labを使って秀逸な少女キャラクターのメッシュオブジェクトを生成し、VRoidStudioの髪をインポートして装着させ、そこそこの服を着せ、キャラクターのマテリアルも弄れるようになり、表情も変更できるようになり、同梱のポーズを組み合わせて新しいポーズも作れるようになりました。
そして服に関しても、まあそれなりに用意できるようになりました。

そろそろ背景が欲しいな、と思います。
単に背景と言っても色々です。
単色塗りつぶしやグラデーションハイライトといった2Dペイントツールでの描画でも良いし、当然ガチンコで屋内屋外の風景を描いても良いです。

ただ、我々は本来「(自称)小説家」です。
自分の小説に挿絵を付けることが目的です。
当然挿絵を書く時間は最小限に留め、その分小説執筆に時間を割きたいですよね?
そのためのいくつかのソリューションを考えました。
ただ残念ながら無料ではありません。
無料でもできなくはないですが、そこはクオリティとのトレードオフになりますね。

本記事はWindows10のChrome推奨です。
その他の環境でどうなるのかは検証していません。
以下のWEBGLコンテンツは参照できますでしょうか?

これはUNITYで作成したWEBGLコンテンツです。
正常に動作していれば、海と空の動く風景が表示されます。
海は波たち、空には雲が流れます。
このような風景が簡単に作れてしまうのです。
UNITY自体は(個人ユースであれば)無料で使えるのですが、今回は以下の有料アセットを使っています。

aquas-water-river-set
massive-clouds-screen-space-volumetric-clouds

屋外風景を考えるに、先ず超遠景としての空や大地もしくは海があり、それよりも近い遠景があり、地形を特徴付ける中近景があり、周囲環境があるわけです。
我々は人物モチーフをMB-Labの3Dキャラクターにする決心をしました。
では風景もある程度は3Dで固めていきましょう。
そのための超遠景に関して先ずは考えよう、というわけです。

上のWEBGLコンテンツをChrome上でマウスオーバーしてアクティブにし、[p]キーを押してみてください。
skyonsea_panorama.png という画像ファイルがダウンロードされれば期待動作です。
適当な画像ビューアで開くと以下のようなパノラマ画像であることが分かります。

上のWEBGLコンテンツは空と海の風景をパノラマ画像として生成するツールでもあるのです。
タイミングを選んで [p]キー を押すことにより、希望の雲の画像を得ることができるのです。

3Dキャラクターを最終的にどのプラットフォームで作品に仕上げるのかは検討すべき課題です。
ただし、今回は結論を出しません。
と言うか、空と海をUNITYで作っておきながら、今回はUNITYでどのように風景を作るのか、という話をしたい訳でもありません。
あくまでも風景画像をBlenderで表示してとりあえずは空と海がある風景の中、3Dキャラクターをレンダリングする方法に関して述べるだけです。

さて、パノラマ画像のサンプルを得ることができました。
この画像を任意のプラットフォームに持っていき、背景に使えば良いわけです。
UNITYのSkyBoxに再移植することもできますし、Daz Studio等、たいていの3Dインテグレーションツールで表示させることができます。
今回はBlenderの背景にする方法を説明したいと思います。

Blenderでの設定方法は簡単です。
先ずオブジェクトモードであることを確認してプロパティウインドウのワールド(青い円のマーク)を選択します。

「サーフェス」の項の「ノードを使用」をクリックします。

「サーフェス」の項に背景が設定されました。

「スクリーンレイアウトの選択」から「compositing」を選択します。

「ノード」を「マテリアル」、「シェーダーを取得するためのデータタイプ」を「ワールド」に切り替えます。

[shift+a]で現れる「追加」メニューから以下を追加します。
・テクスチャ:環境テクスチャ
・ベクトル:マッピング
・入力:テクスチャ座標
そして以下のように連結させます。

環境テクスチャにパノラマ画像を読み込みます。
3Dビューの「3Dビューのシェーディング」で「レンダー」を選択すると背景が描画されます。
もし青っぽくなるけれど全景が表示されないという場合は投影方式が「平行投影」となっています。
テンキーの[5]キーを押して「透視投影」に切り替えます。

後はノードの「マッピング」を調整して背景画像を調整します。
色々極端な設定にすると変化があって面白いです。

MB-Labのキャラクターの背景にしてみました。

いかがでしょうか?
パノラマ画像があれば、それを背景に設定できました。
別に普通の画像でも背景にできますが、パノラマ画像だとアングルやズームが思いのままですので自由度が高いのが良いです。

問題はどのようにしてパノラマ画像を入手するかです。
WEB上では無料素材としてパノラマ画像を公開していらっしゃる方もおられます。
気に入るものがあればそれを使うのでも良いでしょう。
気に入ったものが無いのならば自分で作成するという手もあります。
TwinMotionでも天候を決めてパノラマ画像で出力することができます。
素材を用意できるのならばTwinMotionで背景画像を作るのは良い選択かもしれません。

今回UNITYを使いましたが、正直なところ小説家になろうの諸兄姉にはお勧めしていません。
理由は満足する画像を作れるようになるまでのハードルが高いことです。
無料で希望する絵作りをするにはかなりスキルを磨く必要があります。
もしくは有料アセットを購入する必要があります。
たとえ有料アセットを購入したところで、バージョンやアセット毎の組み合わせがあり、なんかよく分からない障害にぶつかります。
UNITYってバージョンアップが激しくて古いアセットは新しいバージョンで使えなくなっていることが多いんですよね……。
あのアセットとこのアセットを組み合わせれば、と思いホクホクしていると「なんでこうなる?」ということが多々あり、解決のためにログ見たりググったり、そして夜は更けてゆく。
好きなら良いんですが、誰もがそんなマゾという訳じゃないですよね?
UNITYって本当によく分からない。

上のWEBGLコンテンツで生成した画像は小説の挿絵背景としてならば自由にお使い頂いて構いません。

今回はこれで終わりです。
UNITYでWEBGLコンテンツとしてパノラマ画像を生成・ダウンロードする方法に関しては別方面で需要があるかもしれません。
機会があれば別記事にまとめたいと思っています。
また、時間があれば[その12]を書きたいと思います。


Blenderのシェーダーお絵かき


今回は完全に自分用のメモです。
Twitterを見ていて、Blenderのシェーダーノードでお絵かきしている方がいらっしゃったので追実験。

https://twitter.com/nakanasinokusa/status/1186709050114760704

Blender 2.79bしか使えないのでノードは自作です。

なお、twの元ネタは以下であるようです。
楽しい!Unityシェーダー お絵描き入門!

Blenderで平面ポリゴンを追加して、その平面にシェーダーノードで図形を描写します。
凄く面白いアイデアであると思います。

構造は意外と簡単です。

オブジェクトのテクスチャ画像からx,y各座標を取り出し、中央からの距離と角度を分離します。
そして距離と角度を入力パラメータとする関数を作成し て濃度グラフを作成します。
濃度にカラーランプノード等で閾値の範囲を決め、シェーダーで描写します。
等高線を取り出すノリですね。

fract, distance, angleの三つの ノードグループを作っています。
取り出すベクトルがレングス1.0に正規化されている前提としています。

fractは入力をオブジェクトのテクスチャ座標ベクトルとします。
各xyが周期1、レンジ-1.0~1.0の信号であると仮定し、任意倍数に逓倍し、新しいxy座標を個別に出力します。
出力の範囲は-1.0~1.0の実数です。

\(Fract(x,a) = mod(x+1.0 , \frac{2}{a})・\frac{a}{2} – 0.5\\
Fract(y,a) = mod(y+1.0 , \frac{2}{a})・ \frac{a}{2} – 0.5\)

distanceは各xy座標を値として受け取り、原点からの距離を求めます。
出力の範囲は0.0~ \(\sqrt{2}\) の実数です

\(Distance(x,y)=\sqrt{x^2+y^2}\)

グループノードと出力をそのまま画像出力した例を以下に示します。

原点がゼロで値がゼロ、離れるに従い線形に値が増加してゆきます。
同じ距離であれば同じ値になります。
色表現的な意味では四隅は飽和していることに注意が必要です。

distanceを用いれば数式により同心円系の図形が描けます。

\(Func(s) = abs(sin(16πs))\)

angleは 各xy座標を値として受け取り、 原点との角度を計算します。
出力の範囲は \( 0.0~2π \)です。

\(Angle(x,y)=\\
 cos^{-1}(\frac{x}{\sqrt{x^2+y^2} }) (if y ≧ 0)\\
Angle(x,y)=\\
 cos^{-1}(\frac{-x}{\sqrt{x^2+y^2} })+π (if y < 0)\)

グループノードと出力を \( 2π \)で除して画像出力した例を以下に示します。

同じ角度であれば同じ値になり、反時計回りに値が増加してゆきます。

後はもう一つのノードグループfunc1に適当な関数を記述すれば良いわけです。

\(func1(s)=abs(cos(2.5s))\)

関数を設定してカラーランプノードを弄れば、抜きの入った集中線も実現できます。

\(func1(s)=abs(cos(100s))\)

桜の花びら。

\(Func2(s) = min(\\
  abs(cos(2.5x))+0.4 ,\\
  abs(sin(2.5x))+1.1)・0.240\)

この作例ではDistance系とAngle系は乗算ではなく加算しています。

fractノードの入力を変えることにより任意の縮小繰り返しができます。

数式により任意の図形を作成し、それを繰り返しパターンにできるのです。

シェーダーノードはプログラム環境として機能し、様々な図形を描画できるところがとても興味深いです。

参考までにblenderファイルを置いておきます。
shader_node_sample.blend