I CAN SEE

This commit is contained in:
Masashi 2025-12-18 21:32:05 +05:30
commit 75ee5a6da8
5 changed files with 344 additions and 106 deletions

BIN
game/client Executable file

Binary file not shown.

View file

@ -53,7 +53,40 @@ typedef struct {
float shadowIntensity; // How dark shadows are (0-1) float shadowIntensity; // How dark shadows are (0-1)
} DirectionalLight; } DirectionalLight;
// ============== SIMPLEX NOISE ============== // ============== TERRAIN SHADER ==============
typedef struct {
Shader shader;
int locViewPos;
int locLightDir;
int locLightColor;
int locLightIntensity;
int locAmbientIntensity;
int locTerrainColor;
} TerrainShader;
static TerrainShader load_terrain_shader(void) {
TerrainShader ts = {0};
ts.shader = LoadShader("terrain.vs", "terrain.fs");
// Get uniform locations
ts.locViewPos = GetShaderLocation(ts.shader, "viewPos");
ts.locLightDir = GetShaderLocation(ts.shader, "lightDir");
ts.locLightColor = GetShaderLocation(ts.shader, "lightColor");
ts.locLightIntensity = GetShaderLocation(ts.shader, "lightIntensity");
ts.locAmbientIntensity = GetShaderLocation(ts.shader, "ambientIntensity");
ts.locTerrainColor = GetShaderLocation(ts.shader, "terrainColor");
return ts;
}
static void unload_terrain_shader(TerrainShader *ts) {
if (ts && ts->shader.id != 0) {
UnloadShader(ts->shader);
ts->shader.id = 0;
}
}
// ============== END TERRAIN SHADER ==============
static const uint8_t perm[512] = { static const uint8_t perm[512] = {
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7,
225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190,
@ -205,60 +238,40 @@ static Mesh generate_terrain_mesh(void) {
} }
} }
// Calculate normals // Calculate normals using tangent plane approximation from terrain gradients
for (int i = 0; i < vertexCount; i++) { // This preserves terrain curvature better than simple triangle averaging
mesh.normals[i * 3 + 0] = 0; for (int z = 0; z < size; z++) {
mesh.normals[i * 3 + 1] = 1; for (int x = 0; x < size; x++) {
mesh.normals[i * 3 + 2] = 0; int idx = z * size + x;
} float wx = ((float)x - size / 2.0f) * TERRAIN_SCALE;
float wz = ((float)z - size / 2.0f) * TERRAIN_SCALE;
for (int t = 0; t < triangleCount; t++) { // Sample height gradients to compute terrain normal
int i0 = mesh.indices[t * 3 + 0]; // Use neighboring vertices for finite difference approximation
int i1 = mesh.indices[t * 3 + 1]; float h_right = (x + 1 < size) ? mesh.vertices[(z * size + (x + 1)) * 3 + 1] : mesh.vertices[idx * 3 + 1];
int i2 = mesh.indices[t * 3 + 2]; float h_left = (x - 1 >= 0) ? mesh.vertices[(z * size + (x - 1)) * 3 + 1] : mesh.vertices[idx * 3 + 1];
float h_down = (z + 1 < size) ? mesh.vertices[((z + 1) * size + x) * 3 + 1] : mesh.vertices[idx * 3 + 1];
float h_up = (z - 1 >= 0) ? mesh.vertices[((z - 1) * size + x) * 3 + 1] : mesh.vertices[idx * 3 + 1];
float v0x = mesh.vertices[i0 * 3 + 0]; // Compute finite differences
float v0y = mesh.vertices[i0 * 3 + 1]; float dh_dx = (h_right - h_left) / (2.0f * TERRAIN_SCALE);
float v0z = mesh.vertices[i0 * 3 + 2]; float dh_dz = (h_down - h_up) / (2.0f * TERRAIN_SCALE);
float v1x = mesh.vertices[i1 * 3 + 0]; // Normal from height field: (-dh/dx, 1, -dh/dz) then normalized
float v1y = mesh.vertices[i1 * 3 + 1]; float nx = -dh_dx;
float v1z = mesh.vertices[i1 * 3 + 2]; float ny = 1.0f;
float nz = -dh_dz;
float v2x = mesh.vertices[i2 * 3 + 0];
float v2y = mesh.vertices[i2 * 3 + 1];
float v2z = mesh.vertices[i2 * 3 + 2];
float e1x = v1x - v0x, e1y = v1y - v0y, e1z = v1z - v0z;
float e2x = v2x - v0x, e2y = v2y - v0y, e2z = v2z - v0z;
float nx = e1y * e2z - e1z * e2y;
float ny = e1z * e2x - e1x * e2z;
float nz = e1x * e2y - e1y * e2x;
mesh.normals[i0 * 3 + 0] += nx;
mesh.normals[i0 * 3 + 1] += ny;
mesh.normals[i0 * 3 + 2] += nz;
mesh.normals[i1 * 3 + 0] += nx;
mesh.normals[i1 * 3 + 1] += ny;
mesh.normals[i1 * 3 + 2] += nz;
mesh.normals[i2 * 3 + 0] += nx;
mesh.normals[i2 * 3 + 1] += ny;
mesh.normals[i2 * 3 + 2] += nz;
}
// Normalize normals
for (int i = 0; i < vertexCount; i++) {
float nx = mesh.normals[i * 3 + 0];
float ny = mesh.normals[i * 3 + 1];
float nz = mesh.normals[i * 3 + 2];
float len = sqrtf(nx * nx + ny * ny + nz * nz); float len = sqrtf(nx * nx + ny * ny + nz * nz);
if (len > 0.0001f) { if (len > 0.0001f) {
mesh.normals[i * 3 + 0] = nx / len; mesh.normals[idx * 3 + 0] = nx / len;
mesh.normals[i * 3 + 1] = ny / len; mesh.normals[idx * 3 + 1] = ny / len;
mesh.normals[i * 3 + 2] = nz / len; mesh.normals[idx * 3 + 2] = nz / len;
} else {
mesh.normals[idx * 3 + 0] = 0;
mesh.normals[idx * 3 + 1] = 1;
mesh.normals[idx * 3 + 2] = 0;
}
} }
} }
@ -369,7 +382,112 @@ static Vector3 v3_normalize(Vector3 v) {
return (Vector3){0, 1, 0}; return (Vector3){0, 1, 0};
return (Vector3){v.x / len, v.y / len, v.z / len}; return (Vector3){v.x / len, v.y / len, v.z / len};
} }
// ============== END LIGHTING HELPERS ==============
// ============== VECTOR3 HELPERS ==============
static Vector3 v3(float x, float y, float z) {
Vector3 v = {x, y, z};
return v;
}
static Vector3 v3_add(Vector3 a, Vector3 b) {
return v3(a.x + b.x, a.y + b.y, a.z + b.z);
}
static Vector3 v3_sub(Vector3 a, Vector3 b) {
return v3(a.x - b.x, a.y - b.y, a.z - b.z);
}
static float v3_dot(Vector3 a, Vector3 b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
static float v3_len(Vector3 a) {
return sqrtf(a.x * a.x + a.y * a.y + a.z * a.z);
}
static Vector3 v3_norm(Vector3 a) {
float l = v3_len(a);
if (l <= 1e-6f)
return v3(0, 0, 1);
return v3(a.x / l, a.y / l, a.z / l);
}
// ============== END VECTOR3 HELPERS ==============
// ============== ENHANCED LIGHTING & AMBIENT OCCLUSION ==============
// calculate_ambient_occlusion: Estimates how occluded a point is based on terrain curvature
// Samples heights in cardinal directions and computes horizon angle to terrain
// Returns occlusion factor from 0 (fully occluded) to 1 (fully lit)
static float calculate_ambient_occlusion(Vector3 worldPos, Vector3 normal) {
// AO based on terrain curvature: check if terrain rises around the point
float sampleRadius = 3.0f;
float aoAccum = 0.0f;
int numSamples = 8;
float centerHeight = worldPos.y;
// Sample 8 directions around the point
for (int i = 0; i < numSamples; i++) {
float angle = (2.0f * 3.14159265f * (float)i) / (float)numSamples;
float sx = worldPos.x + cosf(angle) * sampleRadius;
float sz = worldPos.z + sinf(angle) * sampleRadius;
float sh = get_terrain_height(sx, sz);
// Check if terrain is higher relative to surface normal
// Higher terrain in shadow-casting areas reduces occlusion
float heightDiff = sh - centerHeight;
if (heightDiff > 0.1f) {
// Terrain is higher, contributes to shadow
float aoAmount = fminf(1.0f, heightDiff / 2.0f);
aoAccum += aoAmount;
}
}
float aoFactor = 1.0f - (aoAccum / (float)numSamples) * 0.6f; // 60% max occlusion
return fmaxf(0.2f, aoFactor); // Min 20% brightness
}
// apply_phong_lighting_per_pixel: Advanced Phong lighting with per-pixel normals
// Includes diffuse, specular highlight, and ambient occlusion for geometry detail
static Color apply_phong_lighting_per_pixel(Color baseColor, Vector3 normal,
Vector3 worldPos, Vector3 camPos,
DirectionalLight light) {
// Normalize inputs
Vector3 lightDir = v3_normalize(light.direction);
Vector3 normal_norm = v3_normalize(normal);
// Compute view direction (from surface to camera)
Vector3 viewDir = v3_normalize(v3_sub(camPos, worldPos));
// Diffuse component: Lambertian shading
float diffuse = fmaxf(0.0f, -v3_dot(lightDir, normal_norm));
// Specular component: Blinn-Phong specular highlight
Vector3 halfVec = v3_normalize(v3_add(v3_norm((Vector3){-lightDir.x, -lightDir.y, -lightDir.z}), viewDir));
float specular = powf(fmaxf(0.0f, v3_dot(halfVec, normal_norm)), 32.0f) * 0.5f;
// Ambient occlusion from terrain geometry
float ao = calculate_ambient_occlusion(worldPos, normal_norm);
// Shadow based on height (distant higher terrain casts softer shadows)
float shadowFactor = calculate_shadow_factor(worldPos, light.direction, 10.0f);
// Combine lighting components
float brightness = light.ambientIntensity * ao;
brightness += diffuse * light.intensity * shadowFactor * (1.0f - light.ambientIntensity) * ao;
brightness += specular * light.intensity * shadowFactor * 0.6f; // Specular less affected by AO
brightness = fminf(1.0f, brightness);
// Apply brightness to base color
int r = (int)(baseColor.r * brightness);
int g = (int)(baseColor.g * brightness);
int b = (int)(baseColor.b * brightness);
// Clamp to valid RGB range
if (r > 255) r = 255;
if (g > 255) g = 255;
if (b > 255) b = 255;
return (Color){r, g, b, baseColor.a};
}
// ============== END ENHANCED LIGHTING & AMBIENT OCCLUSION ==============
static void set_nonblocking(int sock) { static void set_nonblocking(int sock) {
#ifdef _WIN32 #ifdef _WIN32
@ -465,29 +583,6 @@ typedef struct WorldItem {
Vector3 pos; Vector3 pos;
} WorldItem; } WorldItem;
static Vector3 v3(float x, float y, float z) {
Vector3 v = {x, y, z};
return v;
}
static Vector3 v3_add(Vector3 a, Vector3 b) {
return v3(a.x + b.x, a.y + b.y, a.z + b.z);
}
static Vector3 v3_sub(Vector3 a, Vector3 b) {
return v3(a.x - b.x, a.y - b.y, a.z - b.z);
}
static float v3_dot(Vector3 a, Vector3 b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
static float v3_len(Vector3 a) {
return sqrtf(a.x * a.x + a.y * a.y + a.z * a.z);
}
static Vector3 v3_norm(Vector3 a) {
float l = v3_len(a);
if (l <= 1e-6f)
return v3(0, 0, 1);
return v3(a.x / l, a.y / l, a.z / l);
}
static const char *ItemName(uint8_t t) { static const char *ItemName(uint8_t t) {
switch (t) { switch (t) {
case ITEM_MEDKIT: case ITEM_MEDKIT:
@ -565,6 +660,17 @@ int main(void) {
dirLight.shadowBias = 0.005f; dirLight.shadowBias = 0.005f;
dirLight.shadowIntensity = 0.4f; dirLight.shadowIntensity = 0.4f;
// Load terrain shader
TerrainShader terrainShader = load_terrain_shader();
if (terrainShader.shader.id == 0) {
fprintf(stderr, "Warning: Failed to load terrain shader, using default rendering\n");
// Fallback: Make terrain very bright red to show shader failed
terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = (Color){255, 100, 100, 255};
} else {
terrainModel.materials[0].shader = terrainShader.shader;
fprintf(stderr, "Shader loaded successfully!\n");
}
int sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); int sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock < 0) { if (sock < 0) {
perror("socket"); perror("socket");
@ -591,6 +697,8 @@ int main(void) {
int shotsInBurst = 0; int shotsInBurst = 0;
float burstResetTimer = 0.0f; float burstResetTimer = 0.0f;
const float pistolFireRate = 4.0f, rifleFireRate = 12.0f; const float pistolFireRate = 4.0f, rifleFireRate = 12.0f;
const float recoilReturn = 18.0f, crossReturn = 14.0f; const float recoilReturn = 18.0f, crossReturn = 14.0f;
const float pistolKickPitch = 0.010f, pistolKickYaw = 0.004f; const float pistolKickPitch = 0.010f, pistolKickYaw = 0.004f;
@ -738,6 +846,8 @@ int main(void) {
if (IsKeyPressed(KEY_SPACE)) if (IsKeyPressed(KEY_SPACE))
buttons |= BTN_JUMP; buttons |= BTN_JUMP;
if (myId != 255) { if (myId != 255) {
MsgInput in = {0}; MsgInput in = {0};
in.type = MSG_INPUT; in.type = MSG_INPUT;
@ -796,20 +906,30 @@ int main(void) {
BeginMode3D(cam); BeginMode3D(cam);
// Calculate terrain normal at camera position for lighting // Set up shader uniforms for terrain rendering
Vector3 terrainNormal = v3(0, 1, 0); // Default up normal if (terrainShader.shader.id != 0) {
// Convert light direction to shader format (should be pointing TO the light)
float lightDirArray[3] = {-dirLight.direction.x, -dirLight.direction.y, -dirLight.direction.z};
float lightColorArray[3] = {dirLight.color.x, dirLight.color.y, dirLight.color.z};
float viewPosArray[3] = {camPos.x, camPos.y, camPos.z};
// Terrain color in 0-1 range (80, 140, 70) / 255
float terrainColorArray[3] = {80.0f/255.0f, 140.0f/255.0f, 70.0f/255.0f};
// Draw smooth terrain with directional lighting + shadows // Debug mode: 0=normal lighting, 1=show normals as colors, 2=show AO only
// For Raylib, we apply lighting by adjusting material colors
Color terrainBaseColor = (Color){80, 140, 70, 255};
Color litTerrainColor = apply_lighting_with_shadows(
terrainBaseColor, terrainNormal, v3(0, 2, 0), dirLight);
terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = SetShaderValue(terrainShader.shader, terrainShader.locViewPos, viewPosArray, SHADER_UNIFORM_VEC3);
litTerrainColor; SetShaderValue(terrainShader.shader, terrainShader.locLightDir, lightDirArray, SHADER_UNIFORM_VEC3);
SetShaderValue(terrainShader.shader, terrainShader.locLightColor, lightColorArray, SHADER_UNIFORM_VEC3);
SetShaderValue(terrainShader.shader, terrainShader.locLightIntensity, &dirLight.intensity, SHADER_UNIFORM_FLOAT);
SetShaderValue(terrainShader.shader, terrainShader.locAmbientIntensity, &dirLight.ambientIntensity, SHADER_UNIFORM_FLOAT);
SetShaderValue(terrainShader.shader, terrainShader.locTerrainColor, terrainColorArray, SHADER_UNIFORM_VEC3);
}
// Draw terrain with shader
terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = (Color){80, 140, 70, 255};
DrawModel(terrainModel, (Vector3){0, 0, 0}, 1.0f, WHITE); DrawModel(terrainModel, (Vector3){0, 0, 0}, 1.0f, WHITE);
// Items with lighting and shadows // Items with basic lighting (no shader for now to keep it simple)
for (int i = 0; i < MAX_ITEMS; i++) { for (int i = 0; i < MAX_ITEMS; i++) {
if (!wi[i].present) if (!wi[i].present)
continue; continue;
@ -821,13 +941,13 @@ int main(void) {
if (wi[i].type == ITEM_AMMO_RIFLE) if (wi[i].type == ITEM_AMMO_RIFLE)
ic = (Color){255, 180, 120, 255}; ic = (Color){255, 180, 120, 255};
// Apply lighting with shadows to items // Apply basic lighting to items
Color litColor = Vector3 itemNormal = v3(0, 1, 0);
apply_lighting_with_shadows(ic, terrainNormal, wi[i].pos, dirLight); Color litColor = apply_lighting_with_shadows(ic, itemNormal, wi[i].pos, dirLight);
DrawSphere(wi[i].pos, 0.3f, litColor); DrawSphere(wi[i].pos, 0.3f, litColor);
} }
// Players with lighting and shadows // Players with basic lighting
for (int i = 0; i < MAX_PLAYERS; i++) { for (int i = 0; i < MAX_PLAYERS; i++) {
if (!rp[i].present) if (!rp[i].present)
continue; continue;
@ -837,9 +957,9 @@ int main(void) {
if (!rp[i].alive) if (!rp[i].alive)
c = (Color){120, 120, 120, 255}; c = (Color){120, 120, 120, 255};
// Apply lighting with shadows to player models // Apply basic lighting to players
Color litPlayerColor = Vector3 playerNormal = v3(0, 1, 0);
apply_lighting_with_shadows(c, terrainNormal, p, dirLight); Color litPlayerColor = apply_lighting_with_shadows(c, playerNormal, p, dirLight);
DrawCapsule(v3(p.x, p.y - 0.5f, p.z), v3(p.x, p.y + 0.5f, p.z), 0.35f, 8, DrawCapsule(v3(p.x, p.y - 0.5f, p.z), v3(p.x, p.y + 0.5f, p.z), 0.35f, 8,
8, litPlayerColor); 8, litPlayerColor);
} }
@ -923,6 +1043,7 @@ int main(void) {
} }
UnloadModel(terrainModel); UnloadModel(terrainModel);
unload_terrain_shader(&terrainShader);
CloseWindow(); CloseWindow();
CLOSESOCK(sock); CLOSESOCK(sock);
#ifdef _WIN32 #ifdef _WIN32

BIN
game/server Executable file

Binary file not shown.

86
game/terrain.fs Normal file
View file

@ -0,0 +1,86 @@
#version 330
// Input from vertex shader
in vec3 fragPosition;
in vec3 fragNormal;
in vec2 fragTexCoord;
// Uniforms
uniform sampler2D texture0;
uniform vec3 viewPos;
uniform vec3 lightDir;
uniform vec3 lightColor;
uniform float lightIntensity;
uniform float ambientIntensity;
uniform vec3 terrainColor;
// Output
out vec4 finalColor;
// Simplex noise for AO calculation
float hash(float n) {
return fract(sin(n) * 43758.5453123);
}
float noise(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
float n = i.x + i.y * 157.0 + i.z * 113.0;
return mix(
mix(mix(hash(n), hash(n + 1.0), f.x),
mix(hash(n + 157.0), hash(n + 158.0), f.x), f.y),
mix(mix(hash(n + 113.0), hash(n + 114.0), f.x),
mix(hash(n + 270.0), hash(n + 271.0), f.x), f.y),
f.z
);
}
// Calculate ambient occlusion with more contrast
float calculateAO(vec3 position, vec3 normal) {
// Sample 8 directions around the surface
float ao = 0.0;
float radius = 2.5;
int samples = 8;
for (int i = 0; i < samples; i++) {
float angle = 6.28318530718 * float(i) / float(samples);
vec3 offset = vec3(cos(angle) * radius, noise(position * 0.5 + float(i)), sin(angle) * radius);
// Sample height variation (approximated via noise)
float heightVariation = noise((position + offset) * 0.3);
// Calculate occlusion based on height and normal
float dotProduct = max(0.0, dot(normal, normalize(offset)));
ao += mix(0.0, dotProduct * heightVariation, 0.7);
}
// Increased contrast: 0.8 multiplier instead of 0.6
ao = 1.0 - (ao / float(samples)) * 0.8;
// Min 0.1 (darker shadows) instead of 0.2
return max(0.1, ao);
}
void main()
{
// Normalize interpolated normal
vec3 norm = normalize(fragNormal);
// Use normal Y component to create geometry-revealing green shading
// Higher Y normal (upward-facing) = brighter green (peaks/flat areas)
// Lower Y normal (sloped) = darker green (slopes/valleys)
float brightness = (norm.y + 1.0) * 0.5; // Map -1..1 to 0..1
// Increase contrast: darker minimum, brighter maximum
// Old range: 0.4 to 1.0 (0.6 total range)
// New range: 0.2 to 1.2 (1.0 total range) - much more contrast
float contrastMultiplier = 0.2 + brightness * 1.0;
// Create base terrain color modulated by geometry
vec3 baseColor = terrainColor;
vec3 finalColorRGB = baseColor * contrastMultiplier;
finalColor = vec4(finalColorRGB, 1.0);
}

31
game/terrain.vs Normal file
View file

@ -0,0 +1,31 @@
#version 330
// Input vertex attributes
in vec3 vertexPosition;
in vec3 vertexNormal;
in vec2 vertexTexCoord;
// Uniforms
uniform mat4 mvp;
uniform mat4 matModel;
uniform mat4 matNormal;
// Output to fragment shader
out vec3 fragPosition;
out vec3 fragNormal;
out vec2 fragTexCoord;
void main()
{
// Transform vertex position to world space
fragPosition = vec3(matModel * vec4(vertexPosition, 1.0));
// Transform normal to world space
fragNormal = normalize(vec3(matNormal * vec4(vertexNormal, 0.0)));
// Pass texture coordinates
fragTexCoord = vertexTexCoord;
// Transform to clip space
gl_Position = mvp * vec4(vertexPosition, 1.0);
}