Updated TerrainRenderer

* Engine will now use it to render terrain
* Added directional light source
* Added dynamic shadows
* Moved visibility computation to CEngine
* Removed uniform buffers
dev
Tomasz Kapuściński 2021-09-19 20:08:31 +02:00
parent 30d688c1ec
commit 79d4cd9060
8 changed files with 283 additions and 81 deletions

View File

@ -84,11 +84,18 @@ public:
virtual void SetViewMatrix(const glm::mat4& matrix) = 0;
//! Sets model matrix
virtual void SetModelMatrix(const glm::mat4& matrix) = 0;
//! Sets shadow matrix
virtual void SetShadowMatrix(const glm::mat4& matrix) = 0;
//! Sets primary texture, setting texture 0 means using white texture
virtual void SetPrimaryTexture(const Texture& texture) = 0;
//! Sets secondary texture
virtual void SetSecondaryTexture(const Texture& texture) = 0;
//! Sets shadow map
virtual void SetShadowMap(const Texture& texture) = 0;
//! Sets light parameters
virtual void SetLight(const glm::vec4& position, const float& intensity, const glm::vec3& color) = 0;
//! Draws terrain object
virtual void DrawObject(const glm::mat4& matrix, const CVertexBuffer* buffer) = 0;

View File

@ -1927,7 +1927,7 @@ bool CEngine::DetectTriangle(Math::Point mouse, Vertex3D* triangle, int objRank,
}
//! Use only after world transform already set
bool CEngine::IsVisible(int objRank)
bool CEngine::IsVisible(const Math::Matrix& matrix, int objRank)
{
assert(objRank >= 0 && objRank < static_cast<int>(m_objects.size()));
@ -1938,7 +1938,7 @@ bool CEngine::IsVisible(int objRank)
assert(baseObjRank >= 0 && baseObjRank < static_cast<int>(m_baseObjects.size()));
const auto& sphere = m_baseObjects[baseObjRank].boundingSphere;
if (m_device->ComputeSphereVisibility(sphere.pos, sphere.radius) == Gfx::FRUSTUM_PLANE_ALL)
if (ComputeSphereVisibility(matrix, sphere.pos, sphere.radius) == Gfx::FRUSTUM_PLANE_ALL)
{
m_objects[objRank].visible = true;
return true;
@ -1948,6 +1948,87 @@ bool CEngine::IsVisible(int objRank)
return false;
}
int CEngine::ComputeSphereVisibility(const Math::Matrix& m, const Math::Vector& center, float radius)
{
Math::Vector vec[6];
float originPlane[6];
// Left plane
vec[0].x = m.Get(4, 1) + m.Get(1, 1);
vec[0].y = m.Get(4, 2) + m.Get(1, 2);
vec[0].z = m.Get(4, 3) + m.Get(1, 3);
float l1 = vec[0].Length();
vec[0].Normalize();
originPlane[0] = (m.Get(4, 4) + m.Get(1, 4)) / l1;
// Right plane
vec[1].x = m.Get(4, 1) - m.Get(1, 1);
vec[1].y = m.Get(4, 2) - m.Get(1, 2);
vec[1].z = m.Get(4, 3) - m.Get(1, 3);
float l2 = vec[1].Length();
vec[1].Normalize();
originPlane[1] = (m.Get(4, 4) - m.Get(1, 4)) / l2;
// Bottom plane
vec[2].x = m.Get(4, 1) + m.Get(2, 1);
vec[2].y = m.Get(4, 2) + m.Get(2, 2);
vec[2].z = m.Get(4, 3) + m.Get(2, 3);
float l3 = vec[2].Length();
vec[2].Normalize();
originPlane[2] = (m.Get(4, 4) + m.Get(2, 4)) / l3;
// Top plane
vec[3].x = m.Get(4, 1) - m.Get(2, 1);
vec[3].y = m.Get(4, 2) - m.Get(2, 2);
vec[3].z = m.Get(4, 3) - m.Get(2, 3);
float l4 = vec[3].Length();
vec[3].Normalize();
originPlane[3] = (m.Get(4, 4) - m.Get(2, 4)) / l4;
// Front plane
vec[4].x = m.Get(4, 1) + m.Get(3, 1);
vec[4].y = m.Get(4, 2) + m.Get(3, 2);
vec[4].z = m.Get(4, 3) + m.Get(3, 3);
float l5 = vec[4].Length();
vec[4].Normalize();
originPlane[4] = (m.Get(4, 4) + m.Get(3, 4)) / l5;
// Back plane
vec[5].x = m.Get(4, 1) - m.Get(3, 1);
vec[5].y = m.Get(4, 2) - m.Get(3, 2);
vec[5].z = m.Get(4, 3) - m.Get(3, 3);
float l6 = vec[5].Length();
vec[5].Normalize();
originPlane[5] = (m.Get(4, 4) - m.Get(3, 4)) / l6;
int result = 0;
if (InPlane(vec[0], originPlane[0], center, radius))
result |= FRUSTUM_PLANE_LEFT;
if (InPlane(vec[1], originPlane[1], center, radius))
result |= FRUSTUM_PLANE_RIGHT;
if (InPlane(vec[2], originPlane[2], center, radius))
result |= FRUSTUM_PLANE_BOTTOM;
if (InPlane(vec[3], originPlane[3], center, radius))
result |= FRUSTUM_PLANE_TOP;
if (InPlane(vec[4], originPlane[4], center, radius))
result |= FRUSTUM_PLANE_FRONT;
if (InPlane(vec[5], originPlane[5], center, radius))
result |= FRUSTUM_PLANE_BACK;
return result;
}
bool CEngine::InPlane(Math::Vector normal, float originPlane, Math::Vector center, float radius)
{
float distance = originPlane + Math::DotProduct(normal, center);
if (distance < -radius)
return false;
return true;
}
bool CEngine::TransformPoint(Math::Vector& p2D, int objRank, Math::Vector p3D)
{
assert(objRank >= 0 && objRank < static_cast<int>(m_objects.size()));
@ -3301,6 +3382,22 @@ void CEngine::Draw3DScene()
UseShadowMapping(true);
SetState(0);
auto terrainRenderer = m_device->GetTerrainRenderer();
terrainRenderer->Begin();
terrainRenderer->SetProjectionMatrix(m_matProj);
terrainRenderer->SetViewMatrix(m_matView);
terrainRenderer->SetShadowMatrix(m_shadowTextureMat);
terrainRenderer->SetShadowMap(m_shadowMap);
terrainRenderer->SetLight(glm::vec4(1.0, 1.0, -1.0, 0.0), 1.0f, glm::vec3(1.0));
Math::Matrix scale;
scale.Set(3, 3, -1.0f);
auto projectionViewMatrix = Math::MultiplyMatrices(m_matProj, scale);
projectionViewMatrix = Math::MultiplyMatrices(projectionViewMatrix, m_matView);
for (int objRank = 0; objRank < static_cast<int>(m_objects.size()); objRank++)
{
if (! m_objects[objRank].used)
@ -3312,9 +3409,9 @@ void CEngine::Draw3DScene()
if (! m_objects[objRank].drawWorld)
continue;
m_device->SetTransform(TRANSFORM_WORLD, m_objects[objRank].transform);
auto combinedMatrix = Math::MultiplyMatrices(projectionViewMatrix, m_objects[objRank].transform);
if (! IsVisible(objRank))
if (! IsVisible(combinedMatrix, objRank))
continue;
int baseObjRank = m_objects[objRank].baseObjRank;
@ -3331,21 +3428,20 @@ void CEngine::Draw3DScene()
{
EngineBaseObjTexTier& p2 = p1.next[l2];
SetTexture(p2.tex1, 0);
SetTexture(p2.tex2, 1);
terrainRenderer->SetPrimaryTexture(p2.tex1);
terrainRenderer->SetSecondaryTexture(p2.tex2);
for (int l3 = 0; l3 < static_cast<int>( p2.next.size() ); l3++)
{
EngineBaseObjDataTier& p3 = p2.next[l3];
SetMaterial(p3.material);
SetState(p3.state);
DrawObject(p3);
terrainRenderer->DrawObject(m_objects[objRank].transform, p3.buffer);
}
}
}
terrainRenderer->End();
if (!m_qualityShadows)
UseShadowMapping(false);
@ -3373,8 +3469,9 @@ void CEngine::Draw3DScene()
continue;
m_device->SetTransform(TRANSFORM_WORLD, m_objects[objRank].transform);
auto combinedMatrix = Math::MultiplyMatrices(projectionViewMatrix, m_objects[objRank].transform);
if (! IsVisible(objRank))
if (! IsVisible(combinedMatrix, objRank))
continue;
int baseObjRank = m_objects[objRank].baseObjRank;
@ -3435,8 +3532,9 @@ void CEngine::Draw3DScene()
continue;
m_device->SetTransform(TRANSFORM_WORLD, m_objects[objRank].transform);
auto combinedMatrix = Math::MultiplyMatrices(projectionViewMatrix, m_objects[objRank].transform);
if (! IsVisible(objRank))
if (! IsVisible(combinedMatrix, objRank))
continue;
int baseObjRank = m_objects[objRank].baseObjRank;
@ -3930,6 +4028,8 @@ void CEngine::RenderShadowMap()
m_device->SetTexture(1, 0);
m_device->SetTexture(2, 0);
auto projectionViewMatrix = Math::MultiplyMatrices(m_shadowProjMat, m_shadowViewMat);
// render objects into shadow map
for (int objRank = 0; objRank < static_cast<int>(m_objects.size()); objRank++)
{
@ -3956,8 +4056,9 @@ void CEngine::RenderShadowMap()
}
m_device->SetTransform(TRANSFORM_WORLD, m_objects[objRank].transform);
auto combinedMatrix = Math::MultiplyMatrices(projectionViewMatrix, m_objects[objRank].transform);
if (!IsVisible(objRank))
if (!IsVisible(combinedMatrix, objRank))
continue;
int baseObjRank = m_objects[objRank].baseObjRank;
@ -4170,6 +4271,8 @@ void CEngine::DrawInterface()
m_device->SetTransform(TRANSFORM_VIEW, m_matView);
auto projectionViewMatrix = Math::MultiplyMatrices(m_matProj, m_matView);
for (int objRank = 0; objRank < static_cast<int>(m_objects.size()); objRank++)
{
if (! m_objects[objRank].used)
@ -4182,8 +4285,9 @@ void CEngine::DrawInterface()
continue;
m_device->SetTransform(TRANSFORM_WORLD, m_objects[objRank].transform);
auto combinedMatrix = Math::MultiplyMatrices(projectionViewMatrix, m_objects[objRank].transform);
if (! IsVisible(objRank))
if (! IsVisible(combinedMatrix, objRank))
continue;
int baseObjRank = m_objects[objRank].baseObjRank;

View File

@ -1256,7 +1256,11 @@ protected:
Texture CreateTexture(const std::string &texName, const TextureCreateParams &params, CImage* image = nullptr);
//! Tests whether the given object is visible
bool IsVisible(int objRank);
bool IsVisible(const Math::Matrix& matrix, int objRank);
int ComputeSphereVisibility(const Math::Matrix& m, const Math::Vector& center, float radius);
bool InPlane(Math::Vector normal, float originPlane, Math::Vector center, float radius);
//! Detects whether an object is affected by the mouse
bool DetectBBox(int objRank, Math::Point mouse);

View File

@ -260,20 +260,15 @@ CGL33TerrainRenderer::CGL33TerrainRenderer(CGL33Device* device)
// Setup uniforms
auto identity = glm::identity<glm::mat4>();
m_uniforms.projectionMatrix = identity;
m_uniforms.viewMatrix = identity;
m_uniforms.modelMatrix = identity;
glGenBuffers(1, &m_uniformBuffer);
glBindBuffer(GL_COPY_WRITE_BUFFER, m_uniformBuffer);
glBufferData(GL_COPY_WRITE_BUFFER, sizeof(Uniforms), &m_uniforms, GL_STREAM_DRAW);
glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
// Bind uniform block to uniform buffer binding
GLuint blockIndex = glGetUniformBlockIndex(m_program, "Uniforms");
glBindBufferBase(GL_UNIFORM_BUFFER, 0, m_uniformBuffer);
glUniformBlockBinding(m_program, blockIndex, 0);
uni_projectionMatrix = glGetUniformLocation(m_program, "uni_ProjectionMatrix");
uni_viewMatrix = glGetUniformLocation(m_program, "uni_ViewMatrix");
uni_cameraMatrix = glGetUniformLocation(m_program, "uni_CameraMatrix");
uni_shadowMatrix = glGetUniformLocation(m_program, "uni_ShadowMatrix");
uni_modelMatrix = glGetUniformLocation(m_program, "uni_ModelMatrix");
uni_normalMatrix = glGetUniformLocation(m_program, "uni_NormalMatrix");
uni_lightPosition = glGetUniformLocation(m_program, "uni_LightPosition");
uni_lightIntensity = glGetUniformLocation(m_program, "uni_LightIntensity");
uni_lightColor = glGetUniformLocation(m_program, "uni_LightColor");
// Set texture units to 10th and 11th
auto texture = glGetUniformLocation(m_program, "uni_PrimaryTexture");
@ -282,6 +277,9 @@ CGL33TerrainRenderer::CGL33TerrainRenderer(CGL33Device* device)
texture = glGetUniformLocation(m_program, "uni_SecondaryTexture");
glUniform1i(texture, 11);
texture = glGetUniformLocation(m_program, "uni_ShadowMap");
glUniform1i(texture, 12);
// White texture
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &m_whiteTexture);
@ -319,8 +317,7 @@ void CGL33TerrainRenderer::End()
void CGL33TerrainRenderer::SetProjectionMatrix(const glm::mat4& matrix)
{
m_uniforms.projectionMatrix = matrix;
m_uniformsDirty = true;
glUniformMatrix4fv(uni_projectionMatrix, 1, GL_FALSE, value_ptr(matrix));
}
void CGL33TerrainRenderer::SetViewMatrix(const glm::mat4& matrix)
@ -328,14 +325,24 @@ void CGL33TerrainRenderer::SetViewMatrix(const glm::mat4& matrix)
glm::mat4 scale(1.0f);
scale[2][2] = -1.0f;
m_uniforms.viewMatrix = scale * matrix;
m_uniformsDirty = true;
auto viewMatrix = scale * matrix;
auto cameraMatrix = glm::inverse(viewMatrix);
glUniformMatrix4fv(uni_viewMatrix, 1, GL_FALSE, value_ptr(viewMatrix));
glUniformMatrix4fv(uni_cameraMatrix, 1, GL_FALSE, value_ptr(cameraMatrix));
}
void CGL33TerrainRenderer::SetModelMatrix(const glm::mat4& matrix)
{
m_uniforms.modelMatrix = matrix;
m_uniformsDirty = true;
auto normalMatrix = glm::transpose(glm::inverse(glm::mat3(matrix)));
glUniformMatrix4fv(uni_modelMatrix, 1, GL_FALSE, value_ptr(matrix));
glUniformMatrix3fv(uni_normalMatrix, 1, GL_FALSE, value_ptr(normalMatrix));
}
void CGL33TerrainRenderer::SetShadowMatrix(const glm::mat4& matrix)
{
glUniformMatrix4fv(uni_shadowMatrix, 1, GL_FALSE, value_ptr(matrix));
}
void CGL33TerrainRenderer::SetPrimaryTexture(const Texture& texture)
@ -366,6 +373,27 @@ void CGL33TerrainRenderer::SetSecondaryTexture(const Texture& texture)
glBindTexture(GL_TEXTURE_2D, texture.id);
}
void CGL33TerrainRenderer::SetShadowMap(const Texture& texture)
{
if (m_shadowMap == texture.id) return;
m_shadowMap = texture.id;
glActiveTexture(GL_TEXTURE12);
if (texture.id == 0)
glBindTexture(GL_TEXTURE_2D, m_whiteTexture);
else
glBindTexture(GL_TEXTURE_2D, texture.id);
}
void CGL33TerrainRenderer::SetLight(const glm::vec4& position, const float& intensity, const glm::vec3& color)
{
glUniform4fv(uni_lightPosition, 1, glm::value_ptr(position));
glUniform1f(uni_lightIntensity, intensity);
glUniform3fv(uni_lightColor, 1, glm::value_ptr(color));
}
void CGL33TerrainRenderer::DrawObject(const glm::mat4& matrix, const CVertexBuffer* buffer)
{
auto b = dynamic_cast<const CGL33VertexBuffer*>(buffer);
@ -377,18 +405,6 @@ void CGL33TerrainRenderer::DrawObject(const glm::mat4& matrix, const CVertexBuff
}
SetModelMatrix(matrix);
if (m_uniformsDirty)
{
glBindBuffer(GL_COPY_WRITE_BUFFER, m_uniformBuffer);
glBufferData(GL_COPY_WRITE_BUFFER, sizeof(Uniforms), nullptr, GL_STREAM_DRAW);
glBufferSubData(GL_COPY_WRITE_BUFFER, 0, sizeof(Uniforms), &m_uniforms);
glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
m_uniformsDirty = false;
}
glBindBufferBase(GL_UNIFORM_BUFFER, 0, m_uniformBuffer);
glBindVertexArray(b->GetVAO());
glDrawArrays(TranslateGfxPrimitive(b->GetType()), 0, b->Size());

View File

@ -108,11 +108,18 @@ public:
virtual void SetViewMatrix(const glm::mat4& matrix) override;
//! Sets model matrix
virtual void SetModelMatrix(const glm::mat4& matrix) override;
//! Sets shadow matrix
virtual void SetShadowMatrix(const glm::mat4& matrix) override;
//! Sets primary texture, setting texture 0 means using white texture
virtual void SetPrimaryTexture(const Texture& texture) override;
//! Sets secondary texture
virtual void SetSecondaryTexture(const Texture& texture) override;
//! Sets shadow map
virtual void SetShadowMap(const Texture& texture) override;
//! Sets light parameters
virtual void SetLight(const glm::vec4& position, const float& intensity, const glm::vec3& color) override;
//! Draws terrain object
virtual void DrawObject(const glm::mat4& matrix, const CVertexBuffer* buffer) override;
@ -123,19 +130,15 @@ private:
CGL33Device* const m_device;
// Uniform data
struct Uniforms
{
glm::mat4 projectionMatrix;
glm::mat4 viewMatrix;
glm::mat4 modelMatrix;
};
Uniforms m_uniforms = {};
// true means uniforms need to be updated
bool m_uniformsDirty = false;
GLuint m_uniformBuffer = 0;
GLint uni_projectionMatrix;
GLint uni_viewMatrix;
GLint uni_cameraMatrix;
GLint uni_shadowMatrix;
GLint uni_modelMatrix;
GLint uni_normalMatrix;
GLint uni_lightPosition;
GLint uni_lightIntensity;
GLint uni_lightColor;
// Shader program
GLuint m_program = 0;
@ -146,6 +149,8 @@ private:
GLuint m_primaryTexture = 0;
// Currently bound secondary texture
GLuint m_secondaryTexture = 0;
// Currently bound shadow map
GLuint m_shadowMap = 0;
};
} // namespace Gfx

View File

@ -20,8 +20,16 @@
// FRAGMENT SHADER - TERRAIN RENDERER
#version 330 core
uniform mat4 uni_CameraMatrix;
uniform vec4 uni_LightPosition;
uniform float uni_LightIntensity;
uniform vec3 uni_LightColor;
uniform sampler2D uni_PrimaryTexture;
uniform sampler2D uni_SecondaryTexture;
uniform sampler2DShadow uni_ShadowMap;
const float PI = 3.1415926;
in VertexData
{
@ -30,19 +38,80 @@ in VertexData
vec2 TexCoord1;
vec3 Normal;
vec4 ShadowCoord;
vec4 LightColor;
float Distance;
vec3 CameraDirection;
vec3 Position;
} data;
out vec4 out_FragColor;
vec3 schlickFresnel(float LdH, float metalness, vec3 color)
{
vec3 f = mix(vec3(0.04), color, metalness);
return f + (1.0 - f) * pow(1.0 - LdH, 5.0);
}
float geomSmith(float dotProd, float roughness)
{
float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
float denom = dotProd * (1 - k) + k;
return 1.0 / denom;
}
float ggxDistribution(float NdH, float roughness)
{
float alpha2 = roughness * roughness * roughness * roughness;
float d = (NdH * NdH) * (alpha2 - 1) + 1.0;
return alpha2 / (PI * d * d);
}
vec3 PBR(vec3 position, vec3 color, vec3 normal, float roughness, float metalness)
{
vec3 diffuseBrdf = mix(color, vec3(0.0), metalness);
vec3 light = normalize(uni_LightPosition.xyz);
float lightIntensity = 1.0;
vec3 view = normalize(uni_CameraMatrix[3].xyz - position);
vec3 halfway = normalize(view + light);
float NdH = dot(normal, halfway);
float LdH = dot(light, halfway);
float NdL = max(dot(normal, light), 0.0);
float NdV = dot(normal, view);
vec3 specBrdf = 0.25 * ggxDistribution(NdH, roughness)
* schlickFresnel(LdH, metalness, color)
* geomSmith(NdL, roughness)
* geomSmith(NdV, roughness);
return (diffuseBrdf + PI * specBrdf) * lightIntensity * uni_LightColor * NdL;
}
void main()
{
vec4 color = data.Color;
vec3 albedo = data.Color.rgb;
color = color * texture(uni_PrimaryTexture, data.TexCoord0);
color = color * texture(uni_SecondaryTexture, data.TexCoord1);
albedo *= texture(uni_PrimaryTexture, data.TexCoord0).rgb;
albedo *= texture(uni_SecondaryTexture, data.TexCoord1).rgb;
out_FragColor = color;
float roughness = 0.7;
float metalness = 0.0;
float value = texture(uni_ShadowMap, data.ShadowCoord.xyz);
value += textureOffset(uni_ShadowMap, data.ShadowCoord.xyz, ivec2( 1, 0));
value += textureOffset(uni_ShadowMap, data.ShadowCoord.xyz, ivec2(-1, 0));
value += textureOffset(uni_ShadowMap, data.ShadowCoord.xyz, ivec2( 0, 1));
value += textureOffset(uni_ShadowMap, data.ShadowCoord.xyz, ivec2( 0,-1));
float shadow = value * (1.0f / 5.0f);
shadow = mix(0.5, 1.0, shadow);
vec3 lighting = PBR(data.Position, albedo, data.Normal, roughness, metalness);
vec3 skyColor = vec3(1.0);
float skyIntensity = 0.10;
vec3 color = lighting * shadow + albedo * skyColor * skyIntensity;
out_FragColor = vec4(color, 1.0);
}

View File

@ -20,12 +20,11 @@
// VERTEX SHADER - TERRAIN RENDERER
#version 330 core
uniform Uniforms
{
mat4 uni_ProjectionMatrix;
mat4 uni_ViewMatrix;
mat4 uni_ModelMatrix;
};
uniform mat4 uni_ProjectionMatrix;
uniform mat4 uni_ViewMatrix;
uniform mat4 uni_ModelMatrix;
uniform mat4 uni_ShadowMatrix;
uniform mat3 uni_NormalMatrix;
layout(location = 0) in vec4 in_VertexCoord;
layout(location = 1) in vec3 in_Normal;
@ -40,9 +39,7 @@ out VertexData
vec2 TexCoord1;
vec3 Normal;
vec4 ShadowCoord;
vec4 LightColor;
float Distance;
vec3 CameraDirection;
vec3 Position;
} data;
void main()
@ -50,13 +47,13 @@ void main()
vec4 position = uni_ModelMatrix * in_VertexCoord;
vec4 eyeSpace = uni_ViewMatrix * position;
gl_Position = uni_ProjectionMatrix * eyeSpace;
//vec4 shadowCoord = uni_ShadowMatrix * position;
vec4 shadowCoord = uni_ShadowMatrix * position;
data.Color = in_Color;
data.TexCoord0 = in_TexCoord0;
data.TexCoord1 = in_TexCoord1;
data.Normal = in_Normal;//normalize((uni_NormalMatrix * vec4(in_Normal, 0.0f)).xyz);
//data.ShadowCoord = vec4(shadowCoord.xyz / shadowCoord.w, 1.0f);
data.Normal = mat3(uni_NormalMatrix) * in_Normal;
data.ShadowCoord = vec4(shadowCoord.xyz / shadowCoord.w, 1.0f);
//data.Distance = abs(eyeSpace.z);
//data.CameraDirection = uni_CameraPosition - position.xyz;
data.Position = position.xyz;
}

View File

@ -145,7 +145,7 @@ struct Matrix
* \param col column (1 to 4)
* \returns value
*/
float Get(int row, int col)
float Get(int row, int col) const
{
return m[(col-1)*4+(row-1)];
}