[examples] New example: `shaders_normalmap`pull/5096/head
| @ -0,0 +1,64 @@ | |||
| #version 100 | |||
| precision mediump float; | |||
| // Input vertex attributes (from vertex shader) | |||
| varying vec3 fragPosition; | |||
| varying vec2 fragTexCoord; | |||
| varying vec3 fragNormal; //used for when normal mapping is toggled off | |||
| varying vec4 fragColor; | |||
| varying mat3 TBN; | |||
| // Input uniform values | |||
| uniform sampler2D texture0; | |||
| uniform sampler2D normalMap; | |||
| uniform vec4 colDiffuse; | |||
| uniform vec3 viewPos; | |||
| // NOTE: Add your custom variables here | |||
| uniform vec3 lightPos; | |||
| uniform bool useNormalMap; | |||
| uniform float specularExponent; | |||
| void main() | |||
| { | |||
| vec4 texelColor = texture(texture0, vec2(fragTexCoord.x, fragTexCoord.y)); | |||
| vec3 specular = vec3(0.0); | |||
| vec3 viewDir = normalize(viewPos - fragPosition); | |||
| vec3 lightDir = normalize(lightPos - fragPosition); | |||
| vec3 normal; | |||
| if (useNormalMap) | |||
| { | |||
| normal = texture(normalMap, vec2(fragTexCoord.x, fragTexCoord.y)).rgb; | |||
| //Transform normal values to the range -1.0 ... 1.0 | |||
| normal = normalize(normal * 2.0 - 1.0); | |||
| //Transform the normal from tangent-space to world-space for lighting calculation | |||
| normal = normalize(normal * TBN); | |||
| } | |||
| else | |||
| { | |||
| normal = normalize(fragNormal); | |||
| } | |||
| vec4 tint = colDiffuse * fragColor; | |||
| vec3 lightColor = vec3(1.0, 1.0, 1.0); | |||
| float NdotL = max(dot(normal, lightDir), 0.0); | |||
| vec3 lightDot = lightColor * NdotL; | |||
| float specCo = 0.0; | |||
| if (NdotL > 0.0) specCo = pow(max(0.0, dot(viewDir, reflect(-lightDir, normal))), specularExponent); // 16 refers to shine | |||
| specular += specCo; | |||
| finalColor = (texelColor * ((tint + vec4(specular, 1.0)) * vec4(lightDot, 1.0))); | |||
| finalColor += texelColor * (vec4(1.0, 1.0, 1.0, 1.0) / 40.0) * tint; | |||
| // Gamma correction | |||
| gl_FragColor = pow(finalColor, vec4(1.0 / 2.2)); | |||
| } | |||
| @ -0,0 +1,76 @@ | |||
| #version 100 | |||
| // Input vertex attributes | |||
| attribute vec3 vertexPosition; | |||
| attribute vec2 vertexTexCoord; | |||
| attribute vec3 vertexNormal; | |||
| attribute vec4 vertexTangent; | |||
| attribute vec4 vertexColor; | |||
| // Input uniform values | |||
| uniform mat4 mvp; | |||
| uniform mat4 matModel; | |||
| // Output vertex attributes (to fragment shader) | |||
| varying vec3 fragPosition; | |||
| varying vec2 fragTexCoord; | |||
| varying vec3 fragNormal; //used for when normal mapping is toggled off | |||
| varying vec4 fragColor; | |||
| varying mat3 TBN; | |||
| // NOTE: Add your custom variables here | |||
| // https://github.com/glslify/glsl-inverse | |||
| mat3 inverse(mat3 m) | |||
| { | |||
| float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2]; | |||
| float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2]; | |||
| float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2]; | |||
| float b01 = a22 * a11 - a12 * a21; | |||
| float b11 = -a22 * a10 + a12 * a20; | |||
| float b21 = a21 * a10 - a11 * a20; | |||
| float det = a00 * b01 + a01 * b11 + a02 * b21; | |||
| return mat3(b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11), | |||
| b11, (a22 * a00 - a02 * a20), (-a12 * a00 + a02 * a10), | |||
| b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) / det; | |||
| } | |||
| // https://github.com/glslify/glsl-transpose | |||
| mat3 transpose(mat3 m) | |||
| { | |||
| return mat3(m[0][0], m[1][0], m[2][0], | |||
| m[0][1], m[1][1], m[2][1], | |||
| m[0][2], m[1][2], m[2][2]); | |||
| } | |||
| void main() | |||
| { | |||
| // Compute binormal from vertex normal and tangent. W component is the tangent handedness | |||
| vec3 vertexBinormal = cross(vertexNormal, vertexTangent.xyz) * vertexTangent.w; | |||
| // Compute fragment normal based on normal transformations | |||
| mat3 normalMatrix = transpose(inverse(mat3(matModel))); | |||
| // Compute fragment position based on model transformations | |||
| fragPosition = vec3(matModel * vec4(vertexPosition, 1.0)); | |||
| //Create TBN matrix for transforming the normal map values from tangent-space to world-space | |||
| fragNormal = normalize(normalMatrix * vertexNormal); | |||
| vec3 fragTangent = normalize(normalMatrix * vertexTangent.xyz); | |||
| fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal) * fragNormal); | |||
| vec3 fragBinormal = normalize(normalMatrix * vertexBinormal); | |||
| fragBinormal = cross(fragNormal, fragTangent); | |||
| TBN = transpose(mat3(fragTangent, fragBinormal, fragNormal)); | |||
| fragColor = vertexColor; | |||
| fragTexCoord = vertexTexCoord; | |||
| gl_Position = mvp * vec4(vertexPosition, 1.0); | |||
| } | |||
| @ -0,0 +1,62 @@ | |||
| #version 120 | |||
| // Input vertex attributes (from vertex shader) | |||
| varying vec3 fragPosition; | |||
| varying vec2 fragTexCoord; | |||
| varying vec3 fragNormal; //used for when normal mapping is toggled off | |||
| varying vec4 fragColor; | |||
| varying mat3 TBN; | |||
| // Input uniform values | |||
| uniform sampler2D texture0; | |||
| uniform sampler2D normalMap; | |||
| uniform vec4 colDiffuse; | |||
| uniform vec3 viewPos; | |||
| // NOTE: Add your custom variables here | |||
| uniform vec3 lightPos; | |||
| uniform bool useNormalMap; | |||
| uniform float specularExponent; | |||
| void main() | |||
| { | |||
| vec4 texelColor = texture(texture0, vec2(fragTexCoord.x, fragTexCoord.y)); | |||
| vec3 specular = vec3(0.0); | |||
| vec3 viewDir = normalize(viewPos - fragPosition); | |||
| vec3 lightDir = normalize(lightPos - fragPosition); | |||
| vec3 normal; | |||
| if (useNormalMap) | |||
| { | |||
| normal = texture(normalMap, vec2(fragTexCoord.x, fragTexCoord.y)).rgb; | |||
| //Transform normal values to the range -1.0 ... 1.0 | |||
| normal = normalize(normal * 2.0 - 1.0); | |||
| //Transform the normal from tangent-space to world-space for lighting calculation | |||
| normal = normalize(normal * TBN); | |||
| } | |||
| else | |||
| { | |||
| normal = normalize(fragNormal); | |||
| } | |||
| vec4 tint = colDiffuse * fragColor; | |||
| vec3 lightColor = vec3(1.0, 1.0, 1.0); | |||
| float NdotL = max(dot(normal, lightDir), 0.0); | |||
| vec3 lightDot = lightColor * NdotL; | |||
| float specCo = 0.0; | |||
| if (NdotL > 0.0) specCo = pow(max(0.0, dot(viewDir, reflect(-lightDir, normal))), specularExponent); // 16 refers to shine | |||
| specular += specCo; | |||
| finalColor = (texelColor * ((tint + vec4(specular, 1.0)) * vec4(lightDot, 1.0))); | |||
| finalColor += texelColor * (vec4(1.0, 1.0, 1.0, 1.0) / 40.0) * tint; | |||
| // Gamma correction | |||
| gl_FragColor = pow(finalColor, vec4(1.0 / 2.2)); | |||
| } | |||
| @ -0,0 +1,76 @@ | |||
| #version 120 | |||
| // Input vertex attributes | |||
| attribute vec3 vertexPosition; | |||
| attribute vec2 vertexTexCoord; | |||
| attribute vec3 vertexNormal; | |||
| attribute vec4 vertexTangent; | |||
| attribute vec4 vertexColor; | |||
| // Input uniform values | |||
| uniform mat4 mvp; | |||
| uniform mat4 matModel; | |||
| // Output vertex attributes (to fragment shader) | |||
| varying vec3 fragPosition; | |||
| varying vec2 fragTexCoord; | |||
| varying vec3 fragNormal; //used for when normal mapping is toggled off | |||
| varying vec4 fragColor; | |||
| varying mat3 TBN; | |||
| // NOTE: Add your custom variables here | |||
| // https://github.com/glslify/glsl-inverse | |||
| mat3 inverse(mat3 m) | |||
| { | |||
| float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2]; | |||
| float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2]; | |||
| float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2]; | |||
| float b01 = a22 * a11 - a12 * a21; | |||
| float b11 = -a22 * a10 + a12 * a20; | |||
| float b21 = a21 * a10 - a11 * a20; | |||
| float det = a00 * b01 + a01 * b11 + a02 * b21; | |||
| return mat3(b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11), | |||
| b11, (a22 * a00 - a02 * a20), (-a12 * a00 + a02 * a10), | |||
| b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) / det; | |||
| } | |||
| // https://github.com/glslify/glsl-transpose | |||
| mat3 transpose(mat3 m) | |||
| { | |||
| return mat3(m[0][0], m[1][0], m[2][0], | |||
| m[0][1], m[1][1], m[2][1], | |||
| m[0][2], m[1][2], m[2][2]); | |||
| } | |||
| void main() | |||
| { | |||
| // Compute binormal from vertex normal and tangent. W component is the tangent handedness | |||
| vec3 vertexBinormal = cross(vertexNormal, vertexTangent.xyz) * vertexTangent.w; | |||
| // Compute fragment normal based on normal transformations | |||
| mat3 normalMatrix = transpose(inverse(mat3(matModel))); | |||
| // Compute fragment position based on model transformations | |||
| fragPosition = vec3(matModel * vec4(vertexPosition, 1.0)); | |||
| //Create TBN matrix for transforming the normal map values from tangent-space to world-space | |||
| fragNormal = normalize(normalMatrix * vertexNormal); | |||
| vec3 fragTangent = normalize(normalMatrix * vertexTangent.xyz); | |||
| fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal) * fragNormal); | |||
| vec3 fragBinormal = normalize(normalMatrix * vertexBinormal); | |||
| fragBinormal = cross(fragNormal, fragTangent); | |||
| TBN = transpose(mat3(fragTangent, fragBinormal, fragNormal)); | |||
| fragColor = vertexColor; | |||
| fragTexCoord = vertexTexCoord; | |||
| gl_Position = mvp * vec4(vertexPosition, 1.0); | |||
| } | |||
| @ -0,0 +1,67 @@ | |||
| #version 330 | |||
| // Input vertex attributes (from vertex shader) | |||
| in vec3 fragPosition; | |||
| in vec2 fragTexCoord; | |||
| in vec3 fragNormal; //used for when normal mapping is toggled off | |||
| in vec4 fragColor; | |||
| // Input uniform values | |||
| uniform sampler2D texture0; | |||
| uniform sampler2D normalMap; | |||
| uniform vec4 colDiffuse; | |||
| uniform vec3 viewPos; | |||
| uniform vec4 tintColor; | |||
| uniform vec3 lightPos; | |||
| uniform bool useNormalMap; | |||
| uniform float specularExponent; | |||
| // Output fragment color | |||
| out vec4 finalColor; | |||
| in mat3 TBN; | |||
| void main() | |||
| { | |||
| vec4 texelColor = texture(texture0, vec2(fragTexCoord.x, fragTexCoord.y)); | |||
| vec3 specular = vec3(0.0); | |||
| vec3 viewDir = normalize(viewPos - fragPosition); | |||
| vec3 lightDir = normalize(lightPos - fragPosition); | |||
| vec3 normal; | |||
| if (useNormalMap) | |||
| { | |||
| normal = texture(normalMap, vec2(fragTexCoord.x, fragTexCoord.y)).rgb; | |||
| //Transform normal values to the range -1.0 ... 1.0 | |||
| normal = normalize(normal * 2.0 - 1.0); | |||
| //Transform the normal from tangent-space to world-space for lighting calculation | |||
| normal = normalize(normal * TBN); | |||
| } | |||
| else | |||
| { | |||
| normal = normalize(fragNormal); | |||
| } | |||
| vec4 tint = colDiffuse * fragColor; | |||
| vec3 lightColor = vec3(1.0, 1.0, 1.0); | |||
| float NdotL = max(dot(normal, lightDir), 0.0); | |||
| vec3 lightDot = lightColor * NdotL; | |||
| float specCo = 0.0; | |||
| if (NdotL > 0.0) specCo = pow(max(0.0, dot(viewDir, reflect(-lightDir, normal))), specularExponent); // 16 refers to shine | |||
| specular += specCo; | |||
| finalColor = (texelColor * ((tint + vec4(specular, 1.0)) * vec4(lightDot, 1.0))); | |||
| finalColor += texelColor * (vec4(1.0, 1.0, 1.0, 1.0) / 40.0) * tint; | |||
| // Gamma correction | |||
| finalColor = pow(finalColor, vec4(1.0 / 2.2)); | |||
| //finalColor = vec4(normal, 1.0); | |||
| } | |||
| @ -0,0 +1,48 @@ | |||
| #version 330 | |||
| // Input vertex attributes | |||
| in vec3 vertexPosition; | |||
| in vec2 vertexTexCoord; | |||
| in vec3 vertexNormal; | |||
| in vec4 vertexTangent; | |||
| in vec4 vertexColor; | |||
| // Input uniform values | |||
| uniform mat4 mvp; | |||
| uniform mat4 matModel; | |||
| // Output vertex attributes (to fragment shader) | |||
| out vec3 fragPosition; | |||
| out vec2 fragTexCoord; | |||
| out vec3 fragNormal; //used for when normal mapping is toggled off | |||
| out vec4 fragColor; | |||
| out mat3 TBN; | |||
| void main() | |||
| { | |||
| // Compute binormal from vertex normal and tangent. W component is the tangent handedness | |||
| vec3 vertexBinormal = cross(vertexNormal, vertexTangent.xyz) * vertexTangent.w; | |||
| // Compute fragment normal based on normal transformations | |||
| mat3 normalMatrix = transpose(inverse(mat3(matModel))); | |||
| // Compute fragment position based on model transformations | |||
| fragPosition = vec3(matModel * vec4(vertexPosition, 1.0)); | |||
| //Create TBN matrix for transforming the normal map values from tangent-space to world-space | |||
| fragNormal = normalize(normalMatrix * vertexNormal); | |||
| vec3 fragTangent = normalize(normalMatrix * vertexTangent.xyz); | |||
| fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal) * fragNormal); | |||
| vec3 fragBinormal = normalize(normalMatrix * vertexBinormal); | |||
| fragBinormal = cross(fragNormal, fragTangent); | |||
| TBN = transpose(mat3(fragTangent, fragBinormal, fragNormal)); | |||
| fragColor = vertexColor; | |||
| fragTexCoord = vertexTexCoord; | |||
| gl_Position = mvp * vec4(vertexPosition, 1.0); | |||
| } | |||
| @ -0,0 +1,170 @@ | |||
| /******************************************************************************************* | |||
| * | |||
| * raylib [shaders] example - normalmap | |||
| * | |||
| * Example complexity rating: [★★★★] 4/4 | |||
| * | |||
| * NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support, | |||
| * OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version. | |||
| * | |||
| * Example originally created with raylib 5.6, last time updated with raylib 5.6 | |||
| * | |||
| * Example contributed by Jeremy Montgomery (@Sir_Irk) and reviewed by Ramon Santamaria (@raysan5) | |||
| * | |||
| * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, | |||
| * BSD-like license that allows static linking with closed source software | |||
| * | |||
| * Copyright (c) 2025-2025 Jeremy Montgomery (@Sir_Irk) and Ramon Santamaria (@raysan5) | |||
| *k | |||
| ********************************************************************************************/ | |||
| #include <raylib.h> | |||
| #include <raymath.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 | |||
| //-------------------------------------------------------------------------------------- | |||
| SetConfigFlags(FLAG_MSAA_4X_HINT); | |||
| InitWindow(800, 450, "Normal Map"); | |||
| Camera camera = {0}; | |||
| camera.position = (Vector3){0.0f, 2.0f, -4.0f}; | |||
| camera.target = (Vector3){0.0f, 0.0f, 0.0f}; | |||
| camera.up = (Vector3){0.0f, 1.0f, 0.0f}; | |||
| camera.fovy = 45.0f; | |||
| camera.projection = CAMERA_PERSPECTIVE; | |||
| // Load basic normal map lighting shader | |||
| Shader shader = LoadShader(TextFormat("resources/shaders/glsl%i/normalmap.vs", GLSL_VERSION), | |||
| TextFormat("resources/shaders/glsl%i/normalmap.fs", GLSL_VERSION)); | |||
| // Get some required shader locations | |||
| shader.locs[SHADER_LOC_MAP_NORMAL] = GetShaderLocation(shader, "normalMap"); | |||
| 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"); | |||
| // This example uses just 1 point light. | |||
| Vector3 lightPosition = {0.0f, 1.0f, 0.0f}; | |||
| int lightPosLoc = GetShaderLocation(shader, "lightPos"); | |||
| // Load a plane model that has proper normals and tangents | |||
| Model plane = LoadModel("resources/models/plane.glb"); | |||
| // Set the plane model's shader and texture maps | |||
| plane.materials[0].shader = shader; | |||
| plane.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = LoadTexture("resources/tiles_diffuse.png"); | |||
| plane.materials[0].maps[MATERIAL_MAP_NORMAL].texture = LoadTexture("resources/tiles_normal.png"); | |||
| // Generate Mipmaps and use TRILINEAR filtering to help with texture aliasing | |||
| GenTextureMipmaps(&plane.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture); | |||
| GenTextureMipmaps(&plane.materials[0].maps[MATERIAL_MAP_NORMAL].texture); | |||
| SetTextureFilter(plane.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture, TEXTURE_FILTER_TRILINEAR); | |||
| SetTextureFilter(plane.materials[0].maps[MATERIAL_MAP_NORMAL].texture, TEXTURE_FILTER_TRILINEAR); | |||
| // Specular exponent AKA shininess of the material. | |||
| float specularExponent = 8.0f; | |||
| int specularExponentLoc = GetShaderLocation(shader, "specularExponent"); | |||
| // Allow toggling the normal map on and off for comparison purposes | |||
| int useNormalMap = 1; | |||
| int useNormalMapLoc = GetShaderLocation(shader, "useNormalMap"); | |||
| SetTargetFPS(60); // Set our game to run at 60 frames-per-second | |||
| //-------------------------------------------------------------------------------------- | |||
| // Main game loop | |||
| while (!WindowShouldClose()) // Detect window close button or ESC key | |||
| { | |||
| // Update | |||
| //---------------------------------------------------------------------------------- | |||
| // Move the light around on the X and Z axis using WASD keys | |||
| Vector3 direction = {0}; | |||
| if (IsKeyDown(KEY_W)) direction = Vector3Add(direction, (Vector3){0.0f, 0.0f, 1.0f}); | |||
| if (IsKeyDown(KEY_S)) direction = Vector3Add(direction, (Vector3){0.0f, 0.0f, -1.0f}); | |||
| if (IsKeyDown(KEY_D)) direction = Vector3Add(direction, (Vector3){-1.0f, 0.0f, 0.0f}); | |||
| if (IsKeyDown(KEY_A)) direction = Vector3Add(direction, (Vector3){1.0f, 0.0f, 0.0f}); | |||
| direction = Vector3Normalize(direction); | |||
| lightPosition = Vector3Add(lightPosition, Vector3Scale(direction, GetFrameTime() * 3.0f)); | |||
| // Increase/Decrease the specular exponent(shininess) | |||
| if (IsKeyDown(KEY_UP)) specularExponent = Clamp(specularExponent + 40.0f * GetFrameTime(), 2.0f, 128.0f); | |||
| if (IsKeyDown(KEY_DOWN)) specularExponent = Clamp(specularExponent - 40.0f * GetFrameTime(), 2.0f, 128.0f); | |||
| // Toggle normal map on and off | |||
| if (IsKeyPressed(KEY_N)) useNormalMap = !useNormalMap; | |||
| // Spin plane model at a constant rate | |||
| plane.transform = MatrixRotateY(GetTime() * 0.5f); | |||
| // Update shader values | |||
| float lightPos[3] = {lightPosition.x, lightPosition.y, lightPosition.z}; | |||
| SetShaderValue(shader, lightPosLoc, lightPos, SHADER_UNIFORM_VEC3); | |||
| float camPos[3] = {camera.position.x, camera.position.y, camera.position.z}; | |||
| SetShaderValue(shader, shader.locs[SHADER_LOC_VECTOR_VIEW], camPos, SHADER_UNIFORM_VEC3); | |||
| SetShaderValue(shader, specularExponentLoc, &specularExponent, SHADER_UNIFORM_FLOAT); | |||
| SetShaderValue(shader, useNormalMapLoc, &useNormalMap, SHADER_UNIFORM_INT); | |||
| //-------------------------------------------------------------------------------------- | |||
| // Draw | |||
| //---------------------------------------------------------------------------------- | |||
| BeginDrawing(); | |||
| ClearBackground(RAYWHITE); | |||
| BeginMode3D(camera); | |||
| BeginShaderMode(shader); | |||
| DrawModel(plane, Vector3Zero(), 2.0f, WHITE); | |||
| EndShaderMode(); | |||
| //Draw sphere to show light position | |||
| DrawSphereWires(lightPosition, 0.2f, 8, 8, ORANGE); | |||
| EndMode3D(); | |||
| Color textColor = (useNormalMap) ? DARKGREEN : RED; | |||
| const char *toggleStr = (useNormalMap) ? "On" : "Off"; | |||
| DrawText(TextFormat("Use key [N] to toggle normal map: %s", toggleStr), 10, 30, 20, textColor); | |||
| int yOffset = 24; | |||
| DrawText("Use keys [W][A][S][D] to move the light", 10, 30 + yOffset * 1, 20, BLACK); | |||
| DrawText("Use keys [Up][Down] to change specular exponent", 10, 30 + yOffset * 2, 20, BLACK); | |||
| DrawText(TextFormat("Specular Exponent: %.2f", specularExponent), 10, 30 + yOffset * 3, 20, BLUE); | |||
| DrawFPS(10, 10); | |||
| EndDrawing(); | |||
| //-------------------------------------------------------------------------------------- | |||
| } | |||
| // De-Initialization | |||
| //-------------------------------------------------------------------------------------- | |||
| UnloadShader(shader); | |||
| UnloadModel(plane); | |||
| CloseWindow(); // Close window and OpenGL context | |||
| //-------------------------------------------------------------------------------------- | |||
| return 0; | |||
| } | |||