はじめに
最近、OpenSiv3DというC++のツールを触っていて、p5.jsのwebglで書いたコードを落としたいと思ってHLSLを勉強しているところです。一応形になったのでその際にいろいろ気付いたことをまとめたいと思いました。
コード全文
# include <Siv3D.hpp>
// 定数バッファ (PS_1)
struct MyBuffer
{
Float2 FieldSize;
Float2 Mouse;
float time;
};
void Main()
{
// ウィンドウを 1280x720 にリサイズ
Window::Resize(1280, 720);
// セルの数 (1280x720)
constexpr Size FieldSize{ 1280, 720 };
const PixelShader ps = HLSL{ U"myShader/test1.hlsl", U"PS" };
if (not ps)
{
throw Error{ U"Failed to load a shader file" };
}
// 定数バッファ
ConstantBuffer<MyBuffer> cb{ { (Float2{1280.0f, 720.0f}), (Float2{0.0f, 0.0f}), (float{0.0f}) } };
// レンダーテクスチャ 0
RenderTexture renderTexture{ Image{1280, 720, Palette::White} };
float _count = 0.0f;
while (System::Update())
{
Float2 p = Cursor::PosF();
cb->Mouse = p;
cb->time = static_cast<float>(Scene::Time());
{
// 現在の状態を画面に描く
renderTexture.draw(ColorF{ 1.0, 1.0, 1.0 });
{
Graphics2D::SetPSConstantBuffer(1, cb);
const ScopedCustomShader2D shader{ ps };
// 更新後の状態を描く renderTexture1 に描く
const ScopedRenderTarget2D target{ renderTexture };
renderTexture.draw();
}
}
}
}
//-----------------------------------------------
//
// This file is part of the Siv3D Engine.
//
// Copyright (c) 2008-2021 Ryo Suzuki
// Copyright (c) 2016-2021 OpenSiv3D Project
//
// Licensed under the MIT License.
//
//-----------------------------------------------
//
// Textures
//
Texture2D g_texture0 : register(t0);
SamplerState g_sampler0 : register(s0);
// Constants.
static float TAU = 6.28318;
static float PI = 3.14159;
static float3 skyblue = {0.1, 0.65, 0.9}; // これはいいみたいね。定数になるのだろう。
namespace s3d
{
//
// VS Output / PS Input
//
struct PSInput
{
float4 position : SV_POSITION;
float4 color : COLOR0;
float2 uv : TEXCOORD0;
};
}
//
// Constant Buffer
//
cbuffer PSConstants2D : register(b0)
{
float4 g_colorAdd;
float4 g_sdfParam;
float4 g_sdfOutlineColor;
float4 g_sdfShadowColor;
float4 g_internal;
}
cbuffer MyBuffer : register(b1)
{
float2 g_FieldSize;
float2 g_Mouse;
float g_time;
}
// utility.
// getRGB移植できた!modをfmodに変えるだけでよかったんだ・・・
float3 getRGB(float h, float s, float b)
{
float3 c = float3(h, s, b);
float3 rgb = clamp(abs(fmod(h * 6.0 + float3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
rgb = rgb * rgb * (3.0 - 2.0 * rgb);
return c.z * lerp(float3(1.0, 1.0, 1.0), rgb, c.y);
}
void transform(inout float2 p, in float2 c, in float rot)
{
p -= c;
float2x2 mat = float2x2(cos(rot), -sin(rot), sin(rot), cos(rot));
p = mul(p, mat); // さっきはエラーになったのに、どうして。。。。
}
void dihedral_bound(inout float2 p, in float n)
{
float k = PI / n;
float2 e1 = float2(0.0, 1.0);
float2 e2 = float2(sin(k), -cos(k));
for (float i = 0.0; i < 99.0; i += 1.0) {
if (i == n) { break; }
p -= 2.0 * min(dot(p, e1), 0.0) * e1;
p -= 2.0 * min(dot(p, e2), 0.0) * e2;
}
}
/* 図形たち */
// 円(中心cの半径r)
// 長方形(中心c横幅縦幅はq.xとq.y)
// 正方形(中心cで一辺の長さがr)...
float circle(float2 p, float2 c, float r)
{
return length(p - c) - r;
}
float rect(float2 p, float2 c, float rot, float2 q)
{
transform(p, c, rot);
return max(abs(p.x) - q.x * 0.5, abs(p.y) - q.y * 0.5);
}
float square(float2 p, float2 c, float rot, float r)
{
transform(p, c, rot);
return max(abs(p.x) - r * 0.5, abs(p.y) - r * 0.5);
}
float segment(float2 p, float2 c, float rot, float r, float h)
{
transform(p, c, rot);
return length(p - float2(max(-h, min(0.0, p.x)), 0.0)) - r;
}
float trigon(float2 p, float2 c, float rot, float r)
{
transform(p, c, rot);
float2 e1 = float2(0.0, 1.0);
float2 e2 = float2(0.5 * 1.732, -0.5);
for (int i = 0; i < 3; i++) {
p -= 2.0 * min(dot(p, e1), 0.0) * e1;
p -= 2.0 * min(dot(p, e2), 0.0) * e2;
}
return p.x - r;
}
float star(float2 p, float2 c, float rot, float r)
{
transform(p, c, rot);
dihedral_bound(p, 5.0);
float2 e = float2(cos(PI * 0.4), sin(PI * 0.4));
return dot(p - float2(r, 0.0), e);
}
float moon(float2 p, float2 c, float rot, float r)
{
transform(p, c, rot);
return max(length(p) - r, r * 0.65 - length(p - float2(r * 0.5, 0.0)));
}
float arc(float2 p, float2 c, float rot, float theta, float ra, float rb)
{
// theta:(0<theta<PI)は弧の半分のやつの長さ(半径1の場合の)
// raが円の半径でrbは太さね。
// transformが機能しなかった。あれ。機能してる。もうわけわからん・・・!
transform(p, c, rot);
p.y = abs(p.y);
float2 n = float2(cos(theta), sin(theta));
if (p.x * n.y - p.y * n.x > 0.0) {
return abs(length(p) - ra) - rb;
}
return length(p - ra * n) - rb;
}
float getDist(float2 p)
{
float d1 = rect(p, float2(1.2, 0.1), TAU * 0.5 * g_time, float2(0.4, 0.1));
float d2 = rect(p, float2(-0.7, -0.3), TAU * 0.5 * g_time, float2(0.1, 0.4));
float d3 = square(p, float2(0.8, 0.0), TAU * 0.3 * g_time, 0.3);
float d4 = segment(p, float2(0.2, -0.4), -TAU * 0.6 * g_time, 0.05, 0.4);
float d5 = circle(p, float2(-0.7, 0.4), 0.3);
float d6 = segment(p, float2(-1.2, -0.3), -TAU * 0.5 * g_time, 0.07, 0.3);
float d7 = trigon(p, float2(0.9, -0.7), TAU * 0.3 * g_time, 0.1);
float d8 = star(p, float2(1.2, 0.65), TAU * 0.2 * g_time, 0.2);
float d9 = moon(p, float2(-1.25, 0.7), TAU * 0.3 * g_time, 0.25);
float d10 = arc(p, float2(0.4, 0.5), -TAU * 0.3 * g_time, PI * 0.75, 0.3, 0.05);
float d = 99999.0;
d = min(d, min(d1, d2));
d = min(d, min(d3, d4));
d = min(d, min(d5, d6));
d = min(d, min(d7, d8));
d = min(d, min(d9, d10));
return d;
}
float4 PS(s3d::PSInput input) : SV_TARGET
{
float2 p = (input.position.xy * 2.0 - g_FieldSize.xy) / min(g_FieldSize.x, g_FieldSize.y);
float3 col = float3( 0.0, 0.0, 0.0 );
float d1 = getDist(p);
if (d1 < 0.0) {
float sat = exp(d1*24.0);
col = getRGB(0.55, sat, 1.0);
}
float threshold = 0.001;
float2 m = (g_Mouse.xy * 2.0 - g_FieldSize.xy) / min(g_FieldSize.x, g_FieldSize.y);
float2 cur = m;
float2 direction = p - cur;
float l = length(p - cur);
float d = 0.0;
if (l > 0.0) {
direction /= l;
for (float i = 0.0; i < 32.0; i += 1.0) {
d = getDist(cur);
if (d < threshold) { break; }
cur += d * direction;
}
if (length(cur - m) > length(p - m)) {
l = length(p - m);
float blt = (l < 0.5 ? 1.0 : 0.5);
col = getRGB(0.15, 1.0, blt);
}
}
return float4(col, 1.0);
}
HLSLについて
まずfloat4の宣言の仕方について。このように、
float4 x = float4(0.1, 0.3, 0.5, 1.0);
のようにします。{0.1, 0.3, 0.6, 1.0}とかでもいいのですがこれだと再代入できなくなるみたいです(よくわからない・・定数になる?)
グローバルで何か使いたいときは上の方に
static float TAU = 6.28318;
のように書きます。staticがついていると中でGLSLのグローバルみたいに使うことができます。ちなみにstatic付けないとなんか0になってしまい、エラーも出してくれないそうです(参考:シェーダ内に定数を定義したつもりだが値が割り当てられない問題)。
マウスの値を送り込むときはcpp側でCursor::PosF()ってやるとfloat2の値となり、これをそのまま使うことができるようです。
getRGBの構成:
// getRGB移植できた!modをfmodに変えるだけでよかったんだ・・・
float3 getRGB(float h, float s, float b)
{
float3 c = float3(h, s, b);
float3 rgb = clamp(abs(fmod(h * 6.0 + float3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
rgb = rgb * rgb * (3.0 - 2.0 * rgb);
return c.z * lerp(float3(1.0, 1.0, 1.0), rgb, c.y);
}
にもあるようにベクトルの掛け算で成分ごとみたいなことは普通にできるようです。しかしmodではなくfmodを使うのがGLSLと違うところです。あとmixではなくlerpを使います。
transformでは平行移動のあと回転をしていますが・・
void transform(inout float2 p, in float2 c, in float rot)
{
p -= c;
float2x2 mat = float2x2(cos(rot), -sin(rot), sin(rot), cos(rot));
p = mul(p, mat); // さっきはエラーになったのに、どうして。。。。
}
ここにあるように行列matを掛けるときはmulを使います。これは右から掛けるとpは行として扱われるようです。あとy軸は下方向になっています。mulを使うのは成分ごとの掛け算ではないことを明示したいからだそうです(いい記事がありました:HLSLの行列乗算がmul()関数な理由)
またpの値を内部で変更する場合はinoutを付けます。outではエラーになりました。inoutなら入力として受け取った上でその値を変更できます。
そんなところですね・・。あとtriangleだと色が変わってしまったのでtrigonにしたくらいですね。
終わりに
もっといろいろ移植してみて慣れたいです。