diff --git a/examples/models/models_loading_vox.c b/examples/models/models_loading_vox.c index b830c492d..1a2721a63 100644 --- a/examples/models/models_loading_vox.c +++ b/examples/models/models_loading_vox.c @@ -17,117 +17,189 @@ #include "raymath.h" // Required for: MatrixTranslate() -#define MAX_VOX_FILES 3 +#define MAX_VOX_FILES 4 + +#define RLIGHTS_IMPLEMENTATION +#include "rlights.h" + +#if defined(PLATFORM_DESKTOP) +#define GLSL_VERSION 330 +#else // PLATFORM_ANDROID, PLATFORM_WEB +#define GLSL_VERSION 100 +#endif //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ int main(void) { - // Initialization - //-------------------------------------------------------------------------------------- - const int screenWidth = 800; - const int screenHeight = 450; + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; - const char *voxFileNames[] = { - "resources/models/vox/chr_knight.vox", - "resources/models/vox/chr_sword.vox", - "resources/models/vox/monu9.vox" - }; + const char* voxFileNames[] = { + "resources/models/vox/chr_knight.vox", + "resources/models/vox/chr_sword.vox", + "resources/models/vox/monu9.vox", + "resources/models/vox/fez.vox" + }; - InitWindow(screenWidth, screenHeight, "raylib [models] example - magicavoxel loading"); + InitWindow(screenWidth, screenHeight, "raylib [models] example - magicavoxel loading"); - // Define the camera to look into our 3d world - Camera camera = { 0 }; - camera.position = (Vector3){ 10.0f, 10.0f, 10.0f }; // Camera position - camera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; // Camera looking at point - camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector (rotation towards target) - camera.fovy = 45.0f; // Camera field-of-view Y - camera.projection = CAMERA_PERSPECTIVE; // Camera projection type + // Define the camera to look into our 3d world + Camera camera = { 0 }; + camera.position = (Vector3){ 10.0f, 10.0f, 10.0f }; // Camera position + camera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; // Camera looking at point + camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector (rotation towards target) + camera.fovy = 45.0f; // Camera field-of-view Y + camera.projection = CAMERA_PERSPECTIVE; // Camera projection type - // Load MagicaVoxel files - Model models[MAX_VOX_FILES] = { 0 }; + //-------------------------------------------------------------------------------------- + // Load MagicaVoxel files + Model models[MAX_VOX_FILES] = { 0 }; - for (int i = 0; i < MAX_VOX_FILES; i++) - { - // Load VOX file and measure time - double t0 = GetTime()*1000.0; - models[i] = LoadModel(voxFileNames[i]); - double t1 = GetTime()*1000.0; + for (int i = 0; i < MAX_VOX_FILES; i++) + { + // Load VOX file and measure time + double t0 = GetTime() * 1000.0; + models[i] = LoadModel(voxFileNames[i]); + double t1 = GetTime() * 1000.0; - TraceLog(LOG_WARNING, TextFormat("[%s] File loaded in %.3f ms", voxFileNames[i], t1 - t0)); + TraceLog(LOG_WARNING, TextFormat("[%s] File loaded in %.3f ms", voxFileNames[i], t1 - t0)); - // Compute model translation matrix to center model on draw position (0, 0 , 0) - BoundingBox bb = GetModelBoundingBox(models[i]); - Vector3 center = { 0 }; - center.x = bb.min.x + (((bb.max.x - bb.min.x)/2)); - center.z = bb.min.z + (((bb.max.z - bb.min.z)/2)); + // Compute model translation matrix to center model on draw position (0, 0 , 0) + BoundingBox bb = GetModelBoundingBox(models[i]); + Vector3 center = { 0 }; + center.x = bb.min.x + (((bb.max.x - bb.min.x) / 2)); + center.z = bb.min.z + (((bb.max.z - bb.min.z) / 2)); - Matrix matTranslate = MatrixTranslate(-center.x, 0, -center.z); - models[i].transform = matTranslate; - } + Matrix matTranslate = MatrixTranslate(-center.x, 0, -center.z); + models[i].transform = matTranslate; + } - int currentModel = 0; + int currentModel = 0; - SetTargetFPS(60); // Set our game to run at 60 frames-per-second - //-------------------------------------------------------------------------------------- + //-------------------------------------------------------------------------------------- + // Load voxel shader + Shader shader = LoadShader(TextFormat("resources/shaders/glsl%i/voxel_lighting.vs", GLSL_VERSION), + TextFormat("resources/shaders/glsl%i/voxel_lighting.fs", GLSL_VERSION)); - // Main game loop - while (!WindowShouldClose()) // Detect window close button or ESC key - { - // Update - //---------------------------------------------------------------------------------- - UpdateCamera(&camera, CAMERA_ORBITAL); + // Get some required shader locations + shader.locs[SHADER_LOC_VECTOR_VIEW] = GetShaderLocation(shader, "viewPos"); + // NOTE: "matModel" location name is automatically assigned on shader loading, + // no need to get the location again if using that uniform name + //shader.locs[SHADER_LOC_MATRIX_MODEL] = GetShaderLocation(shader, "matModel"); - // Cycle between models on mouse click - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) currentModel = (currentModel + 1)%MAX_VOX_FILES; + // Ambient light level (some basic lighting) + int ambientLoc = GetShaderLocation(shader, "ambient"); + SetShaderValue(shader, ambientLoc, (float[4]) { 0.1f, 0.1f, 0.1f, 1.0f }, SHADER_UNIFORM_VEC4); - // Cycle between models on key pressed - if (IsKeyPressed(KEY_RIGHT)) - { - currentModel++; - if (currentModel >= MAX_VOX_FILES) currentModel = 0; - } - else if (IsKeyPressed(KEY_LEFT)) - { - currentModel--; - if (currentModel < 0) currentModel = MAX_VOX_FILES - 1; - } - //---------------------------------------------------------------------------------- + // Assign out lighting shader to model + for (int i = 0; i < MAX_VOX_FILES; i++) + { + Model m = models[i]; + for (int j = 0; j < m.materialCount; j++) + { + m.materials[j].shader = shader; + } + } - // Draw - //---------------------------------------------------------------------------------- - BeginDrawing(); + // Create lights + Light lights[MAX_LIGHTS] = { 0 }; + lights[0] = CreateLight(LIGHT_POINT, (Vector3) { -20, 20, -20 }, Vector3Zero(), GRAY, shader); + lights[1] = CreateLight(LIGHT_POINT, (Vector3) { 20, -20, 20 }, Vector3Zero(), GRAY, shader); + lights[2] = CreateLight(LIGHT_POINT, (Vector3) { -20, 20, 20 }, Vector3Zero(), GRAY, shader); + lights[3] = CreateLight(LIGHT_POINT, (Vector3) { 20, -20, -20 }, Vector3Zero(), GRAY, shader); - ClearBackground(RAYWHITE); - // Draw 3D model - BeginMode3D(camera); + SetTargetFPS(60); // Set our game to run at 60 frames-per-second - DrawModel(models[currentModel], (Vector3){ 0, 0, 0 }, 1.0f, WHITE); - DrawGrid(10, 1.0); + //-------------------------------------------------------------------------------------- + Vector3 modelpos = { 0 }; + Vector3 camerarot = { 0 }; - EndMode3D(); + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + if (IsMouseButtonDown(MOUSE_BUTTON_MIDDLE)) + { + const Vector2 mouseDelta = GetMouseDelta(); + camerarot.x = mouseDelta.x * 0.05f; + camerarot.y = mouseDelta.y * 0.05f; + } + else + { + camerarot.x = 0; + camerarot.y = 0; + } - // Display info - DrawRectangle(10, 400, 310, 30, Fade(SKYBLUE, 0.5f)); - DrawRectangleLines(10, 400, 310, 30, Fade(DARKBLUE, 0.5f)); - DrawText("MOUSE LEFT BUTTON to CYCLE VOX MODELS", 40, 410, 10, BLUE); - DrawText(TextFormat("File: %s", GetFileName(voxFileNames[currentModel])), 10, 10, 20, GRAY); + UpdateCameraPro(&camera, + (Vector3) { + (IsKeyDown(KEY_W) || IsKeyDown(KEY_UP)) * 0.1f - // Move forward-backward + (IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN)) * 0.1f, + (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT)) * 0.1f - // Move right-left + (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT)) * 0.1f, + 0.0f // Move up-down + }, + camerarot, + GetMouseWheelMove() * -2.0f); // Move to target (zoom) - EndDrawing(); - //---------------------------------------------------------------------------------- - } + // Cycle between models on mouse click + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) currentModel = (currentModel + 1) % MAX_VOX_FILES; - // De-Initialization - //-------------------------------------------------------------------------------------- - // Unload models data (GPU VRAM) - for (int i = 0; i < MAX_VOX_FILES; i++) UnloadModel(models[i]); + // Update the shader with the camera view vector (points towards { 0.0f, 0.0f, 0.0f }) + float cameraPos[3] = { camera.position.x, camera.position.y, camera.position.z }; + SetShaderValue(shader, shader.locs[SHADER_LOC_VECTOR_VIEW], cameraPos, SHADER_UNIFORM_VEC3); - CloseWindow(); // Close window and OpenGL context - //-------------------------------------------------------------------------------------- + // Update light values (actually, only enable/disable them) + for (int i = 0; i < MAX_LIGHTS; i++) UpdateLightValues(shader, lights[i]); - return 0; + //---------------------------------------------------------------------------------- + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + // Draw 3D model + BeginMode3D(camera); + + DrawModel(models[currentModel], modelpos, 1.0f, WHITE); + DrawGrid(10, 1.0); + + // Draw spheres to show where the lights are + for (int i = 0; i < MAX_LIGHTS; i++) + { + if (lights[i].enabled) DrawSphereEx(lights[i].position, 0.2f, 8, 8, lights[i].color); + else DrawSphereWires(lights[i].position, 0.2f, 8, 8, ColorAlpha(lights[i].color, 0.3f)); + } + + EndMode3D(); + + // Display info + DrawRectangle(10, 400, 340, 60, Fade(SKYBLUE, 0.5f)); + DrawRectangleLines(10, 400, 340, 60, Fade(DARKBLUE, 0.5f)); + DrawText("MOUSE LEFT BUTTON to CYCLE VOX MODELS", 40, 410, 10, BLUE); + DrawText("MOUSE MIDDLE BUTTON to ZOOM OR ROTATE CAMERA", 40, 420, 10, BLUE); + DrawText("UP-DOWN-LEFT-RIGHT KEYS to MOVE CAMERA", 40, 430, 10, BLUE); + DrawText(TextFormat("File: %s", GetFileName(voxFileNames[currentModel])), 10, 10, 20, GRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + // Unload models data (GPU VRAM) + for (int i = 0; i < MAX_VOX_FILES; i++) UnloadModel(models[i]); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; } diff --git a/examples/models/resources/models/vox/fez.vox b/examples/models/resources/models/vox/fez.vox new file mode 100644 index 000000000..e9fa72693 Binary files /dev/null and b/examples/models/resources/models/vox/fez.vox differ diff --git a/examples/models/resources/shaders/glsl330/voxel_lighting.fs b/examples/models/resources/shaders/glsl330/voxel_lighting.fs new file mode 100644 index 000000000..1b6c5cffa --- /dev/null +++ b/examples/models/resources/shaders/glsl330/voxel_lighting.fs @@ -0,0 +1,76 @@ +#version 330 + +// Input vertex attributes (from vertex shader) +in vec3 fragPosition; +//in vec2 fragTexCoord; +in vec4 fragColor; +in vec3 fragNormal; + +// Input uniform values +//uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// Output fragment color +out vec4 finalColor; + +// NOTE: Add here your custom variables + +#define MAX_LIGHTS 4 +#define LIGHT_DIRECTIONAL 0 +#define LIGHT_POINT 1 + +struct Light { + int enabled; + int type; + vec3 position; + vec3 target; + vec4 color; +}; + +// Input lighting values +uniform Light lights[MAX_LIGHTS]; +uniform vec4 ambient; +uniform vec3 viewPos; + +void main() +{ + // Texel color fetching from texture sampler + //vec4 texelColor = texture(texture0, fragTexCoord); + vec3 lightDot = vec3(0.0); + vec3 normal = normalize(fragNormal); + vec3 viewD = normalize(viewPos - fragPosition); + vec3 specular = vec3(0.0); + + // NOTE: Implement here your fragment shader code + + for (int i = 0; i < MAX_LIGHTS; i++) + { + if (lights[i].enabled == 1) + { + vec3 light = vec3(0.0); + + if (lights[i].type == LIGHT_DIRECTIONAL) + { + light = -normalize(lights[i].target - lights[i].position); + } + + if (lights[i].type == LIGHT_POINT) + { + light = normalize(lights[i].position - fragPosition); + } + + float NdotL = max(dot(normal, light), 0.0); + lightDot += lights[i].color.rgb*NdotL; + + float specCo = 0.0; + if (NdotL > 0.0) specCo = pow(max(0.0, dot(viewD, reflect(-(light), normal))), 16.0); // 16 refers to shine + specular += specCo; + } + } + + finalColor = (fragColor*((colDiffuse + vec4(specular, 1.0))*vec4(lightDot, 1.0))); + finalColor += fragColor*(ambient/10.0)*colDiffuse; + + // Gamma correction + finalColor = pow(finalColor, vec4(1.0/2.2)); +} diff --git a/examples/models/resources/shaders/glsl330/voxel_lighting.vs b/examples/models/resources/shaders/glsl330/voxel_lighting.vs new file mode 100644 index 000000000..f60bfd221 --- /dev/null +++ b/examples/models/resources/shaders/glsl330/voxel_lighting.vs @@ -0,0 +1,32 @@ +#version 330 + +// Input vertex attributes +in vec3 vertexPosition; +//in vec2 vertexTexCoord; +in vec3 vertexNormal; +in vec4 vertexColor; + +// Input uniform values +uniform mat4 mvp; +uniform mat4 matModel; +uniform mat4 matNormal; + +// Output vertex attributes (to fragment shader) +out vec3 fragPosition; +//out vec2 fragTexCoord; +out vec4 fragColor; +out vec3 fragNormal; + +// NOTE: Add here your custom variables + +void main() +{ + // Send vertex attributes to fragment shader + fragPosition = vec3(matModel*vec4(vertexPosition, 1.0)); + //fragTexCoord = vertexTexCoord; + fragColor = vertexColor; + fragNormal = normalize(vec3(matNormal*vec4(vertexNormal, 1.0))); + + // Calculate final vertex position + gl_Position = mvp*vec4(vertexPosition, 1.0); +} diff --git a/examples/models/rlights.h b/examples/models/rlights.h new file mode 100644 index 000000000..e38b0015a --- /dev/null +++ b/examples/models/rlights.h @@ -0,0 +1,170 @@ +/********************************************************************************************** +* +* raylib.lights - Some useful functions to deal with lights data +* +* CONFIGURATION: +* +* #define RLIGHTS_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2017-2024 Victor Fisac (@victorfisac) and Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RLIGHTS_H +#define RLIGHTS_H + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define MAX_LIGHTS 4 // Max dynamic lights supported by shader + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +// Light data +typedef struct { + int type; + bool enabled; + Vector3 position; + Vector3 target; + Color color; + float attenuation; + + // Shader locations + int enabledLoc; + int typeLoc; + int positionLoc; + int targetLoc; + int colorLoc; + int attenuationLoc; +} Light; + +// Light type +typedef enum { + LIGHT_DIRECTIONAL = 0, + LIGHT_POINT +} LightType; + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +Light CreateLight(int type, Vector3 position, Vector3 target, Color color, Shader shader); // Create a light and get shader locations +void UpdateLightValues(Shader shader, Light light); // Send light properties to shader + +#ifdef __cplusplus +} +#endif + +#endif // RLIGHTS_H + + +/*********************************************************************************** +* +* RLIGHTS IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RLIGHTS_IMPLEMENTATION) + +#include "raylib.h" + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static int lightsCount = 0; // Current amount of created lights + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Create a light and get shader locations +Light CreateLight(int type, Vector3 position, Vector3 target, Color color, Shader shader) +{ + Light light = { 0 }; + + if (lightsCount < MAX_LIGHTS) + { + light.enabled = true; + light.type = type; + light.position = position; + light.target = target; + light.color = color; + + // NOTE: Lighting shader naming must be the provided ones + light.enabledLoc = GetShaderLocation(shader, TextFormat("lights[%i].enabled", lightsCount)); + light.typeLoc = GetShaderLocation(shader, TextFormat("lights[%i].type", lightsCount)); + light.positionLoc = GetShaderLocation(shader, TextFormat("lights[%i].position", lightsCount)); + light.targetLoc = GetShaderLocation(shader, TextFormat("lights[%i].target", lightsCount)); + light.colorLoc = GetShaderLocation(shader, TextFormat("lights[%i].color", lightsCount)); + + UpdateLightValues(shader, light); + + lightsCount++; + } + + return light; +} + +// Send light properties to shader +// NOTE: Light shader locations should be available +void UpdateLightValues(Shader shader, Light light) +{ + // Send to shader light enabled state and type + SetShaderValue(shader, light.enabledLoc, &light.enabled, SHADER_UNIFORM_INT); + SetShaderValue(shader, light.typeLoc, &light.type, SHADER_UNIFORM_INT); + + // Send to shader light position values + float position[3] = { light.position.x, light.position.y, light.position.z }; + SetShaderValue(shader, light.positionLoc, position, SHADER_UNIFORM_VEC3); + + // Send to shader light target position values + float target[3] = { light.target.x, light.target.y, light.target.z }; + SetShaderValue(shader, light.targetLoc, target, SHADER_UNIFORM_VEC3); + + // Send to shader light color values + float color[4] = { (float)light.color.r/(float)255, (float)light.color.g/(float)255, + (float)light.color.b/(float)255, (float)light.color.a/(float)255 }; + SetShaderValue(shader, light.colorLoc, color, SHADER_UNIFORM_VEC4); +} + +#endif // RLIGHTS_IMPLEMENTATION \ No newline at end of file diff --git a/src/external/vox_loader.h b/src/external/vox_loader.h index 6df10b518..0d328c078 100644 --- a/src/external/vox_loader.h +++ b/src/external/vox_loader.h @@ -123,6 +123,7 @@ typedef struct { // Arrays for mesh build ArrayVector3 vertices; + ArrayVector3 normals; ArrayUShort indices; ArrayColor colors; @@ -292,6 +293,16 @@ const VoxVector3 SolidVertex[] = { {1, 1, 1} //7 }; +const VoxVector3 FacesPerSideNormal[] = { + { -1, 0, 0 }, //-X + {1, 0, 0 }, //+X + {0,-1, 0}, //-Y + {0, 1, 0}, //+Y + {0, 0, -1}, //-Z + {0, 0, 1}, //+Z +}; + + // Allocated VoxArray3D size static void Vox_AllocArray(VoxArray3D* pvoxarray, int _sx, int _sy, int _sz) { @@ -508,6 +519,11 @@ static void Vox_Build_Voxel(VoxArray3D* pvoxArray, int x, int y, int z, int matI insertArrayVector3(&pvoxArray->vertices, vertComputed[v2]); insertArrayVector3(&pvoxArray->vertices, vertComputed[v3]); + insertArrayVector3(&pvoxArray->normals, FacesPerSideNormal[i]); + insertArrayVector3(&pvoxArray->normals, FacesPerSideNormal[i]); + insertArrayVector3(&pvoxArray->normals, FacesPerSideNormal[i]); + insertArrayVector3(&pvoxArray->normals, FacesPerSideNormal[i]); + VoxColor col = pvoxArray->palette[matID]; insertArrayColor(&pvoxArray->colors, col); @@ -653,6 +669,7 @@ int Vox_LoadFromMemory(unsigned char* pvoxData, unsigned int voxDataSize, VoxArr // Init Arrays initArrayVector3(&pvoxarray->vertices, 3 * 1024); + initArrayVector3(&pvoxarray->normals, 3 * 1024); initArrayUShort(&pvoxarray->indices, 3 * 1024); initArrayColor(&pvoxarray->colors, 3 * 1024); diff --git a/src/rmodels.c b/src/rmodels.c index 4f634990f..316b91d11 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -5641,6 +5641,7 @@ static Model LoadVOX(const char *fileName) // 6*4 = 12 vertices per voxel Vector3 *pvertices = (Vector3 *)voxarray.vertices.array; + Vector3* pnormals = (Vector3*)voxarray.normals.array; Color *pcolors = (Color *)voxarray.colors.array; unsigned short *pindices = voxarray.indices.array; // 5461*6*6 = 196596 indices max per mesh @@ -5659,6 +5660,10 @@ static Model LoadVOX(const char *fileName) pmesh->vertices = RL_MALLOC(size); memcpy(pmesh->vertices, pvertices, size); + // Copy normals + pmesh->normals = RL_MALLOC(size); //Rk. size as vertices + memcpy(pmesh->normals, pnormals, size); + // Copy indices size = voxarray.indices.used*sizeof(unsigned short); pmesh->indices = RL_MALLOC(size); @@ -5676,6 +5681,7 @@ static Model LoadVOX(const char *fileName) verticesRemain -= verticesMax; pvertices += verticesMax; + pnormals += verticesMax; pcolors += verticesMax; }