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.
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.
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.
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);
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.
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).
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