Maya - RenderOverrideで遊ぼう

# あいさつ

今年はMayaのクラッシュではなく、とあるメーカーのCPUクラッシュで苦しめられた
あまぐりです
どうぞよろしくお願いします

この記事は [Maya Advent Calender 2024](https://qiita.com/advent-calendar/2024/maya "[Maya Advent Calender 2024")の21日目の記事です

みなさん、年越しの準備はお済みでしょうか？

僕は今年こそはMayaのFABRIKプラグインを開発するぞと意気込んでたはずが、
Mayaのレンダリング改造で遊んでたらいつの間に年を越しそうです

さて、そういう訳で（？）今年の記事はMaya Viewport2.0のリアルタイムレンダリングパイプラインをある程度独自にカスタマイズできるMRenderOverrideをつかってポストエフェクトを自作する話を書こうかなと思います

※でもUSD利用を想定したViewportの代替が開発されているので今更感がでてきています
それでも気にせずいきましょう

https://github.com/Autodesk/maya-hydra

## 本記事のMayaバージョンとか言語
Windows 11
Maya 2023.3.1
C++をメインに取り扱います

# 本記事を書いているときに作ったサンプルのコード達です

https://github.com/jet-misoshil-h/vp2MayaPostEffectsSample


# MRenderOverrideとは
MayaAPIにはレンダリングにまつわるモジュールが備わっており、
その名も[OpenMayaRender](https://help.autodesk.com/view/MAYAUL/2023/ENU/?guid=MAYA_API_REF_cpp_ref_group_open_maya_render_html "OpenMayaRender")といいます

ドキュメントにもある通りMayaのレンダリング周りの操作やカスタマイズを行うためのAPIモジュールです

そのなかのMRenderOverrideはMayaのビューポートを独自パイプラインでオーバーライドすることができます
※オーバーライドとはいいますが、完全な置き換えではなくレンダーループの切り替えになります

## Mayaのレンダーループ
レンダーループとは毎フレーム描画されるシーンの描画イテレーションのことです
MRenderOverrideを使っている場合にコールされるレンダーループは主に以下のような仕組みで構成されているそうです
[公式ドキュメントから引用します](https://help.autodesk.com/view/MAYAUL/2023/ENU/?guid=MAYA_API_REF_cpp_ref_class_m_h_w_render_1_1_m_render_override_html "MRenderOverride")

```c++:render_loop_exsample
MRenderOverride override;
override.setup();
if (override.startOperationIterator())
{
    --> Get operation
	MRenderOperation *op = override.renderOperation();
	while (op)
	{
		if (op)
			--> Add to "list" of operations to perform
        --> Iterate to next operation
		if (!override.nextRenderOperation())
			op = NULL;
		else
			op = override.renderOperation();
	}
}
override.cleanup();
```
とてもシンプルですね:blush:（これだけ見れば

MRenderOperationはレンダーループ中にビュー内の**指定の要素を描画するレンダラー**で、
これをレンダリングパイプライン内でイテレーションさせてビューをレンダリングします

MRenderOperationにはいくつか種類があるのですが、そちらの解説は[AreaJapan様の記事](https://area.autodesk.jp/column/tutorial/maya_game_engine/03_customizing_renderer/ "第３回 レンダラをカスタマイズしよう！")か、Mayaのドキュメントにありますので割愛します



# MRenderOverrideでカスタムポストエフェクトを実装する
Mayaではカスタムポストエフェクトを実装するには**MQuadRender**を用います
といってもこれ単品をコマンド的に実行して動くものではなく自身でレンダーループを構築する必要があります

まずはループ構成のためのフロー設計を行います

# レンダーループのフロー構築
今回はシーンレンダー（ジオメトリなど3D空間レンダー）の描画結果に対してスクリーン描画でポストエフェクトをかける実装を例にするため以下のようなレンダーループを構築します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637095/7b42d7f9-f407-acd1-4084-9780a8ae146d.png)


# 途中に出てくるRenderTargetって？
なにやらRenderOparationの間に挟まっているものがありますね
**RenderTarget**とはなにかご存知の方はそのまま読み飛ばしていただくとして、
これらは各レンダーの描画結果をテクスチャにして確保するものです

レンダラーごとの描画はスクリーンの解像度分あるいは、
指定した解像度分の描画結果を取り置きしておき、他のレンダラーで使いまわしたりすることが求められます

これら取り置きしている描画結果をテクスチャとしてメモリ内に保存したものが**RenderTarget**です

# MayaのAPIで公開されている標準RnderTargetの種類
Mayaでは以下の３種類のレンダーターゲットを扱うためのAPIが公開されています
1. カラーターゲット
1. 深度ターゲット
1. ステンシルターゲット

今回の遊戯ではピクセルカラーの描画結果に対して変化を加えるため、このカラーターゲットだけで遊んでいこうと思います

# レンダーターゲットがテクスチャならシェーダーで使えるやん
題目の通りでレンダーターゲットはテクスチャです

MQuadRenderはいわば2Dの四角形レンダラーで、レンダリングするためのシェーダーを持っています
このシェーダーにテクスチャとして渡すことでキャラシェーダーなどのように画面に対して描画を追加できます

# MQuadRender::shader関数

MQuadRenderクラスでは、スクリーンに対する描画処理を担うシェーダーを置き換えることができる、MQuadRender::shader()という関数をオーバーライドできます

shader()関数はMShaderInstanceというシェーダー情報を持つクラスを返す必要があり、
このMShaderInstanceに独自に書いたシェーダーを渡したり、プロパティをセットすることで好きな処理を書くことが許可されています

:::note warn
自由度が高い分、実装する場合のメモリ管理も開発者に責任があります
:::

今回はHLSLでポストエフェクトのシェーダーを書きたいので、
描画APIをDirectX11で行っているものとして進めていきます

# DirectXならシェーダーにHLSLが使えるよ

MRenderOverrideにはどの描画APIをサポートしているのかを示す関数が用意されています

```c++: suport_drawAPI
MHWRender::DrawAPI MRenderOverride::supportedDrawAPIs() const
{
	return MHWRender::kDirectX11;
}
```

ここで指定された描画API以外で立ち上がっているMayaでは描画がなされません
ということでここを上記のようにDirectX11だけで描画しているよーと返しておきます

この状態であればMShaderInstanceにHLSLでかかれたシェーダーを渡すことができます

# シェーダーのヘッダーを書く
MayaでHLSLを書くときには拡張子として`.fx`が基本ですが、`.fxh`も扱えます
C言語ライクらしいですが`.fx`のほうをメイン処理にするとして、宣言やマクロ等を`.fxh`に記載していきます

```hlsl:PostEffectSample10.fxh
// ヘッダーを何度もincludeしないようにインクルードガードを挟みます
#ifndef _POSTEFFECT_SAMPLE_FXH_
#define _POSTEFFECT_SAMPLE_FXH_

// World-view-projection transformation.
float4x4 gWVPXf : WorldViewProjection < string UIWidget = "None"; >;

// ViewportPixelSizeセマンティクスでスクリーンの解像度を利用できます
float2 gScreenSize : ViewportPixelSize < string UIWidget = "None"; >;
// 1ピクセルあたりの大きさ（長さ）
static float2 gTexelSize = 1.0f / gScreenSize;


// Vertex shader input structure.
struct VS_INPUT_ScreenQuad
{
    float3 Pos : POSITION;
    float2 UV : TEXCOORD0;
};

// Vertex shader output structure.
struct VS_TO_PS_ScreenQuad
{
    float4 HPos : SV_Position;
    float2 UV : TEXCOORD0;
};

// Vertex shader.
VS_TO_PS_ScreenQuad VS_ScreenQuad(VS_INPUT_ScreenQuad In)
{
    VS_TO_PS_ScreenQuad Out;
    
    // Transform the position from object space to clip space for output.
    Out.HPos = mul(float4(In.Pos, 1.0f), gWVPXf);
    // Pass the texture coordinates.
    Out.UV = In.UV;
    
    return Out;
}

// ターゲットのピクセルをサンプリングするマクロです
#define SCREEN_SAMPLE(tex, samp, uv) tex.Sample(samp, uv)

#endif // _POSTEFFECT_SAMPLE_FXH_
```

# シェーダーのメイン処理を書く
メインのポストエフェクト処理を書いていきます

```hlsl:PostEffectSample10.fx
// fxhをインクルードします
#include "PostEffectSample10.fxh"

// MQuadRenderは複数用意しますが、同じシェーダーファイルを扱うのでTexture宣言をすべて記述します
// こちらは1つ目のテクスチャの入力プロパティ
Texture2D gInputTex1 < string UIWidget = "None";
> ;

// どれも同じサンプラー情報でよいのでひとつ宣言
// UVをずらすためwrap指定してます
SamplerState gInputSampler
{
    Filter = ANISOTROPIC;
    AddressU = Wrap;
    AddressV = Wrap;
};

// Blend State
BlendState AlphaBlend {
    AlphaToCoverageEnable = false;
    BlendEnable[0] = true;
    SrcBlend = SRC_ALPHA;
    DestBlend = INV_SRC_ALPHA;
    BlendOp = ADD;
    SrcBlendAlpha = ONE;
    DestBlendAlpha = ZERO;
    BlendOpAlpha = ADD;
};

// ------------------------------------------------------------------------------------------------
// ピクセルシェーダー
// ------------------------------------------------------------------------------------------------

// 赤色のみ抽出して描画するピクセルシェーダー
float4 PS_PostEffectSampleRedPass(VS_TO_PS_ScreenQuad i) : SV_TARGET
{
    // 10ピクセル分正方向にずらしたオフセットを用意
    float2 offset = i.UV + (gTexelSize * 10);

    float4 col = SCREEN_SAMPLE(gInputTex1, gInputSampler, i.UV);
    float4 offsetCol = SCREEN_SAMPLE(gInputTex1, gInputSampler, offset);
    

    // ずらした分のカラーから赤色のみ抽出して描画します
    return float4(
        offsetCol.r,
        col.g,
        col.b,
        0.5f
    );
}

// 青色のみ抽出して描画するピクセルシェーダー
float4 PS_PostEffectSampleBluePass(VS_TO_PS_ScreenQuad i) : SV_TARGET
{
    // 10ピクセル分負方向にずらしたオフセットを用意
    float2 offset = i.UV - (gTexelSize * 10);

    float4 col = SCREEN_SAMPLE(gInputTex1, gInputSampler, i.UV);
    float4 offsetCol = SCREEN_SAMPLE(gInputTex1, gInputSampler, offset);
    

    // ずらした分のカラーから青色のみ抽出して描画します
    return float4(
        col.r,
        col.g,
        offsetCol.b,
        0.5f
    );
}

// ------------------------------------------------------------------------------------------------
// テクニックを用意します
// ------------------------------------------------------------------------------------------------

// 赤と青をずらすテクニックです
technique10 Shift
{
    
    pass P0
    {
        SetBlendState(AlphaBlend, float4(0, 0, 0, 0), 0xFFFFFFFF);
        SetVertexShader(CompileShader(vs_4_0, VS_ScreenQuad()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_4_0, PS_PostEffectSampleRedPass()));
    }
    pass P1
    {
        SetBlendState(AlphaBlend, float4(0, 0, 0, 0), 0xFFFFFFFF);
        SetVertexShader(CompileShader(vs_4_0, VS_ScreenQuad()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_4_0, PS_PostEffectSampleBluePass()));
    }
}

```

なにやら`.fx`のほうにだけあやしい`10`の文字が含まれています
これはのちのちシェーダーファイルをMShaderInstanceへ読み込ませるとき
ファイル拡張子まで指定しない場合に特定状況下で必要になります

## メッシュシェーダーとの違い

他は普段キャラシェーダー等を書かれている人には馴染み深い内容になっていますね
違いとしてはVertexShaderでPOSITIONセマンティクスにバインドされている情報がメッシュの頂点情報ではなく、スクリーンのピクセル座標が来ているのが違いになります

ですのでPixelShaderでは画面すべてを覆った４角形メッシュに対して処理を行うイメージになります

# シェーダーファイルを読み込めるようにする
シェーダーファイルをMayaのShaderPathが既に通っているディレクトリ内においてもいいのですが、
MRenderOverrideはプラグインでの配布が想定されているためちょっと不便です

そこでプラグイン側から指定のパスをShaderPathに追加しちゃいましょう
ShaderPathをMayaに追加するにはMShaderManagerを利用します

```c++:PostEffectSample.cpp
// Initialize the shader
void PostEffectSample::initializeShaders()
{
	MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer();
	if (renderer)
	{
		const MHWRender::MShaderManager *shaderMgr = renderer->getShaderManager();
		if (shaderMgr)
		{
			// Get the path to the shader
			struct stat info;
            // MStringのexpandEnvironmentVariablesAndTildeを使うと環境変数に設定された文字列を展開できます
            // モジュールで環境変数を設定しておくと設定時にすでに展開されたPath文字列になっているため便利です
			const MString shaderPath = MString("${EXTERNAL_SHADER_PATH}").expandEnvironmentVariablesAndTilde();
			if (stat(shaderPath.asChar(), &info) != 0)
			{
				MDisplayError("Failed to get shader path : %s", shaderPath.asChar());
			}
			else
			{
				MDisplayInfo("Find to shader path : %s", shaderPath.asChar());
				shaderMgr->addShaderPath(shaderPath);
                // .fxhをインクルードする場合は追加しておきます
				shaderMgr->addShaderIncludePath(shaderPath);
			}
		}
	}
}
```
これは個人的なやり方ですが、
Mayaのモジュールなどを利用して環境変数を追加してMayaを起動、
その環境変数にシェーダーファイルがおいてあるディレクトリを指定しておき、プラグイン内でロードさせます

```
+ MAYAVERSION:2023 PostEffectSample 1.0.0 ..\.
EXTERNAL_SHADER_PATH := 2023\plug-ins\HLSL

+ MAYAVERSION:2024 PostEffectSample 1.0.0 ..\.
EXTERNAL_SHADER_PATH := 2024\plug-ins\HLSL

+ MAYAVERSION:2025 PostEffectSample 1.0.0 ..\.
EXTERNAL_SHADER_PATH := 2025\plug-ins\HLSL
```

モジュールはMayaのバージョンごとに読み込ませるプラグイン自体や、
スクリプトなども別々に管理できるためこの手法が一番安定した提供を可能にしている気がします

# MQuadRender::shader内でMShaderInsatanceを扱う

さてシェーダーを書き終えたところでどういうふうに遊べるのでしょうか
MQaudRenderのshader関数の登場です

オーバーライドするMQuadRenderヘッダーです
```c++:QuadRenderOverride.h
// pragmaでonceを利用するとインクルードガードを簡易記述できます
#pragma once

#include <maya/MViewport2Renderer.h>
#include "RenderTargetsInterface.h"
#include "CustomRenderProcess.h"
#include "OverrideClearColorInterface.h"

class OverrideQuadRender : public MHWRender::MQuadRender, public RenderTargetsInterface, public amagly::CustomRenderProcess, public amagly::OverrideClearColorInterface
{
public:
	enum EPassType
	{
		kRedPass,
		kBluePass,
		kCombainePass,
		kBlitPass,
		kClearPass
	};

	OverrideQuadRender(const MString &name, unsigned int clearMask);
	~OverrideQuadRender() override;

	// from MRenderOperation
	MHWRender::MRenderTarget *const *targetOverrideList(unsigned int &listSize) override;
	MHWRender::MClearOperation &clearOperation() override;
	// from MQuadRender
	const MHWRender::MShaderInstance *shader() override;

	// from CustomRenderProcess
	MStatus preProcess() override;
	MStatus postProcess() override;
	MHWRender::MRenderOperation *renderOperation() override
	{
		return (MHWRender::MRenderOperation*)this;
	}

	void setPassType(EPassType passType) { mPassType = passType; }
	void setInput1Tex(MHWRender::MRenderTarget *texture) { mTexture1 = texture; }
	void setInput2Tex(MHWRender::MRenderTarget *texture) { mTexture2 = texture; }
	void setInput3Tex(MHWRender::MRenderTarget *texture) { mTexture3 = texture; }

protected:
	MHWRender::MShaderInstance *mShaderInstance;
	MHWRender::MRenderTarget *mTexture1;
	MHWRender::MRenderTarget *mTexture2;
	MHWRender::MRenderTarget *mTexture3;
	MFloatPoint mViewRectangle;
	MString mCustomOutputTargetName;
private:
	MHWRender::MBlendState *mBlendState;
	EPassType mPassType;
};
```

実装側です
```c++:QuadRenderOverride.cpp
const MHWRender::MShaderInstance *OverrideQuadRender::shader()
{
	// printf("%s::shader\n", name().asChar());
	if (mShaderInstance == nullptr)
	{
		MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer();
		if (renderer)
		{
			const MHWRender::MShaderManager *shaderMgr = renderer->getShaderManager();
			if (shaderMgr)
			{
				switch (mPassType)
				{
				case EPassType::kRedPass:
                    // 本来MRTやりたかったんですが時間足らず断念してここだけで描画
					mShaderInstance = shaderMgr->getEffectsFileShader("PostEffectSample", "Shift", nullptr, 0, false);
					break;
				case EPassType::kBluePass:
					mShaderInstance = shaderMgr->getEffectsFileShader("PostEffectSample", "BlueShift", nullptr, 0, false);
					break;
				case EPassType::kCombainePass:
					mShaderInstance = shaderMgr->getEffectsFileShader("PostEffectSample", "Combine", nullptr, 0, false);
					break;
				case EPassType::kBlitPass:
					mShaderInstance = shaderMgr->getEffectsFileShader("PostEffectSample", "Blit", nullptr, 0, false);
					break;
				case EPassType::kClearPass:
					mShaderInstance = shaderMgr->getEffectsFileShader("PostEffectSample", "Clear", nullptr, 0, false);
					return mShaderInstance;
				default:
					return nullptr;
				}
			}
		}
	}

	if (mShaderInstance)
	{
		// printf("ShaderInstance : %s\n", name().asChar());
		// Set the input texture
		{
			MHWRender::MRenderTargetAssignment assignment;
			assignment.target = mTexture1;
			MStatus status = mShaderInstance->setParameter("gInputTex1", assignment);
			if (status != MStatus::kSuccess)
			{
				printf("Could not set input render target / texture parameter on invert shader\n");
				return nullptr;
			}
		}

		switch (mPassType)
		{
		case EPassType::kRedPass:
		case EPassType::kBluePass:
		{
			if (mTexture2)
			{
				// Set Depth Texture
				MHWRender::MRenderTargetAssignment assignment;
				assignment.target = mTexture2;
				MStatus status = mShaderInstance->setParameter("gInputTex2", assignment);
				if (status != MStatus::kSuccess)
				{
					printf("Could not set input render target / texture parameter on invert shader\n");
					return nullptr;
				}
			}
			break;
		}
		case EPassType::kCombainePass:
		{
			// Set the second input texture
			if (mTexture2)
			{
				MHWRender::MRenderTargetAssignment assignment;
				assignment.target = mTexture2;
				MStatus status = mShaderInstance->setParameter("gInputTex2", assignment);
				if (status != MStatus::kSuccess)
				{
					printf("Could not set input render target / texture parameter on invert shader\n");
					return nullptr;
				}
			}

			// Set the third input texture
			if (mTexture3)
			{
				MHWRender::MRenderTargetAssignment assignment;
				assignment.target = mTexture3;
				MStatus status = mShaderInstance->setParameter("gInputTex3", assignment);
				if (status != MStatus::kSuccess)
				{
					printf("Could not set input render target / texture parameter on invert shader\n");
					return nullptr;
				}
			}
			break;
		}
		case EPassType::kBlitPass:
		case EPassType::kClearPass:
		default:
			break;
		}
	}

	return mShaderInstance;
}
```

MShaderManager->getEffectsFileShaderのハマりポイントは以下の部分です
ここでは"PostEffectSample"と渡したい場合、ファイルは"PostEffectSample10.fx"にしなければなりません

```cpp:Sample
mShaderInstance = shaderMgr->getEffectsFileShader("PostEffectSample", "RedShift", nullptr, 0, true);
```

公式ドキュメントにも記載があります

:::note info
ファイル名の引数の一部として __ファイル拡張子が与えられない場合__ 、レンダラーが現在使用している描画APIに基づいて、以下の文字列がファイル名に追加される：
基礎となる描画APIがOpenGLの場合、「.cgfx 」が付加される。
基礎となる描画APIが __DirectX 10以上の場合、「10.fx 」が付加される。__
基礎となる描画APIがOpenGLCoreProfileの場合、「.ogsfx 」が付加されます。
:::

SDKにある公式サンプルだと大抵拡張子まで引数になかったりするので、
**サンプルからコード持ってきたのに自分のシェーダーファイルを指定すると動かないんだが！**
とかなったりします

戒めとしてドキュメントはしっかり読み込みましょうと心得ました
ルールを守って遊びましょう

# レンダーループを構築する
さてQuadRenderも用意できて、シェーダーとテクスチャも扱えるようになったので
いよいよ本番のレンダーループを構築します

SDKのサンプルにあるやり方だと
1. オペレーションリスト配列をつくる
1. 現在のオペレーションのindex値(int)を宣言
1. nextRenderOperation()でindexを加算する
1. index値がオペレーションリストのサイズを超えた場合にループをブレークする

となっています

しかし今回はちょっと実験がてらサンプルどおりには実装せず遊んで見ようと思います

# レンダー定義
今回は見やすさを重視してMRenderOverrideのsetup内には定義せず、別ファイルにわけています

```c++: RenderOparationManager
MStatus RenderOparationManager::setup()
{
    auto *frameTexManager = static_cast<FrameTextureManager *>(FrameTextureManager::Get());
    MStatus stat = frameTexManager->UpdateRenderTargets();
    if (stat == MStatus::kFailure)
    {
        MGlobal::displayError("Failed to Update RenderTargets");
        return MStatus::kFailure;
    }

    if (mRenderOperations.size() == 0)
    {
        // -----------------------------------
        // inisialize the rendering oparations
        // -----------------------------------

        /*
        ここにMRenderOparationの定義と追加処理
        */

        // Set the rendering flow
        // 今回はここで決めきっていますがループのセットアップ時に
        // なんらか外出しのノードからの更新でフローを変更することもしやすいと思っています
        std::vector<unsigned int> renderingFlow;
        renderingFlow = {
            RenderOparationType::BaseSceneOparation,
            RenderOparationType::BlitSceneOparation,
            // RenderOparationType::ClearTempRedBufferOparation,
            RenderOparationType::RedPassRenderOparation,
            // RenderOparationType::ClearTempBlueBufferOparation,
            // RenderOparationType::BluePassRenderOparation,
            // RenderOparationType::CombinePassRenderOparation,
            RenderOparationType::PresentTargetOparation
        };
        setRenderingFlow(renderingFlow);
    }

    for (int i = 0; i < mRenderOperations.size(); i++)
    {
        if (!mRenderOperations[i])
        {
            return MStatus::kFailure;
        }
    }

    // printf("setuped RenderOparationManager\n");
    return MStatus::kSuccess;
}
```

# ループ処理改造
MRenderOverrideは冒頭で軽く説明した通り、シンプルかつ開発者がループ中の処理をある程度フックしやすくしてくれています
このため今回はOparationManagerというクラスを定義してそちらにループ中の処理をフックしてあげることで、追加処理も挟めるという狙いもありました

```cpp:RenderLoop
bool vp2MayaPostEffectsSample::startOperationIterator()
{
	mRenderOparationManager->start();
	return true;
}

MHWRender::MRenderOperation *vp2MayaPostEffectsSample::renderOperation()
{
	MStatus status = mRenderOparationManager->executePreRenderProcess();
	if (status != MStatus::kSuccess)
	{
		printf("Could not execute pre render process\n");
		return nullptr;
	}
	return mRenderOparationManager->renderOparation();
}

bool vp2MayaPostEffectsSample::nextRenderOperation()
{
	MStatus status = mRenderOparationManager->executePostRenderProcess();
	if (status != MStatus::kSuccess)
	{
		printf("Could not execute post render process\n");
		return false;
	}
	mRenderOparationManager->next();
	return mRenderOparationManager->isVaildNextOparation();
}

MStatus vp2MayaPostEffectsSample::setup(const MString &destination)
{
	MHWRender::MRenderer *theRenderer = MHWRender::MRenderer::theRenderer();
	if (!theRenderer)
	{
		return MStatus::kFailure;
	}

	return mRenderOparationManager->setup();
}

MStatus vp2MayaPostEffectsSample::cleanup()
{
	return MStatus::kSuccess;
}
```

# MRenderOparationの関数と実行順
Mayaドキュメントで見つけられなかったのですが、MRenderOparationはMUserRenderOparationやMHUDRender、MPresentTargetといったオペレーションを除いたオペレーションは、関数に実行順が指定されています

処理順を出力してみると以下のような感じで実行されていました
1. clearOparationが実行
1. writableTargetsが明示的にOverrideされている場合ここでターゲットのOverrideCountが処理されます
1. targetOverrideListが実行
1. オペレーションが実行されます

ですのでレンダーループ中、オペレーションを受け渡す前にレンダーターゲットのサイズ更新やらなんやらを操作する場合は純粋なオペレーションを定義するだけだと不可能で、MUserRenderOparationを用いてデバイスアクセス含め自分でやることになってしまうようです

clearOparationも実際はバックバッファのクリアを行っているのかなぁとおもっていたりしています
実際サンプルではMRenderOverride内で作成したターゲットのクリアは自身で行っているようです


# ビルドする
具体的なC++のプラグインビルド方法はSDKや[解説](https://area.autodesk.jp/column/tutorial/maya-api/01-development-env/ "第1回：開発環境の準備、サンプルプラグインのビルドと実行")がございますのでそちらを見ていただくとして以下のようなCMakeファイルを用意しました

```cmake:cmakeSample.txt
cmake_minimum_required(VERSION 3.13)

# set env variable DEVKIT_LOCATION to the location of Maya devkit
set(ENV{DEVKIT_LOCATION} C:/work/maya_devkit/devkitBase)

# include the project setting file
include($ENV{DEVKIT_LOCATION}/cmake/pluginEntry.cmake)

# specify project name
set(PROJECT_NAME vp2MayaPostEffectsSample)

# set SOURCE_FILES
set(SOURCE_FILES
Common.h
CustomRenderProcess.h
FrameTextureManager.cpp
FrameTextureManager.h
OverrideClearColorInterface.cpp
OverrideClearColorInterface.h
OverridePresentTarget.cpp
OverridePresentTarget.h
OverrideQuadRender.cpp
OverrideQuadRender.h
OverrideSceneRender.cpp
OverrideSceneRender.h
RenderOparationManager.cpp
RenderOparationManager.h
RenderTargetsInterface.cpp
RenderTargetsInterface.h
vp2MayaPostEffectsSample.cpp
vp2MayaPostEffectsSample.h
vp2MayaPostEffectsSampleMain.cpp
)

# set linking libraries
set(LIBRARIES
     OpenMaya
     OpenMayaRender
     Foundation
)

# Build plugin
build_plugin()

```

# Mayaを立ち上げて結果を見てみる
やりました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637095/5edc9b8d-ee09-ec17-8cf1-3e1863661ac3.png)

本来の色収差はレンズの状態などから全体にではなく、カメラからの距離などによって計算すべきかと思いますが今回はポストエフェクトをかけることが目的なので一旦良しとしましょう

# おわりに

いつものツール開発くらいのノリでどんなものかと触り始めてみましたが、あーでもないこーでもないと試行錯誤していくとすぐにMayaのドキュメントではなくdirectXのドキュメントと睨めっこすることになり、改めて描画エンジニアの方を尊敬する気持ちになりました

とはいえやりごたえもありますし、さらに理解を進めればオリジナルの描画エンジンも作れそうな予感です

みなさんも是非、年末年始はMRenderoverrideで遊びながら年を越してみては如何でしょうか

それでは良いお年を！
ありがとうございました