3D游戏引擎系列十一

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。

CSDN课程视频网址:http://edu.csdn.net/lecturer/144

后处理是游戏场景渲染的重要一环,利用这节给读者介绍运用于场景中的后处理渲染效果,后处理效果也是体现GPU强大的处理能力。最常用的后处理效果渲染:Bloom渲染、Blur渲染、HDR渲染等被称为后处理渲染。Bloom渲染又称为全屏泛光,后处理渲染在端游里应用的最广泛,通俗的讲就是场景首先经过CPU处理后,是每帧处理后再通过GPU在原有渲染的基础上再渲染一遍,最后将其在屏幕上显示出来的过程称为后处理渲染。实现方式新建一个文本文件把扩展名改成.fx。在编写Shader文件之前先讲一下Bloom实现原理主要分为4步:

1、提取原场景贴图中的亮色;

2、针对提取贴图进行横向Blur模糊;

3、在横向模糊基础上进行纵向Blur模糊;

4、所得贴图与原场景贴图叠加得最终效果图。

根据这4个步骤,开始着手编写Shader代码,还是新建一个文本文件将扩展名命名为fx。对应的Shader完整代码如下所示:

texture texSource;
texture texScene;

struct VS_INPUT
{
	float4 pos : POSITION;
	float2 uv  : TEXCOORD0;
};

struct VS_OUTPUT
{
	float4 pos : POSITION;
	float2 uv  : TEXCOORD0;
};

VS_OUTPUT quad_vs(VS_INPUT vert)
{
	VS_OUTPUT vsout = (VS_OUTPUT)0;
	
	vsout.pos = vert.pos;
	vsout.uv = vert.uv;
	
	return vsout;
}

sampler sourceSampler = sampler_state
{	
	Texture = <texSource>;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
};

// 向下取样
//---------------------------------------------------
const float HighlightThreshold = 0.5;

float luminance(float3 c)
{
	return dot( c, float3(0.3, 0.59, 0.11) );
}

// this function should be baked into a texture lookup for performance
float highlights(float3 c)
{
	return smoothstep(HighlightThreshold, 1.0, luminance(c.rgb));
}

float4 ps_downsample(float2 uv : TEXCOORD0) : COLOR
{
	float4 color = tex2D(sourceSampler, uv);
	
	// store hilights in alpha
	color.a = highlights(color);
	
	return color;
}

// blur
//----------------------------------------------------------------
// blur filter weights
const float weights7[7] = {0.05, 0.1, 0.2, 0.3, 0.2, 0.1, 0.05};	
//横向Blur
float4 ps_hblur(float2 uv : TEXCOORD0) : COLOR
{
	float texelSize = 1.0f/512.0f;	//1除以贴图大小

	float4 color = 0;
	for(int i=0; i<7; i++)
	{
		// 把uv的生成放到vs会节省很多运算
		float2 uvi = uv + float2(texelSize*(i-3),0);
		color += tex2D(sourceSampler, uvi) * weights7[i];
	}
	
	return color;
}

//纵向Blur
float4 ps_vblur(float2 uv : TEXCOORD0) : COLOR
{
	float texelSize = 1.0f/384.0f;	//1除以贴图大小

	float4 color = 0;
	for(int i=0; i<7; i++)
	{
		// 把uv的生成放到vs会节省很多运算
		float2 uvi = uv + float2(0, texelSize*(i-3));
		color += tex2D(sourceSampler, uvi) * weights7[i];
	}
	
	return color;
}

// 组合
//------------------------------------------------------------------
sampler sceneSampler = sampler_state
{	
	Texture = <texScene>;
    MinFilter = None;
    MagFilter = None;
    AddressU = CLAMP;
    AddressV = CLAMP;
};

float4 ps_compose(float2 uv : TEXCOORD0) : COLOR
{
	float4 blurColor = tex2D(sourceSampler, uv);
	float4 sceneColor = tex2D(sceneSampler, uv);
	
	return sceneColor*0.8 + blurColor*0.2 + blurColor*blurColor.a;
}

// technique
//------------------------------------------------------------------
technique PP_Bloom
{
	pass DownSample
	{
		CullMode = none;
		ZEnable = false;
		ZWriteEnable = false;
		
		VertexShader = compile vs_1_1 quad_vs();
		PixelShader = compile ps_2_0 ps_downsample();
	}
	
	pass HBlur
	{
		VertexShader = compile vs_1_1 quad_vs();
		PixelShader = compile ps_2_0 ps_hblur();
	}
	
	pass VBlur
	{
		VertexShader = compile vs_1_1 quad_vs();
		PixelShader = compile ps_2_0 ps_vblur();
	}
	
	pass Compose
	{
		VertexShader = compile vs_1_1 quad_vs();
		PixelShader = compile ps_2_0 ps_compose();
	}
}

以上是整个Shader代码的编写,接下来对于一些核心知识点重点介绍一下:第一步是实现提取原场景贴图中的亮色,因为Bloom又称为全屏泛光,所以要提取贴图中的亮色。对应的代码函数如下所示:

const float HighlightThreshold = 0.5;

float luminance(float3 c)
{
	return dot( c, float3(0.3, 0.59, 0.11) );
}

// this function should be baked into a texture lookup for performance
float highlights(float3 c)
{
	return smoothstep(HighlightThreshold, 1.0, luminance(c.rgb));
}

第二步在第一步的基础上,针对需要渲染的贴图进行横向Blur模糊。函数代码如下:

const float weights7[7] = {0.05, 0.1, 0.2, 0.3, 0.2, 0.1, 0.05};	

float4 ps_hblur(float2 uv : TEXCOORD0) : COLOR
{
	float texelSize = 1.0f/512.0f;	//1除以贴图大小

	float4 color = 0;
	for(int i=0; i<7; i++)
	{
		// 把uv的生成放到vs会节省很多运算
		float2 uvi = uv + float2(texelSize*(i-3),0);
		color += tex2D(sourceSampler, uvi) * weights7[i];
	}
	
	return color;
}

第三步在第二步的基础上,针对提取贴图进行纵向Blur模糊。函数代码如下:

float4 ps_vblur(float2 uv : TEXCOORD0) : COLOR
{
	float texelSize = 1.0f/384.0f;	//1除以贴图大小

	float4 color = 0;
	for(int i=0; i<7; i++)
	{
		// 把uv的生成放到vs会节省很多运算
		float2 uvi = uv + float2(0, texelSize*(i-3));
		color += tex2D(sourceSampler, uvi) * weights7[i];
	}
	
	return color;
}

第四步将模糊处理的图片和原场景贴图分别进行纹理取样,最后将二者按照线性公式混合处理得到最终的结果。函数代码如下:

sampler sceneSampler = sampler_state
{	
	Texture = <texScene>;
    MinFilter = None;
    MagFilter = None;
    AddressU = CLAMP;
    AddressV = CLAMP;
};

float4 ps_compose(float2 uv : TEXCOORD0) : COLOR
{
	float4 blurColor = tex2D(sourceSampler, uv);
	float4 sceneColor = tex2D(sceneSampler, uv);
	
	return sceneColor*0.8 + blurColor*0.2 + blurColor*blurColor.a;
}

下面开始介绍在引擎中的实现了,其C++实现也是根据Shader的思路编写的。首先通过函数创建三张纹理,这三张纹理都是在内存中的,第一张用于存放游戏中提取原场景贴图中的亮色,第二张用于存放横向模糊的图片,第三张用于存放纵向模糊的图片。另外编写的Shader文件必须要程序加载才能生效,C++方面也有对应着Shader编程的代码,主要分了四步:取样,横向Blur,纵向Blur,组合。现将C++核心代码展示如下:

bool PostProcessBloom::init(IDirect3DDevice9 *pD3DDevice)
{
	m_pD3DDevice = pD3DDevice;

	if(!m_scene.init(pD3DDevice))
		return false;

	// 创建所需的render targets
	HRESULT hr = pD3DDevice->CreateTexture(clientWidth, clientHeight,
		1,//mip-map
		D3DUSAGE_RENDERTARGET,
		D3DFMT_A8R8G8B8,
		D3DPOOL_DEFAULT,
		&m_pRTFull,
		NULL);

	if(FAILED(hr))
		return false;
	//创建纹理存放横向Blur
	hr = pD3DDevice->CreateTexture(clientWidth/2, clientHeight/2,
		1,//mip-map
		D3DUSAGE_RENDERTARGET,
		D3DFMT_A8R8G8B8,
		D3DPOOL_DEFAULT,
		&m_pRTQuarterA,
		NULL);
	if(FAILED(hr))
		return false;
	
	hr = pD3DDevice->CreateTexture(clientWidth/2, clientHeight/2,
		1,//mip-map
		D3DUSAGE_RENDERTARGET,
		D3DFMT_A8R8G8B8,
		D3DPOOL_DEFAULT,
		&m_pRTQuarterB,
		NULL);
	if(FAILED(hr))
		return false;

	// create effect
	m_pEffect = DrawingUtil::getInst()->loadEffect(pD3DDevice,"Shader\\Bloom.fx");

	// pictue
	hr = D3DXCreateTextureFromFileEx(pD3DDevice, "Media\\gf.jpg",
		D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT_NONPOW2,
		1, //mip lv
		0,//usage
		D3DFMT_A8R8G8B8,
		D3DPOOL_MANAGED,
		D3DX_DEFAULT,
		D3DX_DEFAULT,
		0,
		NULL,
		NULL,
		&m_pPic);

	if(FAILED(hr))
		return false;
	
	D3DSURFACE_DESC sd;
	hr = m_pPic->GetLevelDesc(0, &sd);

	return m_screenQuad.init(pD3DDevice);
}

void PostProcessBloom::render()
{
	HRESULT hr;

	IDirect3DTexture9 *pScene = NULL;
	
	if(m_bUsePic)
		pScene = m_pPic;
	else
	{
		// 把场景渲染到RT full
		SetRenderTarget SRT(m_pD3DDevice, m_pRTFull);
		m_scene.render();

		pScene = m_pRTFull;
	}
	UINT numPass = 0;
	hr = m_pEffect->Begin(&numPass,0);
	assert(numPass == 4);
	// down sample
	{
		hr = m_pEffect->SetTexture("texSource", pScene);
		hr = m_pEffect->BeginPass(0);
		
		SetRenderTarget SRT(m_pD3DDevice, m_pRTQuarterA);
		m_screenQuad.draw();

		hr = m_pEffect->EndPass();
	}

	// H blur
	{
		hr = m_pEffect->SetTexture("texSource", m_pRTQuarterA);
		hr = m_pEffect->BeginPass(1);

		SetRenderTarget SRT(m_pD3DDevice, m_pRTQuarterB);
		m_screenQuad.draw();

		hr = m_pEffect->EndPass();
	}

	// V blur
	{
		hr = m_pEffect->SetTexture("texSource", m_pRTQuarterB);
		hr = m_pEffect->BeginPass(2);

		SetRenderTarget SRT(m_pD3DDevice, m_pRTQuarterA);
		m_screenQuad.draw();

		hr = m_pEffect->EndPass();
	}
	// compose
	hr = m_pEffect->SetTexture("texSource", m_pRTQuarterA);
	hr = m_pEffect->SetTexture("texScene", pScene);
	hr = m_pEffect->BeginPass(3);
	m_screenQuad.draw();
	hr = m_pEffect->EndPass();

	hr = m_pEffect->End();
}

上述代码是根据我们说的Bloom四步法实现的,C++代码中设置了四个Pass通道与Shader文件对应着处理。Bloom渲染技术也是面试官经常询问的问题,比如问面试者关于Bloom实现的原理,希望通过本节课的讲解对大家有所帮助。Bloom后处理渲染在游戏中的效果如下图:



Bloom在端游特别是次时代游戏中使用是最多的,可以说是必不可少的后处理渲染,在评价一款游戏品质方面也占很大的比重,所以必须要重点掌握。

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页