diff --git a/examples/Makefile b/examples/Makefile index 32a3a75ab..a77a7ce65 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -631,6 +631,7 @@ SHADERS = \ shaders/shaders_mesh_instancing \ shaders/shaders_model_shader \ shaders/shaders_multi_sample2d \ + shaders/shaders_normalmap \ shaders/shaders_palette_switch \ shaders/shaders_postprocessing \ shaders/shaders_raymarching \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 35ae70a18..267832d25 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -513,6 +513,7 @@ SHADERS = \ shaders/shaders_mesh_instancing \ shaders/shaders_model_shader \ shaders/shaders_multi_sample2d \ + shaders/shaders_normalmap \ shaders/shaders_palette_switch \ shaders/shaders_postprocessing \ shaders/shaders_raymarching \ diff --git a/examples/README.md b/examples/README.md index 7553ad113..f29362ef6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -202,6 +202,7 @@ Examples using raylib shaders functionality, including shaders loading, paramete | 141 | [shaders_lightmap](shaders/shaders_lightmap.c) | shaders_lightmap | ⭐️⭐️⭐️☆ | 4.5 | 4.5 | [Jussi Viitala](https://github.com/nullstare) | | 142 | [shaders_rounded_rectangle](shaders/shaders_rounded_rectangle.c) | shaders_rounded_rectangle | ⭐️⭐️⭐️☆ | 5.5 | 5.5 | [Anstro Pleuton](https://github.com/anstropleuton) | | 143 | [shaders_view_depth](shaders/shaders_view_depth.c) | shaders_view_depth | ⭐️⭐️⭐️☆ | 5.6-dev | 5.6-dev | [Luís Almeida](https://github.com/luis605) | +| 144 | [shaders_normalmap](shaders/shaders_normalmap.c) | shaders_normalmap | ⭐️⭐️⭐️⭐️ | 5.6-dev | 5.6-dev | [Jeremy Montgomery](https://github.com/Sir-Irk) | ### category: audio @@ -209,14 +210,14 @@ Examples using raylib audio functionality, including sound/music loading and pla | ## | example | image | difficulty
level | version
created | last version
updated | original
developer | |----|----------|--------|:-------------------:|:------------------:|:-----------------------:|:----------------------| -| 144 | [audio_module_playing](audio/audio_module_playing.c) | audio_module_playing | ⭐️☆☆☆ | 1.5 | 3.5 | [Ray](https://github.com/raysan5) | -| 145 | [audio_music_stream](audio/audio_music_stream.c) | audio_music_stream | ⭐️☆☆☆ | 1.3 | 4.2 | [Ray](https://github.com/raysan5) | -| 146 | [audio_raw_stream](audio/audio_raw_stream.c) | audio_raw_stream | ⭐️⭐️⭐️☆ | 1.6 | 4.2 | [Ray](https://github.com/raysan5) | -| 147 | [audio_sound_loading](audio/audio_sound_loading.c) | audio_sound_loading | ⭐️☆☆☆ | 1.1 | 3.5 | [Ray](https://github.com/raysan5) | -| 148 | [audio_mixed_processor](audio/audio_mixed_processor.c) | audio_mixed_processor | ⭐️⭐️⭐️⭐️ | 4.2 | 4.2 | [hkc](https://github.com/hatkidchan) | -| 149 | [audio_stream_effects](audio/audio_stream_effects.c) | audio_stream_effects | ⭐️⭐️⭐️⭐️ | 4.2 | 5.0 | [Ray](https://github.com/raysan5) | -| 150 [audio_sound_multi](audio/audio_sound_multi.c) | audio_sound_multi | ⭐️⭐️☆☆ | 4.6 | 4.6 | [Jeffery Myers](https://github.com/JeffM2501) | -| 151 | [audio_sound_positioning](audio/audio_sound_positioning.c) | audio_sound_positioning | ⭐️⭐️☆☆ | 5.5 | 5.5 | [Le Juez Victor](https://github.com/Bigfoot71) | +| 145 | [audio_module_playing](audio/audio_module_playing.c) | audio_module_playing | ⭐️☆☆☆ | 1.5 | 3.5 | [Ray](https://github.com/raysan5) | +| 146 | [audio_music_stream](audio/audio_music_stream.c) | audio_music_stream | ⭐️☆☆☆ | 1.3 | 4.2 | [Ray](https://github.com/raysan5) | +| 147 | [audio_raw_stream](audio/audio_raw_stream.c) | audio_raw_stream | ⭐️⭐️⭐️☆ | 1.6 | 4.2 | [Ray](https://github.com/raysan5) | +| 148 | [audio_sound_loading](audio/audio_sound_loading.c) | audio_sound_loading | ⭐️☆☆☆ | 1.1 | 3.5 | [Ray](https://github.com/raysan5) | +| 149 | [audio_mixed_processor](audio/audio_mixed_processor.c) | audio_mixed_processor | ⭐️⭐️⭐️⭐️ | 4.2 | 4.2 | [hkc](https://github.com/hatkidchan) | +| 150 | [audio_stream_effects](audio/audio_stream_effects.c) | audio_stream_effects | ⭐️⭐️⭐️⭐️ | 4.2 | 5.0 | [Ray](https://github.com/raysan5) | +| 151 | [audio_sound_multi](audio/audio_sound_multi.c) | audio_sound_multi | ⭐️⭐️☆☆ | 4.6 | 4.6 | [Jeffery Myers](https://github.com/JeffM2501) | +| 152 | [audio_sound_positioning](audio/audio_sound_positioning.c) | audio_sound_positioning | ⭐️⭐️☆☆ | 5.5 | 5.5 | [Le Juez Victor](https://github.com/Bigfoot71) | ### category: others @@ -224,11 +225,11 @@ Ex150amples showing raylib misc functionality that does not fit in other categor | ## | example | image | difficulty
level | version
created | last version
updated | original
developer | |----|----------|--------|:-------------------:|:------------------:|:-----------------------:|:----------------------| -| 152 | [rlgl_standalone](others/rlgl_standalone.c) | rlgl_standalone | ⭐️⭐️⭐️⭐️ | 1.6 | 4.0 | [Ray](https://github.com/raysan5) | -| 153 | [rlgl_compute_shader](others/rlgl_compute_shader.c) | rlgl_compute_shader | ⭐️⭐️⭐️⭐️ | 4.0 | 4.0 | [Teddy Astie](https://github.com/tsnake41) | -| 154 | [easings_testbed](others/easings_testbed.c) | easings_testbed | ⭐️⭐️⭐️☆ | 2.5 | 3.0 | [Juan Miguel López](https://github.com/flashback-fx) | -| 155 | [raylib_opengl_interop](others/raylib_opengl_interop.c) | raylib_opengl_interop | ⭐️⭐️⭐️⭐️ | 3.8 | 4.0 | [Stephan Soller](https://github.com/arkanis) | -| 156 | [embedded_files_loading](others/embedded_files_loading.c) | embedded_files_loading | ⭐️⭐️☆☆ | 3.0 | 3.5 | [Kristian Holmgren](https://github.com/defutura) | -| 157 | [raymath_vector_angle](others/raymath_vector_angle.c) | raymath_vector_angle | ⭐️⭐️☆☆ | 1.0 | 4.6 | [Ray](https://github.com/raysan5) | +| 153 | [rlgl_standalone](others/rlgl_standalone.c) | rlgl_standalone | ⭐️⭐️⭐️⭐️ | 1.6 | 4.0 | [Ray](https://github.com/raysan5) | +| 154 | [rlgl_compute_shader](others/rlgl_compute_shader.c) | rlgl_compute_shader | ⭐️⭐️⭐️⭐️ | 4.0 | 4.0 | [Teddy Astie](https://github.com/tsnake41) | +| 155 | [easings_testbed](others/easings_testbed.c) | easings_testbed | ⭐️⭐️⭐️☆ | 2.5 | 3.0 | [Juan Miguel López](https://github.com/flashback-fx) | +| 156 | [raylib_opengl_interop](others/raylib_opengl_interop.c) | raylib_opengl_interop | ⭐️⭐️⭐️⭐️ | 3.8 | 4.0 | [Stephan Soller](https://github.com/arkanis) | +| 157 | [embedded_files_loading](others/embedded_files_loading.c) | embedded_files_loading | ⭐️⭐️☆☆ | 3.0 | 3.5 | [Kristian Holmgren](https://github.com/defutura) | +| 158 | [raymath_vector_angle](others/raymath_vector_angle.c) | raymath_vector_angle | ⭐️⭐️☆☆ | 1.0 | 4.6 | [Ray](https://github.com/raysan5) | As always contributions are welcome, feel free to send new examples! Here is an [examples template](examples_template.c) to start with! diff --git a/examples/shaders/resources/shaders/glsl100/normalmap.fs b/examples/shaders/resources/shaders/glsl100/normalmap.fs new file mode 100644 index 000000000..02902a61f --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/normalmap.fs @@ -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)); +} diff --git a/examples/shaders/resources/shaders/glsl100/normalmap.vs b/examples/shaders/resources/shaders/glsl100/normalmap.vs new file mode 100644 index 000000000..072e0a6c2 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/normalmap.vs @@ -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); +} diff --git a/examples/shaders/resources/shaders/glsl120/normalmap.fs b/examples/shaders/resources/shaders/glsl120/normalmap.fs new file mode 100644 index 000000000..caeb4d5c1 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/normalmap.fs @@ -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)); +} diff --git a/examples/shaders/resources/shaders/glsl120/normalmap.vs b/examples/shaders/resources/shaders/glsl120/normalmap.vs new file mode 100644 index 000000000..d8921a68f --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/normalmap.vs @@ -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); +} diff --git a/examples/shaders/resources/shaders/glsl330/normalmap.fs b/examples/shaders/resources/shaders/glsl330/normalmap.fs new file mode 100644 index 000000000..644130ab6 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/normalmap.fs @@ -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); +} diff --git a/examples/shaders/resources/shaders/glsl330/normalmap.vs b/examples/shaders/resources/shaders/glsl330/normalmap.vs new file mode 100644 index 000000000..1e0a161b8 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/normalmap.vs @@ -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); +} diff --git a/examples/shaders/resources/tiles_diffuse.png b/examples/shaders/resources/tiles_diffuse.png new file mode 100644 index 000000000..1c7b84035 Binary files /dev/null and b/examples/shaders/resources/tiles_diffuse.png differ diff --git a/examples/shaders/resources/tiles_normal.png b/examples/shaders/resources/tiles_normal.png new file mode 100644 index 000000000..719cb2e0d Binary files /dev/null and b/examples/shaders/resources/tiles_normal.png differ diff --git a/examples/shaders/shaders_normalmap.c b/examples/shaders/shaders_normalmap.c new file mode 100644 index 000000000..96cbf414a --- /dev/null +++ b/examples/shaders/shaders_normalmap.c @@ -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 +#include + +#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; +} diff --git a/examples/shaders/shaders_normalmap.png b/examples/shaders/shaders_normalmap.png new file mode 100644 index 000000000..b2db75eec Binary files /dev/null and b/examples/shaders/shaders_normalmap.png differ