🧱
Siv3D | Polygon から 3D の壁を作る
これは Siv3D Advent Calendar 2021 の参加記事です。
画像のように、Polygon
から 3D の壁の Mesh
を作成するコードを紹介します。実装した関数は
MeshData CreateWall(const Polygon& polygon, double h);
だけです。
これとは別に、壁の接地感を高めるために、
- 疑似的な Ambient Occlusion を床のテクスチャに事前に描き込んでおく
- 高さと法線ベースの非常に単純な Ambient Occlusion を triplanar ピクセルシェーダに追加する
というテクニックを導入しています。その ON/OFF をチェックボックスから切り替えられます。
# include <Siv3D.hpp> // OpenSiv3D v0.6.3
MeshData CreateWall(const Polygon& polygon, double h)
{
if (not polygon)
{
return{};
}
const float hF = static_cast<float>(h);
// Face Vertex
const uint32 faceVertexSize = static_cast<uint32>(polygon.vertices().size());
Array<Vertex3D> vertices(faceVertexSize * 2);
{
uint32 index = 0;
for (size_t i = 0; i < faceVertexSize; ++i)
{
const Float2 pos = polygon.vertices()[index];
auto& vertexUp = vertices[i];
auto& vertexBottom = vertices[i + faceVertexSize];
vertexUp.pos.set(pos.x, hF, -pos.y);
vertexUp.normal.set(0.0f, 1.0f, 0.0f);
vertexUp.tex.set(0.0f, 0.0f);
vertexBottom.pos.set(pos.x, 0.0f, -pos.y);
vertexBottom.normal.set(0.0f, -1.0f, 0.0f);
vertexBottom.tex.set(0.0f, 0.0f);
++index;
}
}
// Face Index
const size_t faceIndexSize = polygon.indices().size();
Array<TriangleIndex32> indices(faceIndexSize * 2);
{
const auto& polygonIndices = polygon.indices();
for (size_t i = 0; i < faceIndexSize; ++i)
{
const auto& t = polygonIndices[i];
indices[i] = { t.i0, t.i1, t.i2 };
}
for (size_t i = 0; i < faceIndexSize; ++i)
{
const auto& t = polygonIndices[i];
indices[faceIndexSize * 2 - i - 1] = { (t.i0 + faceVertexSize), (t.i2 + faceVertexSize), (t.i1 + faceVertexSize) };
}
}
// Outer Vertex
const size_t outerVertexSizeOne = polygon.outer().size();
const size_t outerVertexSize = (outerVertexSizeOne * 4);
Array<Float3> outerPositions(outerVertexSize);
{
for (size_t i = 0; i < outerVertexSizeOne; ++i)
{
const Float2 posA = polygon.outer()[i];
const Float2 posB = polygon.outer()[(i + 1) % outerVertexSizeOne];
outerPositions[i * 4 + 0].set(posA.x, hF, -posA.y);
outerPositions[i * 4 + 1].set(posA.x, 0.0f, -posA.y);
outerPositions[i * 4 + 2].set(posB.x, hF, -posB.y);
outerPositions[i * 4 + 3].set(posB.x, 0.0f, -posB.y);
}
}
// Outer Index
const size_t outerIndexSize = outerVertexSizeOne * 2;
Array<TriangleIndex32> outerIndices(outerIndexSize);
{
for (uint32 i = 0; i < outerVertexSizeOne; ++i)
{
outerIndices[i * 2 + 0] =
{
(i * 4 + 0),
(i * 4 + 1),
(i * 4 + 3)
};
outerIndices[i * 2 + 1] =
{
(i * 4 + 3),
(i * 4 + 2),
(i * 4 + 0)
};
}
}
// Holes Vertex & Index
uint32 holeIndexOffsetBase = static_cast<uint32>(outerVertexSize);
for (const auto& hole : polygon.inners())
{
const uint32 holeVertexSizeOne = static_cast<uint32>(hole.size());
const uint32 holeVertexSize = static_cast<uint32>(hole.size()) * 4;
Array<Float3> holePositions(holeVertexSize);
for (size_t i = 0; i < holeVertexSizeOne; ++i)
{
const Float2 posA = hole[i];
const Float2 posB = hole[(i + 1) % holeVertexSizeOne];
holePositions[i * 4 + 0].set(posA.x, hF, -posA.y);
holePositions[i * 4 + 1].set(posA.x, 0.0f, -posA.y);
holePositions[i * 4 + 2].set(posB.x, hF, -posB.y);
holePositions[i * 4 + 3].set(posB.x, 0.0f, -posB.y);
}
outerPositions.insert(outerPositions.end(), holePositions.begin(), holePositions.end());
const size_t holeIndexSize = holeVertexSizeOne * 2;
Array<TriangleIndex32> holeIndices(holeIndexSize);
for (uint32 i = 0; i < holeVertexSizeOne; ++i)
{
holeIndices[i * 2 + 0] =
{
(holeIndexOffsetBase + i * 4 + 0),
(holeIndexOffsetBase + i * 4 + 1),
(holeIndexOffsetBase + i * 4 + 3)
};
holeIndices[i * 2 + 1] =
{
(holeIndexOffsetBase + i * 4 + 3),
(holeIndexOffsetBase + i * 4 + 2),
(holeIndexOffsetBase + i * 4 + 0)
};
}
holeIndexOffsetBase += holeVertexSize;
outerIndices.insert(outerIndices.end(), holeIndices.begin(), holeIndices.end());
}
{
const uint32 outerIndexOffset = faceVertexSize * 2;
for (auto& outerIndex : outerIndices)
{
outerIndex.i0 += outerIndexOffset;
outerIndex.i1 += outerIndexOffset;
outerIndex.i2 += outerIndexOffset;
}
}
// Union
{
const size_t outerVertxSizeAll = outerPositions.size();
vertices.resize(vertices.size() + outerVertxSizeAll);
for (size_t i = 0; i < outerVertxSizeAll; ++i)
{
auto& vertex = vertices[faceVertexSize * 2 + i];
vertex.pos = outerPositions[i];
}
const size_t outerIndexSizeAll = outerIndices.size();
indices.append(outerIndices);
}
MeshData meshData{ std::move(vertices), std::move(indices) };
meshData.computeNormals();
return meshData;
}
void Main()
{
Window::Resize(1280, 720);
const ColorF backgroundColor = ColorF{ 0.4, 0.6, 0.8 }.removeSRGBCurve();
Image uvCheckerImage{ U"example/texture/uv.png" };
const Texture uvChecker{ uvCheckerImage, TextureDesc::MippedSRGB };
const MSRenderTexture renderTexture{ Scene::Size(), TextureFormat::R8G8B8A8_Unorm_SRGB, HasDepth::Yes };
DebugCamera3D camera{ renderTexture.size(), 30_deg, Vec3{ 10, 16, -32 } };
// triplanar 用のシェーダ
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 }} };
// 疑似的な Ambient Occlusion を追加した triplanar 用のシェーダ
const PixelShader psAO = HLSL{ U"forward_triplanar2.hlsl", U"PS" }
| GLSL{ U"forward_triplanar2.frag", {{ U"PSPerFrame", 0 }, { U"PSPerView", 1 }, { U"PSPerMaterial", 3 }} };
if ((not ps) || (not psAO))
{
return;
}
// 木材のテクスチャ
const Texture woodTexture{ U"example/texture/wood.jpg", TextureDesc::MippedSRGB };
// 壁の Polygon と高さ
const Array<std::pair<Polygon, double>> polygons =
{
{ Shape2D::Arrow(Vec2{-24, -16}, Vec2{-12, -16}, 1, Vec2{ 4, 4 }), 0.5 },
{ Shape2D::Hexagon(6.0, Vec2{0, -16}), 4.0 },
{ Shape2D::Cross(6.0, 3.0, Vec2{16, -16}), 2.0 },
{ Shape2D::Stairs(Vec2{ -8, 0 }, 8, 8, 8), 2.0 },
{ Shape2D::Star(8).asPolygon().addHole(Circle{ 2 }.asPolygon().outer().reversed()) , 0.5 },
{ Rect{ Arg::center(16, 0), 8 }.asPolygon().addHole(Rect{ Arg::center(16, 0), 6 }.asPolygon().outer().reversed()), 2.0 }
};
// 疑似的な Ambient Occlusion を床テクスチャに描き込み
{
Image image{ 2048, 2048, Color{255} };
{
for (const auto& polygon : polygons)
{
polygon.first.movedBy(32, 32).scale(2048 / 64.0).overwrite(image, ColorF{ 0.5 });
}
image.gaussianBlur(17);
}
for (auto p : step(image.size()))
{
Color& color = uvCheckerImage[p];
color = (ColorF{ color } * ColorF{ image[p] });
}
}
const Texture uvCheckerAO{ uvCheckerImage, TextureDesc::MippedSRGB };
// 壁のメッシュ
const Array<Mesh> walls = polygons.map([](const std::pair<Polygon, double>& shape)
{
return Mesh{ CreateWall(shape.first, shape.second) };
});
// 疑似的な Ambient Occlusion
bool simpleAO = true;
while (System::Update())
{
camera.update(2.0);
Graphics3D::SetCameraTransform(camera);
// 3D 描画
{
const ScopedRenderTarget3D target{ renderTexture.clear(backgroundColor) };
if (simpleAO)
{
Plane{ 64 }.draw(uvCheckerAO);
const ScopedCustomShader3D shader{ psAO };
for (const auto& wall : walls)
{
wall.draw(woodTexture);
}
}
else
{
Plane{ 64 }.draw(uvChecker);
const ScopedCustomShader3D shader{ ps };
for (const auto& wall : walls)
{
wall.draw(woodTexture);
}
}
}
// 3D シーンを 2D シーンに描画
{
Graphics3D::Flush();
renderTexture.resolve();
Shader::LinearToScreen(renderTexture);
}
SimpleGUI::CheckBox(simpleAO, U"Simple AO", Vec2{ 20,20 });
}
}
ピクセルシェーダはそれぞれ // *
で注釈をつけた 2 行のみ変更しています。
forward_triplanar2.hlsl
//-----------------------------------------------
//
// 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);
namespace s3d
{
//
// VS Output / PS Input
//
struct PSInput
{
float4 position : SV_POSITION;
float3 worldPosition : TEXCOORD0;
float2 uv : TEXCOORD1;
float3 normal : TEXCOORD2;
};
}
//
// Constant Buffer
//
cbuffer PSPerFrame : register(b0)
{
float3 g_gloablAmbientColor;
float3 g_sunColor;
float3 g_sunDirection;
}
cbuffer PSPerView : register(b1)
{
float3 g_eyePosition;
}
cbuffer PSPerMaterial : register(b3)
{
float3 g_amibientColor;
uint g_hasTexture;
float4 g_diffuseColor;
float3 g_specularColor;
float g_shininess;
float3 g_emissionColor;
}
//
// Functions
//
float4 Triplanar(float3 worldPos, float3 normal, float uvScale)
{
float4 diffuseColor = g_diffuseColor;
if (g_hasTexture)
{
float3 blend = abs(normal);
blend /= (blend.x + blend.y + blend.z);
worldPos *= uvScale;
const float4 c0 = g_texture0.Sample(g_sampler0, worldPos.yz);
const float4 c1 = g_texture0.Sample(g_sampler0, worldPos.xz);
const float4 c2 = g_texture0.Sample(g_sampler0, worldPos.xy);
diffuseColor *= (c0 * blend.x + c1 * blend.y + c2 * blend.z);
}
return diffuseColor;
}
float3 CalculateDiffuseReflection(float3 n, float3 l, float3 lightColor, float3 diffuseColor, float3 ambientColor)
{
const float3 directColor = lightColor * saturate(dot(n, l));
return ((ambientColor + directColor) * diffuseColor);
}
float3 CalculateSpecularReflection(float3 n, float3 h, float shininess, float nl, float3 lightColor, float3 specularColor)
{
const float highlight = pow(saturate(dot(n, h)), shininess) * float(0.0 < nl);
return (lightColor * specularColor * highlight);
}
float4 PS(s3d::PSInput input) : SV_TARGET
{
const float3 lightColor = g_sunColor;
const float3 lightDirection = g_sunDirection;
const float3 n = normalize(input.normal);
const float3 l = lightDirection;
const float4 diffuseColor = Triplanar(input.worldPosition, n, 0.125f);
const float simpleAO = 0.5 + min(input.worldPosition.y + input.normal.y, 0.5); // *
const float3 ambientColor = (g_amibientColor * g_gloablAmbientColor) * simpleAO; // *
// Diffuse
const float3 diffuseReflection = CalculateDiffuseReflection(n, l, lightColor, diffuseColor.rgb, ambientColor);
// Specular
const float3 v = normalize(g_eyePosition - input.worldPosition);
const float3 h = normalize(v + lightDirection);
const float3 specularReflection = CalculateSpecularReflection(n, h, g_shininess, dot(n, l), lightColor, g_specularColor);
return float4(diffuseReflection + specularReflection + g_emissionColor, diffuseColor.a);
}
forward_triplanar2.frag
// Copyright (c) 2008-2021 Ryo Suzuki.
// Copyright (c) 2016-2021 OpenSiv3D Project.
// Licensed under the MIT License.
# version 410
//
// Textures
//
uniform sampler2D Texture0;
//
// PSInput
//
layout(location = 0) in vec3 WorldPosition;
layout(location = 1) in vec2 UV;
layout(location = 2) in vec3 Normal;
//
// PSOutput
//
layout(location = 0) out vec4 FragColor;
//
// Constant Buffer
//
layout(std140) uniform PSPerFrame // slot 0
{
vec3 g_gloablAmbientColor;
vec3 g_sunColor;
vec3 g_sunDirection;
};
layout(std140) uniform PSPerView // slot 1
{
vec3 g_eyePosition;
};
layout(std140) uniform PSPerMaterial // slot 3
{
vec3 g_amibientColor;
uint g_hasTexture;
vec4 g_diffuseColor;
vec3 g_specularColor;
float g_shininess;
vec3 g_emissionColor;
};
//
// Functions
//
vec4 Triplanar(vec3 worldPos, vec3 normal, float uvScale)
{
vec4 diffuseColor = g_diffuseColor;
if (g_hasTexture == 1)
{
vec3 blend = abs(normal);
blend /= (blend.x + blend.y + blend.z);
worldPos *= uvScale;
vec4 c0 = texture(Texture0, worldPos.yz);
vec4 c1 = texture(Texture0, worldPos.xz);
vec4 c2 = texture(Texture0, worldPos.xy);
diffuseColor *= (c0 * blend.x + c1 * blend.y + c2 * blend.z);
}
return diffuseColor;
}
vec3 CalculateDiffuseReflection(vec3 n, vec3 l, vec3 lightColor, vec3 diffuseColor, vec3 ambientColor)
{
vec3 directColor = lightColor * max(dot(n, l), 0.0f);
return ((ambientColor + directColor) * diffuseColor);
}
vec3 CalculateSpecularReflection(vec3 n, vec3 h, float shininess, float nl, vec3 lightColor, vec3 specularColor)
{
float highlight = pow(max(dot(n, h), 0.0f), shininess) * float(0.0f < nl);
return (lightColor * specularColor * highlight);
}
void main()
{
vec3 lightColor = g_sunColor;
vec3 lightDirection = g_sunDirection;
vec3 n = normalize(Normal);
vec3 l = lightDirection;
vec4 diffuseColor = Triplanar(WorldPosition, n, 0.25f);
float simpleAO = 0.5f + min(WorldPosition.y + Normal.y, 0.5f); // *
vec3 ambientColor = (g_amibientColor * g_gloablAmbientColor) * simpleAO; // *
// Diffuse
vec3 diffuseReflection = CalculateDiffuseReflection(n, l, lightColor, diffuseColor.rgb, ambientColor);
// Specular
vec3 v = normalize(g_eyePosition - WorldPosition);
vec3 h = normalize(v + lightDirection);
vec3 specularReflection = CalculateSpecularReflection(n, h, g_shininess, dot(n, l), lightColor, g_specularColor);
FragColor = vec4(diffuseReflection + specularReflection + g_emissionColor, diffuseColor.a);
}
Discussion