Color Filtering

Color Filtering

现在我们使用FullScreenRenderTarget和FullScreenQuad类编写一个color filter演示程序。一个color filter就是以某种方法修改输出的颜色值。如果你曾经佩戴过彩色的眼镜,你就已经见过color filtering的效果。在这个示例程序中,我们将会编写多个color filter shaders,包括grayscale,inverse,sepia以及一个通用的color filtering系统用于生成任意的color effects。

A Grayscale Filter

首先创建一个ColorFilter.fx文件,该shader的代码如列表18.5所示。

列表18.5 A Grayscale Shader

static const float3 GrayScaleIntensity = { 0.299f, 0.587f, 0.114f };

static const float3x3 SepiaFilter = { 0.393f, 0.349f, 0.272f,
                                      0.769f, 0.686f, 0.534f,
                                      0.189f, 0.168f, 0.131f };

/************* Resources *************/

cbuffer CBufferPerObject
{
    float4x4 ColorFilter = { 1, 0, 0, 0,
                             0, 1, 0, 0,
                             0, 0, 1, 0,
                             0, 0, 0, 1 };
}

Texture2D ColorTexture;

SamplerState TrilinearSampler
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = WRAP;
    AddressV = WRAP;
};

/************* Data Structures *************/

struct VS_INPUT
{
    float4 Position : POSITION;
    float2 TextureCoordinate : TEXCOORD;
};

struct VS_OUTPUT
{
    float4 Position : SV_Position;
    float2 TextureCoordinate : TEXCOORD;  
};

/************* Vertex Shader *************/

VS_OUTPUT vertex_shader(VS_INPUT IN)
{
    VS_OUTPUT OUT = (VS_OUTPUT)0;
    
    OUT.Position = IN.Position;
    OUT.TextureCoordinate = IN.TextureCoordinate;
    
    return OUT;
}

/************* Pixel Shaders *************/

float4 grayscale_pixel_shader(VS_OUTPUT IN) : SV_Target
{
    float4 color = ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);	
    float intensity = dot(color.rgb, GrayScaleIntensity);
    
    return float4(intensity.rrr, color.a);
}

float4 inverse_pixel_shader(VS_OUTPUT IN) : SV_Target
{
    float4 color = ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);	

    return float4(1 - color.rgb, color.a);
}

float4 sepia_pixel_shader(VS_OUTPUT IN) : SV_Target
{
    float4 color = ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);	

    return float4(mul(color.rgb, SepiaFilter), color.a);
}

float4 genericfilter_pixel_shader(VS_OUTPUT IN) : SV_Target
{
    float4 color = ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);	
    
    return float4(mul(color, ColorFilter).rgb, color.a);
}

/************* Techniques *************/

technique11 grayscale_filter
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, grayscale_pixel_shader()));
    }
}

technique11 inverse_filter
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, inverse_pixel_shader()));
    }
}

technique11 sepia_filter
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, sepia_pixel_shader()));
    }
}

technique11 generic_filter
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, genericfilter_pixel_shader()));
    }
}


这是一种grayscale filter;通过查找color的强度值把把采样的RGB颜色转换成grayscale样式。一种最容易想到的方法是通过计算RGB三个通道的平均值得到一个color的强度值:
Intensity = (R + G + B) / 3
但是人的眼睛对红,绿,蓝三种颜色的感知是不同的。具体地说就是,我们对绿色更敏感,其次是红色,最后才蓝色。因此,一种更精确的表示grayscale强度的方法是计算三个color通道值的加权平均值,该计算公式如下:
Intensity = 0.299 * R + 0.587 * G + 0.114 * B
在grayscale pixel shader中通过一个简单的dot product运算就可以计算出grayscale强度值。

另外,需要注意的是在vertex shader中,传递vertex坐标值的时候不需要对坐标进行变换,因为这些坐标值已经指定为sceen space中。

A Color Filter Demo

现在我们创建一个派生自Game类的新类ColorFilteringGame,用于在前面几章所编写的示例中添加color filter渲染。与之前一样,在ColorFilteringGame类中需要初始化mouse和keyboard,camera,skybox,reference grid,frame-rate component,以及point light demo component。还包括SpriteBatch和SpriteFont类型的成员变量用于在屏幕上渲染文字。在成员列表最后,需要添加以下的成员变量:
FullScreenRenderTarget* mRenderTarget;
FullScreenQuad* mFullScreenQuad;
Effect* mColorFilterEffect;
ColorFilterMaterial* mColorFilterMaterial;


其中包括新的FullScreenRenderTarget和FullScreenQuad类型的成员变量。此外还有一个mColorFilterEffect存储编译好的color filter shader,并用于初始化mColorFilterMaterial变量。ColorFilterMaterial类与ColorFilter.fx shader的输入数据匹配。
然后在ColorFilteringGame::Initialize()函数中使用以下的代码初始化这些成员变量:
void ColorFilteringGame::Initialize()
{
	if (FAILED(DirectInput8Create(mInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&mDirectInput, nullptr)))
	{
		throw GameException("DirectInput8Create() failed");
	}

	mKeyboard = new Keyboard(*this, mDirectInput);
	mComponents.push_back(mKeyboard);
	mServices.AddService(Keyboard::TypeIdClass(), mKeyboard);

	mMouse = new Mouse(*this, mDirectInput);
	mComponents.push_back(mMouse);
	mServices.AddService(Mouse::TypeIdClass(), mMouse);

	mCamera = new FirstPersonCamera(*this);
	mComponents.push_back(mCamera);
	mServices.AddService(Camera::TypeIdClass(), mCamera);

	mFpsComponent = new FpsComponent(*this);
	mFpsComponent->Initialize();

	mSkybox = new Skybox(*this, *mCamera, L"Content\\Textures\\Maskonaive2_1024.dds", 500.0f);
	mComponents.push_back(mSkybox);

	mGrid = new Grid(*this, *mCamera);
	mComponents.push_back(mGrid);

	RasterizerStates::Initialize(mDirect3DDevice);
	SamplerStates::BorderColor = ColorHelper::Black;
	SamplerStates::Initialize(mDirect3DDevice);

	mPointLightDemo = new PointLightDemo(*this, *mCamera);
	mComponents.push_back(mPointLightDemo);

	mRenderStateHelper = new RenderStateHelper(*this);

	mRenderTarget = new FullScreenRenderTarget(*this);

	SetCurrentDirectory(Utility::ExecutableDirectory().c_str());
	mColorFilterEffect = new Effect(*this);
	mColorFilterEffect->LoadCompiledEffect(L"Content\\Effects\\ColorFilter.cso");

	mColorFilterMaterial = new ColorFilterMaterial();
	mColorFilterMaterial->Initialize(*mColorFilterEffect);

	mFullScreenQuad = new FullScreenQuad(*this, *mColorFilterMaterial);		
	mFullScreenQuad->Initialize();
	mFullScreenQuad->SetActiveTechnique(ColorFilterTechniqueNames[mActiveColorFilter], "p0");
	mFullScreenQuad->SetCustomUpdateMaterial(std::bind(&ColorFilteringGame::UpdateColorFilterMaterial, this));

	mSpriteBatch = new SpriteBatch(mDirect3DDeviceContext);
	mSpriteFont = new SpriteFont(mDirect3DDevice, L"Content\\Fonts\\Arial_14_Regular.spritefont");
	Game::Initialize();

	mCamera->SetPosition(0.0f, 0.0f, 25.0f);
}


在该函数中,使用color filtering material以及grayscale_filter technique中的pass p0初始化full-screen quad变量。并指定ColorFilteringGame::UpdateColorFilterMaterial()函数作为full-screen quad的custom material回调函数,该函数实现代码如下:
void ColorFilteringGame::UpdateColorFilterMaterial()
{
	XMMATRIX colorFilter = XMLoadFloat4x4(&mGenericColorFilter);

	mColorFilterMaterial->ColorTexture() << mRenderTarget->OutputTexture();
}


每一次绘制full-screen quad时都会调用一次该函数,并把render target的输出数据传递给shader变量ColorTexture。最后要分析的是ColorFilteringGame::Draw()函数 ,该函数的实现代码如下:

void ColorFilteringGame::Draw(const GameTime &gameTime)
{
	mRenderTarget->Begin();

	mDirect3DDeviceContext->ClearRenderTargetView(mRenderTarget->RenderTargetView() , reinterpret_cast<const float*>(&BackgroundColor));
	mDirect3DDeviceContext->ClearDepthStencilView(mRenderTarget->DepthStencilView(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

	Game::Draw(gameTime);

	mRenderTarget->End();

	mDirect3DDeviceContext->ClearRenderTargetView(mRenderTargetView, reinterpret_cast<const float*>(&BackgroundColor));
	mDirect3DDeviceContext->ClearDepthStencilView(mDepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

	mFullScreenQuad->Draw(gameTime);

	mRenderStateHelper->SaveAll();
	mFpsComponent->Draw(gameTime);

	mSpriteBatch->Begin();

	std::wostringstream helpLabel;
	helpLabel << L"Ambient Intensity (+PgUp/-PgDn): " << mPointLightDemo->GetAmbientColor().a << "\n";
	helpLabel << L"Point Light Intensity (+Home/-End): " << mPointLightDemo->GetPointLight().Color().a << "\n";
	helpLabel << L"Specular Power (+Insert/-Delete): " << mPointLightDemo->GetSpecularPower() << "\n";
	helpLabel << L"Move Point Light (8/2, 4/6, 3/9)\n";
	helpLabel << std::setprecision(2) << L"Active Filter (Space Bar): " << ColorFilterDisplayNames[mActiveColorFilter].c_str();
	if (mActiveColorFilter == ColorFilterGeneric)
	{
		helpLabel << L"\nBrightness (+Comma/-Period): " << mGenericColorFilter._11 << "\n";
	}

	mSpriteFont->DrawString(mSpriteBatch, helpLabel.str().c_str(), mTextPosition);

	mSpriteBatch->End();

	mRenderStateHelper->RestoreAll();

	HRESULT hr = mSwapChain->Present(0, 0);
	if (FAILED(hr))
	{
		throw GameException("IDXGISwapChain::Present() failed.", hr);
	}
}


在Draw()函数中使用了本章开始部分所讨论的post-processing步骤。首先,调用mRenderTarget->Begin()函数把off-screen render target绑定到管线的output-merger阶段。然后绘制场景,先通过调用ClearRenderTargetView()和ClearDepthStencilView()函数clear off-screen的render target以及depth-stencil views,再调用mRenderTarget->End()函数恢复back buffer作为与output-merger阶段绑定的render target。最后,把full-screen quad渲染到back buffer中。由于在full-screen quad已经使用了ColorFilter.fx shader中的grayscale_filter technique,因此在最终的back buffer中就是应用了该effect的渲染结果。
图18.1中显示了在一个包含有point light,reference grid和skybox的场景中使用grayscale filter的输出结果。与之前的point light示例程序一样,在ColorFilter示例中也可以通过数字键盘与场景中的point light进行交互。其中post-processing effect与渲染到off-screen render target中的数据是完全独立的。


图18.1 Output of the grayscale post-processing effect. (Skybox texture by Emil Persson. Earth texture from Reto Stöckli, NASA Earth Observatory.)

A Color Inverse Filter

只需要在ColorFilter.fx shader中做一点点修改就可以增加更多的color filters。例如,使用下面的pixel shader和technique就可以实现一种color inverse filter:

float4 inverse_pixel_shader(VS_OUTPUT IN) : SV_Target
{
	float4 color = ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);	

	return float4(1 - color.rgb, color.a);
}

technique11 inverse_filter
{
	pass p0
	{
		SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
		SetGeometryShader(NULL);
		SetPixelShader(CompileShader(ps_5_0, inverse_pixel_shader()));
	}
}


在这种filter中只是把每一个颜色的RGB通道值进行反转,最终产生的输出结果如图18.2所示。


图18.2 Output of the color inverse post-processing effect. (Skybox texture by Emil Persson. Earth texture from Reto Stöckli, NASA Earth Observatory.)

A Sepia Filter

现在我们开始创建一种filter用于近似模拟古老的照片。在那个年代,黑白照片实际上是红褐色(reddish-brown)的阴影,称为sepia色调。与grayscale effect一样,通过计算RGB各个通道的加权平均值得到一个pixel的强度值,可以创建一种sepia shader。但是在sepia effect中,对于每一个color channel计算一个不同的强度值。计算公式为:


通过分别执行三次dot product运算操作就可以实现。但是一种更简洁的方法是构建一个表示sepia相关系数的3×3矩阵,并执行一次简单的矩阵乘法运算(矩阵乘法实际上就是一系列dot product操作)。列表18.6列出了用于sepia post-processing effect的pixel shader和technique代码。可以在ColorFilter.fx文件中直接添加这些代码,而不用再创建一个完全独立的effect文件。

列表18.6 A Sepia Shader

static const float3x3 SepiaFilter = { 0.393f, 0.349f, 0.272f,
	0.769f, 0.686f, 0.534f,
	0.189f, 0.168f, 0.131f };

float4 sepia_pixel_shader(VS_OUTPUT IN) : SV_Target
{
	float4 color = ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);	

	return float4(mul(color.rgb, SepiaFilter), color.a);
}

technique11 sepia_filter
{
	pass p0
	{
		SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
		SetGeometryShader(NULL);
		SetPixelShader(CompileShader(ps_5_0, sepia_pixel_shader()));
	}
}

Sepia shader的输出结果与图18.3所示。


图18.3 Output of the sepia post-processing effect. (Skybox texture by Emil Persson. Earth texture from Reto Stöckli, NASA Earth Observatory.)

A Generic Color Filter

在sepia shader的基础上做一点点扩展,就可以支持在应用程序运行时动态指定color filter矩阵值。使用这种方法,可以只使用一种technique表示上述所有的color filters。为了使用generic-filter尽可能通用,其中使用了一个4×4的color filter矩阵。列表18.7中列出了generic-filter的pixel shader和technique代码。
列表18.7 A Generic Color Filter Shader

cbuffer CBufferPerObject
{
	float4x4 ColorFilter = { 1, 0, 0, 0,
		0, 1, 0, 0,
		0, 0, 1, 0,
		0, 0, 0, 1 };
}

float4 genericfilter_pixel_shader(VS_OUTPUT IN) : SV_Target
{
	float4 color = ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);	

	return float4(mul(color, ColorFilter).rgb, color.a);
}

technique11 generic_filter
{
	pass p0
	{
		SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
		SetGeometryShader(NULL);
		SetPixelShader(CompileShader(ps_5_0, genericfilter_pixel_shader()));
	}
}


在使用generic-filter shader的情况下,通过以下的矩阵可以分别表示grayscale,inverse以及sepia effects:


在本书的配套网站上提供了ColorFilteringGame类的代码,通过使用一个单位缩放矩阵模拟brightness(color filter矩阵对角线上的值在0与1之间变动)的应用程序演示了generic color filter。在该示例中,可以使用键盘上的逗号和句号按键分别增加或减少亮度。另外,还可以通过按下空格键盘在各种color filters之间进行切换。要实现这种功能,需要在ColorFilteringGame::UpdateColorFilterMaterial()回调函数中实时更新传递给shader变量ColoFilter的值。函数的实现如下所示:

void ColorFilteringGame::UpdateColorFilterMaterial()
{
	XMMATRIX colorFilter = XMLoadFloat4x4(&mGenericColorFilter);

	mColorFilterMaterial->ColorTexture() << mRenderTarget->OutputTexture();
	mColorFilterMaterial->ColorFilter() << colorFilter;
}


图18.4显示了使用generic color filter模拟一种full-screen brightness的输出结果。


图18.4 Output of the generic color filter shader simulating brightness. (Skybox texture by Emil Persson. Earth texture from Reto Stöckli, NASA Earth Observatory.)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值