AlphaTest,在Programming.Role.Playing.Games.with.DirectX书上讲的是一张带孔的图片,书上只给了个实现代码,而且用的是ColorKey这玩意,没有详细的介绍,源码也没有,图片也没有,难道需要我手绘,那就尴尬了,还好想起我之前学《UnitytShader入门精要》中有AlphaTest章节,刚好把那张贴图拿过用。还是老规则,左边的是用固定管线实现的,右边是用Shader实现的。右边的变化还是比较明显的。
因为书上没详细代码,我只好以自己的方式来实现固定管线的代码,是在之前的Draw3D代码上进行修改的,用Cube来做效果会好点。跟之前的AlphaBlend一样,开启和关闭AlphaTest很简单,SetRenderState(D3DRS_ALPHATESTENABLE,bIsEnable),bIsEnable是bool值,是否开启。
来看下渲染的代码:
固定管线:
void ModelClass::Render(IDirect3DDevice9* device, float time, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, float alphaTestValue)
{
D3DXMATRIX xRotationMatrix, yRotationMatrix;
D3DXMATRIX translationMatrix;
// Rotation
::D3DXMatrixRotationX(&xRotationMatrix, D3DX_PI * 0.25f);
::D3DXMatrixRotationY(&yRotationMatrix, -time);
// Translate
::D3DXMatrixTranslation(&translationMatrix, -2.0f, 0.0f, 0.0f);
::D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &xRotationMatrix);
::D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &yRotationMatrix);
::D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &translationMatrix);
device->SetTransform(D3DTS_WORLD, &worldMatrix);
device->SetTransform(D3DTS_VIEW, &viewMatrix);
device->SetTransform(D3DTS_PROJECTION, &projectionMatrix);
device->SetRenderState(D3DRS_LIGHTING, false);
device->SetTexture(0, m_texture->GetTexture());
device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT);
// Disable cull the back
device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
// Enable the alpha test
device->SetRenderState(D3DRS_ALPHATESTENABLE, true);
device->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);
device->SetRenderState(D3DRS_ALPHAREF, (DWORD)(alphaTestValue * 255));
device->SetStreamSource(0, m_vertexBuffer, 0, sizeof(VertexType));
device->SetFVF(VERTEX_FVF);
device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, m_vertexCount / 3);
device->SetTexture(0, nullptr);
// Don't forget to disable the alpha test
device->SetRenderState(D3DRS_ALPHATESTENABLE, false);
}
代码很简单,在之前Draw3D上加了几句AlphaTest代码而已,只是的注意下SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE)这句,这句代码的功能是关闭剔除背面的功能,意思是就是需要绘制背面,如果不写这句的话,dx是默认剔除背面的,你就会看不到背面,得不到上面的效果,有兴趣的可以试一下。
还是跟之前一样,shader代码之后再来解释下SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL)和SetRenderState(D3DRS_ALPHAREF, (DWORD)(alphaTestValue * 255))函数。
Shader渲染:
void ShaderModelClass::Render(IDirect3DDevice9* device, float time, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, float alphaTestValue)
{
D3DXMATRIX xRotationMatrix, yRotationMatrix;
D3DXMATRIX translationMatrix;
bool result;
UINT passMaxNum;
// Rotation
::D3DXMatrixRotationX(&xRotationMatrix, D3DX_PI * 0.25f);
::D3DXMatrixRotationY(&yRotationMatrix, time);
// Translate
::D3DXMatrixTranslation(&translationMatrix, 2.0f, 0.0f, 0.0f);
::D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &xRotationMatrix);
::D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &yRotationMatrix);
::D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &translationMatrix);
device->SetStreamSource(0, m_vertexBuffer, 0, sizeof(VertexType));
device->SetFVF(VERTEX_FVF);
result = m_colorShader->Render(device, m_texture->GetTexture(), time, worldMatrix, viewMatrix, projectionMatrix, alphaTestValue);
if (!result)
return;
m_colorShader->GetEffect()->Begin(&passMaxNum, 0);
for (UINT pass = 0; pass < passMaxNum; ++pass)
{
m_colorShader->GetEffect()->BeginPass(pass);
device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, m_vertexCount / 3);
m_colorShader->GetEffect()->EndPass();
}
m_colorShader->GetEffect()->End();
}
跟之前Draw3D的代码几乎没什么变化,只是向effect中传了AlphaTestValue值,这个值在GraphicsClass中定义的,后面会说到的。
#pragma once
#include "D3DClass.h"
#include "ModelClass.h"
#include "ShaderModelClass.h"
const bool FULL_SCREEN = false;
const UINT DEFAULT_SCREEN_WIDTH = 1280;
const UINT DEFAULT_SCREEN_HEIGHT = 720;
const float AlphaTestValue = 0.6f;
class GraphicsClass
{
public:
GraphicsClass();
GraphicsClass(const GraphicsClass& other);
~GraphicsClass();
bool Initialize(int screenWidth, int screenHeight, HWND hwnd, bool fullScreen);
void Shutdown();
bool Frame();
private:
D3DClass* m_direct3D;
ModelClass* m_model;
ShaderModelClass* m_shaderModel;
};
Shader代码:
texture modelTexture;
float time;
float4x4 worldMatrix;
float4x4 viewMatrix;
float4x4 projectionMatrix;
float alphaTestValue;
sampler ModelTextureSampler = sampler_state
{
Texture = <modelTexture>;
MipFilter = POINT;
MinFilter = POINT;
MagFilter = POINT;
//MipFilter = LINEAR;
//MinFilter = LINEAR;
//MagFilter = LINEAR;
//MipFilter = ANISOTROPIC;
//MinFilter = ANISOTROPIC;
//MagFilter = ANISOTROPIC;
};
struct VertexInputType
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct PixelInputType
{
float4 pos : POSITION;
float2 texcoord : TEXCOORD0;
};
PixelInputType ColorVertexShader(VertexInputType input)
{
PixelInputType output;
float4x4 worldViewMaxtrix = mul(worldMatrix, viewMatrix);
float4x4 worldViewProjectionMatrix = mul(worldViewMaxtrix, projectionMatrix);
output.pos = mul(input.vertex, worldViewProjectionMatrix);
output.texcoord = input.texcoord;
return output;
}
float4 ColorPixelShader(PixelInputType input) : COLOR
{
float param = max(0.5, cos(time));
float4 color = tex2D(ModelTextureSampler, input.texcoord) * param;
if (color.a < 0.5)
discard;
return color;
}
technique ColorTechnique
{
pass pass0
{
CullMode = None;
VertexShader = compile vs_2_0 ColorVertexShader();
PixelShader = compile ps_2_0 ColorPixelShader();
}
}
Shader代码与之前Draw3D的几乎一样,只是在像素着色器(PixelShader)和Pass中有稍微的区别。param是一个参数,用来改变当前的alpha值。重点来了,之前SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL)和SetRenderState(D3DRS_ALPHAREF, (DWORD)(alphaTestValue * 255))函数,在shader中就可以用标红的代码来解释,如果不满足条件就抛弃,意味着不渲染。我这句代码的意思就是,取当前像素的alpha之和0.5来进行比较,如果满足条件就进行渲染,不满足就抛弃,不渲染。
D3DCMP_GREATEREQUAL的意思就是大于等于,(DWORD)(alphaTestValue * 255)就是一个参考值,所以SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL)和SetRenderState(D3DRS_ALPHAREF, (DWORD)(alphaTestValue * 255))函数的意思就是,当前像素的alpha值大于等于(DWORD)(alphaTestValue * 255))的时候才进行渲染,跟shader代码是不是很像啊。D3DRS_ALPHAFUNC可以理解为一个条件,比如等于,小于啊什么的,D3DRS_ALPHAREF则是参数设置,连起来就是小于什么参数或者等于什么参数才进行渲染。
为什么shader中我写死了0.5这个值,不用代码传过来的alphaTestValue这个值的原因是在测试的时候,发现这个效果很炫,用这个alphaTestValue值效果不是很好,就没有用,如果有兴趣的话,可以将0.5改成alphaTestValue值,在随意调整alphaTestValue这个值,测试下效果。
源码下载:下载地址