From 54f630774d62699dfdd79d2a9f4eba9269f38aa4 Mon Sep 17 00:00:00 2001 From: Gleb A Date: Fri, 6 Mar 2026 10:52:28 -0500 Subject: [PATCH] [example] cel-shading and outline using inverted hull (#5615) * added cel-shading and outline using inverted hull example * new screenshot * added glsl100+120 compat * updated view * unnecessary spacing --- .../shaders/resources/shaders/glsl100/cel.fs | 58 +++++ .../shaders/resources/shaders/glsl100/cel.vs | 47 ++++ .../resources/shaders/glsl100/outline_hull.fs | 8 + .../resources/shaders/glsl100/outline_hull.vs | 15 ++ .../shaders/resources/shaders/glsl120/cel.fs | 56 +++++ .../shaders/resources/shaders/glsl120/cel.vs | 48 +++++ .../resources/shaders/glsl120/outline_hull.fs | 6 + .../resources/shaders/glsl120/outline_hull.vs | 15 ++ .../shaders/resources/shaders/glsl330/cel.fs | 60 ++++++ .../shaders/resources/shaders/glsl330/cel.vs | 25 +++ .../resources/shaders/glsl330/outline_hull.fs | 7 + .../resources/shaders/glsl330/outline_hull.vs | 15 ++ examples/shaders/shaders_cel_shading.c | 202 ++++++++++++++++++ examples/shaders/shaders_cel_shading.png | Bin 0 -> 45710 bytes 14 files changed, 562 insertions(+) create mode 100644 examples/shaders/resources/shaders/glsl100/cel.fs create mode 100644 examples/shaders/resources/shaders/glsl100/cel.vs create mode 100644 examples/shaders/resources/shaders/glsl100/outline_hull.fs create mode 100644 examples/shaders/resources/shaders/glsl100/outline_hull.vs create mode 100644 examples/shaders/resources/shaders/glsl120/cel.fs create mode 100644 examples/shaders/resources/shaders/glsl120/cel.vs create mode 100644 examples/shaders/resources/shaders/glsl120/outline_hull.fs create mode 100644 examples/shaders/resources/shaders/glsl120/outline_hull.vs create mode 100644 examples/shaders/resources/shaders/glsl330/cel.fs create mode 100644 examples/shaders/resources/shaders/glsl330/cel.vs create mode 100644 examples/shaders/resources/shaders/glsl330/outline_hull.fs create mode 100644 examples/shaders/resources/shaders/glsl330/outline_hull.vs create mode 100644 examples/shaders/shaders_cel_shading.c create mode 100644 examples/shaders/shaders_cel_shading.png diff --git a/examples/shaders/resources/shaders/glsl100/cel.fs b/examples/shaders/resources/shaders/glsl100/cel.fs new file mode 100644 index 000000000..dfc22d0c0 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/cel.fs @@ -0,0 +1,58 @@ +#version 100 + +precision mediump float; + +varying vec3 fragPosition; +varying vec2 fragTexCoord; +varying vec4 fragColor; +varying vec3 fragNormal; + +uniform sampler2D texture0; +uniform vec4 colDiffuse; +uniform vec3 viewPos; +uniform float numBands; + +struct Light { + int enabled; + int type; + vec3 position; + vec3 target; + vec4 color; +}; +uniform Light lights[4]; + +void main() +{ + vec4 texColor = texture2D(texture0, fragTexCoord); + vec3 baseColor = texColor.rgb * fragColor.rgb * colDiffuse.rgb; + vec3 norm = normalize(fragNormal); + + float lightAccum = 0.08; // ambient floor + + for (int i = 0; i < 4; i++) + { + if (lights[i].enabled == 1) // no continue in GLSL ES 1.0 + { + vec3 lightDir; + if (lights[i].type == 0) + { + // Directional: direction is from position toward target. + lightDir = normalize(lights[i].position - lights[i].target); + } + else + { + // Point: direction from surface to light. + lightDir = normalize(lights[i].position - fragPosition); + } + + float NdotL = max(dot(norm, lightDir), 0.0); + + // Quantize NdotL into numBands discrete steps. + float quantized = min(floor(NdotL * numBands), numBands - 1.0) / (numBands - 1.0); + lightAccum += quantized * lights[i].color.r; + } + } + + lightAccum = clamp(lightAccum, 0.0, 1.0); + gl_FragColor = vec4(baseColor * lightAccum, texColor.a * colDiffuse.a); +} \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl100/cel.vs b/examples/shaders/resources/shaders/glsl100/cel.vs new file mode 100644 index 000000000..153842a9b --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/cel.vs @@ -0,0 +1,47 @@ +#version 100 + +attribute vec3 vertexPosition; +attribute vec2 vertexTexCoord; +attribute vec3 vertexNormal; +attribute vec4 vertexColor; + +uniform mat4 mvp; +uniform mat4 matModel; + +varying vec3 fragPosition; +varying vec2 fragTexCoord; +varying vec4 fragColor; +varying vec3 fragNormal; + +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; +} + +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() +{ + fragPosition = vec3(matModel * vec4(vertexPosition, 1.0)); + fragTexCoord = vertexTexCoord; + fragColor = vertexColor; + + mat3 normalMatrix = transpose(inverse(mat3(matModel))); + fragNormal = normalize(normalMatrix * vertexNormal); + + gl_Position = mvp * vec4(vertexPosition, 1.0); +} \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl100/outline_hull.fs b/examples/shaders/resources/shaders/glsl100/outline_hull.fs new file mode 100644 index 000000000..4f82cfc87 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/outline_hull.fs @@ -0,0 +1,8 @@ +#version 100 + +precision mediump float; + +void main() +{ + gl_FragColor = vec4(0.05, 0.05, 0.05, 1.0); +} \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl100/outline_hull.vs b/examples/shaders/resources/shaders/glsl100/outline_hull.vs new file mode 100644 index 000000000..af7be8831 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/outline_hull.vs @@ -0,0 +1,15 @@ +#version 100 + +attribute vec3 vertexPosition; +attribute vec3 vertexNormal; +attribute vec2 vertexTexCoord; +attribute vec4 vertexColor; + +uniform mat4 mvp; +uniform float outlineThickness; + +void main() +{ + vec3 extruded = vertexPosition + vertexNormal * outlineThickness; + gl_Position = mvp * vec4(extruded, 1.0); +} \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl120/cel.fs b/examples/shaders/resources/shaders/glsl120/cel.fs new file mode 100644 index 000000000..88321e60f --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/cel.fs @@ -0,0 +1,56 @@ +#version 120 + +varying vec3 fragPosition; +varying vec2 fragTexCoord; +varying vec4 fragColor; +varying vec3 fragNormal; + +uniform sampler2D texture0; +uniform vec4 colDiffuse; +uniform vec3 viewPos; +uniform float numBands; + +struct Light { + int enabled; + int type; + vec3 position; + vec3 target; + vec4 color; +}; +uniform Light lights[4]; + +void main() +{ + vec4 texColor = texture2D(texture0, fragTexCoord); + vec3 baseColor = texColor.rgb * fragColor.rgb * colDiffuse.rgb; + vec3 norm = normalize(fragNormal); + + float lightAccum = 0.08; // ambient floor + + for (int i = 0; i < 4; i++) + { + if (lights[i].enabled == 1) + { + vec3 lightDir; + if (lights[i].type == 0) + { + // Directional: direction is from position toward target. + lightDir = normalize(lights[i].position - lights[i].target); + } + else + { + // Point: direction from surface to light. + lightDir = normalize(lights[i].position - fragPosition); + } + + float NdotL = max(dot(norm, lightDir), 0.0); + + // Quantize NdotL into numBands discrete steps. + float quantized = min(floor(NdotL * numBands), numBands - 1.0) / (numBands - 1.0); + lightAccum += quantized * lights[i].color.r; + } + } + + lightAccum = clamp(lightAccum, 0.0, 1.0); + gl_FragColor = vec4(baseColor * lightAccum, texColor.a * colDiffuse.a); +} \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl120/cel.vs b/examples/shaders/resources/shaders/glsl120/cel.vs new file mode 100644 index 000000000..3c736aec7 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/cel.vs @@ -0,0 +1,48 @@ +#version 120 + +attribute vec3 vertexPosition; +attribute vec2 vertexTexCoord; +attribute vec3 vertexNormal; +attribute vec4 vertexColor; + +uniform mat4 mvp; +uniform mat4 matModel; + +varying vec3 fragPosition; +varying vec2 fragTexCoord; +varying vec4 fragColor; +varying vec3 fragNormal; + +// inverse() and transpose() are not built-in until GLSL 1.40 +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; +} + +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() +{ + fragPosition = vec3(matModel * vec4(vertexPosition, 1.0)); + fragTexCoord = vertexTexCoord; + fragColor = vertexColor; + + mat3 normalMatrix = transpose(inverse(mat3(matModel))); + fragNormal = normalize(normalMatrix * vertexNormal); + + gl_Position = mvp * vec4(vertexPosition, 1.0); +} \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl120/outline_hull.fs b/examples/shaders/resources/shaders/glsl120/outline_hull.fs new file mode 100644 index 000000000..b6e4fc828 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/outline_hull.fs @@ -0,0 +1,6 @@ +#version 120 + +void main() +{ + gl_FragColor = vec4(0.05, 0.05, 0.05, 1.0); +} diff --git a/examples/shaders/resources/shaders/glsl120/outline_hull.vs b/examples/shaders/resources/shaders/glsl120/outline_hull.vs new file mode 100644 index 000000000..7ad125be1 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/outline_hull.vs @@ -0,0 +1,15 @@ +#version 120 + +attribute vec3 vertexPosition; +attribute vec3 vertexNormal; +attribute vec2 vertexTexCoord; +attribute vec4 vertexColor; + +uniform mat4 mvp; +uniform float outlineThickness; + +void main() +{ + vec3 extruded = vertexPosition + vertexNormal * outlineThickness; + gl_Position = mvp * vec4(extruded, 1.0); +} \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl330/cel.fs b/examples/shaders/resources/shaders/glsl330/cel.fs new file mode 100644 index 000000000..78a2097c2 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/cel.fs @@ -0,0 +1,60 @@ +#version 330 + +in vec3 fragPosition; +in vec2 fragTexCoord; +in vec4 fragColor; +in vec3 fragNormal; + +// Raylib standard uniforms +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// View position for future specular / fresnel use. +uniform vec3 viewPos; + +// Number of discrete toon bands (2 = hard binary, 10 = default, 20 = near-smooth). +uniform float numBands; + +// rlights.h compatible light block. +struct Light { + int enabled; + int type; // 0 = directional, 1 = point + vec3 position; + vec3 target; + vec4 color; + float attenuation; +}; +uniform Light lights[4]; + +out vec4 finalColor; + +void main() { + vec4 texColor = texture(texture0, fragTexCoord); + vec3 baseColor = texColor.rgb * fragColor.rgb * colDiffuse.rgb; + vec3 norm = normalize(fragNormal); + + float lightAccum = 0.08; // ambient floor + + for (int i = 0; i < 4; i++) { + if (lights[i].enabled == 0) continue; + + vec3 lightDir; + if (lights[i].type == 0) { + // Directional: direction is from position toward target. + lightDir = normalize(lights[i].position - lights[i].target); + } else { + // Point: direction from surface to light. + lightDir = normalize(lights[i].position - fragPosition); + } + + float NdotL = max(dot(norm, lightDir), 0.0); + + // Quantize NdotL into numBands discrete steps. + // min() guards against NdotL == 1.0 producing an out-of-range index. + float quantized = min(floor(NdotL * numBands), numBands - 1.0) / (numBands - 1.0); + lightAccum += quantized * lights[i].color.r; + } + + lightAccum = clamp(lightAccum, 0.0, 1.0); + finalColor = vec4(baseColor * lightAccum, texColor.a * colDiffuse.a); +} diff --git a/examples/shaders/resources/shaders/glsl330/cel.vs b/examples/shaders/resources/shaders/glsl330/cel.vs new file mode 100644 index 000000000..37a8d200d --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/cel.vs @@ -0,0 +1,25 @@ +#version 330 + +// Raylib standard attributes +in vec3 vertexPosition; +in vec2 vertexTexCoord; +in vec3 vertexNormal; +in vec4 vertexColor; + +// Raylib standard uniforms +uniform mat4 mvp; +uniform mat4 matModel; +uniform mat4 matNormal; + +out vec3 fragPosition; +out vec2 fragTexCoord; +out vec4 fragColor; +out vec3 fragNormal; + +void main() { + fragPosition = vec3(matModel * vec4(vertexPosition, 1.0)); + fragTexCoord = vertexTexCoord; + fragColor = vertexColor; + fragNormal = normalize(vec3(matNormal * vec4(vertexNormal, 0.0))); + gl_Position = mvp * vec4(vertexPosition, 1.0); +} diff --git a/examples/shaders/resources/shaders/glsl330/outline_hull.fs b/examples/shaders/resources/shaders/glsl330/outline_hull.fs new file mode 100644 index 000000000..c8a33de08 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/outline_hull.fs @@ -0,0 +1,7 @@ +#version 330 + +out vec4 finalColor; + +void main() { + finalColor = vec4(0.05, 0.05, 0.05, 1.0); +} diff --git a/examples/shaders/resources/shaders/glsl330/outline_hull.vs b/examples/shaders/resources/shaders/glsl330/outline_hull.vs new file mode 100644 index 000000000..2e350201e --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/outline_hull.vs @@ -0,0 +1,15 @@ +#version 330 + +in vec3 vertexPosition; +in vec3 vertexNormal; +in vec2 vertexTexCoord; +in vec4 vertexColor; + +uniform mat4 mvp; +uniform float outlineThickness; + +void main() { + // Extrude vertex along its normal to create the hull. + vec3 extruded = vertexPosition + vertexNormal * outlineThickness; + gl_Position = mvp * vec4(extruded, 1.0); +} diff --git a/examples/shaders/shaders_cel_shading.c b/examples/shaders/shaders_cel_shading.c new file mode 100644 index 000000000..cab86a26f --- /dev/null +++ b/examples/shaders/shaders_cel_shading.c @@ -0,0 +1,202 @@ +/******************************************************************************************* +* +* raylib [shaders] example - cel shading +* +* Example complexity rating: [★★★☆] 3/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 +* +* NOTE: Shaders used in this example are #version 330 (OpenGL 3.3) +* +* Example contributed by Gleb A (@ggrizzly) +* +* 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) 2015-2026 Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" +#include "rlgl.h" +#include +#include + +#define RLIGHTS_IMPLEMENTATION +#include "rlights.h" + +#if defined(PLATFORM_DESKTOP) + #define GLSL_VERSION 330 +#else // PLATFORM_ANDROID, PLATFORM_WEB + #define GLSL_VERSION 100 +#endif + +//------------------------------------------------------------------------------------ +// Model table: path, optional diffuse texture path (NULL = embedded), draw scale +//------------------------------------------------------------------------------------ +typedef struct { + const char *modelPath; + const char *texturePath; // NULL for GLB files with embedded textures + float scale; + float outlineThickness; +} ModelInfo; + +static const ModelInfo MODEL = { "resources/models/old_car_new.glb", NULL, 0.75f, 0.005f }; + + +//------------------------------------------------------------------------------------ +// Load model and its diffuse texture (if any). Does NOT assign a shader. +//------------------------------------------------------------------------------------ +static Model celLoadModel() +{ + Model model = LoadModel(MODEL.modelPath); + + if (MODEL.texturePath != NULL) + { + Texture2D tex = LoadTexture(MODEL.texturePath); + model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = tex; + } + + return model; +} + +static void ApplyShaderToModel(Model model, Shader shader) +{ + model.materials[0].shader = shader; +} + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + SetConfigFlags(FLAG_MSAA_4X_HINT); + InitWindow(screenWidth, screenHeight, "raylib [shaders] example - cel shading"); + + Camera camera = { 0 }; + camera.position = (Vector3){ 9.0f, 6.0f, 9.0f }; + camera.target = (Vector3){ 0.0f, 1.0f, 0.0f }; + camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; + camera.fovy = 45.0f; + camera.projection = CAMERA_PERSPECTIVE; + + // Load cel shader + Shader celShader = LoadShader(TextFormat("resources/shaders/glsl%i/cel.vs", GLSL_VERSION), + TextFormat("resources/shaders/glsl%i/cel.fs", GLSL_VERSION)); + celShader.locs[SHADER_LOC_VECTOR_VIEW] = GetShaderLocation(celShader, "viewPos"); + + // numBands: controls toon quantization steps (2 = hard binary, 20 = near-smooth) + float numBands = 10.0f; + int numBandsLoc = GetShaderLocation(celShader, "numBands"); + SetShaderValue(celShader, numBandsLoc, &numBands, SHADER_UNIFORM_FLOAT); + + // Inverted-hull outline shader: draws back faces extruded along normals + Shader outlineShader = LoadShader( + TextFormat("resources/shaders/glsl%i/outline_hull.vs", GLSL_VERSION), + TextFormat("resources/shaders/glsl%i/outline_hull.fs", GLSL_VERSION)); + int outlineThicknessLoc = GetShaderLocation(outlineShader, "outlineThickness"); + + // Single directional white light, angled so toon bands are visible on the model sides. + // Spins opposite to CAMERA_ORBITAL (0.5 rad/s) so lighting changes as you watch. + Light lights[MAX_LIGHTS] = { 0 }; + lights[0] = CreateLight(LIGHT_DIRECTIONAL, (Vector3){ 50.0f, 50.0f, 50.0f }, Vector3Zero(), WHITE, celShader); + + + bool celEnabled = true; + bool outlineEnabled = true; + + Model model = celLoadModel(); + Shader defaultShader = model.materials[0].shader; + ApplyShaderToModel(model, celShader); + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) + { + // Update + //---------------------------------------------------------------------------------- + UpdateCamera(&camera, CAMERA_ORBITAL); + + float cameraPos[3] = { camera.position.x, camera.position.y, camera.position.z }; + SetShaderValue(celShader, celShader.locs[SHADER_LOC_VECTOR_VIEW], cameraPos, SHADER_UNIFORM_VEC3); + + // [Z] Toggle cel shading on/off + if (IsKeyPressed(KEY_Z)) + { + celEnabled = !celEnabled; + ApplyShaderToModel(model, celEnabled ? celShader : defaultShader); + } + + // [C] Toggle outline on/off + if (IsKeyPressed(KEY_C)) outlineEnabled = !outlineEnabled; + + // [Q/E] Decrease/increase toon band count (press or hold to repeat) + if (IsKeyPressed(KEY_E) || IsKeyPressedRepeat(KEY_E)) numBands = Clamp(numBands + 1.0f, 2.0f, 20.0f); + if (IsKeyPressed(KEY_Q) || IsKeyPressedRepeat(KEY_Q)) numBands = Clamp(numBands - 1.0f, 2.0f, 20.0f); + SetShaderValue(celShader, numBandsLoc, &numBands, SHADER_UNIFORM_FLOAT); + + // Spin light opposite to CAMERA_ORBITAL (0.5 rad/s), angled 45 degrees off vertical + float t = (float)GetTime(); + lights[0].position = (Vector3){ + sinf(-t * 0.3f) * 5.0f, + 5.0f, + cosf(-t * 0.3f) * 5.0f + }; + + for (int i = 0; i < MAX_LIGHTS; i++) UpdateLightValues(celShader, lights[i]); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + BeginMode3D(camera); + + if (outlineEnabled) + { + // Outline pass: cull front faces, draw extruded back faces as silhouette + float thickness = MODEL.outlineThickness; + SetShaderValue(outlineShader, outlineThicknessLoc, &thickness, SHADER_UNIFORM_FLOAT); + rlSetCullFace(RL_CULL_FACE_FRONT); + ApplyShaderToModel(model, outlineShader); + DrawModel(model, Vector3Zero(), MODEL.scale, WHITE); + ApplyShaderToModel(model, celEnabled ? celShader : defaultShader); + rlSetCullFace(RL_CULL_FACE_BACK); + } + + DrawModel(model, Vector3Zero(), MODEL.scale, WHITE); + DrawSphereEx(lights[0].position, 0.2f, 50, 50, YELLOW); // Light position indicator + DrawGrid(10, 10.0f); + + EndMode3D(); + + DrawFPS(10, 10); + DrawText(TextFormat("Cel: %s [Z]", celEnabled ? "ON" : "OFF"), 10, 65, 20, celEnabled ? DARKGREEN : DARKGRAY); + DrawText(TextFormat("Outline: %s [C]", outlineEnabled ? "ON" : "OFF"), 10, 90, 20, outlineEnabled ? DARKGREEN : DARKGRAY); + DrawText(TextFormat("Bands: %.0f [Q/E]", numBands), 10, 115, 20, DARKGRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadModel(model); + UnloadShader(celShader); + UnloadShader(outlineShader); + CloseWindow(); + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/shaders/shaders_cel_shading.png b/examples/shaders/shaders_cel_shading.png new file mode 100644 index 0000000000000000000000000000000000000000..51b98225e093acc13d235192e2213a3c760da4a1 GIT binary patch literal 45710 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� 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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 literal 0 HcmV?d00001