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.)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值