始めに
過去記事です。
色々触ってみてもうちょっと面白いことが出来るんじゃないかと検証してみました。
半分今までの復習みたいな感じです。
プロジェクトはこちらです。
VTF+テッセレーション
VTF(Vertex Texture Fetch)とは頂点シェーダー内でテクスチャを参照することを指すそうです。 (参照: wgld.org | WebGL: 頂点テクスチャフェッチ(VTF) |)
テッセレーションはポリゴンメッシュをさらに分割して表現することを意味します。(参照: テッセレーション - Wikipedia テッセレーション基礎 - しゅみぷろ)
今回はGrayScottで得た模様テクスチャを使って頂点を上下したいと思います。
シェーダープログラム
Shader "Unlit/HeightShader" { Properties{ _TessFactor("Tess Factor", Vector) = (2, 2, 2, 2) _LODFactor("LOD Factor", Range(0, 10)) = 1 _MainTex("Main Texture", 2D) = "white" {} _ParallaxScale("ParallaxScale", Float) = 1 } SubShader{ Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #include "UnityCG.cginc" #include "UnityLightingCommon.cginc" #pragma vertex VS #pragma fragment FS #pragma hull HS #pragma domain DS #define INPUT_PATCH_SIZE 3 #define OUTPUT_PATCH_SIZE 3 uniform vector _TessFactor; uniform float _LODFactor; uniform sampler2D _MainTex; uniform sampler2D _ParallaxMap; uniform float _ParallaxScale; struct appdata { float4 w_vert : POSITION; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; }; struct v2h { float4 pos : POS; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; float4 worldPos : TEXCOORD1; }; struct h2d_main { float3 pos : POS; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; float4 worldPos : TEXCOORD1; }; struct h2d_const { float tess_factor[3] : SV_TessFactor; float InsideTessFactor : SV_InsideTessFactor; }; struct d2f { float4 pos : SV_Position; float2 uv : TEXCOORD0; float3 normal : NORMAL; float3 worldPos : TEXCOORD1; }; struct f_input { float4 vertex : SV_Position; float2 uv : TEXCOORD0; float3 normal : NORMAL; float3 worldPos : TEXCOORD1; }; v2h VS(appdata i) { v2h o = (v2h)0; o.pos = float4(i.w_vert.xyz, 1.0f); o.texcoord = i.texcoord; o.normal = i.normal; o.worldPos = mul(unity_ObjectToWorld, i.w_vert); return o; } h2d_const HSConst(InputPatch<v2h, INPUT_PATCH_SIZE> i) { h2d_const o = (h2d_const)0; o.tess_factor[0] = _TessFactor.x * _LODFactor; o.tess_factor[1] = _TessFactor.y * _LODFactor; o.tess_factor[2] = _TessFactor.z * _LODFactor; o.InsideTessFactor = _TessFactor.w * _LODFactor; return o; } [domain("tri")] [partitioning("integer")] [outputtopology("triangle_cw")] [outputcontrolpoints(OUTPUT_PATCH_SIZE)] [patchconstantfunc("HSConst")] h2d_main HS(InputPatch<v2h, INPUT_PATCH_SIZE> i, uint id : SV_OutputControlPointID) { h2d_main o = (h2d_main)0; o.pos = i[id].pos; o.texcoord = i[id].texcoord; o.normal = i[id].normal; o.worldPos = i[id].worldPos; return o; } [domain("tri")] d2f DS(h2d_const hs_const_data, const OutputPatch<h2d_main, OUTPUT_PATCH_SIZE> i, float3 bary:SV_DomainLocation) { d2f o = (d2f)0; float3 pos = i[0].pos * bary.x + i[1].pos * bary.y + i[2].pos * bary.z; float2 uv = i[0].texcoord * bary.x + i[1].texcoord * bary.y + i[2].texcoord * bary.z; float3 normal = i[0].normal * bary.x + i[1].normal * bary.y + i[2].normal * bary.z; float3 worldPos = i[0].worldPos * bary.x + i[1].worldPos * bary.y + i[2].worldPos * bary.z; // 表面の凹凸を計算 float parallax = tex2Dlod(_MainTex, float4(uv.xy, 0, 0)).r; float parallaxHeight = parallax * _ParallaxScale; pos += parallaxHeight * normal; o.pos = UnityObjectToClipPos(float4(pos, 1)); o.uv = uv; o.normal = UnityObjectToWorldNormal(normal); o.worldPos = worldPos; return o; } float4 FS(f_input i) : SV_Target{ // テクスチャマップからカラー値をサンプリング float4 tex = tex2D(_MainTex, i.uv); return float4(tex.r, tex.r, tex.r, 1); } ENDCG } } }
こちらのシェーダーは過去にやった内容を使用しています。
結果
このシェーダーはComputeShaderとCustomRenderTextureの両方に適用できます。
テクスチャ書き込み+動的パラメータ変更
今まではシーンを再生するとパラメータを変更しても模様に変化はありませんでした。
そこで、シーンを再生しながらパラメータを変更するとどうなるのか確かめることが出来るシーンを作成しました。加えて、マウスからテクスチャに値を書き込み出来るようにしました。
プログラムについてですが、突貫で書いた部分が多くあまりまとまっていないのでお見せするのが難しいです。
パラメータの変更はLerpを使って、あるパラメータから別のパラメータへ緩やかに変更している形になります。
テクスチャの書き込みは、RaycastHitを使ってuv座標を受け取りComputeShaderでそこに値を書き込む流れです。
結果
触った感じとても面白いです!
GrayScottで遊んでみたけど想像以上に面白かった pic.twitter.com/OmmhGwyjor
— ひさし (@CCPJ_a) 2020年8月15日
簡単なVTFとの組み合わせ pic.twitter.com/ALdgelFJtu
— ひさし (@CCPJ_a) 2020年8月15日
シェーダー内でパラメータを変更
最後に、CustomRenderTexture内でsin波を流して2つのパラメータが連続して切り替わるシェーダーを作成しました。
やることはテクスチャの中心から広がるようにsin波を流してその値に合わせてパラメータを変更します。 こちらの内容もほぼほぼ前回と同じです。
シェーダープログラム
Shader "Unlit/UpdateGrayScott_Changing" { Properties { _F1("F1", Float) = 0.04 _K1("K1", Float) = 0.06 _F2("F2", Float) = 0.035 _K2("K2", Float) = 0.065 _SimulateSpeed1("SimulateSpeed1", Float) = 1 _SimulateSpeed2("SimulateSpeed2", Float) = 1 [Space(10)] _WaveSpeed("WaveSpeed", Float) = 1 _T("T", Float) = 1 _GridSize("Delta UV", Float) = 1 _Du("Du", Float) = 0.2 _Dv("Dv", Float) = 0.1 } SubShader { Cull Off ZWrite Off ZTest Always Pass { Name "Update" CGPROGRAM #pragma vertex CustomRenderTextureVertexShader #pragma fragment frag #include "UnityCustomRenderTexture.cginc" float _F1; float _K1; float _F2; float _K2; fixed _WaveSpeed; fixed _T; float _SimulateSpeed1; float _SimulateSpeed2; float _GridSize; float _Du; float _Dv; fixed4 frag (v2f_customrendertexture i) : SV_Target { float2 uv = i.globalTexcoord; float distance = (uv.x-0.5)*(uv.x-0.5)+(uv.y-0.5)*(uv.y-0.5); float sinValue = sin(_Time.y * _WaveSpeed - distance * _T); float rate = (sinValue + 1) / 2.0; float f = lerp(_F1, _F2, rate); float k = lerp(_K1, _K2, rate); // 1pxあたりの単位を計算 float du = 1.0 / _CustomRenderTextureWidth; float dv = 1.0 / _CustomRenderTextureHeight; float3 duv = float3(du, dv, 0) * _GridSize; // 現在のテクスチャの取得 float2 c = tex2D(_SelfTexture2D, uv); float u = c.x; float v = c.y; // ラプラスの演算を行う float2 laplacian = tex2D(_SelfTexture2D, uv - duv.zy).xy + tex2D(_SelfTexture2D, uv + duv.zy).xy + tex2D(_SelfTexture2D, uv - duv.xz).xy + tex2D(_SelfTexture2D, uv + duv.xz) - 4.0*float2(u, v); float uvv = u*v*v; // Gray-Scottモデルの反応拡散方程式 float dudt = _Du * laplacian.x - uvv + f * (1.0 - u); float dvdt = _Dv * laplacian.y + uvv - (f + k) * v; float speed = lerp(_SimulateSpeed1, _SimulateSpeed2, rate); return float4(saturate(u + dudt * speed), saturate(v + dvdt * speed), 0, 0); } ENDCG } } }
結果
最後に
これまでにやったことを使って色々遊んでみました。
Gray-Scottを使うだけで面白い模様も作成できますし、インタラクションを追加すればさらに面白いことが出来ると感じました。
これでGray-Scottの内容については終わりになると思います。また、何かしらで興味が出たらまた触るかもしれません。
参考
参考と言っていいかわかりませんが、この動画が今回のブログを書くモチベーションとなりました。