dev.nlited.com

>>

Direct3D Lighting

<<<< prev
next >>>>

2017-11-25 20:44:26 chip Page 2066 📢 PUBLIC

Nov 24 2017

I need some shadows to help provide visual clues. CreateInputLayout() fails if I enable any light effects.

This page makes me think my model requires normals (use VertexPositionNormalColor instead of VertexPositionColor) to enable the lighting effects.

This allowed CreateInputLayout() to succeed, but now my color information is messed up. Converting my vertex types worked.

Getting closer... In the first image I forgot to normalize the light vector. In the second image all the normal vectors are set to UP (0,-1,0) resulting in uniform lighting. Creating real normals for each vertex meant promoting my model to VertexPositionNormalColor and calculating real normal and color values. The third image uses the VPNC model, still using fake normals, but with the color of the vertices now based on (x,y,z)=>(r,g,b). The fourth image uses a base color of purple fading to green with elevation (x,y,z)=>(0.5,y,0.5) and real normals. The light is shining down and to the back-left.

[Image 2233] [Image 2234] [Image 2235] [Image 2236]


Lighting Effects: class Game { private: //Grid int vertexCt; //Grid size (vertex/side) std::vector<DirectX::VertexPositionNormalColor> m_vertex; DirectX::VertexPositionNormalColor &getVertex(int x0, int y0) { return(m_vertex.at(y0*vertexCt+x0)); };
void Game::CreateDevice2(void) { m_states= std::make_unique<CommonStates>(m_d3dDevice.Get()); m_effect= std::make_unique<BasicEffect>(m_d3dDevice.Get()); m_effect->SetVertexColorEnabled(true); m_effect->SetLightingEnabled(true); m_effect->SetLightEnabled(0,true); m_effect->SetLightDiffuseColor(0,Colors::White); Vector3 light(1.0f,2.0f,1.0f); light.Normalize(); m_effect->SetLightDirection(0,light); void const *shaderByteCode; size_t byteCodeLength; m_effect->GetVertexShaderBytecode(&shaderByteCode,&byteCodeLength); DX::ThrowIfFailed(m_d3dDevice->CreateInputLayout( VertexPositionNormalColor::InputElements, VertexPositionNormalColor::InputElementCount, shaderByteCode,byteCodeLength, m_inputLayout.ReleaseAndGetAddressOf() )); m_batch= std::make_unique<PrimitiveBatch<VertexPositionNormalColor>>(m_d3dContext.Get()); } void Game::CreateResources2(void) { // Create the perspective. m_doWireFrame= false; m_world= Matrix::Identity; m_viewPt= Vector3(0.5f,0.1f,0.5f); m_yaw= XMConvertToRadians(-170.0f); m_pitch= XMConvertToRadians(-2.0f); //m_view= Matrix::CreateLookAt(Vector3(0.5f,1.0f,0.5f),Vector3::Zero,Vector3::UnitY); m_proj= Matrix::CreatePerspectiveFieldOfView(XM_PI/4.0f,float(m_outputWidth)/float(m_outputHeight),0.1f,10.0f); //m_effect->SetView(m_view); m_effect->SetProjection(m_proj); // Create the grid. vertexCt= 20; m_vertex.resize(vertexCt*vertexCt); // Put the grid in the XZ plane with elevation along the Y axis. // First pass calculates the position and color. for(int z0=0;z0<vertexCt;z0++) { for(int x0=0;x0<vertexCt;x0++) { //(x0,y0) is an integer index into the X/Y plane of the grid. float x1= (float)x0/(float)vertexCt; float z1= (float)z0/(float)vertexCt; float y1= -cosf(x1*4.0f*XM_PI)*sinf(z1*2.0f*XM_PI); //(x1,y1,z1) is a scaled value from 0.0 to 1.0 across the grid. float x2= x1*0.5f; float y2= y1*0.1f; float z2= z1*0.5f; //(x2,y2,z2) is the actual 3D position of the vertex. Vector3 pos(x2,y2,z2); Vector3 normal= -Vector3::UnitY; Vector3 color(0.5f,y1,0.5f); getVertex(x0,z0)= VertexPositionNormalColor(pos,normal,color); } } // Second pass calculates the normals (for lighting effects) for(int z0=0;z0+1<vertexCt;z0++) { for(int x0=0;x0+1<vertexCt;x0++) { // Assuming CCW order: +-----> X // V1= (x0,z0) | // V2= (x0,z0+1) | // V3= (x0+1,z0) Z // Normal is (V2-V1) X (V3-V1) where X is cross-product. Vector3 v1= getVertex(x0,z0).position; Vector3 v2= getVertex(x0,z0+1).position; Vector3 v3= getVertex(x0+1,z0).position; Vector3 v2v1= v2-v1; Vector3 v3v1= v3-v1; Vector3 cross= v2v1; cross.Cross(v3v1); cross.Normalize(); getVertex(x0,z0).normal= cross; } } } void Game::Render2(void) { ... m_batch->Begin(); VertexPositionNormalColor v0,v1,v2,v3; for(int x0=0;x0+1<vertexCt;x0++) { for(int z0=0;z0+1<vertexCt;z0++) { v0= getVertex(x0,z0); v1= getVertex(x0+1,z0); v2= getVertex(x0+1,z0+1); v3= getVertex(x0,z0+1); if(m_doWireFrame) { m_batch->DrawLine(v0,v1); m_batch->DrawLine(v1,v2); } else { m_batch->DrawQuad(v0,v1,v2,v3); } } } m_batch->End(); }

There may be a problem with either my normals or how I am calculating the lighting effect. I tried to make the light source orbit above the grid but the result looked as though the light was simply rising and setting.

Orbiting light: void Game::Update2(float totalTime,float elapsedTime) { // time input Vector3 lightPt(4.0f*cosf(totalTime),+10.0f,4.0f*sinf(totalTime)); Vector3 gridPt(0,0,0); Vector3 light= +lightPt + -gridPt; light.Normalize(); m_effect->SetLightDirection(0,light);

Adding Shapes

I'm not sure what is happening with my lighting, it could be the position of the light source, the light vector, the vertex normals, or a combination of all of the above. I need to be able to see the location of the light source while I debug. I need to create the sun.

I want to be able to combine my terrain primitives with predefined shapes such as cubes and spheres. I created a "sun" and placed it in a fixed position above the grid.

Sun: Game.h: class Game { private: //Objects std::unique_ptr<DirectX::GeometricPrimitive> m_sun; DirectX::SimpleMath::Matrix m_sunMatrix;
SimpleGrid.cpp: void Game::CreateDevice2(void) { ... m_sun= GeometricPrimitive::CreateSphere(m_d3dContext.Get(),0.25f); m_sunMatrix= Matrix::Identity; } void Game::Update2(float totalTime,float elapsedTime) { ... m_sunMatrix= Matrix::Identity; Vector3 sunPt(0,1.0f,0); m_sunMatrix.Translation(sunPt); } void Game::Render2(void) { ... m_sun->Draw(m_sunMatrix,view,m_proj); m_batch->End(); }

The first attempt resulted in horrible flickering and warnings:
D3D11 WARNING: ID3D11DeviceContext::DrawIndexed: Index buffer has not enough space! [ EXECUTION WARNING #359: DEVICE_DRAW_INDEX_BUFFER_TOO_SMALL]

I found that the terrain and shape each use their own batch processor. Everything ran smoothly when I moved the m_sun->Draw() outside the terrain's m_batch->End().
m_batch->End(); m_sun->Draw(m_sunMatrix,view,m_proj);

Direct3D simple grid

The orbiting sun tells me the light source is correct. I looked at the normal calculations again and found that the X component is always zero. That isn't right because it means the normals are constricted to a single plane, which is exactly what I am seeing in the live shadows.

My second attempt seemed closer, but the grid was completely in shadow for about a quarter of the orbit. This is not right.

Direct3D simple grid Direct3D simple grid dir

This StackOverflow explains an efficient solution to this exact problem, a uniform triangle mesh over an XY (XZ in my case) grid with varying elevation. The solution requires computing the sum of the 4 adjacent normals and normalizing the result.
| \|/ | N = N1 + N2 + N3 + N4 ..--+----U----+--.. = ( (Zleft - Zright) / ax, | /|\ | (Zdown - Zup ) / ay, | / | \ | 2 ) \ | / 1|2 \ | / \|/ | \|/ ..--L----P----R--... /|\ | /|\ / | \ 4|3 / | \ | \ | / | | \|/ | ..--+----D----+--.. | /|\ |

Vertex Normals: void Game::CreateResources2(void) { ... // Second pass calculates the normals (for lighting effects) Vector3 vMax= getVertex(vertexCt-1,vertexCt-1).position; float dx= vMax.x/vertexCt; float dz= vMax.z/vertexCt; for(int z0=1;z0+1<vertexCt;z0++) { for(int x0=1;x0+1<vertexCt;x0++) { // Vertex normal is the sum of the adjacent vertices normals. // See https://stackoverflow.com/questions/6656358/calculating-normals-in-a-triangle-mesh/ Vector3 vP= getVertex(x0,z0).position; Vector3 vU= getVertex(x0,z0+1).position; Vector3 vR= getVertex(x0+1,z0).position; Vector3 vD= getVertex(x0,z0-1).position; Vector3 vL= getVertex(x0-1,z0).position; Vector3 sum; sum.x= (vL.y - vR.y)/dx; sum.z= (vD.y - vU.y)/dz; sum.y= 2; sum.Normalize(); getVertex(x0,z0).normal= sum; } } }

I also adjusted the position of the sun to move it closer to the grid. The grid is quite small, ranging from (0,0) - (0.5,0.1,0.5).

Sun Position: void Game::Update2(float totalTime,float elapsedTime) { // time input Vector3 lightPt(2.0f*cosf(totalTime),+2.0f,2.0f*sinf(totalTime)); Vector3 gridPt= getVertex(vertexCt/2,vertexCt/2).position; gridPt.y= 0; Vector3 light= gridPt - lightPt; light.Normalize(); m_effect->SetLightDirection(0,light); m_sunMatrix= Matrix::Identity; Vector3 sunPt(lightPt); m_sunMatrix.Translation(sunPt);

This works. I can watch the shadows move around with the position of the sun. The location of the light source does not affect the depth of shadow or the amount of light, only the light direction. Light sources are point sources, assumed to be an infinite distance away so that all the light is colaminar like a laser beam.



WebV7 (C)2018 nlited | Rendered by tikope in 54.389ms | 3.138.121.79