けいブログ

端くれ描画プログラマによるNote

HDRPとお知り合いになる

Unityの公式RenderPipelineであるURPとHDRP。URPはチョットワカルのですが、HDRPのことは何も知らなかったので学んでみます。

物理ベースのテクニックが散りばめられてそうなRenderPipelineにドキドキワクワクです。

はじめに

まずは Unity Learning Materials (https://learning.unity3d.jp/) の「ゼロから始めるHDRP」シリーズの第3回~第7回をざっと見てみました。

内容としては

  • 第3回と第4回は Unite 2020 (海外公演) の日本語翻訳の動画+プチ解説
  • 第5回~第7回は Unity 2020.2 から一新されたテンプレートプロジェクトの解説

という感じになっていました。

上記動画では 2020.1系 や 2020.2系 が使われています。一方で、自分の環境は 2020系 最新の 2020.3.18 を横で弄りつつ動画を視聴しました。(Assetsの構成など一部異なるようでしたが理解には概ね問題なかったです)

個人的に気になったことを書いてみます。

Render Pipeline Debug

ハイエンド向けの様々な機能、物理ベースな各種シェーダやポスプロ、露出関連、物理単位でライトを扱うなど・・・そういったものがたぶん重要なのですが、とにかく RendeRender Pipeline Debug が便利そうだなぁというところに目がいきました。

様々な描画要素を表示・非表示できる優れものです。こういうのを実装しておくと何かと便利ですよね。

それがデフォルトで備わっているのは便利ですね~! (URPは空のウィンドウが開きます(泣) そのうち搭載されるのだろうか・・・?)

Volume

このあたりのベースの仕組みはURPと同じようです。ただURPに比べて、多くの見た目を決めていく設定がVolumeに集約されているようです。

HDRPでは「現在のどのVolumeに影響を受けて、各種パラメータがいくつになっているか」は前述の Render Pipeline Debug で見ることができるようです。便利ですね。

露出関連

Project Settings > HDRP Default Settings の Lens Attenuation Mode で「Imperfect Lens」「Perfect Lens」が選べるようです。

実装の方も含めて軽く見ると、

// Imperfect Lens : S = 100, q = 0.65
lensImperfectionExposureScale = 78 / ( S * q ) = 1.2
EV100ToExposure = 1.0 / ( lensImperfectionExposureScale * 2^EV ) = 0.833... / 2^EV
// Perfect Lens : S = 100, q = 0.78
lensImperfectionExposureScale = 78 / ( S * q ) = 1.0
EV100ToExposure = 1.0 / ( lensImperfectionExposureScale * 2^EV )  = 1.0 /  2^EV

のようになっており、「Perfect Lens」はExposure変換するときの光のエネルギー損失が無いようですね。

今後

実装の方、気になるところを少し見てみようと思います。

VulkanでHLSL

わりと久々にブログ更新。

最近、昔にほんのちょろっと触ったVulkanを再入門してみています。

Vulkanは、C APIだと少しツライところがあるので、C++ APIを使います。 (GitHub - KhronosGroup/Vulkan-Hpp: Open-Source Vulkan C++ API)

シェーダ言語どうするか

はじめはなんとなくGLSLでシェーダを記述していたのですが、やっぱりHLSLで書きたいよね!ということで。

VulkanSDK付属の glslangValidator でも変換できるようですが、今回 DirectX Shader Compiler (DXC) を使ってみることにします。 ( DirectXShaderCompiler/SPIR-V.rst at master · microsoft/DirectXShaderCompiler · GitHub)

まずはHLSLを用意する

例として、テクスチャと頂点カラーを乗算して出力するなんの変哲もないピクセルシェーダを用意します。

Texture2D    tex  : register(t0);
SamplerState samp : register(s0);

struct PSInput
{
    float4 color : TEXCOORD0;
    float2 uv : TEXCOORD1;
    float4 position : SV_POSITION;
};

float4 main(PSInput input) : SV_TARGET
{
    float4 output;;
    output = tex.Sample(samp, input.uv) * input.color;
    return output;
}

HLSL→SPIR-V

dxc に SPIR-V のオプションを渡しつつ、プロファイルやエントリポイントなどを指定します。

dxc -spirv -T ps_6_0 -E main "shader.hlsl" -Fo "shader.spv" -fvk-s-shift 10 0

少し気を付けないといけないのが、-fvk-s-shift の指定。

HLSLだとTextureやSampler,Bufferなど、タイプ別にregister指定が独立していますが、GLSL/Vulkanで同じような役割を担うbindingは各タイプで共通になります。

dxcではコンバート時にregister指定の数値をシフトしてbindingに割り当てることができるようです。

今回は s0 を binding = 10 にシフトさせています。

Descriptor

GLSLで実装している際は特に気にせず sampler2D を使用していました。

HLSLで Texture2D, SamplerState を使用しているので、C++側も変更します。

DescriptorType::eCombinedImageSampler が eSampledImage + eSampler になります。

std::array<vk::WriteDescriptorSet, 2> writeSets;

auto& tex = writeSets[0];
vk::DescriptorImageInfo imageInfo;
imageInfo.setImageView(m_textureView)
    .setImageLayout(vk::ImageLayout::eShaderReadOnlyOptimal);
tex.setDstSet(m_descriptorSet[0])
    .setDstBinding(0)
    .setDescriptorCount(1)
    .setDescriptorType(vk::DescriptorType::eSampledImage)
    .setImageInfo(imageInfo);

auto& smp = writeSets[1];
vk::DescriptorImageInfo samplerInfo;
samplerInfo.setSampler(m_sampler);
smp.setDstSet(m_descriptorSet[0])
    .setDstBinding(10)
    .setDescriptorCount(1)
    .setDescriptorType(vk::DescriptorType::eSampler)
    .setImageInfo(samplerInfo);

m_device.updateDescriptorSets(writeSets, nullptr);

NsightGraphicsでの見え方

Nsight Graphics でシェーダを確認すると、以下のようにGLSLに変換された状態で確認ができました。 (SPIRV-Cross によって生成されていそうなコメントがついていました)

// GLSL generated using SPIRV-Cross and might not be representative
// of original shader source

#version 450

layout(set = 0, binding = 0) uniform texture2D tex;
layout(set = 0, binding = 10) uniform sampler samp;

layout(location = 0) out vec4 out_var_SV_TARGET;
layout(location = 1) in vec2 in_var_TEXCOORD1;
layout(location = 0) in vec4 in_var_TEXCOORD0;

void main()
{
    out_var_SV_TARGET = texture(sampler2D(tex, samp), in_var_TEXCOORD1) * in_var_TEXCOORD0;
}

GLSLにはなってしまいますが、Nsight上でのコードの確認や編集がひとまずできました。

RenderGraphその1

フレームワークのためにRenderGraph的なものを実装してみようと思います。

依存関係に基づいた実行順の決定やリソース管理をする役割を担います。

利用側のコードは以下のようなものを想定してみます。

//-- RenderGraph 構築
renderGraph->AddRenderPass("GBuffer"sv, std::make_shared<GBufferPass>());
renderGraph->AddRenderPass("SSAO"sv, std::make_shared<SSAOPass>());
renderGraph->AddRenderPass("Lighting"sv, std::make_shared<LightingPass>());
renderGraph->AddRenderPass("ColorGrading"sv, std::make_shared<ColorGradingPass>());
// (-> SSAO)
renderGraph->AddConnection("GBuffer.normal"sv, "SSAO.normal"sv);
renderGraph->AddConnection("GBuffer.depth"sv, "SSAO.depth"sv);
// (-> Lighting)
renderGraph->AddConnection("GBuffer.color"sv, "Lighting.color"sv);
renderGraph->AddConnection("GBuffer.normal"sv, "Lighting.normal"sv);
renderGraph->AddConnection("GBuffer.depth"sv, "Lighting.depth"sv);
renderGraph->AddConnection("SSAO.dst"sv, "Lighting.ao"sv);
// (-> ColorGrading)
renderGraph->AddConnection("Lighting.dst"sv, "ColorGrading.src"sv);
// (-> End)
renderGraph->MarkEnd("ColorGrading.dst"sv);

//-- RenderGraph コンパイル
renderGraph->Compile();

//-- RenderGraph 実行
renderGraph->Execute(*context);

(きっと続く)

C++20本

技術書典で初めて物理本を買ってみました。 techbookfest.org

昨年、CEDECの講演も視聴しましたが、改めてパラパラと。

C++20、熱いですね。(ようやくモダンな感じになってきたとも)

使える機会では積極的に使っていきたいですね。

Reversed Z のメモ

今更感ありますが、ちょっと気になることがあり Reversed Z について再確認。

以下のページが十分にまとまっているように思います。

developer.nvidia.com

そういえば infinite far plane の利用率ってどんなものなのでしょう。

Falcor中を覗いてみる

中身を覗いて見たメモ。

Samples を見ると、IRendererインターフェイスの各コールバックを実装することで実装ができそうです。

Windowはglfwをバッググラウンドとして利用、Guiはimguiをラップしています。

Deviceクラスがありますが、共通部分はDevice.cppで実装、D3D12依存部分はD3D12Device.cppで実装といった感じに切り分けがされています。

主に api とついたプロパティやメソッドがAPI依存部分になっていそうですね。

DeviceがRenderContextを作成しますが、そちらもDeviceと同じように共通部分とAPI依存部分を分けています。

このRenderContextを通して、clear,drawcall,raytraceなどができる感じですね。