3D 描画のサンプル¶
1. Plot3D¶
コード
# include <Siv3D.hpp>
void Main()
{
Window::Resize(1280, 720);
const ColorF backgroundColor = ColorF{ 0.4, 0.6, 0.8 }.removeSRGBCurve();
const Texture woodTexture{ U"example/texture/wood.jpg", TextureDesc::MippedSRGB };
MeshData meshDataFront = MeshData::Grid(Vec2{ 20,20 }, 256, 256);
MeshData meshDataBack = MeshData::Grid(Vec2{ 20,20 }, 256, 256).rotate(Quaternion::RotateX(180_deg));
DynamicMesh meshFront{ meshDataFront }, meshBack{ meshDataBack };
const PixelShader ps = HLSL{ U"example/shader/hlsl/forward_triplanar.hlsl", U"PS" }
| GLSL{ U"example/shader/glsl/forward_triplanar.frag", { { U"PSPerFrame", 0 }, { U"PSPerView", 1 }, { U"PSPerMaterial", 3 } } };
const MSRenderTexture renderTexture{ Scene::Size(), TextureFormat::R8G8B8A8_Unorm_SRGB, HasDepth::Yes };
DebugCamera3D camera{ renderTexture.size(), 30_deg, Vec3{ 16, 12, -24 } };
TextEditState te;
while (System::Update())
{
if (not te.active)
{
camera.update(2.0);
Graphics3D::SetCameraTransform(camera);
}
// 3D
{
const ScopedRenderTarget3D target{ renderTexture.clear(backgroundColor) };
const ScopedCustomShader3D shader{ ps };
meshFront.draw(woodTexture);
meshBack.draw(woodTexture);
}
// RenderTexture を 2D シーンに描画する
{
Graphics3D::Flush();
renderTexture.resolve();
Shader::LinearToScreen(renderTexture);
}
if (SimpleGUI::TextBox(te, Vec2{ 20,20 }, 1240))
{
// 数式パーサ
MathParser parser{ (te.text ? te.text : U"0") };
// 変数 x, y を設定する
double x = 0;
double z = 0;
parser.setVaribale(U"x", &x);
parser.setVaribale(U"y", &z);
for (auto& vertex : meshDataFront.vertices)
{
x = vertex.pos.x;
z = vertex.pos.z;
if (auto y = parser.evalOpt())
{
vertex.pos.y = static_cast<float>(*y);
}
else
{
// 式が不正な場合は処理を行わない
break;
}
}
for (auto& vertex : meshDataBack.vertices)
{
x = vertex.pos.x;
z = vertex.pos.z;
if (auto y = parser.evalOpt())
{
vertex.pos.y = static_cast<float>(*y);
}
else
{
// 式が不正な場合は処理を行わない
break;
}
}
// 法線を計算する
meshDataFront.computeNormals();
meshDataBack.computeNormals();
// DynamicTexture を更新する
meshFront.fill(meshDataFront);
meshBack.fill(meshDataBack);
}
}
}
2. 3D テキスト¶
コード
# include <Siv3D.hpp>
class Font3D
{
public:
Font3D() = default;
SIV3D_NODISCARD_CXX20
explicit Font3D(const Font& font)
: m_font{ font } {}
[[nodiscard]]
Array<MeshGlyph> getGlyphs(StringView s) const
{
Array<MeshGlyph> results;
for (auto ch : s)
{
auto it = m_table.find(ch);
if (it == m_table.end())
{
it = m_table.emplace(ch, m_font.createMesh(ch)).first;
}
results << it->second;
}
return results;
}
void drawCylindricalInner(StringView s, const Vec3& center, double r, double scale, double startAngle, const ColorF& color) const
{
const double perimeter = (r * Math::TwoPi);
double penPosX = 0.0;
startAngle += 90_deg;
for (auto meshGlyph : getGlyphs(s))
{
penPosX += (meshGlyph.xOffset * scale);
if (meshGlyph.mesh)
{
const double angle = (penPosX / perimeter) * 360_deg;
const Quaternion q = Quaternion::RotateY(-90_deg + angle - startAngle);
const Vec3 pos = Cylindrical{ r, (-180_deg - angle + startAngle), 0.0 } + center;
const Mat4x4 mat = Mat4x4::Translate(-meshGlyph.xOffset, 0, 0)
.scaled(scale)
.rotated(q)
.translated(pos);
meshGlyph.mesh.draw(mat, color);
}
penPosX += (meshGlyph.xAdvance - meshGlyph.xOffset) * scale;
}
}
void drawCylindricalOuter(StringView s, const Vec3& center, double r, double scale, double startAngle, const ColorF& color) const
{
const double perimeter = (r * Math::TwoPi);
double penPosX = 0.0;
startAngle += 90_deg;
for (auto meshGlyph : getGlyphs(s))
{
penPosX += (meshGlyph.xOffset * scale);
if (meshGlyph.mesh)
{
const double angle = (penPosX / perimeter) * 360_deg;
const Quaternion q = Quaternion::RotateY(90_deg - angle - startAngle);
const Vec3 pos = Cylindrical{ r, (180_deg + angle + startAngle), 0.0 } + center;
const Mat4x4 mat = Mat4x4::Translate(-meshGlyph.xOffset, 0, 0)
.scaled(scale)
.rotated(q)
.translated(pos);
meshGlyph.mesh.draw(mat, color);
}
penPosX += (meshGlyph.xAdvance - meshGlyph.xOffset) * scale;
}
}
private:
Font m_font;
mutable HashTable<char32, MeshGlyph> m_table;
};
void Main()
{
Window::Resize(1280, 720);
const ColorF backgroundColor = ColorF{ 0.5, 0.6, 0.6 }.removeSRGBCurve();
const Texture uvChecker{ U"example/texture/uv.png", TextureDesc::MippedSRGB };
const MSRenderTexture renderTexture{ Scene::Size(), TextureFormat::R8G8B8A8_Unorm_SRGB, HasDepth::Yes };
DebugCamera3D camera{ renderTexture.size(), 30_deg, Vec3{ 10, 16, -32 } };
const Font3D font3D{ Font{ 60 } };
while (System::Update())
{
const double t = Scene::Time();
camera.update(2.0);
Graphics3D::SetCameraTransform(camera);
// 3D 描画
{
Graphics3D::SetGlobalAmbientColor(Graphics3D::DefaultGlobalAmbientColor);
Graphics3D::SetSunColor(ColorF{ 0.75 });
const ScopedRenderTarget3D target{ renderTexture.clear(backgroundColor) };
Plane{ 64 }.draw(uvChecker);
Cylinder{ Vec3{0,0,0}, Vec3{0, 16, 0}, 5.6 }.draw(ColorF{ 0.25 }.removeSRGBCurve());
// 3D Text Circle
{
// 両面描画を行う
const ScopedRenderStates3D rasterizer{ RasterizerState::SolidCullNone, BlendState::Additive };
// ライティングを無効化する
Graphics3D::SetGlobalAmbientColor(ColorF{ 1.0 });
Graphics3D::SetSunColor(ColorF{ 0.0 });
font3D.drawCylindricalOuter(DateTime::Now().format(U"HH:mm:ss"), Vec3{ 0, 11.5, 0 }, 6 * 1.2, 3.0 * 1.2, (t * -25_deg), ColorF{ 1.0, 0.98, 0.9 }.removeSRGBCurve());
font3D.drawCylindricalOuter(DateTime::Now().format(U"HH:mm:ss"), Vec3{ 0, 11.5, 0 }, 6 * 1.2, 3.0 * 1.2, (t * -25_deg) + 180_deg, ColorF{ 1.0, 0.98, 0.9 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"Monday, September 27, 2021", Vec3{ 0, 10, 0 }, 6 * 1.2, 1.2 * 1.2, (t * -50_deg), ColorF{ 1.0, 0.98, 0.9 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"NIKKEI 225 30,248.81 +609.41", Vec3{ 0, 8, 0 }, 6, 1.0, (t * -72_deg), ColorF{ 0.6, 1.0, 0.8 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"HANG SENG 24,192,16 -318.82", Vec3{ 0, 7, 0 }, 6, 1.0, (t * -62_deg), ColorF{ 1.0, 0.6, 0.6 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"SHANGHAI 3,613.07 -29.15", Vec3{ 0, 6, 0 }, 6, 1.0, (t * -58_deg), ColorF{ 1.0, 0.6, 0.6 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"FTSE 7,051.48 -26.87", Vec3{ 0, 5, 0 }, 6, 1.0, (t * -70_deg), ColorF{ 1.0, 0.6, 0.6 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"CAC 6,638.46 -63.52", Vec3{ 0, 4, 0 }, 6, 1.0, (t * -60_deg), ColorF{ 1.0, 0.6, 0.6 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"DAX 15,531.75 -112.22", Vec3{ 0, 3, 0 }, 6, 1.0, (t * -66_deg), ColorF{ 1.0, 0.6, 0.6 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"NASDAQ 15,047.70 -4.54", Vec3{ 0, 2, 0 }, 6, 1.0, (t * -68_deg), ColorF{ 1.0, 0.6, 0.6 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"NIKKEI 225 30,248.81 +609.41", Vec3{ 0, 8, 0 }, 6, 1.0, (t * -72_deg) + 180_deg, ColorF{ 0.6, 1.0, 0.8 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"HANG SENG 24,192,16 -318.82", Vec3{ 0, 7, 0 }, 6, 1.0, (t * -62_deg) + 180_deg, ColorF{ 1.0, 0.6, 0.6 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"SHANGHAI 3,613.07 -29.15", Vec3{ 0, 6, 0 }, 6, 1.0, (t * -58_deg) + 180_deg, ColorF{ 1.0, 0.6, 0.6 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"FTSE 7,051.48 -26.87", Vec3{ 0, 5, 0 }, 6, 1.0, (t * -70_deg) + 180_deg, ColorF{ 1.0, 0.6, 0.6 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"CAC 6,638.46 -63.52", Vec3{ 0, 4, 0 }, 6, 1.0, (t * -60_deg) + 180_deg, ColorF{ 1.0, 0.6, 0.6 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"DAX 15,531.75 -112.22", Vec3{ 0, 3, 0 }, 6, 1.0, (t * -66_deg) + 180_deg, ColorF{ 1.0, 0.6, 0.6 }.removeSRGBCurve());
font3D.drawCylindricalOuter(U"NASDAQ 15,047.70 -4.54", Vec3{ 0, 2, 0 }, 6, 1.0, (t * -68_deg) + 180_deg, ColorF{ 1.0, 0.6, 0.6 }.removeSRGBCurve());
}
}
// 3D シーンを 2D シーンに描画する
{
Graphics3D::Flush();
renderTexture.resolve();
Shader::LinearToScreen(renderTexture);
}
}
}
3. 3D のボードゲームテンプレート¶
コード
# include <Siv3D.hpp>
// テーブル用のテクスチャを手続き的に生成する関数
Image CreateTableImage()
{
PerlinNoise noise;
return Image::Generate(Size{ 1024, 1024 }, [&](Point p)
{
const double x = Fraction(noise.octave2D0_1(p * Vec2{ 0.03, 0.0005 }, 2) * 25) * 0.3 + 0.55;
return ColorF{ x, 0.85 * x, 0.7 * x }.removeSRGBCurve();
}).gaussianBlurred(3);
}
// ブロック用のテクスチャを手続き的に生成する関数
Image CreateBlockImage()
{
PerlinNoise noise;
return Image::Generate(Size{ 256, 256 }, [&](Point p)
{
const double x = Fraction(noise.octave2D0_1(p * Vec2{ 0.05, 0.0005 }, 2) * 25) * 0.15 + 0.85;
return ColorF{ x }.removeSRGBCurve();
}).gaussianBlurred(2);
}
// テーブルを描画する関数
void DrawTable(const Texture& tableTexture)
{
Plane{ Vec3{ 0, -0.4, 0 }, 24 }.draw(tableTexture);
}
// 盤を描画する関数
void DrawBoard(const Mesh& mesh)
{
const ColorF BoardColor = ColorF{ 0.9, 0.85, 0.75 }.removeSRGBCurve();
const ColorF LineColor = ColorF{ 0.3, 0.2, 0.0 }.removeSRGBCurve();
mesh.draw(BoardColor);
// 盤上の線
for (int32 i = -4; i <= 4; ++i)
{
Line3D{ Vec3{ -4, 0.01, i }, Vec3{ 4, 0.01, i } }.draw(LineColor);
Line3D{ Vec3{ i, 0.01, -4 }, Vec3{ i, 0.01, 4 } }.draw(LineColor);
}
Line3D{ Vec3{ -4.1, 0.01, -4.1 }, Vec3{ 4.1, 0.01, -4.1 } }.draw(LineColor);
Line3D{ Vec3{ -4.1, 0.01, 4.1 }, Vec3{ 4.1, 0.01, 4.1 } }.draw(LineColor);
Line3D{ Vec3{ -4.1, 0.01, 4.1 }, Vec3{ -4.1, 0.01, -4.1 } }.draw(LineColor);
Line3D{ Vec3{ 4.1, 0.01, 4.1 }, Vec3{ 4.1, 0.01, -4.1 } }.draw(LineColor);
}
// 盤上のインデックス (x, y, z) から Box を作成する関数
Box MakeBox(int32 x, int32 y, int32 z)
{
return Box::FromPoints(Vec3{ (x - 4), (y + 1), (4 - z) }, Vec3{ (x - 3), y, (3 - z) });
}
// ブロックを描く関数
void DrawBlock(int32 x, int32 y, int32 z, const ColorF& color, double scale = 1.0)
{
MakeBox(x, y, z).scaled(scale).draw(color);
}
// ブロックを描く関数
void DrawBlock(int32 x, int32 y, int32 z, const ColorF& color, const Texture& blockTexture)
{
MakeBox(x, y, z).draw(blockTexture, color);
}
// ゲームの状態
struct Game
{
static constexpr int32 MaxY = 15;
int32 s[8][(MaxY + 1)][8] = {};
int32 getHeight(int32 x, int32 z) const
{
for (int32 y = MaxY; 0 <= y; --y)
{
if (s[x][y][z])
{
return (y + 1);
}
}
return 0;
}
};
// ゲームの状態に基づいてブロックを描く関数
void DrawGame(const Game& game, const Texture& blockTexture)
{
const ColorF BlockColor1 = ColorF{ 1.0, 0.85, 0.6 }.removeSRGBCurve();
const ColorF BlockColor2 = ColorF{ 0.4, 0.15, 0.15 }.removeSRGBCurve();
for (int32 y = 0; y <= Game::MaxY; ++y)
{
for (int32 x = 0; x < 8; ++x)
{
for (int32 z = 0; z < 8; ++z)
{
const int32 s = game.s[x][y][z];
if (s == 1)
{
DrawBlock(x, y, z, BlockColor1, blockTexture);
}
else if (s == 2)
{
DrawBlock(x, y, z, BlockColor2, blockTexture);
}
}
}
}
}
// カメラの制御クラス
class CameraController
{
public:
explicit CameraController(const Size& sceneSize)
: m_camera{ sceneSize, m_verticalFOV, m_eyePosition, m_focusPosition } {}
void update()
{
const Ray ray = getMouseRay();
// 盤のまわり部分
const std::array<Box, 4> boxes =
{
Box::FromPoints(Vec3{ -5, 0.0, 5 }, Vec3{ 5, -0.4, 4 }),
Box::FromPoints(Vec3{ 4, 0.0, 5 }, Vec3{ 5, -0.4, -5 }),
Box::FromPoints(Vec3{ -5, 0.0, -4 }, Vec3{ 5, -0.4, -5 }),
Box::FromPoints(Vec3{ -5, 0.0, 5 }, Vec3{ -4, -0.4, -5 })
};
if (MouseL.up())
{
m_grabbed = false;
}
if (m_grabbed)
{
const double before = (m_cursorPos - Scene::Center()).getAngle();
const double after = (Cursor::Pos() - Scene::Center()).getAngle();
m_phi -= (after - before);
m_cursorPos = Cursor::Pos();
}
for (const auto& box : boxes)
{
if (box.intersects(ray))
{
// マウスカーソルを手のアイコンにする
Cursor::RequestStyle(CursorStyle::Hand);
if ((not m_grabbed) && MouseL.down())
{
m_grabbed = true;
m_cursorPos = Cursor::Pos();
}
}
}
// 視点を球面座標系で計算する
m_eyePosition = Spherical{ 24, m_theta, (270_deg - m_phi) };
// カメラを更新する
m_camera.setView(m_eyePosition, m_focusPosition);
// シーンにカメラを設定する
Graphics3D::SetCameraTransform(m_camera);
}
Ray getMouseRay() const
{
return m_camera.screenToRay(Cursor::PosF());
}
bool isGrabbing() const
{
return m_grabbed;
}
private:
// 縦方向の視野角(ラジアン)
double m_verticalFOV = 25_deg;
// カメラの視点の位置
Vec3 m_eyePosition{ 16, 16, -16 };
// カメラの注視点の位置
Vec3 m_focusPosition{ 0, 0, 0 };
double m_phi = -20_deg;
double m_theta = 50_deg;
// カメラ
BasicCamera3D m_camera;
bool m_grabbed = false;
Vec2 m_cursorPos = Scene::Center();
};
void Main()
{
// ウインドウとシーンを 1280x720 にリサイズする
Window::Resize(1280, 720);
// 環境光を設定する
Graphics3D::SetGlobalAmbientColor(ColorF{ 0.75, 0.75, 0.75 });
// 太陽光を設定する
Graphics3D::SetSunColor(ColorF{ 0.5, 0.5, 0.5 });
// 太陽の方向を設定する
Graphics3D::SetSunDirection(Vec3{ 0, 1, -0.3 }.normalized());
// 背景色 (3D 用の色は .removeSRGBCurve() で sRGB カーブを除去)
const ColorF backgroundColor = ColorF{ 0.4, 0.6, 0.8 }.removeSRGBCurve();
// 3D シーンを描く、マルチサンプリング対応レンダーテクスチャ
const MSRenderTexture renderTexture{ Scene::Size(), TextureFormat::R8G8B8A8_Unorm_SRGB, HasDepth::Yes };
// テーブル用のテクスチャ
const Texture tableTexture{ CreateTableImage(), TextureDesc::MippedSRGB };
// ブロック用のテクスチャ
const Texture blockTexture{ CreateBlockImage(), TextureDesc::MippedSRGB };
// 盤用の 3D メッシュ
const Mesh meshBoard{ MeshData::RoundedBox(0.1, Vec3{ 10, 0.4, 10 }, 5).translate(0, -0.2, 0) };
// カメラ制御
CameraController cameraController{ renderTexture.size() };
// ゲームの状態
Game game;
game.s[0][0][0] = game.s[1][0][1] = 1;
game.s[4][0][4] = game.s[5][0][4] = 2;
while (System::Update())
{
// アクティブなボクセル
Point activeVoxelXZ{ -1, -1 };
////////////////////////////////
//
// 状態の更新
//
////////////////////////////////
{
if (not cameraController.isGrabbing())
{
const Ray ray = cameraController.getMouseRay();
float minDistance = 99999.9f;
for (int32 x = 0; x < 8; ++x)
{
for (int32 z = 0; z < 8; ++z)
{
const int32 height = game.getHeight(x, z);
const Box box = MakeBox(x, height, z);
if (Optional<float> distacne = box.intersects(ray))
{
if (*distacne < minDistance)
{
minDistance = *distacne;
activeVoxelXZ.set(x, z);
}
}
}
}
if (activeVoxelXZ != Point{ -1, -1 })
{
auto& voxel = game.s[activeVoxelXZ.x][game.getHeight(activeVoxelXZ.x, activeVoxelXZ.y)][activeVoxelXZ.y];
if (MouseL.down())
{
voxel = 1;
}
else if (MouseR.down())
{
voxel = 2;
}
}
}
cameraController.update();
}
////////////////////////////////
//
// 3D 描画
//
////////////////////////////////
{
{
// renderTexture を背景色で塗りつぶし、3D 描画のレンダーターゲットに
const ScopedRenderTarget3D target{ renderTexture.clear(backgroundColor) };
DrawTable(tableTexture);
DrawBoard(meshBoard);
DrawGame(game, blockTexture);
{
// 半透明を有効に
const ScopedRenderStates3D blend{ BlendState::OpaqueAlphaToCoverage };
for (int32 x = 0; x < 8; ++x)
{
for (int32 z = 0; z < 8; ++z)
{
const int32 height = game.getHeight(x, z);
DrawBlock(x, height, z, ColorF{ 0.2, 0.8, 0.8, 0.5 }, ((activeVoxelXZ == Point{ x, z }) ? 1.0 : 0.25));
}
}
}
}
Graphics3D::Flush();
renderTexture.resolve();
Shader::LinearToScreen(renderTexture);
}
////////////////////////////////
//
// 2D 描画
//
////////////////////////////////
{
if (SimpleGUI::Button(U"片づける", Vec2{ 1100, 20 }, 160))
{
game = Game{};
}
}
}
}
4. 低解像度風の 3D レンダリング¶
コード
# include <Siv3D.hpp>
void Main()
{
constexpr Size SceneSize{ 256, 192 };
const ColorF backgroundColor = ColorF{ 0.4, 0.6, 0.8 }.removeSRGBCurve();
const Texture wiindmill{ Image{ U"example/windmill.png" }.clipped(200, 230, 64, 64), TextureDesc::UnmippedSRGB };
const Texture siv3dKun{ Image{ U"example/spritesheet/siv3d-kun-16.png" }.clipped(0, 0, 20, 32), TextureDesc::UnmippedSRGB };
const Mesh spriteMesh{ MeshData::TwoSidedPlane(SizeF{ 2.0, 3.2 }).rotate(Quaternion::RotateX(-90_deg)) };
const RenderTexture renderTexture{ SceneSize, TextureFormat::R8G8B8A8_Unorm_SRGB, HasDepth::Yes };
DebugCamera3D camera{ renderTexture.size(), 30_deg, Vec3{ 10, 2, -32 } };
while (System::Update())
{
camera.update(2.0);
Graphics3D::SetCameraTransform(camera);
// [3D rendering]
{
const ScopedRenderTarget3D target{ renderTexture.clear(backgroundColor) };
Plane{ 64 }.draw(ColorF{ 0.7 }.removeSRGBCurve());
Box::FromPoints(Vec3{ -4, 0, -4 }, Vec3{ -2, 4, 4 }).draw(ColorF{ 0.8, 0.6, 0.4 }.removeSRGBCurve());
Plane{ Vec3{0, 4, 0 }, 64 }.draw(ColorF{ 0.5 }.removeSRGBCurve());
{
const ScopedRenderStates3D sampler{ SamplerState::ClampNearest };
Box{ 4, 2, 0, 4 }.draw(wiindmill);
}
{
const ScopedRenderStates3D sampler{ SamplerState::ClampNearest, BlendState::Default2D };
spriteMesh.draw(Vec3{ 0, 1.6, -4 }, siv3dKun);
}
}
// [2D rendering]
{
Graphics3D::Flush();
// TextureFilter::Nearest
Shader::LinearToScreen(renderTexture, TextureFilter::Nearest);
}
}
}
5. 3D オブジェクトの手動回転¶
コード
# include <Siv3D.hpp>
void Main()
{
Window::Resize(1280, 720);
const ColorF backgroundColor = ColorF{ 0.4, 0.6, 0.8 }.removeSRGBCurve();
const Texture uvChecker{ U"example/texture/uv.png", TextureDesc::MippedSRGB };
const Texture earthTexture{ U"example/texture/earth.jpg", TextureDesc::MippedSRGB };
const MSRenderTexture renderTexture{ Scene::Size(), TextureFormat::R8G8B8A8_Unorm_SRGB, HasDepth::Yes };
DebugCamera3D camera{ renderTexture.size(), 30_deg, Vec3{ 10, 16, -32 } };
const Sphere sphere{ 0,4,0,4 };
Quaternion rotation;
bool grabbedX = false;
bool grabbedY = false;
const Cylinder cY{ sphere.center, 4.6, 1 };
const Cylinder cX{ sphere.center, 4.5, 1, Quaternion::RotateZ(90_deg) };
while (System::Update())
{
ClearPrint();
camera.update(2.0);
Graphics3D::SetCameraTransform(camera);
{
const Ray ray = camera.screenToRay(Cursor::PosF());
const ScopedRenderTarget3D target{ renderTexture.clear(backgroundColor) };
Plane{ 64 }.draw(uvChecker);
sphere.draw(rotation, earthTexture);
cY.draw(ColorF{ (grabbedY ? 0.8 : 0.5), 0.0, 0.0 }.removeSRGBCurve());
cX.draw(ColorF{ 0.0, (grabbedX ? 0.8 : 0.5), 0.0 }.removeSRGBCurve());
if (grabbedX)
{
rotation *= Quaternion::RotateX(-1_deg * Cursor::DeltaF().y);
}
if (grabbedY)
{
rotation *= Quaternion::RotateY(-1_deg * Cursor::DeltaF().x);
}
const Optional<float> cyd = ray.intersects(cY);
const Optional<float> cxd = ray.intersects(cX);
Print << U"cyd: " << cyd;
Print << U"cxd: " << cxd;
if (cyd || cxd)
{
Cursor::RequestStyle(CursorStyle::Hand);
if (MouseL.down())
{
if (cxd && cyd)
{
((cxd < cyd) ? grabbedX : grabbedY) = true;
}
else
{
((cxd) ? grabbedX : grabbedY) = true;
}
}
}
if (MouseL.up())
{
grabbedY = grabbedX = false;
}
}
{
Graphics3D::Flush();
renderTexture.resolve();
Shader::LinearToScreen(renderTexture);
}
}
}
6. 高さマップの編集¶
コード
# include <Siv3D.hpp>
void Main()
{
Window::Resize(1280, 720);
const VertexShader vsTerrain = HLSL{ U"example/shader/hlsl/terrain_forward.hlsl", U"VS" }
| GLSL{ U"example/shader/glsl/terrain_forward.vert", { { U"VSPerView", 1 }, { U"VSPerObject", 2 }, { U"VSPerMaterial", 3 } } };
const PixelShader psTerrain = HLSL{ U"example/shader/hlsl/terrain_forward.hlsl", U"PS" }
| GLSL{ U"example/shader/glsl/terrain_forward.frag", { { U"PSPerFrame", 0 }, { U"PSPerView", 1 }, { U"PSPerMaterial", 3 } } };
const PixelShader psNormal = HLSL{ U"example/shader/hlsl/terrain_normal.hlsl", U"PS" }
| GLSL{ U"example/shader/glsl/terrain_normal.frag", { {U"PSConstants2D", 0} } };
if ((not vsTerrain) || (not psTerrain) || (not psNormal))
{
return;
}
const ColorF backgroundColor = ColorF{ 0.4, 0.6, 0.8 }.removeSRGBCurve();
const Texture terrainTexture{ U"example/texture/grass.jpg", TextureDesc::MippedSRGB };
const Texture rockTexture{ U"example/texture/rock.jpg", TextureDesc::MippedSRGB };
const Texture brushTexture{ U"example/particle.png" };
const MSRenderTexture renderTexture{ Scene::Size(), TextureFormat::R8G8B8A8_Unorm_SRGB, HasDepth::Yes };
const Mesh gridMesh{ MeshData::Grid({ 128, 128 }, 128, 128) };
DebugCamera3D camera{ renderTexture.size(), 30_deg, Vec3{ 10, 16, -32 } };
RenderTexture heightmap{ Size{ 256, 256 }, ColorF{ 0.0 }, TextureFormat::R32_Float };
RenderTexture normalmap{ Size{ 256, 256 }, ColorF{ 0.0, 0.0, 0.0 }, TextureFormat::R16G16_Float };
while (System::Update())
{
camera.update(2.0);
// 3D
{
Graphics3D::SetCameraTransform(camera);
const ScopedCustomShader3D shader{ vsTerrain, psTerrain };
const ScopedRenderTarget3D target{ renderTexture.clear(backgroundColor) };
const ScopedRenderStates3D ss{ { ShaderStage::Vertex, 0, SamplerState::ClampLinear} };
Graphics3D::SetVSTexture(0, heightmap);
Graphics3D::SetPSTexture(1, normalmap);
Graphics3D::SetPSTexture(2, rockTexture);
gridMesh.draw(terrainTexture);
}
// RenderTexture を 2D シーンに描画
{
Graphics3D::Flush();
renderTexture.resolve();
Shader::LinearToScreen(renderTexture);
}
if (const bool gen = SimpleGUI::Button(U"Random", Vec2{ 270, 10 });
(gen || (MouseL | MouseR).pressed())) // 地形を編集
{
// heightmap を編集
if (gen)
{
const PerlinNoiseF perlin{ RandomUint64() };
Grid<float> grid(256, 256);
for (auto p : step(grid.size()))
{
grid[p] = perlin.octave2D0_1(p / 256.0f, 5) * 16.0f;
}
const RenderTexture noise{ grid };
const ScopedRenderTarget2D target{ heightmap };
noise.draw();
}
else
{
const ScopedRenderTarget2D target{ heightmap };
const ScopedRenderStates2D blend{ BlendState::Additive };
brushTexture.scaled(1.0 + MouseL.pressed()).drawAt(Cursor::PosF(), ColorF{ Scene::DeltaTime() * 15.0 });
}
// normal map を更新
{
const ScopedRenderTarget2D target{ normalmap };
const ScopedCustomShader2D shader{ psNormal };
const ScopedRenderStates2D blend{ BlendState::Opaque, SamplerState::ClampLinear };
heightmap.draw();
}
}
heightmap.draw(ColorF{ 0.1 });
normalmap.draw(0, 260);
}
}