最近刚看完UnityShader的一些介绍和例子,发现理论上的东西都是含糊过去的,就还是老老实实去看dx。之前有学过dx,dx10,11的初始化太繁琐,代码是dx9的好几倍,手上也有本《DirectX3D HLSL高级实例精讲》里面用的就是dx9,所以就选择用dx9来写。
最近刚看到不用effect来写,分成vertexshader.vs和pixeslshader.ps来写,就跟dx11的差不多,就没仔细看,直接开始写,然后遇到bug,调试了很久。刚开始还以为是用的版本太高的问题,用的是vs2017和Microsoft DirectX SDK (June 2010)。想了想effect都能用,那就应该不是这个问题。而且这本《DirectX3D HLSL高级实例精讲》书上的源码完全不跟实例相关,坑啊。只好一步步断点调试,发现有一个空指针。(抽个空会把《DirectX3D HLSL高级实例精讲》的源码上传下,不要期待有多大用处,用的是DXUT框架,vs2017+win10sdk不能编译成lib,就算编译好了,还会各种报错,也懒得弄个xp系统的,直接用之前学习的那套框架,简单粗暴点)
先来effect源码:文件名是ColorShader.fx,如果用的是vs2017的话,记得在HLSL compiler设置为effect,shader model 2
float time; float4x4 worldMatrix; float4x4 viewMatrix; float4x4 projectionMatrix; texture modelTexture; sampler ModelTextureSampler = sampler_state { Texture = <modelTexture>; MipFilter = LINEAR; MinFilter = LINEAR; MagFilter = LINEAR; }; struct VertexInputType { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; }; struct PixelInputType { float4 pos : POSITION; float2 texcoord : TEXCOORD0; }; PixelInputType ColorVertexShader(VertexInputType input) { PixelInputType output; float4x4 worldViewMatrix = mul(worldMatrix, viewMatrix); float4x4 worldViewProjectionMatrix = mul(worldViewMatrix, projectionMatrix); output.pos = mul(input.vertex, worldViewProjectionMatrix); output.texcoord = input.texcoord; return output; } float4 ColorPixelShader(PixelInputType input) : COLOR { float4 color = tex2D(ModelTextureSampler, input.texcoord); return color; } technique ColorTechnique { pass pass0 { CullMode = None; VertexShader = compile vs_2_0 ColorVertexShader(); PixelShader = compile ps_2_0 ColorPixelShader(); } }
接着是VertexShader,文件名为ColorVertexShader.vs
float4x4 worldMatrix; float4x4 viewMatrix; float4x4 projectionMatrix; sampler modelTexture; struct VertexInputType { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; }; struct PixelInputType { float4 pos : POSITION; float2 texcoord : TEXCOORD0; }; PixelInputType ColorVertexShader(VertexInputType input) { PixelInputType output; float4x4 worldViewMatrix = mul(worldMatrix, viewMatrix); float4x4 worldViewProjectionMatrix = mul(worldViewMatrix, projectionMatrix); output.pos = mul(input.vertex, worldViewProjectionMatrix); output.texcoord = input.texcoord; return output; }
PixelShader,文件名为ColorPixelShader.ps
float time; sampler modelTexture; struct PixelInputType { float4 pos : POSITION; float2 texcoord : TEXCOORD0; }; float4 ColorPixelShader(PixelInputType input) : COLOR { float4 color = tex2D(modelTexture, input.texcoord) * cos(time); return color; }
Shader代码其实很简单,就不多说了,先来说我遇到的问题:
bool ColorShaderClass::SetShaderParameters(IDirect3DDevice9* device, float time, IDirect3DTexture9* texture, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) { HRESULT result; D3DXHANDLE textureHandle; D3DXCONSTANT_DESC textureDesc; UINT count; result = device->SetVertexShader(m_vertexShader); if (FAILED(result)) return false; result = device->SetPixelShader(m_pixelShader); if (FAILED(result)) return false; result = m_vertexShaderTable->SetMatrix(device, "worldMatrix", &worldMatrix); if (FAILED(result)) return false; result = m_vertexShaderTable->SetMatrix(device, "viewMatrix", &viewMatrix); if (FAILED(result)) return false; result = m_vertexShaderTable->SetMatrix(device, "projectionMatrix", &projectionMatrix); if (FAILED(result)) return false; // The variable of shader must be used, otherwise ID3DXConstantTable can't find it result = m_pixelShaderTable->SetFloat(device, "time", time); if (FAILED(result)) return false; textureHandle = m_pixelShaderTable->GetConstantByName(nullptr, "modelTexture"); if (!textureHandle) return false; // The first way to set texture in shader /*result = m_pixelShaderTable->GetConstantDesc(textureHandle, &textureDesc, &count); if (FAILED(result)) return false; result = device->SetTexture(textureDesc.RegisterIndex, texture); if (FAILED(result)) return false;*/ // The second way to set texture in shader result = device->SetTexture(m_pixelShaderTable->GetSamplerIndex(textureHandle), texture); if (FAILED(result)) return false; return true; }
我遇到的问题就是代码第一个英文注释的地方,(我写的,英文烂,语法错误什么的就不管了,如果要装逼,我可以用日文写),其实这个问题是在pixelshader中遇到的,问题就是不能设置pixelshader的变量。而且hlsl调试只能靠猜,搞得我还以为pixelshader是不能设置变量的。后来通过dx的ID3DXConstantTable中的GetConstantByName方法调试得到,在pixelshader中获取time的D3DXHANDLE为空指针,接着我在vertexshader中添加一个新的矩阵来测试,发现了问题所在,就是在VertexShader和PixelShder中不使用的变量,如果用代码去获得和设置都会报错,因为得到的都是空指针,你对空指针操作肯定出问题,估计是dx过滤了不使用的变量。但是最主要的一点是在Effect中没有这个问题,看我effect源码中的time就没有用到,但没有任何问题。
使用effect的源码:
ModelClass:
#include "ModelClass.h" ModelClass::ModelClass() { m_mesh = nullptr; m_colorShader = nullptr; m_texture = nullptr; } ModelClass::ModelClass(const ModelClass& other) { } ModelClass::~ModelClass() { } bool ModelClass::Initialize(IDirect3DDevice9* device, TCHAR* modelFilePath, TCHAR* textureFilePath) { bool result; result = InitializeMesh(device, modelFilePath); if (!result) return false; result = InitializeTexture(device, textureFilePath); if (!result) return false; m_colorShader = new ColorShaderClass(); if (!m_colorShader) return false; result = m_colorShader->Initialize(device); if (!result) return false; return true; } void ModelClass::Shutdown() { if (m_mesh) { m_mesh->Release(); m_mesh = nullptr; } if (m_colorShader) { m_colorShader->Shutdown(); delete m_colorShader; m_colorShader = nullptr; } if (m_texture) { m_texture->Shutdown(); delete m_texture; m_texture = nullptr; } } void ModelClass::Render(IDirect3DDevice9* device, float time, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) { bool result; UINT passMaxNum; result = m_colorShader->Render(device, time, m_texture->GetTexture(), worldMatrix, viewMatrix, projectionMatrix); if (!result) return; m_colorShader->GetEffect()->Begin(&passMaxNum, 0); for (UINT pass = 0; pass < passMaxNum; ++pass) { m_colorShader->GetEffect()->BeginPass(pass); m_mesh->DrawSubset(0); m_colorShader->GetEffect()->EndPass(); } m_colorShader->GetEffect()->End(); } bool ModelClass::InitializeMesh(IDirect3DDevice9* device, TCHAR* modelFilePath) { HRESULT result; result = ::D3DXLoadMeshFromX( modelFilePath, D3DXMESH_MANAGED, device, nullptr, nullptr, nullptr, nullptr, &m_mesh); if (FAILED(result)) return false; return true; } bool ModelClass::InitializeTexture(IDirect3DDevice9* device, TCHAR* textureFiltPath) { bool result; m_texture = new TextureClass(); if (!m_texture) return false; result = m_texture->Initialize(device, textureFiltPath); if (!result) return false; return true; }
ColorShaderClass:
#include "ColorShaderClass.h" ColorShaderClass::ColorShaderClass() { m_effect = nullptr; } ColorShaderClass::ColorShaderClass(const ColorShaderClass& other) { } ColorShaderClass::~ColorShaderClass() { } bool ColorShaderClass::Initialize(IDirect3DDevice9* device) { bool result; result = InitializeShader(device, TEXT("ColorShader.fx")); if (!result) return false; return true; } void ColorShaderClass::Shutdown() { if (m_effect) { m_effect->Release(); m_effect = nullptr; } } bool ColorShaderClass::Render(IDirect3DDevice9* device, float time, IDirect3DTexture9* texture, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) { bool result; result = SetShaderParameters(time, texture, worldMatrix, viewMatrix, projectionMatrix); if (!result) return false; return true; } ID3DXEffect* ColorShaderClass::GetEffect() { return m_effect; } bool ColorShaderClass::InitializeShader(IDirect3DDevice9* device, TCHAR* shaderPath) { HRESULT result; ID3DXBuffer* errorMessage; errorMessage = nullptr; result = ::D3DXCreateEffectFromFile(device, shaderPath, nullptr, nullptr, 0, nullptr, &m_effect, &errorMessage); if (FAILED(result)) { if (errorMessage) OutputShaderErrorMessage(errorMessage, shaderPath); else ::MessageBox(nullptr, shaderPath, TEXT("Missing Shader File"), MB_OK | MB_ICONERROR); return false; } return true; } void ColorShaderClass::OutputShaderErrorMessage(ID3DXBuffer* errorMessage, TCHAR* shaderPath) { char* compileErrors; ULONG bufferSize; ofstream fout; compileErrors = (char*)(errorMessage->GetBufferPointer()); bufferSize = errorMessage->GetBufferSize(); fout.open("shader-error.txt"); for (ULONG i = 0; i < bufferSize; ++i) fout << compileErrors[i]; fout.close(); errorMessage->Release(); errorMessage = nullptr; ::MessageBox(nullptr, TEXT("Error compiling shader. Check shader-error.txt for message!"), shaderPath, MB_OK | MB_ICONERROR); } bool ColorShaderClass::SetShaderParameters(float time, IDirect3DTexture9* texture, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) { HRESULT result; result = m_effect->SetFloat("time", time); if (FAILED(result)) return false; result = m_effect->SetTexture("modelTexture", texture); if (FAILED(result)) return false; result = m_effect->SetMatrix("worldMatrix", &worldMatrix); if (FAILED(result)) return false; result = m_effect->SetMatrix("viewMatrix", &viewMatrix); if (FAILED(result)) return false; result = m_effect->SetMatrix("projectionMatrix", &projectionMatrix); if (FAILED(result)) return false; result = m_effect->SetTechnique("ColorTechnique"); if (FAILED(result)) return false; return true; }
接着是VertexShader和PixelShader的源码,MoedlClass只有渲染地方不一样其它都是一样的
ModelClass:
#include "ModelClass.h" ModelClass::ModelClass() { m_mesh = nullptr; m_colorShader = nullptr; m_texture = nullptr; } ModelClass::ModelClass(const ModelClass& other) { } ModelClass::~ModelClass() { } bool ModelClass::Initialize(IDirect3DDevice9* device, TCHAR* modelFilePath, TCHAR* textureFilePath) { bool result; result = InitializeMesh(device, modelFilePath); if (!result) return false; result = InitializeTexture(device, textureFilePath); if (!result) return false; m_colorShader = new ColorShaderClass(); if (!m_colorShader) return false; result = m_colorShader->Initialize(device); if (!result) return false; return true; } void ModelClass::Shutdown() { if (m_mesh) { m_mesh->Release(); m_mesh = nullptr; } if (m_colorShader) { m_colorShader->Shutdown(); delete m_colorShader; m_colorShader = nullptr; } if (m_texture) { m_texture->Shutdown(); delete m_texture; m_texture = nullptr; } } void ModelClass::Render(IDirect3DDevice9* device, float time, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) { bool result; result = m_colorShader->Render(device, time, m_texture->GetTexture(), worldMatrix, viewMatrix, projectionMatrix); if (!result) return; m_mesh->DrawSubset(0); } bool ModelClass::InitializeMesh(IDirect3DDevice9* device, TCHAR* modelFilePath) { HRESULT result; result = ::D3DXLoadMeshFromX( modelFilePath, D3DXMESH_MANAGED, device, nullptr, nullptr, nullptr, nullptr, &m_mesh); if (FAILED(result)) return false; return true; } bool ModelClass::InitializeTexture(IDirect3DDevice9* device, TCHAR* textureFilePath) { bool result; m_texture = new TextureClass(); if (!m_texture) return false; result = m_texture->Initialize(device, textureFilePath); if (!result) return false; return true; }
ColorShaderClass:
#include "ColorShaderClass.h" ColorShaderClass::ColorShaderClass() { m_vertexShader = nullptr; m_vertexShaderTable = nullptr; m_pixelShader = nullptr; m_pixelShaderTable = nullptr; } ColorShaderClass::ColorShaderClass(const ColorShaderClass& other) { } ColorShaderClass::~ColorShaderClass() { } bool ColorShaderClass::Initialize(IDirect3DDevice9* device) { TCHAR* vertexShaderPath; TCHAR* pixelShaderPath; DWORD flag; HRESULT result; ID3DXBuffer* tempBuffer; ID3DXBuffer* errorMessage; vertexShaderPath = TEXT("ColorVertexShader.vs"); pixelShaderPath = TEXT("ColorPixelShader.ps"); flag = D3DXSHADER_DEBUG | D3DXSHADER_SKIPOPTIMIZATION; errorMessage = nullptr; result = ::D3DXCompileShaderFromFile(vertexShaderPath, nullptr, nullptr, "ColorVertexShader", "vs_2_0", flag, &tempBuffer, &errorMessage, &m_vertexShaderTable); if (FAILED(result)) { if (errorMessage != nullptr) OutputShaderErrorMessage(errorMessage, vertexShaderPath); else ::MessageBox(nullptr, vertexShaderPath, TEXT("Missing Shader File"), MB_OK | MB_ICONERROR); return false; } result = device->CreateVertexShader((DWORD*)tempBuffer->GetBufferPointer(), &m_vertexShader); if (FAILED(result)) return false; result = ::D3DXCompileShaderFromFile(pixelShaderPath, nullptr, nullptr, "ColorPixelShader", "ps_2_0", flag, &tempBuffer, &errorMessage, &m_pixelShaderTable); if (FAILED(result)) { if (errorMessage != nullptr) OutputShaderErrorMessage(errorMessage, pixelShaderPath); else ::MessageBox(nullptr, pixelShaderPath, TEXT("Missing Shader File"), MB_OK | MB_ICONERROR); return false; } result = device->CreatePixelShader((DWORD*)tempBuffer->GetBufferPointer(), &m_pixelShader); if (FAILED(result)) return false; return true; } void ColorShaderClass::Shutdown() { if (m_vertexShader) { m_vertexShader->Release(); m_vertexShader = nullptr; } if (m_vertexShaderTable) { m_vertexShaderTable->Release(); m_vertexShaderTable = nullptr; } if (m_pixelShader) { m_pixelShader->Release(); m_pixelShader = nullptr; } if (m_pixelShaderTable) { m_pixelShaderTable->Release(); m_pixelShaderTable = nullptr; } } bool ColorShaderClass::Render(IDirect3DDevice9* device, float time, IDirect3DTexture9* texture, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) { bool result; result = SetShaderParameters(device, time, texture, worldMatrix, viewMatrix, projectionMatrix); if (!result) return false; return true; } void ColorShaderClass::OutputShaderErrorMessage(ID3DXBuffer* errorMessage, TCHAR* shaderPath) { char* compileErrors; ULONG bufferSize; ofstream fout; compileErrors = (char*)(errorMessage->GetBufferPointer()); bufferSize = errorMessage->GetBufferSize(); fout.open("shader-error.txt"); for (ULONG i = 0; i < bufferSize; ++i) fout << compileErrors[i]; fout.close(); errorMessage->Release(); errorMessage = nullptr; ::MessageBox(nullptr, TEXT("Error compiling shader. Check shader-error.txt for message!"), shaderPath, MB_OK | MB_ICONERROR); } bool ColorShaderClass::SetShaderParameters(IDirect3DDevice9* device, float time, IDirect3DTexture9* texture, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) { HRESULT result; D3DXHANDLE textureHandle; D3DXCONSTANT_DESC textureDesc; UINT count; result = device->SetVertexShader(m_vertexShader); if (FAILED(result)) return false; result = device->SetPixelShader(m_pixelShader); if (FAILED(result)) return false; result = m_vertexShaderTable->SetMatrix(device, "worldMatrix", &worldMatrix); if (FAILED(result)) return false; result = m_vertexShaderTable->SetMatrix(device, "viewMatrix", &viewMatrix); if (FAILED(result)) return false; result = m_vertexShaderTable->SetMatrix(device, "projectionMatrix", &projectionMatrix); if (FAILED(result)) return false; // The variable of shader must be used, otherwise ID3DXConstantTable can't find it result = m_pixelShaderTable->SetFloat(device, "time", time); if (FAILED(result)) return false; textureHandle = m_pixelShaderTable->GetConstantByName(nullptr, "modelTexture"); if (!textureHandle) return false; // The first way to set texture in shader /*result = m_pixelShaderTable->GetConstantDesc(textureHandle, &textureDesc, &count); if (FAILED(result)) return false; result = device->SetTexture(textureDesc.RegisterIndex, texture); if (FAILED(result)) return false;*/ // The second way to set texture in shader result = device->SetTexture(m_pixelShaderTable->GetSamplerIndex(textureHandle), texture); if (FAILED(result)) return false; return true; }