원문: http://www.rastertek.com/dx11tut10.html
이 튜토리얼은 DirectX 11에서 HLSL을 이용한 정반사광(Specular lighting) 표현에 대해 다룰 것입니다. 소스 코드는 지난 튜토리얼에서 이어집니다.
정반사광은 밝은 점광원의 위치가 어디인지 알려주는 단서의 역할을 합니다. 예를 들어 단순 조명과 주변광이 적용된 빨간 구체가 있다면 다음과 같을 것입니다.
여기에 하얀 정반사광 효과를 넣어주면 다음과 같이 될 것입니다.
정반사광은 거울이나 반짝반짝한 금속 표면과 같은 재질에서의 빛의 반사를 표현하는 거의 모든 경우에 사용됩니다. 또한 태양빛이 물에 반사되는 것과 같은 다른 재질도 이것으로 표현이 가능합니다. 잘 사용된다면 3D 장면의 리얼리티 수준을 한층 더 끌어올려줄 수 있습니다.
정반사광의 방정식은 다음과 같습니다.
반사광 = 반사색 * ( 반사된빛의색 * ((법선과 하프벡터의 내적) ^ 반사강도) * 감쇄계수 * 스폿라이트계수 )
(역자주: 위 식에 대해 좀 더 알고싶으신 분들은 http://blog.naver.com/frogpjn?Redirect=Log&logNo=60015118840을 참고해주세요)
기본적인 정반사광 효과만을 위해 다음과 같이 식을 수정합니다.
반사광 = 반사빛의색 * (보는방향과 반사광의 내적) ^ 반사강도
이 식에서 반사광 벡터는 빛의 강도의 두배의 크기를 정점의 법선에 곱하고 여기에 빛의 방향을 빼서 얻어집니다.
반사벡터 = 2 * 빛의강도 * 법선 - 빛의방향
식에서 보는방향은 카메라의 위치에서 정점의 위치를 뺌으로 계산됩니다.
보는방향 = 카메라위치 - 정점위치
수정된 조명 셰이더를 보면서 어떻게 이것이 구현되었는가를 보도록 하겠습니다.
Light.vs
//////////////////////////////////////////////////////////////////////////////// // Filename: light.vs //////////////////////////////////////////////////////////////////////////////// ///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
여기에 카메라의 정보를 저장할 새로운 상수 버퍼를 추가합니다. 이 셰이더에서는 정반사의 계산을 위해 이 정점이 어디서 보여지는지의 정보가 필요하므로 카메라의 위치를 알아야 합니다.
cbuffer CameraBuffer { float3 cameraPosition; float padding; }; ////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; };
PixelInputType 구조체는 정점 셰이더에서 보는 방향을 계산해야 하기 때문에 이에 대한 변수를 추가하고 정반사광의 계산을 위해 픽셀 셰이더로 넘겨집니다.
struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; float3 viewDirection : TEXCOORD1; }; //////////////////////////////////////////////////////////////////////////////// // Vertex Shader //////////////////////////////////////////////////////////////////////////////// PixelInputType LightVertexShader(VertexInputType input) { PixelInputType output; float4 worldPosition; // Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; // Calculate the normal vector against the world matrix only. output.normal = mul(input.normal, (float3x3)worldMatrix); // Normalize the normal vector. output.normal = normalize(output.normal);
정점 셰이더에서는 여기서 보는 방향이 계산됩니다. 정점의 월드상의 위치를 계산하고 카메라 위치에서 이 위치를 뺌으로 실제로 어떤 방향으로 보는지를 알아냅니다. 이 계산의 결과는 정규화되어 픽셀 셰이더로 보내집니다.
// Calculate the position of the vertex in the world. worldPosition = mul(input.position, worldMatrix); // Determine the viewing direction based on the position of the camera and the position of the vertex in the world. output.viewDirection = cameraPosition.xyz - worldPosition.xyz; // Normalize the viewing direction vector. output.viewDirection = normalize(output.viewDirection); return output; }
Light.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: light.ps //////////////////////////////////////////////////////////////////////////////// ///////////// // GLOBALS // ///////////// Texture2D shaderTexture; SamplerState SampleType;
LightBuffer 역시 정반사광의 계산을 위해 specularColor(반사색)과 specularPower(반사강도) 변수가 추가되었습니다.
cbuffer LightBuffer { float4 ambientColor; float4 diffuseColor; float3 lightDirection; float specularPower; float4 specularColor; }; ////////////// // TYPEDEFS // //////////////
PixelInputType 구조체도 정점 셰이더에서의 변화를 반영하기 위해 수정되었습니다.
struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; float3 viewDirection : TEXCOORD1; }; //////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 LightPixelShader(PixelInputType input) : SV_TARGET { float4 textureColor; float3 lightDir; float lightIntensity; float4 color; float3 reflection; float4 specular; // Sample the pixel color from the texture using the sampler at this texture coordinate location. textureColor = shaderTexture.Sample(SampleType, input.tex); // Set the default output color to the ambient light value for all pixels. color = ambientColor; // Initialize the specular color. specular = float4(0.0f, 0.0f, 0.0f, 0.0f); // Invert the light direction for calculations. lightDir = -lightDirection; // Calculate the amount of light on this pixel. lightIntensity = saturate(dot(input.normal, lightDir)); if(lightIntensity > 0.0f) { // Determine the final diffuse color based on the diffuse color and the amount of light intensity. color += (diffuseColor * lightIntensity); // Saturate the ambient and diffuse color. color = saturate(color);
픽셀 셰이더에서의 정반사 벡터는 주어진 빛의 강도가 0보다 큰 경우에만 여기서 계산됩니다. 서두에서 설명했던 것과 같은 방정식을 사용합니다.
// Calculate the reflection vector based on the light intensity, normal vector, and light direction. reflection = normalize(2 * lightIntensity * input.normal - lightDir);
정반사광의 총량은 반사벡터와 보는 방향을 이용하여 계산됩니다. 광원과 카메라의 방향 사이의 각도가 작을수록 더 많은 정반사광이 비쳐 들어올 것입니다. 그런 결과는 specularPower값만큼의 제곱으로 나타내게 합니다. specularPower의 값이 작을수록 마지막에 빛이 더 많이 들어오겠죠.
// Determine the amount of specular light based on the reflection vector, viewing direction, and specular power. specular = pow(saturate(dot(reflection, input.viewDirection)), specularPower); } // Multiply the texture pixel and the input color to get the textured result. color = color * textureColor;
정반사 효과는 마지막에 넣게 되는데, 이 효과는 마지막에 더해져야 하는 최후의 하이라이트 값이며 그렇게 처리되지 않으면 제대로 표시되지 않을 것입니다.
// Add the specular component last to the output color. color = saturate(color + specular); return color; }
Lightshaderclass.h
LightShaderClass 역시 정반사광을 다루기 위하여 수정되었습니다.
//////////////////////////////////////////////////////////////////////////////// // Filename: lightshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _LIGHTSHADERCLASS_H_ #define _LIGHTSHADERCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std; //////////////////////////////////////////////////////////////////////////////// // Class name: LightShaderClass //////////////////////////////////////////////////////////////////////////////// class LightShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; };
정점 셰이더의 카메라 상수 버퍼와 마찬가지로 이곳에도 새로운 카메라 버퍼 구조체를 만듭니다. 또한 CreateBuffer 함수가 실패하는 일이 없도록 구조체에 padding을 붙여 sizeof을 했을 때 16의 배수가 되도록 합니다.
struct CameraBufferType { D3DXVECTOR3 cameraPosition; float padding; };
LightBufferType 구조체는 픽셀 셰이더의 상수 버퍼와 마찬가지로 반사광의 색상과 강도를 저장하도록 바뀌었습니다. 주의해야 할 것은 기존의 16바이트의 배수 크기를 유지하기 위한 padding을 없애고 그 자리에 specularPower을 넣었다는 사실입니다. 만약 padding을 넣지 않고 lightDirection 바로 밑에 specularColor을 넣었다면 셰이더가 올바로 동작하지 않았을 것입니다. 구조체의 크기가 16byte의 배수이긴 하지만 각 변수들이 16byte로 정렬되지 않았기 때문입니다.
struct LightBufferType { D3DXVECTOR4 ambientColor; D3DXVECTOR4 diffuseColor; D3DXVECTOR3 lightDirection; float specularPower; D3DXVECTOR4 specularColor; }; public: LightShaderClass(); LightShaderClass(const LightShaderClass&); ~LightShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4, D3DXVECTOR4, D3DXVECTOR3, D3DXVECTOR4, float); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4, D3DXVECTOR4, D3DXVECTOR3, D3DXVECTOR4, float); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11SamplerState* m_sampleState; ID3D11Buffer* m_matrixBuffer;
그리고 정점 셰이더에서 카메라의 위치를 잡는 데 사용할 카메라 상수 버퍼를 더합니다.
ID3D11Buffer* m_cameraBuffer; ID3D11Buffer* m_lightBuffer; }; #endif
Lightshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: lightshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "lightshaderclass.h" LightShaderClass::LightShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_sampleState = 0; m_matrixBuffer = 0;
새 카메라 상수 버퍼를 null로 초기화합니다.
m_cameraBuffer = 0; m_lightBuffer = 0; } LightShaderClass::LightShaderClass(const LightShaderClass& other) { } LightShaderClass::~LightShaderClass() { } bool LightShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/light.vs", L"../Engine/light.ps"); if(!result) { return false; } return true; } void LightShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; }
Render 함수는 cameraPosition, specularColor ,specularPower 값을 입력으로 받고 이들을 SetShaderParameter 함수를 통해 렌더링이 일어날 때 빛 셰이더에서 사용할 수 있게 합니다.
bool LightShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor, D3DXVECTOR3 cameraPosition, D3DXVECTOR4 specularColor, float specularPower) { bool result; // Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, lightDirection, ambientColor, diffuseColor, cameraPosition, specularColor, specularPower); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; } bool LightShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[3]; unsigned int numElements; D3D11_SAMPLER_DESC samplerDesc; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_BUFFER_DESC cameraBufferDesc; D3D11_BUFFER_DESC lightBufferDesc; // Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "LightVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "LightPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; polygonLayout[2].SemanticName = "NORMAL"; polygonLayout[2].SemanticIndex = 0; polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[2].InputSlot = 0; polygonLayout[2].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[2].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Create a texture sampler state description. samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.MipLODBias = 0.0f; samplerDesc.MaxAnisotropy = 1; samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; samplerDesc.BorderColor[0] = 0; samplerDesc.BorderColor[1] = 0; samplerDesc.BorderColor[2] = 0; samplerDesc.BorderColor[3] = 0; samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; // Create the texture sampler state. result = device->CreateSamplerState(&samplerDesc, &m_sampleState); if(FAILED(result)) { return false; } // Setup the description of the dynamic matrix constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; matrixBufferDesc.MiscFlags = 0; matrixBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; }
카메라 버퍼의 description을 작성하고 버퍼를 생성합니다. 이것은 정점 셰이더에 카메라의 위치를 정할 수 있게 해 주는 인터페이스를 제공할 것입니다.
// Setup the description of the camera dynamic constant buffer that is in the vertex shader. cameraBufferDesc.Usage = D3D11_USAGE_DYNAMIC; cameraBufferDesc.ByteWidth = sizeof(CameraBufferType); cameraBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; cameraBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; cameraBufferDesc.MiscFlags = 0; cameraBufferDesc.StructureByteStride = 0; // Create the camera constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&cameraBufferDesc, NULL, &m_cameraBuffer); if(FAILED(result)) { return false; } // Setup the description of the light dynamic constant buffer that is in the pixel shader. // Note that ByteWidth always needs to be a multiple of 16 if using D3D11_BIND_CONSTANT_BUFFER or CreateBuffer will fail. lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC; lightBufferDesc.ByteWidth = sizeof(LightBufferType); lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; lightBufferDesc.MiscFlags = 0; lightBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&lightBufferDesc, NULL, &m_lightBuffer); if(FAILED(result)) { return false; } return true; } void LightShaderClass::ShutdownShader() { // Release the light constant buffer. if(m_lightBuffer) { m_lightBuffer->Release(); m_lightBuffer = 0; }
ShutdownShader 함수에 카메라 상수 버퍼를 해제하는 코드를 넣습니다.
// Release the camera constant buffer. if(m_cameraBuffer) { m_cameraBuffer->Release(); m_cameraBuffer = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; } void LightShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout; // Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; iRelease(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }
SetShaderParameters 함수는 cameraPosition, specularColor, specularPower을 인자로 받도록 수정되었습니다.
bool LightShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor, D3DXVECTOR3 cameraPosition, D3DXVECTOR4 specularColor, float specularPower) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; unsigned int bufferNumber; MatrixBufferType* dataPtr; LightBufferType* dataPtr2; CameraBufferType* dataPtr3; // Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Now set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);
여기서 카메라 버퍼를 잠그고 카메라 위치값을 입력합니다.
// Lock the camera constant buffer so it can be written to. result = deviceContext->Map(m_cameraBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr3 = (CameraBufferType*)mappedResource.pData; // Copy the camera position into the constant buffer. dataPtr3->cameraPosition = cameraPosition; dataPtr3->padding = 0.0f; // Unlock the camera constant buffer. deviceContext->Unmap(m_cameraBuffer, 0);
상수 버퍼를 세팅할 때 bufferNumber을 0 대신 1로 지정한 것에 주의하기 바랍니다. 카메라 버퍼는 정점 셰이더에서 두번째 버퍼이기 때문입니다.
// Set the position of the camera constant buffer in the vertex shader. bufferNumber = 1; // Now set the camera constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_cameraBuffer); // Set shader texture resource in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture); // Lock the light constant buffer so it can be written to. result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the light constant buffer. dataPtr2 = (LightBufferType*)mappedResource.pData;
빛 상수 버퍼는 반사색과 반사강도를 입력하여 픽셀 셰이더에서 정반사광 계산을 할 수 있도록 합니다.
// Copy the lighting variables into the light constant buffer. dataPtr2->ambientColor = ambientColor; dataPtr2->diffuseColor = diffuseColor; dataPtr2->lightDirection = lightDirection; dataPtr2->specularColor = specularColor; dataPtr2->specularPower = specularPower; // Unlock the light constant buffer. deviceContext->Unmap(m_lightBuffer, 0); // Set the position of the light constant buffer in the pixel shader. bufferNumber = 0; // Finally set the light constant buffer in the pixel shader with the updated values. deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer); return true; } void LightShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
Lightclass.h
LightClass는 정반사 성분, 그리고 그와 관련된 함수들이 추가되었습니다.
//////////////////////////////////////////////////////////////////////////////// // Filename: lightclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _LIGHTCLASS_H_ #define _LIGHTCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3dx10math.h> //////////////////////////////////////////////////////////////////////////////// // Class name: LightClass //////////////////////////////////////////////////////////////////////////////// class LightClass { public: LightClass(); LightClass(const LightClass&); ~LightClass(); void SetAmbientColor(float, float, float, float); void SetDiffuseColor(float, float, float, float); void SetDirection(float, float, float); void SetSpecularColor(float, float, float, float); void SetSpecularPower(float); D3DXVECTOR4 GetAmbientColor(); D3DXVECTOR4 GetDiffuseColor(); D3DXVECTOR3 GetDirection(); D3DXVECTOR4 GetSpecularColor(); float GetSpecularPower(); private: D3DXVECTOR4 m_ambientColor; D3DXVECTOR4 m_diffuseColor; D3DXVECTOR3 m_direction; D3DXVECTOR4 m_specularColor; float m_specularPower; }; #endif
Lightclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: lightclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "lightclass.h" LightClass::LightClass() { } LightClass::LightClass(const LightClass& other) { } LightClass::~LightClass() { } void LightClass::SetAmbientColor(float red, float green, float blue, float alpha) { m_ambientColor = D3DXVECTOR4(red, green, blue, alpha); return; } void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha) { m_diffuseColor = D3DXVECTOR4(red, green, blue, alpha); return; } void LightClass::SetDirection(float x, float y, float z) { m_direction = D3DXVECTOR3(x, y, z); return; } void LightClass::SetSpecularColor(float red, float green, float blue, float alpha) { m_specularColor = D3DXVECTOR4(red, green, blue, alpha); return; } void LightClass::SetSpecularPower(float power) { m_specularPower = power; return; } D3DXVECTOR4 LightClass::GetAmbientColor() { return m_ambientColor; } D3DXVECTOR4 LightClass::GetDiffuseColor() { return m_diffuseColor; } D3DXVECTOR3 LightClass::GetDirection() { return m_direction; } D3DXVECTOR4 LightClass::GetSpecularColor() { return m_specularColor; } float LightClass::GetSpecularPower() { return m_specularPower; }
Graphicsclass.h
GraphicsClass의 헤더는 바뀌지 않았습니다.
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_ /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" #include "lightshaderclass.h" #include "lightclass.h" ///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f; //////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(float); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; LightShaderClass* m_LightShader; LightClass* m_Light; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h" GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; m_LightShader = 0; m_Light = 0; } GraphicsClass::GraphicsClass(const GraphicsClass& other) { } GraphicsClass::~GraphicsClass() { } bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { bool result; // Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Set the initial position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/seafloor.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } // Create the light shader object. m_LightShader = new LightShaderClass; if(!m_LightShader) { return false; } // Initialize the light shader object. result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK); return false; } // Create the light object. m_Light = new LightClass; if(!m_Light) { return false; }
LightClass 객체에 반사색과 반사강도 값을 입력합니다. 이 튜토리얼에서는 반사색을 흰색으로, 그리고 반사 강도를 32로 합니다. 반사강도 값이 낮을수록 반사광 효과가 커진다는 사실을 기억하기 바랍니다.
// Initialize the light object. m_Light->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f); m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f); m_Light->SetDirection(0.0f, 0.0f, 1.0f); m_Light->SetSpecularColor(1.0f, 1.0f, 1.0f, 1.0f); m_Light->SetSpecularPower(32.0f); return true; } void GraphicsClass::Shutdown() { // Release the light object. if(m_Light) { delete m_Light; m_Light = 0; } // Release the light shader object. if(m_LightShader) { m_LightShader->Shutdown(); delete m_LightShader; m_LightShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; } bool GraphicsClass::Frame() { bool result; static float rotation = 0.0f; // Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.005f; if(rotation > 360.0f) { rotation -= 360.0f; } // Render the graphics scene. result = Render(rotation); if(!result) { return false; } return true; } bool GraphicsClass::Render(float rotation) { D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix; bool result; // Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // Rotate the world matrix by the rotation value so that the triangle will spin. D3DXMatrixRotationY(&worldMatrix, rotation); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext());
LightShaderClass의 render 함수가 카메라 위치, 반사색, 반사 광도를 입력으로 받습니다.
// Render the model using the light shader. result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetAmbientColor(), m_Light->GetDiffuseColor(), m_Camera->GetPosition(), m_Light->GetSpecularColor(), m_Light->GetSpecularPower()); if(!result) { return false; } // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
마치면서
정반사광(specular lighting)을 추가하여 육면체가 카메라의 방향과 맞을 때 밝은 흰색의 반사광 효과를 낼 수 있게 되었습니다.
연습 문제
1. 프로젝트를 다시 컴파일하고 실행하여 육면체가 카메라와 맞을 때 밝은 반사광 효과가 나는 것을 확인해 보십시오.
2. m_Light->SetDirection(1.0f, 0.0f, 1.0f)와 같이 빛의 방향을 수정하여 광원이 다른 방향일 때의 결과를 확인해 보십시오.
3. 폴리곤 5000개 이상의 빨간 텍스쳐를 가진 공 모델을 만들고 튜토리얼의 맨 윗부분의 이미지를 다시 만들어 보십시오.
소스 코드
Visual Studio 2010 프로젝트: dx11tut10.zip
소스 코드: dx11src10.zip
실행 파일: dx11exe10.zip
'강좌번역 > DirectX 11' 카테고리의 다른 글
DirectX11 Tutorial 12: 글꼴 엔진 (15) | 2013.03.22 |
---|---|
DirectX11 Tutorial 11: 2D 렌더링 (5) | 2013.03.13 |
DirectX11 Tutorial 9 - 주변광 (0) | 2013.02.10 |
DirectX11 Tutorial 8 - 마야 2011 모델 불러오기 (16) | 2013.02.09 |
DirectX11 Tutorial 7 - 3D 모델 렌더링 (1) | 2013.02.02 |