diff --git a/punyverse/assets/textures/earth_normal.jpg b/punyverse/assets/textures/earth_normal.jpg
index 674350c..44d98cd 100644
Binary files a/punyverse/assets/textures/earth_normal.jpg and b/punyverse/assets/textures/earth_normal.jpg differ
diff --git a/punyverse/entity.py b/punyverse/entity.py
index 7611487..73df6a2 100644
--- a/punyverse/entity.py
+++ b/punyverse/entity.py
@@ -292,12 +292,16 @@ class SphericalBody(Body):
         self.type = info.get('type', 'planet')
 
         self.texture = get_best_texture(info['texture'])
+        self.normal_texture = None
         self.sphere = self._get_sphere(division, tangent=self.type == 'planet')
 
         self.atmosphere = None
         self.clouds = None
         self.ring = 0
 
+        if 'normal_map' in info:
+            self.normal_texture = get_best_texture(info['normal_map'])
+
         if 'atmosphere' in info:
             atmosphere_data = info['atmosphere']
             atm_size = world.evaluate(atmosphere_data.get('diffuse_size', None))
@@ -333,10 +337,14 @@ class SphericalBody(Body):
 
         glActiveTexture(GL_TEXTURE0)
         glBindTexture(GL_TEXTURE_2D, self.texture)
-        shader.uniform_bool('u_planet.hasDiffuse', True)
         shader.uniform_texture('u_planet.diffuseMap', 0)
 
-        shader.uniform_bool('u_planet.hasNormal', False)
+        shader.uniform_bool('u_planet.hasNormal', self.normal_texture)
+        if self.normal_texture:
+            glActiveTexture(GL_TEXTURE1)
+            glBindTexture(GL_TEXTURE_2D, self.normal_texture)
+            shader.uniform_texture('u_planet.normalMap', 1)
+
         shader.uniform_bool('u_planet.hasSpecular', False)
         shader.uniform_bool('u_planet.hasEmission', False)
 
diff --git a/punyverse/shaders/planet.fragment.glsl b/punyverse/shaders/planet.fragment.glsl
index 7335681..5a2dc79 100644
--- a/punyverse/shaders/planet.fragment.glsl
+++ b/punyverse/shaders/planet.fragment.glsl
@@ -7,7 +7,6 @@ in vec3 v_camDirection;
 in mat3 v_TBN;
 
 struct Surface {
-    bool hasDiffuse;
     sampler2D diffuseMap;
     bool hasNormal;
     sampler2D normalMap;
@@ -35,7 +34,7 @@ uniform Surface u_planet;
 
 void main() {
     vec3 normal = u_planet.hasNormal ? normalize(v_TBN * texture2D(u_planet.normalMap, v_uv).rgb * 2 - 1) : v_normal;
-    vec3 diffuse = u_planet.hasDiffuse ? texture2D(u_planet.diffuseMap, v_uv).rgb : vec3(1);
+    vec3 diffuse = texture2D(u_planet.diffuseMap, v_uv).rgb;
     vec3 specular = u_planet.hasSpecular ? texture2D(u_planet.specularMap, v_uv).rgb : vec3(1);
     vec3 emission = u_planet.hasEmission ? texture2D(u_planet.emissionMap, v_uv).rgb : vec3(1);
 
diff --git a/punyverse/texture.py b/punyverse/texture.py
index 274ad70..b1d9dd4 100644
--- a/punyverse/texture.py
+++ b/punyverse/texture.py
@@ -249,7 +249,7 @@ def load_clouds(file):
     return id
 
 
-def get_best_texture(info, loader=load_texture, **kwargs):
+def get_best_texture(info, loader=load_texture, optional=False, **kwargs):
     if isinstance(info, list):
         for item in info:
             try:
@@ -258,4 +258,5 @@ def get_best_texture(info, loader=load_texture, **kwargs):
                 pass
     else:
         return loader(info, **kwargs)
-    raise ValueError('No texture found')
+    if not optional:
+        raise ValueError('No texture found')
diff --git a/punyverse/world.json b/punyverse/world.json
index 00f6d79..0ccdee4 100644
--- a/punyverse/world.json
+++ b/punyverse/world.json
@@ -56,7 +56,7 @@
       "mass": 5.97219e+24,
       "rotation": 86400,
       "division": 90,
-      "normal": "earth_normal.jpg",
+      "normal_map": ["earth_normal.jpg", "earth_normal_small.jpg"],
       "atmosphere": {
         "cloud_texture": ["cloudmap.jpg", "cloudmap_small.jpg"],
         "diffuse_texture": "atmosphere_earth.png",