Added Comments

Added ability to hold shift and skip the terrain generation when loading
Added ability for the perlin terrain to save a raw image of the terrain to use as a cache
This commit is contained in:
iDunnoDev
2022-06-13 16:41:25 +01:00
committed by iDunnoDev
parent 51afdeecbd
commit 616f68bf8b
40 changed files with 468 additions and 51 deletions

View File

@ -185,7 +185,9 @@ void Camera::Update(void)
}
else
{
// Trying some rotation so that the camera actually follows the model instead of staying in place
XMVECTOR offset = XMVectorSet(_followOffset.x, _followOffset.y, _followOffset.z, 1.0f);
// The pitch and roll rotations really mess it up and i dont have time to look into it, the simple turn follow is good enough for now
XMMATRIX offsetRotationMatrix = XMMatrixRotationAxis(defaultUp, _cameraYaw); // *XMMatrixRotationAxis(defaultForward, _cameraPitch);
cameraPosition = _nodeFollowed->GetNodePosition() + XMVector3TransformCoord(offset, offsetRotationMatrix);

View File

@ -27,6 +27,8 @@ public:
float GetRoll() const;
void SetLeftRight(float leftRight);
void SetForwardBack(float forwardBack);
// Method for setting which object node is being followed
void SetFollowNode(shared_ptr<ObjectNode> nodeFollowed, XMFLOAT3 followOffset, bool positionOnly);
private:
@ -38,6 +40,7 @@ private:
float _moveForwardBack;
float _cameraYaw;
// Camera yaw previous to help with the rotation, remove later
float _cameraYawPrev;
float _cameraPitch;
float _cameraRoll;

View File

@ -6,6 +6,7 @@ ControlledMeshNode::ControlledMeshNode(wstring name, wstring modelName) : MeshNo
void ControlledMeshNode::Update(FXMMATRIX& currentWorldTransformation)
{
// Run the update for both parent node classes
MeshNode::Update(currentWorldTransformation);
ObjectNode::Update(_worldTransformation);
}

View File

@ -2,6 +2,7 @@
#include "ObjectNode.h"
#include "MeshNode.h"
// Class for handing the moveable mesh nodes, inherits from both the mesh and object nodes, the object node contains what is needed for pitch, yaw and forward back movement
class ControlledMeshNode : public MeshNode, public ObjectNode
{
public:

View File

@ -6,6 +6,7 @@ ControlledSplitMeshNode::ControlledSplitMeshNode(wstring name, wstring modelName
void ControlledSplitMeshNode::Update(FXMMATRIX& currentWorldTransformation)
{
// Run the update for both parent node classes
ObjectNode::Update(_worldTransformation);
SceneGraph::Update(currentWorldTransformation);
}

View File

@ -4,6 +4,7 @@
#include "ObjectNode.h"
#include "SubMeshNode.h"
// Class for handing the moveable split mesh nodes, inherits from both the mesh and object nodes, the object node contains what is needed for pitch, yaw and forward back movement
class ControlledSplitMeshNode : public SplitMeshNode, public ObjectNode
{
public:

View File

@ -11,6 +11,7 @@ GamePadController::~GamePadController(void)
{
}
// Method for processing the current inputs, i added params for the current inputs vector and a bool to set the boosting varaible
void GamePadController::ProcessGameController(set<ControlInputs>& currentInputs, bool &boostHit)
{
DWORD magnitudeSquared;

View File

@ -5,6 +5,7 @@
#include <XInput.h>
#pragma comment(lib, "XInput.lib")
// Emum class for helping with the select of which controller inputs to use
enum class ControlInputs { Forward, Back, TurnLeft, TurnRight, StrafeLeft, StrafeRight, Up, Down, Fire1, Fire2 };
class GamePadController

View File

@ -2,6 +2,7 @@
GlobalLighting::GlobalLighting()
{
// Set the default lighting to 0
_ambientLight = XMFLOAT4(0.0f, 0.0f, 0.0f, 0.0f);
_directionalLightVector = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
_directionalLightColor = XMFLOAT4(0.0f, 0.0f, 0.0f, 0.0f);

View File

@ -1,6 +1,7 @@
#pragma once
#include "DirectXCore.h"
// Class for holding the global lighting settings, this saves having to go through every node to change these values
class GlobalLighting
{
public:

View File

@ -4,6 +4,7 @@ Graphics2 app;
void Graphics2::CreateSceneGraph()
{
// Reset our variables for movement
_boostMultiplier = _boostMin;
_flySpeed = 1;
_turnSpeed = 1;
@ -13,7 +14,16 @@ void Graphics2::CreateSceneGraph()
_currentPlayerObject = nullptr;
// Check if the shift and ctrl keys are held to disable the clutter
if (GetAsyncKeyState(VK_SHIFT) && GetAsyncKeyState(VK_CONTROL))
{
_noClutter = true;
}
// Set the default camera position
GetCamera()->SetCameraPosition(0.0f, 550.0f, -80.0f);
// Get the main scene graph
SceneGraphPointer sceneGraph = GetSceneGraph();
// This is where you add nodes to the scene graph
@ -21,29 +31,40 @@ void Graphics2::CreateSceneGraph()
//cube->SetWorldTransform(XMMatrixScaling(5.0f, 8.0f, 2.5f) * XMMatrixTranslation(0, 23.0f, 0)); XMMatrixScaling(50.0f, 800.0f, 50.0f) * XMMatrixTranslation(-65.0f, 350.0f, -65.0f)
//sceneGraph->Add(cube);
// Set some global lighting variables for the ambient and directional lights, if i get time look into other lighting?
DirectXFramework::GetDXFramework()->GetGlobalLighting()->SetAmbientLight(XMFLOAT4(0.7f, 0.7f, 0.7f, 1.0f));
DirectXFramework::GetDXFramework()->GetGlobalLighting()->SetDirectionalLight(XMVectorSet(0.5f, -1.0f, -1.0f, 0.0f), XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f));
shared_ptr<SkyNode> skyDome = make_shared<SkyNode>(L"SkyDome", L"Textures\\SkyWater.dds", 180.0f);
// Add the skybox node
shared_ptr<SkyNode> skyDome = make_shared<SkyNode>(L"SkyDome", L"Textures\\SkyWater.dds", 360.0f);
sceneGraph->Add(skyDome);
// Set the terrain node, we can use the heightmap or perlin noise nodes
//shared_ptr<HeightMapTerrainNode> terrainNode = make_shared<HeightMapTerrainNode>(L"MainTerrain", L"Textures\\Example_HeightMap.raw", L"RandomWords");
//My height maps do not load with the given function, i have no idea what format its in then...
//shared_ptr<HeightMapTerrainNode> terrainNode = make_shared<HeightMapTerrainNode>(L"MainTerrain", L"TerrainCache\\cache_LovelyCat_10_000000.raw", L"RandomWords");
shared_ptr<PerlinTerrainNode> terrainNode = make_shared<PerlinTerrainNode>(L"MainTerrain", L"LovelyCat", 10.0f);
terrainNode->SetAmbientLight(DirectXFramework::GetDXFramework()->GetGlobalLighting()->GetAmbientLight());
terrainNode->SetDirectionalLight(DirectXFramework::GetDXFramework()->GetGlobalLighting()->GetDirectionalLightDirection(), DirectXFramework::GetDXFramework()->GetGlobalLighting()->GetDirectionalLightColor());
// Set the water color for tinting here
terrainNode->SetWaterColor(XMFLOAT4(SharedMethods::RGBValueToIntensity(0x84), SharedMethods::RGBValueToIntensity(0xC1), SharedMethods::RGBValueToIntensity(0xF9), 1.0f));
//terrainNode->SetWaterColor(XMFLOAT4(1.0f, 0.0f, 0.8f, 1.0f));
//terrainNode->SetWaterColor(XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f));
// Rivers of blood mode
//terrainNode->SetWaterColor(XMFLOAT4(SharedMethods::RGBValueToIntensity(0x66), 0.0f, 0.0f, 1.0f));
sceneGraph->Add(terrainNode);
// Add the controlled plane model
shared_ptr<ControlledSplitMeshNode> plane1Node = make_shared<ControlledSplitMeshNode>(L"Plane1", L"Models\\Plane\\Bonanza.3DS");
plane1Node->SetStartOrientation(XMMatrixRotationAxis(XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f), XM_PI) * XMMatrixRotationAxis(XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f), 0.5f * XM_PI));
plane1Node->SetNodePosition(0.0f, 550.0f, -50.0f);
sceneGraph->Add(plane1Node);
// Add the boat scene graph so i can scale and position the model
shared_ptr<SceneGraph> boatGraph = make_shared<SceneGraph>(L"boatGraph");
boatGraph->SetWorldTransform(XMMatrixScaling(0.1f, 0.1f, 0.1f) * XMMatrixTranslation(-1880.0f, 290.0f, 610.0f));
// Added the moveable boat model
shared_ptr<ControlledMeshNode> boat1Node = make_shared<ControlledMeshNode>(L"Boat1", L"Models\\Boat\\Boat.FBX");
boat1Node->SetStartOrientation(XMMatrixRotationAxis(XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f), (90.0f * XM_PI) / 180.0f) * XMMatrixRotationAxis(XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f), (90.0f * XM_PI) / 180.0f));
boat1Node->SetNodePosition(0.0f, 0.0f, 0.0f);
@ -51,9 +72,11 @@ void Graphics2::CreateSceneGraph()
sceneGraph->Add(boatGraph);
// Set the current player object to the plane and turn on follow mode
_currentPlayerObject = plane1Node;
GetCamera()->SetFollowNode(plane1Node, XMFLOAT3(0.0f, 40.0f, -100.0f), false);
// Set the background color although i assume this will be deprecated when the skybox is added
SetBackgroundColour(XMFLOAT4(0.29f, 0.38f, 0.72f, 1.0f));
//SetBackgroundColour(XMFLOAT4(SharedMethods::RGBValueToIntensity(0x89), 0, 1, 1));
@ -62,18 +85,23 @@ void Graphics2::CreateSceneGraph()
_currentPropRotation = 0;
}
// Method for generating the random clutter on the terrain
void Graphics2::GenerateClutter()
{
SceneGraphPointer sceneGraph = GetSceneGraph();
// Get the current terrain node so that we can access its methods
shared_ptr<TerrainNode> mainTerrain = dynamic_pointer_cast<TerrainNode>(sceneGraph->Find(L"MainTerrain"));
// Check if the terrain is actually loaded before trying to access it.
if (!_initDone && mainTerrain != nullptr)
{
// Create the spawn object lists
vector<TerrainPopNode> rngSpawnList;
vector<TerrainPopNode> rngBushSpawnList;
vector<TerrainPopNode> rngSnowSpawnList;
vector<TerrainPopNode> rngDirtSpawnList;
// Trees
TerrainPopNode treeModelA = TerrainPopNode();
treeModelA.modelName = L"Models\\Tree\\Tree1.fbx";
treeModelA.nodeBaseName = L"treeA";
@ -89,10 +117,13 @@ void Graphics2::GenerateClutter()
shared_ptr<SceneGraph> treeGroupA = make_shared<SceneGraph>(L"TreeGroupA");
// Populate the terrain and add the child scene graph to the main scenegraph
mainTerrain->PopulateTerrain(treeGroupA, rngSpawnList, 20, 20, 500.0f, 650.0f, 0.9f, 1.0f);
// Initialise the newly created models
treeGroupA->Initialise();
sceneGraph->Add(treeGroupA);
// Bushes
TerrainPopNode bushModelA = TerrainPopNode();
bushModelA.modelName = L"Models\\Tree\\Bush1.fbx";
bushModelA.nodeBaseName = L"bushA";
@ -130,6 +161,7 @@ void Graphics2::GenerateClutter()
bushGroupA->Initialise();
sceneGraph->Add(bushGroupA);
// The Snow trees
TerrainPopNode treeModelSA = TerrainPopNode();
treeModelSA.modelName = L"Models\\Tree\\Tree3.fbx";
treeModelSA.nodeBaseName = L"treeSA";
@ -143,6 +175,7 @@ void Graphics2::GenerateClutter()
treeGroupSA->Initialise();
sceneGraph->Add(treeGroupSA);
// The Rocks and desert tree
TerrainPopNode treeModelDA = TerrainPopNode();
treeModelDA.modelName = L"Models\\Tree\\Tree4.fbx";
treeModelDA.nodeBaseName = L"treeDA";
@ -181,16 +214,21 @@ void Graphics2::GenerateClutter()
void Graphics2::UpdateSceneGraph()
{
SceneGraphPointer sceneGraph = GetSceneGraph();
shared_ptr<TerrainNode> mainTerrain = dynamic_pointer_cast<TerrainNode>(sceneGraph->Find(L"MainTerrain"));
if (!_initDone && mainTerrain != nullptr)
// Check if the main terrain exists and runs the generate clutter method, we have to run it in the update because
// the terrain hasnt actually been generated til now, and we need the heights and normals so we can check if something can fit in a place
if (!_initDone && mainTerrain != nullptr && !_noClutter)
{
//GenerateClutter();
GenerateClutter();
}
// This block gets the current inputs from either the keyboard of the gamepad, gamepad is a little iffy since the pitch is kind of wanky
GetCurrentControlInputs();
XMVECTOR startCameraPos = GetCamera()->GetCameraPosition();
// If the current player object is set, then always move forward
if (_currentPlayerObject != nullptr)
{
_currentPlayerObject->SetForwardBack(_flySpeed * _boostMultiplier);
@ -278,9 +316,11 @@ void Graphics2::UpdateSceneGraph()
}
}
// Fetch the plane and boat nodes so that we can move them
shared_ptr<ControlledSplitMeshNode> plane1 = dynamic_pointer_cast<ControlledSplitMeshNode>(sceneGraph->Find(L"Plane1"));
shared_ptr<ControlledMeshNode> boat1 = dynamic_pointer_cast<ControlledMeshNode>(sceneGraph->Find(L"Boat1"));
// Move the plane
if (plane1 != nullptr)
{
//plane1->SetWorldTransform(SharedMethods::RotateFromPoint(-60.0f, 0, 0, XMMatrixRotationAxis(XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f), _currentRotation * XM_PI / 180.0f)));
@ -288,15 +328,19 @@ void Graphics2::UpdateSceneGraph()
//sceneGraph->Find(L"Plane1")->GetFirstChild()->GetFirstChild()->SetWorldTransform(XMMatrixRotationAxis(XMVectorSet(0.0f, -1.0f, -1.0f, 0.0f), _currentSideRotation * XM_PI / 180.0f));
//sceneGraph->Find(L"Plane1")->Update((SharedMethods::RotateFromPoint(30.0f, -20.0f, 0, XMMatrixRotationAxis(XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f), _currentSideRotation * XM_PI / 180.0f))) * XMMatrixRotationAxis(XMVectorSet(0.0f, -1.0f, 0.0f, 0.0f), _currentRotation * XM_PI / 180.0f));
// Spin the planes propeller, it goes faster if you are boosting
plane1->Find(L"airscrew")->SetWorldTransform(SharedMethods::RotateFromPoint(0.0f, 15.471f, 14.5f, XMMatrixRotationAxis(XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f), _currentPropRotation * XM_PI / 180.0f)));
}
// Move the boat
if (boat1 != nullptr)
{
boat1->SetForwardBack(-3.0f);
boat1->SetYaw(-0.2f);
}
// Reset the rotation numbers when theyve done a full rotation so they just just increase forever
if (_currentRotation == 360)
{
_currentRotation = 0;
@ -320,6 +364,7 @@ void Graphics2::UpdateSceneGraph()
GetCamera()->Update();
if (mainTerrain != nullptr)
{
// If a boundary is hit then move the object back
bool boundaryHit = false;
XMFLOAT4 cameraOffset = XMFLOAT4(XMVectorGetX(startCameraPos), XMVectorGetY(startCameraPos), XMVectorGetZ(startCameraPos), 0.0f);
@ -372,6 +417,7 @@ void Graphics2::UpdateSceneGraph()
ResetCurrentControlInputs();
}
// Method for getting the current frames inputs
void Graphics2::GetCurrentControlInputs()
{
// Check if the window has focus before accepting any keypresses
@ -393,25 +439,25 @@ void Graphics2::GetCurrentControlInputs()
}
// Forward
if (GetAsyncKeyState(0x57))
if (GetAsyncKeyState(0x57) || GetAsyncKeyState(VK_UP))
{
_currentInputs.insert(ControlInputs::Forward);
}
// Back
if (GetAsyncKeyState(0x53))
if (GetAsyncKeyState(0x53) || GetAsyncKeyState(VK_DOWN))
{
_currentInputs.insert(ControlInputs::Back);
}
// Turn Left
if (GetAsyncKeyState(0x41))
if (GetAsyncKeyState(0x41) || GetAsyncKeyState(VK_LEFT))
{
_currentInputs.insert(ControlInputs::TurnLeft);
}
// Turn Right
if (GetAsyncKeyState(0x44))
if (GetAsyncKeyState(0x44) || GetAsyncKeyState(VK_RIGHT))
{
_currentInputs.insert(ControlInputs::TurnRight);
}
@ -442,6 +488,7 @@ void Graphics2::GetCurrentControlInputs()
}
}
// Method for clearing the current inputs ready for the next frame
void Graphics2::ResetCurrentControlInputs()
{
_currentInputs.clear();

View File

@ -28,9 +28,9 @@ private:
float _boostStep = 0.5f;
float _boostMax = 5.0f;
float _flySpeed;
float _turnSpeed;
int _invertPitch;
float _flySpeed = 0.0f;
float _turnSpeed = 0.0f;
int _invertPitch = 0;
float _currentRotation = 0.0f;
float _currentSideRotation = 0.0f;
@ -43,6 +43,8 @@ private:
shared_ptr<ObjectNode> _currentPlayerObject;
bool _noClutter = false;
void GetCurrentControlInputs();
void ResetCurrentControlInputs();
void GenerateClutter();

View File

@ -5,6 +5,7 @@
HeightMapTerrainNode::HeightMapTerrainNode(wstring name, wstring heightMap, wstring seed, float waterHeight, int widthX, int widthZ, int cellSizeX, int cellSizeZ) : TerrainNode(name, seed, waterHeight, widthX, widthZ, cellSizeX, cellSizeZ)
{
_heightMap = heightMap;
// Try to load the height map otherwise fail and set the error bool
if (!LoadHeightMap(_heightMap))
{
_initError = true;

View File

@ -3,6 +3,7 @@
#include "SharedMethods.h"
#include "TerrainNode.h"
// Height map terrain node, contains the methods needed to generate a terrain node with the given weird height map
class HeightMapTerrainNode : public TerrainNode
{
public:

View File

@ -1,6 +1,9 @@
#pragma once
#include "DirectXCore.h"
// The object node contains all of the methods and variables needed to move a node using pitch, yaw, roll, and the directions
// It should be inherited by another node so that it doesnt interupt with the normal rendering functions and saves us having to
// duplicate code for every time, it also allows the child classes to be accessed by the ObjectNode type when needed
class ObjectNode
{
public:

View File

@ -2,17 +2,29 @@
PerlinTerrainNode::PerlinTerrainNode(wstring name, wstring seed, float chunkSize, int widthX, int widthZ, float waterHeight, int cellSizeX, int cellSizeZ) : TerrainNode(name, seed, waterHeight, widthX, widthZ, cellSizeX, cellSizeZ)
{
// Set the chink size
_chunkSize = chunkSize;
GeneratePerlinValues();
if (!GeneratePerlinHeights())
// Check if a cache image exists and load it if so
if (!ReadCache())
{
_initError = true;
// No Cache image existed so generate the perlin noise map
GeneratePerlinValues();
if (!GeneratePerlinHeights())
{
_initError = true;
}
else
{
// The generation was successful, save it to the cache
CacheOutput();
}
}
}
void PerlinTerrainNode::GeneratePerlinValues()
{
// Setup the RNG
std::seed_seq seedGenerator(_seedString.begin(), _seedString.end());
std::mt19937 generator;
generator.seed(seedGenerator);
@ -20,9 +32,12 @@ void PerlinTerrainNode::GeneratePerlinValues()
char bufferChar;
int newPosition;
// Clear the permutation array so we can reinsert a new copy
_p.clear();
copy(_staticP.begin(), _staticP.end(), back_inserter(_p));
// Loop through and alternate the numbers so they are in a random position, this will not create any new numbers so we should always have what we started with
// just in a different order
for (int i = 0; i < 512; i++)
{
newPosition = dist(generator);
@ -32,20 +47,83 @@ void PerlinTerrainNode::GeneratePerlinValues()
}
}
bool PerlinTerrainNode::ReadCache()
{
ifstream cachedImage;
string stringSeed = string(_seedString.begin(), _seedString.end());
string chunkSizeString = to_string(_chunkSize);
replace(chunkSizeString.begin(), chunkSizeString.end(), '.', '_');
cachedImage.open("TerrainCache\\cache_" + stringSeed + "_" + chunkSizeString + ".raw", std::ios_base::binary);
// Couldnt open the cache, either doesnt exist or some issue opening the file, a new one should be generated
if (!cachedImage)
{
return false;
}
// Load the files bytes into a vector we can foreach loop
vector<char> fileBytes((istreambuf_iterator<char>(cachedImage)), (istreambuf_iterator<char>()));
for (BYTE currentByte : fileBytes)
{
// Change the number back into a float value, some precision is lost because of this but i cant notice it really
float heightValue = currentByte / 255.0f;
_heightValues.push_back(heightValue);
}
cachedImage.close();
return true;
}
void PerlinTerrainNode::CacheOutput()
{
// Create a unique string name for the seed and cluster size given
string stringSeed = string(_seedString.begin(), _seedString.end());
string chunkSizeString = to_string(_chunkSize);
replace(chunkSizeString.begin(), chunkSizeString.end(), '.', '_');
ofstream output("TerrainCache\\cache_" + stringSeed + "_" + chunkSizeString + ".raw", std::ios_base::binary);
// Loop through the height values array
int current = 0;
for (float value : _heightValues)
{
// Force the value to be within 255, we lose some precision from this conversion but to generate an image it has to be this way
BYTE currentByte = (int)(255 * value) & 0xff;
// Write the value to the file buffer
output << currentByte;
// Constantly flush the buffer so that we dont have it waiting in memory for too long
if (current >= 100)
{
output.flush();
current = 0;
}
else
{
current++;
}
}
// Close the file
output.close();
}
// Method for filling the height values array for the terrain generation
bool PerlinTerrainNode::GeneratePerlinHeights()
{
for (int z = 0; z < _gridRows; z++)
for (int z = 0; z < (int)_gridRows; z++)
{
float mapZ = ((float)z / (float)_gridRows);
for (int x = 0; x < _gridCols; x++)
for (int x = 0; x < (int)_gridCols; x++)
{
float mapX = ((float)x / (float)_gridCols);
//float currentHeightValue = GetPerlinValueAt(mapX, mapZ, 8, 0.85f) - 0.25f;
float currentHeightValue = 0.0f;
// Octaves adds "noise" to the final outcome, 12 is really the max before it tanks the system for little effect
int octaves = 12;
float currentOctaveValue = 1.0f;
float stepOff = 1.0f;
@ -53,6 +131,7 @@ bool PerlinTerrainNode::GeneratePerlinHeights()
// n covers the persistance, frequency and
for (int n = 1; n <= octaves; n++)
{
// Generate a noise at the given position
currentHeightValue += stepOff * GetNoiseValueAt(((float)x * currentOctaveValue) / ((float)_gridCols / _chunkSize), ((float)z * currentOctaveValue) / ((float)_gridRows / _chunkSize));
currentOctaveValue *= 2;
stepTotal += stepOff;
@ -62,6 +141,7 @@ bool PerlinTerrainNode::GeneratePerlinHeights()
//currentHeightValue = currentHeightValue / stepTotal; // *(currentHeightValue * 2.0f);
//currentHeightValue = currentHeightValue - floor(currentHeightValue);
// Normalise the result to a number between 0 and 1, since most like to generate numbers between -1 and 1, we just have to add 1 and half the value
_heightValues.push_back((currentHeightValue + 1.0f) * 0.5f);
}
}
@ -76,6 +156,7 @@ float PerlinTerrainNode::Fade(float t)
float PerlinTerrainNode::Grad(int hash, float x, float y)
{
// To be honest this is something i dont really understand, it shifts bits around to see which direction we need to multiply in
int h = hash & 7;
float u = y;
float v = x;

View File

@ -3,9 +3,11 @@
#include <vector>
#include <numeric>
#include <random>
#include <fstream>
#include "SharedMethods.h"
#include "TerrainNode.h"
// Class for generating a terrain using a perlin noise algorithm, this now caches the result too so it only has to run on the initial time its loaded.
class PerlinTerrainNode : public TerrainNode
{
public:
@ -41,11 +43,20 @@ private:
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
};
// Vector to hold the actual randomised perlin noise permutation array
vector<unsigned char> _p;
// The chunk size lets us control how large a noise spot should be, lower numbers will make things flatter, larger numbers will make things spikey
float _chunkSize;
// Methods for Caching the output to a raw image format, and reading it back in the next time its loaded
bool ReadCache();
void CacheOutput();
// Methods for fading a number, used for the noise calculation
float Fade(float t);
// Method for generating a perlin gradient.
float Grad(int hash, float x, float y);
void GeneratePerlinValues();

View File

@ -71,6 +71,7 @@ SceneNodePointer SceneGraph::Find(wstring name)
for (SceneNodePointer& currentSceneGraphPtr : _children)
{
foundValue = currentSceneGraphPtr->Find(name);
// Keep going til we find a value, then break the loop all the way down
if (foundValue != nullptr)
{
break;

View File

@ -17,10 +17,15 @@ public:
void Add(SceneNodePointer node);
void Remove(SceneNodePointer node);
SceneNodePointer Find(wstring name);
// Placeholder methods for possibly going up the graph to find the Root and Parent nodes
SceneNodePointer GetRootNode();
SceneNodePointer GetParent();
// Returns a scenegraphs first child element, useful if you dont know the names but know the structure
SceneNodePointer GetFirstChild();
// Method for returning a pointer to the child list, needed for the split mesh renderer mainly
list<SceneNodePointer>& GetChildren();
protected:

View File

@ -6,13 +6,13 @@ XMMATRIX SharedMethods::RotateFromPoint(const float x, const float y, const floa
return XMMatrixTranslation(x, y, z) * rotationMatrix * XMMatrixTranslation(-x, -y, -z);
}
// Method so i can use normal RGB values and hopefully hex values too since im use to those
// Function so i can use normal RGB values and hopefully hex values too since im use to those
float SharedMethods::RGBValueToIntensity(const int value)
{
return (float)value / 255.0f;
}
// Method to generate a random intensity rating between 2 floats
// Function to generate a random intensity rating between 2 floats
float SharedMethods::GenerateRandomIntensity(const float min, const float max)
{
float randNo = (float)rand() / (float)RAND_MAX;
@ -22,7 +22,7 @@ float SharedMethods::GenerateRandomIntensity(const float min, const float max)
return (float)roundedNo * 0.01f;
}
// Method for genering random UV's, maybe get rid of this in future
// Function for genering random UV's, maybe get rid of this in future
float SharedMethods::GenerateRandomUV(const float min, const float max)
{
return SharedMethods::Clamp<float>((float)(rand() % (int)(max * 100.0f)) * 0.01f, (float)min, (float)max);

View File

@ -2,6 +2,7 @@
#include "DirectXCore.h"
#include <string>
// The generic CBuffer used in most of the nodes
struct CBUFFER
{
XMMATRIX completeTransformation;
@ -18,6 +19,7 @@ struct CBUFFER
float padding[1];
};
// Struct to hold the terrain populate methods variables
struct TerrainPopNode
{
std::wstring nodeBaseName;
@ -25,12 +27,16 @@ struct TerrainPopNode
float scaleFactor;
};
// The Shared methods namespace should be used for functions that are used in many places to stop duplicate code and keep things consolidated
namespace SharedMethods
{
// Function for creating a rotation matrix that should be around a specific point
XMMATRIX RotateFromPoint(const float x, const float y, const float z, const XMMATRIX rotationMatrix);
// Function for converting a 255 style RGB value to its normalised intensity value
float RGBValueToIntensity(const int value);
// Template function for clamping a number between 2 values, the template allows this to be used for any numeric type
template <typename T = float> T Clamp(const T value, const T min, const T max)
{
if (value < min)
@ -47,12 +53,13 @@ namespace SharedMethods
}
}
// Methods for lerping values
// Template function for lerping values
template <typename T = float> float Lerp(const T a, const T b, const float p)
{
return (float)a + p * ((float)b - (float)a);
}
// Template function for doing a cubic interpolate, i think this is unused currently but i did play around with the idea
template <typename T = float> float CubicInterpolate(const T n0, const T n1, const T n2, const T n3, const float a)
{
float p = ((float)n3 - (float)n2) - ((float)n0 - (float)n1);
@ -63,7 +70,10 @@ namespace SharedMethods
return p * a * a * a + q * a * a + r * a + s;
}
// Function for generating a random float value
float GenerateRandomIntensity(const float min, const float max);
// Function for generating random UV values
float GenerateRandomUV(const float min, const float max);
};

View File

@ -13,6 +13,7 @@ bool SplitMeshNode::Initialise()
}
else
{
// Loop the meshes sub meshes to generate the scene graph tree
Add(AddMeshNode(_mesh->GetRootNode()));
}
@ -40,6 +41,7 @@ void SplitMeshNode::Render(void)
void SplitMeshNode::Shutdown(void)
{
// Release the mesh from the resource manager
_resourceManager->ReleaseMesh(_modelName);
SceneGraph::Shutdown();
}

View File

@ -4,6 +4,7 @@
#include "SubMeshRenderer.h"
#include "SubMeshNode.h"
// Class for creating a scenegraph centric split mesh node, that allows for transformation of any of the sub meshes
class SplitMeshNode : public SceneGraph
{
public:

View File

@ -2,6 +2,7 @@
#include "DirectXFramework.h"
#include "SceneNode.h"
// Class for holding the details of the sub meshes of a split node class
class SubMeshNode : public SceneNode
{
public:

View File

@ -2,6 +2,7 @@
void SubMeshRenderer::SetRootChildren(list<SceneNodePointer>& rootChildren)
{
// Sets the root children since the actual sub mesh root could have multiple children...
_rootGraph = &rootChildren;
}
@ -35,8 +36,10 @@ bool SubMeshRenderer::Initialise()
void SubMeshRenderer::RenderChild(SceneNodePointer node, bool renderTransparent)
{
// Check if the current node is a sub mesh
shared_ptr<SubMeshNode> currentMeshNode = dynamic_pointer_cast<SubMeshNode>(node);
// If the current node is a submesh, render it
if (currentMeshNode != nullptr)
{
XMMATRIX projectionTransformation = DirectXFramework::GetDXFramework()->GetProjectionTransformation();
@ -82,6 +85,7 @@ void SubMeshRenderer::RenderChild(SceneNodePointer node, bool renderTransparent)
}
}
// If the current node is not a sub mesh its most likely a scene graph, if so loop the children of it
shared_ptr<SceneGraph> currentGraphNode = dynamic_pointer_cast<SceneGraph>(node);
if (currentGraphNode != nullptr)
@ -118,6 +122,9 @@ void SubMeshRenderer::Render()
float blendFactors[] = { 0.0f, 0.0f, 0.0f, 0.0f };
_deviceContext->OMSetBlendState(_transparentBlendState.Get(), blendFactors, 0xffffffff);
// IT REALLY NEEDS TO DO BOTH TO RENDER SOMEWHAT CORRECTLY
// This should solve that now
// We do two passes through the nodes. The first time we render nodes
// that are not transparent (i.e. their opacity == 1.0f).
for (SceneNodePointer& child : *_rootGraph)

View File

@ -5,6 +5,7 @@
#include "Renderer.h"
#include "Mesh.h"
// Class for rendering a sub mesh since its different to a normal mesh render
class SubMeshRenderer : public Renderer
{
public:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,9 +4,11 @@
TerrainNode::TerrainNode(wstring name, wstring seed, float waterHeight, int widthX, int widthZ, int cellSizeX, int cellSizeZ) : SceneNode(name)
{
// Get the seed string and hash it
_seedString = seed;
_seedHash = std::hash<wstring>{}(_seedString);
_seedHash = (unsigned int)std::hash<wstring>{}(_seedString);
// Set the random seed number
srand(_seedHash);
_widthX = widthX;
@ -83,9 +85,9 @@ void TerrainNode::GenerateTerrainData()
float bv = 1.0f / (float)(_gridRows - 1u);
int currentVertexCount = 0;
for (int z = 0; z < _gridRows; z++)
for (int z = 0; z < (int)_gridRows; z++)
{
for (int x = 0; x < _gridCols; x++)
for (int x = 0; x < (int)_gridCols; x++)
{
int currentIndex = (z * _gridCols) + x;
int offsetIndexX = 1;
@ -165,9 +167,9 @@ void TerrainNode::GenerateTerrainData()
void TerrainNode::GenerateTerrainNormals()
{
int currentNormalIndex = 0;
for (int z = 0; z < _gridRows; z++)
for (int z = 0; z < (int)_gridRows; z++)
{
for (int x = 0; x < _gridCols; x++)
for (int x = 0; x < (int)_gridCols; x++)
{
XMVECTOR v1 = XMVectorSet(_terrainVerts[currentNormalIndex + 1].Position.x - _terrainVerts[currentNormalIndex].Position.x,
_terrainVerts[currentNormalIndex + 1].Position.y - _terrainVerts[currentNormalIndex].Position.y,
@ -185,6 +187,7 @@ void TerrainNode::GenerateTerrainNormals()
XMStoreFloat3(&_terrainVerts[currentNormalIndex + 2].Normal, XMVectorAdd(XMLoadFloat3(&_terrainVerts[currentNormalIndex + 2].Normal), polygonNormal));
XMStoreFloat3(&_terrainVerts[currentNormalIndex + 3].Normal, XMVectorAdd(XMLoadFloat3(&_terrainVerts[currentNormalIndex + 3].Normal), polygonNormal));
// Process all the connected normals
AddNormalToVertex(z - 1, x - 1, 3, polygonNormal);
AddNormalToVertex(z - 1, x, 2, polygonNormal);
AddNormalToVertex(z - 1, x, 3, polygonNormal);
@ -218,6 +221,7 @@ void TerrainNode::AddNormalToVertex(int row, int col, int vertexIndex, XMVECTOR
int finalIndex = vertexIndex + ((rowIndexOffset + colIndexOffset) * 4);
// Check if the index is not around the edges
if (row >= 0 && row < (int)_gridRows && col >= 0 && col < (int)_gridCols)
{
XMStoreFloat3(&_terrainVerts[finalIndex].Normal, XMVectorAdd(XMLoadFloat3(&_terrainVerts[finalIndex].Normal), normal));
@ -498,13 +502,15 @@ void TerrainNode::LoadTerrainTextures()
));
}
// Method for populating the terrain with random clutter described in the structs
void TerrainNode::PopulateTerrain(SceneGraphPointer currentSceneGraph, vector<TerrainPopNode>& nodesForPop, int xStep, int zStep, float heightLower, float heightUpper, float slopeLower, float slopeUpper)
{
for (int z = 0; z < _gridRows; z += zStep)
// Step through the z and x with the given spacing
for (int z = 0; z < (int)_gridRows; z += zStep)
{
for (int x = 0; x < _gridCols; x += xStep)
for (int x = 0; x < (int)_gridCols; x += xStep)
{
int currentIndex = ((z * _gridCols) + x) * 4;
int currentIndex = ((z * (int)_gridCols) + x) * 4;
float totalHeights = 0.0f;
float totalSlope = 0.0f;
@ -516,12 +522,15 @@ void TerrainNode::PopulateTerrain(SceneGraphPointer currentSceneGraph, vector<Te
float heightValue = totalHeights / 4.0f;
// If the height value is within the given range
if (heightValue >= heightLower && heightValue <= heightUpper)
{
float avgSlope = totalSlope / 4.0f;
// If the slope value is within the given range
if (avgSlope >= slopeLower && avgSlope <= slopeUpper)
{
// The Height level and slope values are in range, create a mesh node with the provided details
int nodeIndex = 0;
if (nodesForPop.size() > 1)
{
@ -529,12 +538,13 @@ void TerrainNode::PopulateTerrain(SceneGraphPointer currentSceneGraph, vector<Te
}
float scale = (rand() % 100 * 0.01f) * nodesForPop[nodeIndex].scaleFactor;
float yRotation = rand() % 360;
float yRotation = (float)(rand() % 360);
float xPos = x * _cellSizeX + _terrainStartX;
float yPos = heightValue;
float zPos = (-z + 1) * _cellSizeZ + _terrainStartZ;
// Made a unique name to stop conflicts
wstring nodeName = L"_" + to_wstring(z) + L"_" + to_wstring(x);
shared_ptr<MeshNode> newNode = make_shared<MeshNode>(nodesForPop[nodeIndex].nodeBaseName + nodeName, nodesForPop[nodeIndex].modelName);
newNode->SetWorldTransform(XMMatrixRotationAxis(XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f), yRotation * XM_PI / 180.0f) * XMMatrixScaling(scale, scale, scale) * XMMatrixTranslation(xPos, yPos, zPos));
@ -547,11 +557,13 @@ void TerrainNode::PopulateTerrain(SceneGraphPointer currentSceneGraph, vector<Te
void TerrainNode::GenerateBlendMap()
{
// Set up the color gradients for each type of texture
RGBA snowTops = RGBA{ 0u, 0u, 0u, 255u };
RGBA grassLand = RGBA{ 0u, 0u, 0u, 0u };
RGBA lightDirt = RGBA{ 50u, 0u, 255u, 0u };
RGBA darkDirt = RGBA{ 255u, 50u, 50u, 0u };
// Create a vector of these gradients, multiple grass because i want it to cover more area, i need to come up with a better way if i have time
vector<RGBA> colorSteps = {darkDirt, lightDirt, grassLand, grassLand, grassLand, snowTops};
float waterOffset = _waterHeight - 50.0f;
@ -560,6 +572,7 @@ void TerrainNode::GenerateBlendMap()
waterOffset = 0.0f;
}
// Set up the gradient
ColorGradient terrainBlendGradient = ColorGradient(waterOffset, 820.0f, colorSteps);
// Note that _numberOfRows and _numberOfColumns need to be setup
@ -593,6 +606,7 @@ void TerrainNode::GenerateBlendMap()
float totalHeights = 0.0f;
float totalSlope = 0.0f;
// Calculate the average height and slope values
for (int si = 0; si < 4; si++)
{
totalHeights += _terrainVerts[currentIndex + si].Position.y;
@ -602,6 +616,7 @@ void TerrainNode::GenerateBlendMap()
float avgHeight = totalHeights / 4.0f;
float avgSlope = totalSlope / 4.0f;
// Get the RGB value from the gradient at the given height value
RGBA currentBlend = terrainBlendGradient.GetRGBAValue(avgHeight);
r = (BYTE)currentBlend.red;
g = (BYTE)currentBlend.green;
@ -612,7 +627,7 @@ void TerrainNode::GenerateBlendMap()
{
if (avgSlope < 0.6f)
{
g = (BYTE)SharedMethods::Clamp<int>(avgHeight - 450.0f, 0, 150);
g = (BYTE)SharedMethods::Clamp<int>((int)(avgHeight - 450.0f), 0, 150);
}
}
@ -667,6 +682,7 @@ void TerrainNode::BuildExtraMaps()
));
}
// Method for getting the height value at a given point, can also check if we are colliding with water or not
float TerrainNode::GetHeightAtPoint(float x, float z, bool waterCollide)
{
int cellX = (int)((x - _terrainStartX) / _cellSizeX);
@ -695,6 +711,7 @@ float TerrainNode::GetHeightAtPoint(float x, float z, bool waterCollide)
XMFLOAT4 normal;
XMStoreFloat4(&normal, XMVector3Normalize(XMVector3Cross(v1, v2)));
// Get the normal value for the vert we landed on
float result = _terrainVerts[currentIndex].Position.y + (normal.x * dx + normal.z * dz) / -normal.y;
if (waterCollide)
{
@ -707,6 +724,7 @@ float TerrainNode::GetHeightAtPoint(float x, float z, bool waterCollide)
return result;
}
// Method for checking if we are within the X Boundary
bool TerrainNode::CheckXBoundary(float x)
{
if (!(x > _terrainStartX && x < _terrainEndX))
@ -717,6 +735,7 @@ bool TerrainNode::CheckXBoundary(float x)
return true;
}
// Method for checking if we are withing the Z Boundary
bool TerrainNode::CheckZBoundary(float z)
{
if (!(z < _terrainStartZ && z > _terrainEndZ))

View File

@ -12,6 +12,7 @@
#include "MeshNode.h"
#include "SplitMeshNode.h"
// Struct for holding the terrain vertex data
typedef struct TerrainVertex
{
XMFLOAT3 Position;
@ -20,6 +21,8 @@ typedef struct TerrainVertex
XMFLOAT2 BlendMapTexCoord;
} TerrainVertex;
// Custom CBuffer for the terrain that can hold the water values, THE BYTE TOTAL FOR THIS MUST HAVE A FACTOR OF 16!!
// a float is 4 bytes, so we techically dont need to pad with the array at the bottom but i left it in to remind myself
struct TCBUFFER
{
XMMATRIX completeTransformation;
@ -38,6 +41,7 @@ struct TCBUFFER
float padding[4];
};
// Class for processing the data of a terrain, one of the child classes should be passing the values to here to process as this class does not generate any actual values.
class TerrainNode : public SceneNode
{
public:
@ -51,13 +55,16 @@ public:
void Render(void);
void virtual Shutdown(void);
// Method for randomly placing models onto the terrain with the given commands
void PopulateTerrain(SceneGraphPointer currentSceneGraph, vector<TerrainPopNode>& nodesForPop, int xStep, int zStep, float heightLower, float heightUpper, float slopeLower, float slopeUpper);
// Methods for checking if you are in the bounds
float GetHeightAtPoint(float x, float z, bool waterCollide = false);
bool CheckXBoundary(float x);
bool CheckZBoundary(float z);
protected:
// Bool so we can set this if the children have an error processing the data and we wont run
bool _initError = false;
int _widthX;
@ -80,6 +87,7 @@ protected:
float _terrainEndX;
float _terrainEndZ;
// Vars for dealing with the random number seed
wstring _seedString;
unsigned int _seedHash;
@ -111,7 +119,9 @@ protected:
ComPtr<ID3D11RasterizerState> _defaultRasteriserState;
ComPtr<ID3D11RasterizerState> _wireframeRasteriserState;
// Pointer for the snow texture since i could not load it with the other textures, i think its because of mixed formats
ComPtr<ID3D11ShaderResourceView> _snowTest;
ComPtr<ID3D11ShaderResourceView> _waterNormalMap;
XMFLOAT4 _waterColor;
@ -126,9 +136,13 @@ protected:
void LoadTerrainTextures();
void GenerateBlendMap();
// Method to get a height value at the given index
float GetHeightValueAt(int index);
// Method for adding the normals to each vertex, easier to manage this way
void AddNormalToVertex(int row, int col, int vertexIndex, XMVECTOR normal);
// Method for loading the extra image maps we had, but now just for the water normals
void BuildExtraMaps();
};

View File

@ -22,6 +22,8 @@ cbuffer ConstantBuffer
Texture2D BlendMap : register(t0);
Texture2DArray TexturesArray : register(t1);
// Textures for the water normals and the snow
Texture2D WaterNormalMap : register(t2);
Texture2D snowTest : register(t3);
@ -51,6 +53,7 @@ struct PixelShaderInput
float2 BlendMapTexCoord : TEXCOORD1;
};
// Function for rotating a UV around a point
float2 UVRotate(float2 uvCoord, float2 pivotPoint, float rotation)
{
float2x2 rotateMatrix = float2x2(float2(sin(rotation), -cos(rotation)), float2(cos(rotation), sin(rotation)));
@ -63,31 +66,35 @@ float2 UVRotate(float2 uvCoord, float2 pivotPoint, float rotation)
}
// Typical HLSL hasing function for psuedo random number generation
float4 hash4(float2 v)
{
float4 p = mul(float4x2(127.1f, 311.7f, 269.5f, 183.3f, 113.5f, 271.9f, 246.1f, 124.6f), v);
return frac(sin(p) * 43758.5453123);
}
PixelShaderInput VShader(VertexShaderInput vin)
{
PixelShaderInput output;
float3 position = vin.Position;
output.PositionOG = output.PositionWS = mul(worldTransformation, float4(position, 1.0f));
output.TexCoord = vin.TexCoord;
// Check if the y pos is lower than the water point, and set the position to it minus a tiny offset because the textures were clipping and causing a weird effect
// Check if the y pos is lower than the water point
if (position.y < waterHeight)
{
position.y = waterHeight;
// Change up the flat water height with some psuedo RNG height generation
float4 yOffset = hash4(float2(-position.y, position.y));
// the offset should return as a value between 0 and 1, i change this to be a value between -1 and 1, then add some height modifier so you can actually see the difference
position.y = waterHeight + (((yOffset.y * 2.0f) - 1.0f) * 3.0f);
}
output.Position = mul(completeTransformation, float4(position, 1.0f));
output.PositionWS = mul(worldTransformation, float4(position, 1.0f));
//output.PositionWS = mul(worldTransformation, float4(position, 1.0f));
output.NormalWS = float4(mul((float3x3)worldTransformation, vin.Normal), 1.0f);
output.BlendMapTexCoord = vin.BlendMapTexCoord;
return output;
}
float4 hash4(float2 v)
{
float4 p = mul(float4x2(127.1, 311.7, 269.5, 183.3, 113.5, 271.9, 246.1, 124.6), v);
return frac(sin(p) * 43758.5453123);
}
float4 PShader(PixelShaderInput input) : SV_TARGET
{
float4 directionToCamera = normalize(input.PositionWS - cameraPosition);
@ -95,14 +102,13 @@ float4 PShader(PixelShaderInput input) : SV_TARGET
float surfaceShininess = shininess;
float4 adjustedNormal = normalize(input.NormalWS);
float currentHeightPer = (waterHeight - input.PositionOG.y) / waterHeight;
if (input.PositionOG.y < waterHeight)
{
// Sample the normal from the water normal map and calculate it with the worldtransform
float3 n0 = 2.0f * (float3)WaterNormalMap.Sample(ss, input.TexCoord) - 1.0f;
float4 n2 = float4(mul((float3x3)worldTransformation, n0), 1.0f);
adjustedNormal = normalize(n2);
float4 n0 = WaterNormalMap.Sample(ss, input.TexCoord);
float4 n1 = float4((n0.xyz * 2.0f) - 1.0f, 1.0f);
//float4 n2 = float4(mul((float3x3)worldTransformation, n0), 1.0f);
adjustedNormal = n1; // normalize(n2);
surfaceShininess = waterShininess;
}
@ -123,8 +129,6 @@ float4 PShader(PixelShaderInput input) : SV_TARGET
// Randomly rotate the UV coordinates using the noise map
float2 scaleCenter = float2(0.5f, 0.5f);
// The noise map is greyscale so we only need to check 1 channel since they should all be the same value
float2 scaledUV = frac(UVRotate(input.TexCoord, scaleCenter, input.BlendMapTexCoord.x)); // (input.TexCoord - scaleCenter) * currentScale + scaleCenter;
float2 xChange = ddx(scaledUV);
@ -145,6 +149,7 @@ float4 PShader(PixelShaderInput input) : SV_TARGET
float4 c2 = TexturesArray.SampleGrad(ss, float3(scaledUV, 2.0f), xChange, yChange);
float4 c3 = TexturesArray.SampleGrad(ss, float3(scaledUV, 3.0f), xChange, yChange);
//float4 c4 = TexturesArray.SampleGrad(ss, float3(scaledUV, 4.0f), xChange, yChange);
// Using the single snow texture because of the snow dds loading glitch
float4 c4 = snowTest.SampleGrad(ss, float2(scaledUV), xChange, yChange);
// Sample the blend map.
@ -157,7 +162,7 @@ float4 PShader(PixelShaderInput input) : SV_TARGET
color = lerp(color, c3, t.b);
color = lerp(color, c4, t.a);
// Combine all components
// Combine all components, and check if this is water
if (input.PositionOG.y < waterHeight)
{
// Tint the water

View File

@ -64,6 +64,7 @@ float4 PShader(PixelShaderInput input) : SV_TARGET
// Combine all components
float4 color;
// Check if the texture is valid before trying to sample it, some meshes use materials to color them and dont export an image texture file even though the color value is stored with the vertex
// This might be totally my end because ive seen others be able to load the plane with all the correct textures???
if (validTexture == 1)
{
color = saturate((ambientLight + diffuse + specular) * Texture.Sample(ss, input.TexCoord));

Binary file not shown.