UnityのUnlitシェーダーで軽率にRaymarchingを始めるハンズオン
Unityでお手軽にRaymarchingを始めましょう !
Compute Shaderを使ってUnityシーンでやると手間ですが、今回はUnlitシェーダーにRaymarchingを描いて、一枚絵を作っていきます。
https://gyazo.com/e7dfdc4b0332cc42d2f603dcd85d9d92
座標、カメラ、レイ、レイマーチングループ、法線、ライティング(ランバート)を扱います。
変数と配列を読み取るのに不慣れであれば、数字の個数がいくつかに注目すると良いです。
0.0から1.0までの数字を作ります。
流れを見失った時は途中経過を丸ごとコピーすればリカバリーできます。
コード例(途中経過)
code:HLSL
Shader "Unlit/RaymarchingPractice#1"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
sampler2D _MainTex;
float4 _MainTex_ST;
float dSphere(float3 p) {
return length(p) - 1;
}
float map(float3 p) {
float s = dSphere(p - float3(0.5, 0.5, 0));
return min(s,s);
}
fixed4 frag(v2f_img i) : SV_Target
{
float2 uv = 2 * i.uv - 1;
float3 p = float3(uv, 0);
fixed4 col = (1).xxxx;
float3 cam = float3(0, 0, -3);
float3 up = float3(0,1,0);
float3 fwd = normalize(-cam);
float3 side = normalize(cross(up, fwd));
float3 rayPos = cam;
float3 rayDir = p.x * side + p.y * up + fwd;
float d = 0;
for (int j = 0; j < 16;j++) {
d = map(rayPos);
if (d < 0.0001) break;
rayPos += d * rayDir;
}
if (d < 0.0001) {
return (1).xxxx;
}
uv += 0.5 * sin(_Time.y) + 0.5;
col.rgb = float3(uv, pow(1 - dot(uv, uv), 0.2));
return col;
}
ENDCG
}
}
}
手順
順にやっていこう!
カメラを置く
https://gyazo.com/186a904212e56e66a0c17ff3cb49da1b
板ポリを置く
https://gyazo.com/231366b2b382d2dd71b1e6b680bd43c6
https://gyazo.com/d9ae10a0bad6cd3ff0182fcb35017809
Unlitシェーダーを作ってマテリアルを板ポリに割り当てる
https://gyazo.com/5ef70e2a18048d2760a036940b41fa33
Unlitシェーダーをエディタで開く
https://gyazo.com/4cb472e09ae392aff5954f1a146e8914
デフォルトのシェーダー内容から不要な部分を取り除く
以下リンクの小さなサイズにしたUnlitシェーダーをコピーして上書きすると楽です。
とりあえずピンクにしてみる
code:HLSL
fixed4 frag(v2f_img i) : SV_Target
{
}
とりあえず黒にしてみる
code:HLSL
fixed4 frag(v2f_img i) : SV_Target
{
fixed4 col = (0).xxxx;
return col;
}
板ポリを3次元っぽく扱えるようにしてみる
code:HLSL
fixed4 frag(v2f_img i) : SV_Target
{
fixed4 col = (0).xxxx;
float2 uv = 2 * i.uv - 1;
float3 p = float3(uv, 0);
return col;
}
ここから先はしばらく絵に変化がなくて寂しいのでなんか背景を描いてみる
code:HLSL
fixed4 frag(v2f_img i) : SV_Target
{
fixed4 col = (1).xxxx;
float2 uv = 2 * i.uv - 1;
float3 p = float3(uv, 0);
col.rg = uv;
col.b = dot(uv, uv);
return col;
}
アレンジしてみる。
col.b = 1 - dot(uv, uv)
さらにアレンジしてみる。
col.b = pow(dot(uv, uv), 0.2);
動きが欲しいので背景を動かしてみる
code:HLSL
uv = 2 * i.uv - 1 + frac(_Time.y);
後で使うかもしれない関数を書き足す
code:HLSL
float2 rot(float2 p, float a) {
return float2(p.x * cos(a) - p.y * sin(a), p.x * sin(a) + p.y * cos(a));
}
float mod(float x, float y) {
return x - floor(x / y) * y;
}
float2 mod(float2 x, float2 y) {
return x - floor(x / y) * y;
}
float3 mod(float3 x, float3 y) {
return x - floor(x / y) * y;
}
float4 mod(float4 x, float4 y) {
return x - floor(x / y) * y;
}
// 他には?
// noise, HSV
途中経過
code:HLSL
// ↑この上に各種関数がある
fixed4 frag(v2f_img i) : SV_Target
{
// 座標の初期設定
float2 uv = 2 * i.uv - 1;
float3 p = float3(uv, 0);
// 色
fixed4 col = (1).xxxx;
// 背景
uv = rot(uv, _Time.y);
uv += sin(_Time.y);
col.rg = uv;
col.b = pow(1 - dot(uv, uv), 0.4);
return col;
}
カメラを置く
code:HLSL
float2 uv = 2 * i.uv - 1;
float3 p = float3(uv, 0);
// カメラ
float3 cam = float3(0, 0, -3);
3つの軸を作る
code:HLSL
float3 cam = float3(0, 0, -3);
// 上、前、横
float3 up = float3(0,1,0);
float3 fwd = normalize(-cam);
float3 side = normalize(cross(up, fwd));
レイの初期座標を決める
float3 rayPos = cam;
レイが進む向きを決める
float3 rayDir = p.x * side + p.y * up + fwd
map関数を作って球を描けるようにしてみる
frag関数の上に追記。
code:HLSL
float map(float3 p) {
float s = length(p - (1).xxx);
return s;
}
float s = length(p - float3(0.5, 0.5, 0));
code:HLSL
float dSphere(float3 p) {
return length(p - float3(0.5, 0.5, 0)) - 0.5;
}
float map(float3 p) {
return dSphere(p);
}
レイマーチするループを作る
レイを定義した次の行に追記。
code:HLSL
for(int j=0; j<16; j++) {
float d = map(p);
if(d < 0.0001) break;
rayPos += d * rayDir;
}
mapの結果を使って白で描いてみる
code:HLSL
if(d < 0.0001) {
return (1).xxxx;
}
途中経過
code:HLSL
// ↑この上に略
float dSphere(float3 p) {
return length(p) - 0.5;
}
float map(float3 p) {
return dSphere(p - float3(0.5, 0.5, 0));
}
fixed4 frag(v2f_img i) : SV_Target
{
float2 uv = 2 * i.uv - 1;
float3 p = float3(uv, 0);
fixed4 col = (1).xxxx;
float3 cam = float3(0, 0, -3);
float3 up = float3(0,1,0);
float3 fwd = normalize(-cam);
float3 side = normalize(cross(up, fwd));
// レイ
float3 rayPos = cam;
float3 rayDir = p.x * side + p.y * up + fwd;
// レイマーチングループ
float d = 0;
for (int j = 0; j < 16; j++) {
d = map(rayPos);
if (d < 0.0001) break;
rayPos += d * rayDir;
}
// レイが何か(mapで決められた形)に衝突した
if (d < 0.0001) {
return (1).xxxx;
}
// 背景(レイは衝突しなかった)
uv += 0.5 * sin(_Time.y) + 0.5;
col.rgb = float3(uv, pow(1 - dot(uv, uv), 0.2));
return col;
}
動かしてみる
code:HLSL
float map(float3 p) {
float3 q = p;
q.xy += sin(_Time.y + UNITY_PI / 2);
return dSphere(q);
}
立体感を出すために法線を使ってみる
map関数の下に追記。
code:HLSL
float3 normal(float3 p) {
float EPSILON = 0.0001;
return normalize(float3(
map(float3(p.x + EPSILON, p.y, p.z)) - map(float3(p.x - EPSILON, p.y, p.z)),
map(float3(p.x, p.y + EPSILON, p.z)) - map(float3(p.x, p.y - EPSILON, p.z)),
map(float3(p.x, p.y, p.z + EPSILON)) - map(float3(p.x, p.y, p.z - EPSILON))
));
}
code:HLSL
// レイマーチングループ
float d = 0;
float3 N;
for (int j = 0; j < 16; j++) {
d = map(rayPos);
if (d < 0.0001) {
N = normal(rayPos);
break;
}
rayPos += d * rayDir;
}
code:HLSL
// レイが何か(mapで決められた形)に衝突した
if (d < 0.0001) {
return fixed4(abs(N), 1);
}
軽い法線
code:HLSL
float3 normal(float3 pos) {
float2 e = float2(1.0, -1.0) * 0.5773 * 0.0001;
return normalize(e.xyy * map(pos + e.xyy) +
e.yyx * map(pos + e.yyx) +
e.yxy * map(pos + e.yxy) +
e.xxx * map(pos + e.xxx));
}
途中経過
code:HLSL
Shader "Unlit/RaymarchingPractice#1"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
sampler2D _MainTex;
float4 _MainTex_ST;
float2 rot(float2 p, float a) {
return float2(p.x * cos(a) - p.y * sin(a), p.x * sin(a) + p.y * cos(a));
}
float dSphere(float3 p) {
return length(p) - 0.5;
}
float map(float3 p) {
float3 q = p;
q.xy += sin(_Time.y + UNITY_PI / 2);
float s = dSphere(q - float3(0.5, 0.5, 0));
return s;
}
float3 normal(float3 pos) {
float2 e = float2(1.0, -1.0) * 0.5773 * 0.0001;
return normalize(e.xyy * map(pos + e.xyy) +
e.yyx * map(pos + e.yyx) +
e.yxy * map(pos + e.yxy) +
e.xxx * map(pos + e.xxx));
}
fixed4 frag(v2f_img i) : SV_Target
{
float2 uv = 2 * i.uv - 1;
float3 p = float3(uv, 0);
fixed4 col = (1).xxxx;
float3 cam = float3(0, 0, -3);
float3 up = float3(0,1,0);
float3 fwd = normalize(-cam);
float3 side = normalize(cross(up, fwd));
float3 rayPos = cam;
float3 rayDir = p.x * side + p.y * up + fwd;
float d = 0;
float3 N;
for (int j = 0; j < 16; j++) {
d = map(rayPos);
if (d < 0.0001) {
N = normal(rayPos);
break;
}
rayPos += d * rayDir;
}
if (d < 0.0001) {
return fixed4(abs(N), 1);
}
uv += 0.5 * sin(_Time.y) + 0.5;
col.rgb = float3(uv, pow(1 - dot(uv, uv), 0.2));
return col;
}
ENDCG
}
}
}
ライトの位置と色を決めてみる
float3 L = normalize(float3(-1, -1, 1));
float3 Lcol = float3(0.9, 0.7, 0.4);
ライトと法線の角度を元にライティングしてみる
code:HLSL
if (d < 0.0001) {
float3 L = normalize(float3(-1.0, 0.5, -0.2));
float3 Lcol = float3(2.6, 2.3, 1.4);
return fixed4(Lcol * dot(L, N) * 0.5 + 0.5, 1); // Half Lambert
}
そこはかとなくいじっておしまい
code:HLSL
Shader "Unlit/RaymarchingPractice#1"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
sampler2D _MainTex;
float4 _MainTex_ST;
float3 mod(float3 x, float3 y) {
return x - floor(x / y) * y;
}
float2 rot(float2 p, float a) {
return float2(p.x * cos(a) - p.y * sin(a), p.x * sin(a) + p.y * cos(a));
}
float dSphere(float3 p) {
return length(p) - 1;
}
float map(float3 p) {
float3 q = p;
q.x -= sin(_Time.y + UNITY_PI / 2);
float s = dSphere(q - float3(0.5, 0.5, 0));
return s;
}
float3 normal(float3 pos) {
float2 e = float2(1.0, -1.0) * 0.5773 * 0.0001;
return normalize(e.xyy * map(pos + e.xyy) +
e.yyx * map(pos + e.yyx) +
e.yxy * map(pos + e.yxy) +
e.xxx * map(pos + e.xxx));
}
fixed4 frag(v2f_img i) : SV_Target
{
float2 uv = 2 * i.uv - 1;
float2 uv2 = uv + 0.5 * sin(_Time.y) + 0.5;
uv2.x += sin(floor(4 * uv2.y) - _Time.y);
uv2.x += cos(floor(4 * uv2.x) + _Time.y);
float3 p = float3(uv + step(0.4, frac(_Time.y)) * (uv2 - uv), 0);
fixed4 col = (1).xxxx;
float3 cam = float3(0, 0, -3);
float3 up = float3(0,1,0);
float3 fwd = normalize(-cam);
float3 side = normalize(cross(up, fwd));
float3 rayPos = cam;
float3 rayDir = p.x * side + p.y * up + fwd;
float d = 0;
float3 N;
for (int j = 0; j < 16; j++) {
d = map(rayPos);
if (d < 0.0001) {
N = normal(rayPos); // 衝突した場合は法線を算出
break;
}
rayPos += d * rayDir;
}
if (d < 0.0001) {
float3 L = normalize(float3(-1.0, 0.5, -0.2));
float3 Lcol = lerp(float3(2.6, 2.3, 1.4), float3(1.4, 2.6, 2.0), sin(_Time.y));
return fixed4(pow(dot(L, N) * 0.5 + 0.5, 3) * lerp(Lcol, 1.2 * Lcol, 1 * (0.1 + 0.2 * N)), 1);
}
col.rgb = float3(1.2 + pow(abs(uv + dot(uv, uv)), 2), pow(1 - dot(uv2, uv2), 0.2));
col.rgb = lerp(col.rgb, 1 - col.rgb, frac(_Time.y));
return col;
}
ENDCG
}
}
}
https://gyazo.com/094ae9d2c12fd29dbd0352f3edaa0be3
https://gyazo.com/045f9c9af58c5e39e8eb6384715f4633
もう少しアレンジしてみる
code:HLSL
Shader "Unlit/RaymarchingPractice#1"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 100
Pass
{
CGPROGRAM
sampler2D _MainTex;
float4 _MainTex_ST;
float3 mod(float3 x, float3 y) {
return x - floor(x / y) * y;
}
float2 rot(float2 p, float a) {
return float2(p.x * cos(a) - p.y * sin(a), p.x * sin(a) + p.y * cos(a));
}
float dSphere(float3 p) {
float m = length(mod(p, 4) - 2) - 0.5; // modを使って繰り返し
float s = length(p - 1) - 1.8;
return s + step(abs(cos(_Time.y)), 0.5) * (m-s);
}
float map(float3 p) {
float3 q = p;
q.x -= sin(_Time.y + UNITY_PI / 2);
float s = dSphere(q - float3(0.5, 0.5, 0));
return s;
}
float3 normal(float3 pos) {
float2 e = float2(1.0, -1.0) * 0.5773 * 0.0001;
return normalize(e.xyy * map(pos + e.xyy) +
e.yyx * map(pos + e.yyx) +
e.yxy * map(pos + e.yxy) +
e.xxx * map(pos + e.xxx));
}
fixed4 frag(v2f_img i) : SV_Target
{
float2 uv = 2 * i.uv - 1;
float2 uv2 = uv + 0.5 * sin(_Time.y) + 0.5;
uv2.x += sin(floor(4 * uv2.y) - _Time.y);
uv2.x += cos(floor(4 * uv2.x) + _Time.y);
float3 p = float3(uv + step(0.4, frac(_Time.y)) * (uv2 - uv), 0);
fixed4 col = (1).xxxx;
float3 cam = float3(0, 1, -3);
float3 up = normalize(float3(0,1,0));
float3 fwd = normalize(float3(0,0,1));
float3 side = normalize(cross(up, fwd));
float3 rayPos = cam;
float3 rayDir = p.x * side + p.y * up + fwd;
float d = 0;
float3 N;
for (int j = 0; j < 16; j++) {
d = map(rayPos);
if (d < 0.0001) {
N = normal(rayPos);
break;
}
rayPos += d * rayDir;
}
if (d < 0.0001) {
float3 L = normalize(float3(-1.0, 0.5, -0.2));
float3 Lcol = lerp(float3(2.6, 2.3, 1.4), float3(1.4, 2.6, 2.0), sin(_Time.y));
return fixed4(pow(dot(L, N) * 0.5 + 0.5, 3) * lerp(Lcol, 1.2 * Lcol, 1 * (0.1 + 0.2 * N)), 1);
}
col.rgb = float3(1.2 + pow(abs(uv + dot(uv, uv)), 2), pow(1 - dot(uv2, uv2), 0.2));
col.rgb = lerp(col.rgb, 1 - col.rgb, frac(_Time.y));
return col;
}
ENDCG
}
}
}
https://gyazo.com/3c809d6105ff8691972085ee88c63c18
https://gyazo.com/8cc9832c2976dedeaaf2724c28079cf3
https://gyazo.com/88055076043ab313718cedc2a8ed49d7
https://gyazo.com/ad78289403513b5a13a6e7c338245adf