Explorar el Código

read skyboxes as materials, fix #38

Ivan Avdeev hace 6 años
padre
commit
4b60100b13
Se han modificado 7 ficheros con 143 adiciones y 168 borrados
  1. 2 3
      Makefile
  2. 1 0
      src/ahash.h
  3. 8 18
      src/bsp.c
  4. 2 2
      src/bsp.h
  5. 26 19
      src/material.c
  6. 23 16
      src/material.h
  7. 81 110
      src/render.c

+ 2 - 3
Makefile

@@ -7,7 +7,6 @@ MAX_MAPS ?= 99
 ARGS ?= -p $(VPKDIR)/hl2_textures_dir.vpk -p $(VPKDIR)/hl2_misc_dir.vpk -p $(VPKDIR)/hl2_pak_dir.vpk -d $(VPKDIR) -n $(MAX_MAPS)
 
 CFLAGS += -Wall -Wextra -D_GNU_SOURCE -Isrc/atto -fPIE
-LIBS = -lX11 -lXfixes -lGL -lm -pthread
 BUILDDIR ?= build
 
 ifeq ($(DEBUG), 1)
@@ -41,7 +40,7 @@ ifeq ($(RASPBERRY), 1)
 
 	CFLAGS += -I$(RPI_VCDIR)/include -I$(RPI_VCDIR)/include/interface/vcos/pthreads
 	CFLAGS += -I$(RPI_VCDIR)/include/interface/vmcs_host/linux -DATTO_PLATFORM_RPI
-	LIBS = -lGLESv2 -lEGL -lbcm_host -lvcos -lvchiq_arm -L$(RPI_VCDIR)/lib -lrt -lm
+	LIBS += -lGLESv2 -lEGL -lbcm_host -lvcos -lvchiq_arm -L$(RPI_VCDIR)/lib -lrt -lm
 
 	SOURCES += \
 		src/atto/src/app_linux.c \
@@ -50,7 +49,7 @@ ifeq ($(RASPBERRY), 1)
 else
 	PLATFORM = desktop
 	CC ?= cc
-	LIBS = -lX11 -lXfixes -lGL -lm -pthread
+	LIBS += -lX11 -lXfixes -lGL -lm -pthread
 	SOURCES += \
 		src/atto/src/app_linux.c \
 		src/atto/src/app_x11.c

+ 1 - 0
src/ahash.h

@@ -103,6 +103,7 @@ void *aHashInsert(AHash *hash, const void *key, const void *value) {
 
 	void *const new_item = hash->alloc(hash->alloc_param, hash->impl_.item_size);
 	const struct AHashItemData_ item_data = a__hashGetItemData(hash, new_item);
+	// FIXME key is string and is < key_size most of the time
 	memcpy(item_data.key, key, hash->key_size);
 	memcpy(item_data.value, value, hash->value_size);
 	item_data.next[0] = 0;

+ 8 - 18
src/bsp.c

@@ -81,7 +81,7 @@ struct Face {
 	const struct VBSPLumpDispInfo *dispinfo;
 	int dispquadvtx[4]; // filled only when displaced
 	int dispstartvtx;
-	const struct Material *material;
+	const Material *material;
 
 	/* filled as a result of atlas allocation */
 	int atlas_x, atlas_y;
@@ -112,7 +112,7 @@ enum FacePreload {
 };
 
 static struct {
-	const struct Material *coarse_material;
+	const Material *coarse_material;
 	struct {
 		int color[256];
 		int exponent[256];
@@ -579,7 +579,7 @@ static int faceMaterialCompare(const void *a, const void *b) {
 	if (fa->material == fb->material)
 		return 0;
 
-	return fa->material->base_texture[0] - fb->material->base_texture[0];
+	return fa->material->base_texture.texture - fb->material->base_texture.texture;
 }
 
 static enum BSPLoadResult bspLoadModelDraws(const struct LoadModelContext *ctx, struct Stack *persistent,
@@ -737,16 +737,8 @@ static enum BSPLoadResult bspLoadModel(
 	return BSPLoadResult_Success;
 } // bspLoadModel()
 
-static const struct {
-	const char *suffix;
-	BSPSkyboxDir dir;
-} bsp_skybox_suffix[6] = {
-	{"rt", BSPSkyboxDir_RT},
-	{"lf", BSPSkyboxDir_LF},
-	{"ft", BSPSkyboxDir_FT},
-	{"bk", BSPSkyboxDir_BK},
-	{"up", BSPSkyboxDir_UP},
-	{"dn", BSPSkyboxDir_DN}};
+static const char *bsp_skybox_suffix[6] = {
+	"rt", "lf", "ft", "bk", "up", "dn" };
 
 static void bspLoadSkybox(StringView name, ICollection *coll, Stack *tmp, struct BSPModel *model) {
 	PRINTF("Loading skybox %.*s", name.length, name.str);
@@ -756,11 +748,9 @@ static void bspLoadSkybox(StringView name, ICollection *coll, Stack *tmp, struct
 	memcpy(zname, "skybox/", 7);
 	memcpy(zname + 7, name.str, name.length);
 
-	Texture localtex;
-	renderTextureInit(&localtex.texture);
 	for (int i = 0; i < 6; ++i) {
-		memcpy(zname + name.length + 7, bsp_skybox_suffix[i].suffix, 2);
-		model->skybox[bsp_skybox_suffix[i].dir] = textureGet(zname, coll, tmp);
+		memcpy(zname + name.length + 7, bsp_skybox_suffix[i], 2);
+		model->skybox[i] = materialGet(zname, coll, tmp);
 	}
 }
 
@@ -840,7 +830,7 @@ BSPLoadResult bspProcessEntityWorldspawn(struct BSPLoadModelContext *ctx, const
 
 	if (skyname.length > 0) {
 		const StringView sky = { skyname.str, skyname.length };
-		bspLoadSkybox(sky, ctx->collection, ctx->persistent, ctx->model);
+		bspLoadSkybox(sky, ctx->collection, ctx->tmp, ctx->model);
 	}
 
 	return BSPLoadResult_Success;

+ 2 - 2
src/bsp.h

@@ -14,7 +14,7 @@ struct BSPModelVertex {
 };
 
 struct BSPDraw {
-	const struct Material *material;
+	const Material *material;
 	unsigned int start, count;
 	unsigned int vbo_offset;
 };
@@ -47,7 +47,7 @@ struct BSPModel {
 	RTexture lightmap;
 	RBuffer vbo, ibo;
 
-	const struct Texture *skybox[BSPSkyboxDir_COUNT];
+	const Material *skybox[BSPSkyboxDir_COUNT];
 
 	struct BSPDrawSet detailed;
 	struct BSPDrawSet coarse;

+ 26 - 19
src/material.c

@@ -9,7 +9,7 @@ typedef struct {
 	ICollection *collection;
 	Stack *temp;
 	StringView shader;
-	struct Material *mat;
+	Material *mat;
 	int depth;
 } MaterialContext;
 
@@ -18,11 +18,11 @@ static VMFAction materialReadKeyValue(MaterialContext *ctx, const VMFKeyValue *k
 		return VMFAction_SemanticError;
 
 	char value[128];
+	memset(value, 0, sizeof value);
 	memcpy(value, kv->value.str, kv->value.length);
-	value[kv->value.length] = '\0';
 
 	if (strncasecmp("$basetexture", kv->key.str, kv->key.length) == 0) {
-		ctx->mat->base_texture[0] = textureGet(value, ctx->collection, ctx->temp);
+		ctx->mat->base_texture.texture = textureGet(value, ctx->collection, ctx->temp);
 	} else if (strncasecmp("include", kv->key.str, kv->key.length) == 0) {
 		char *vmt = strstr(value, ".vmt");
 		if (vmt)
@@ -85,10 +85,19 @@ static VMFAction materialShaderCallback(VMFState *state, VMFEntryType entry, con
 	return retval;
 }
 
+static void mtextureInit(MTexture *t) {
+	memset(t, 0, sizeof(*t));
+	t->transform.scale = aVec2f(1.f, 1.f);
+}
+
 static VMFAction materialParserCallback(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv) {
 	//PRINTF("Entry %d (%.*s -> %.*s)", entry, PRI_SVV(kv->key), PRI_SVV(kv->value));
 	MaterialContext *ctx = state->user_data;
 
+	ctx->mat->average_color = aVec3f(0,1,1);
+	ctx->mat->shader = MShader_Unknown;
+	mtextureInit(&ctx->mat->base_texture);
+
 	VMFAction retval = VMFAction_SemanticError;
 
 	switch (entry) {
@@ -100,6 +109,14 @@ static VMFAction materialParserCallback(VMFState *state, VMFEntryType entry, con
 				state->callback = materialPatchCallback;
 			} else {
 				ctx->shader = kv->key;
+				if (strncasecmp("unlitgeneric", kv->key.str, kv->key.length) == 0
+					|| strncasecmp("sky", kv->key.str, kv->key.length) == 0)
+					ctx->mat->shader = MShader_UnlitGeneric;
+				else if (strncasecmp("lightmappedgeneric", kv->key.str, kv->key.length) == 0
+					|| strncasecmp("worldvertextransition", kv->key.str, kv->key.length) == 0)
+					ctx->mat->shader = MShader_LightmappedGeneric;
+				else
+					PRINTF("Unknown material shader " PRI_SV, PRI_SVV(kv->key));
 				state->callback = materialShaderCallback;
 			}
 			retval = VMFAction_Continue;
@@ -111,7 +128,7 @@ static VMFAction materialParserCallback(VMFState *state, VMFEntryType entry, con
 	return retval;
 }
 
-static int materialLoad(struct IFile *file, struct ICollection *coll, struct Material *output, struct Stack *tmp) {
+static int materialLoad(struct IFile *file, struct ICollection *coll, Material *output, struct Stack *tmp) {
 	char *buffer = stackAlloc(tmp, file->size);
 
 	if (!buffer) {
@@ -137,26 +154,16 @@ static int materialLoad(struct IFile *file, struct ICollection *coll, struct Mat
 
 	const int success = VMFResult_Success == vmfParse(&parser_state);
 
-	if (success) {
-		if (!ctx.mat->base_texture[0]) {
-			PRINTF("Material with ctx.shader %.*s doesn't have base texture", ctx.shader.length, ctx.shader.str);
-			ctx.mat->shader = MaterialShader_LightmappedAverageColor;
-			// HACK to notice these materials
-			ctx.mat->average_color = aVec3f(1.f, 0.f, 1.f);
-		} else {
-			ctx.mat->shader = MaterialShader_LightmappedGeneric;
-			ctx.mat->average_color = ctx.mat->base_texture[0]->avg_color;
-		}
-	}
-
+	if (success && ctx.mat->base_texture.texture)
+		ctx.mat->average_color = ctx.mat->base_texture.texture->avg_color;
 
 	stackFreeUpToPosition(tmp, buffer);
 
 	return success;
 }
 
-const struct Material *materialGet(const char *name, struct ICollection *collection, struct Stack *tmp) {
-	const struct Material *mat = cacheGetMaterial(name);
+const Material *materialGet(const char *name, struct ICollection *collection, struct Stack *tmp) {
+	const Material *mat = cacheGetMaterial(name);
 	if (mat) return mat;
 
 	struct IFile *matfile;
@@ -165,7 +172,7 @@ const struct Material *materialGet(const char *name, struct ICollection *collect
 		return cacheGetMaterial("opensource/placeholder");
 	}
 
-	struct Material localmat;
+	Material localmat;
 	memset(&localmat, 0, sizeof localmat);
 	if (materialLoad(matfile, collection, &localmat, tmp) == 0) {
 		PRINTF("Material \"%s\" found, but could not be loaded", name);

+ 23 - 16
src/material.h

@@ -5,22 +5,29 @@ struct ICollection;
 struct Texture;
 struct Stack;
 
-enum MaterialShader {
-	MaterialShader_LightmappedAverageColor,
-	MaterialShader_LightmappedGeneric,
-};
+typedef struct AVec2f AVec2f;
 
-struct Material {
-	enum MaterialShader shader;
+typedef enum {
+	MShader_Unknown,
+	MShader_LightmappedOnly,
+	MShader_LightmappedGeneric,
+	MShader_UnlitGeneric,
+
+	MShader_COUNT
+} MShader;
+
+typedef struct {
+	const struct Texture *texture;
+	struct {
+		AVec2f translate;
+		AVec2f scale;
+	} transform;
+} MTexture;
+
+typedef struct Material {
+	MShader shader;
 	struct AVec3f average_color;
-	const struct Texture *base_texture[2];
-	/* TODO:
-	 * - bump
-	 * - detail
-	 * - texture transforms
-	 * - special material type (e.g. water, translucent, ...)
-	 * ...
-	 */
-};
+	MTexture base_texture;
+} Material;
 
-const struct Material *materialGet(const char *name, struct ICollection *collection, struct Stack *tmp);
+const Material *materialGet(const char *name, struct ICollection *collection, struct Stack *tmp);

+ 81 - 110
src/render.c

@@ -266,17 +266,10 @@ RENDER_LIST_ATTRIBS
 
 #define RENDER_LIST_UNIFORMS \
 	RENDER_DECLARE_UNIFORM(mvp) \
-	RENDER_DECLARE_UNIFORM(lmn) \
 	RENDER_DECLARE_UNIFORM(far) \
 	RENDER_DECLARE_UNIFORM(lightmap) \
 	RENDER_DECLARE_UNIFORM(tex0) \
-	RENDER_DECLARE_UNIFORM(tex1) \
 	RENDER_DECLARE_UNIFORM(tex0_size) \
-	RENDER_DECLARE_UNIFORM(tex1_size) \
-	RENDER_DECLARE_UNIFORM(tex2) \
-	RENDER_DECLARE_UNIFORM(tex3) \
-	RENDER_DECLARE_UNIFORM(tex4) \
-	RENDER_DECLARE_UNIFORM(tex5) \
 
 static const RUniform uniforms[] = {
 #define RENDER_DECLARE_UNIFORM(n) {"u_" # n},
@@ -300,15 +293,26 @@ typedef struct RProgram {
 	int uniform_locations[RUniformKind_COUNT];
 } RProgram;
 
-enum {
-	Program_LightmapColor,
-	Program_LightmapTexture,
-	Program_Skybox,
-	Program_COUNT
-};
-
-static RProgram programs[Program_COUNT] = {
-	/*LightmapColor*/
+static RProgram programs[MShader_COUNT] = {
+	/* MShader_Unknown */
+	{-1, {
+			/* common */
+			"varying vec3 v_pos;\n",
+			/* vertex */
+			"attribute vec3 a_vertex;\n"
+			"uniform mat4 u_mvp;\n"
+			"void main() {\n"
+				"v_pos = a_vertex;\n"
+				"gl_Position = u_mvp * vec4(a_vertex, 1.);\n"
+			"}\n",
+			/* fragment */
+			"void main() {\n"
+				"gl_FragColor = vec4(mod(v_pos,100.)/100., 1.);\n"
+			"}\n",
+			},
+		{ -1 }, { -1 }
+	},
+	/* MShader_LightmappedOnly */
 	{-1, {
 			/*common*/
 			"varying vec2 v_lightmap_uv;\n"
@@ -330,59 +334,50 @@ static RProgram programs[Program_COUNT] = {
 			},
 		{ -1 }, { -1 }
 	},
-	/*LightmapTexture*/
+	/* MShader_LightmappedGeneric */
 	{-1, {
 			/*common*/
-	"varying vec2 v_lightmap_uv, v_tex_uv;\n",
+			"varying vec2 v_lightmap_uv, v_tex_uv;\n",
 			/*vertex*/
-	"attribute vec3 a_vertex;\n"
-	"attribute vec2 a_lightmap_uv, a_tex_uv;\n"
-	"uniform mat4 u_mvp;\n"
-	"void main() {\n"
-		"v_lightmap_uv = a_lightmap_uv;\n"
-		"v_tex_uv = a_tex_uv;\n"
-		"gl_Position = u_mvp * vec4(a_vertex, 1.);\n"
-	"}\n",
+			"attribute vec3 a_vertex;\n"
+			"attribute vec2 a_lightmap_uv, a_tex_uv;\n"
+			"uniform mat4 u_mvp;\n"
+			"void main() {\n"
+				"v_lightmap_uv = a_lightmap_uv;\n"
+				"v_tex_uv = a_tex_uv;\n"
+				"gl_Position = u_mvp * vec4(a_vertex, 1.);\n"
+			"}\n",
 			/*fragment*/
-	"uniform sampler2D u_lightmap, u_tex0, u_tex1;\n"
-	"uniform vec2 u_lightmap_size, u_tex0_size, u_tex1_size;\n"
-	"uniform float u_lmn;\n"
-	"void main() {\n"
-		"vec3 tc = vec3(fract(v_tex_uv/u_tex0_size), 0.);\n"
-		"vec4 albedo = texture2D(u_tex0, v_tex_uv/u_tex0_size);\n"
-		"albedo = mix(albedo, texture2D(u_tex1, v_tex_uv/u_tex1_size), .0);\n"
-		"vec3 lm = texture2D(u_lightmap, v_lightmap_uv).xyz;\n"
-		"vec3 color = albedo.xyz * lm;\n"
-		"gl_FragColor = vec4(mix(color, tc, u_lmn), 1.);\n"
-	"}\n"
+			"uniform sampler2D u_lightmap, u_tex0;\n"
+			"uniform vec2 u_lightmap_size, u_tex0_size;\n"
+			"void main() {\n"
+				"vec4 albedo = texture2D(u_tex0, v_tex_uv/u_tex0_size);\n"
+				"vec3 lm = texture2D(u_lightmap, v_lightmap_uv).xyz;\n"
+				"vec3 color = albedo.xyz * lm;\n"
+				"gl_FragColor = vec4(color, 1.);\n"
+			"}\n"
 			},
 		{ -1 }, { -1 }
 	},
-	/* Skybox */
+	/* MShader_UnlitGeneric */
 	{-1, { /* common */
-		"varying vec2 v_uv;\n"
-		"varying float texid;\n",
+		"varying vec2 v_uv;\n",
 		/* vertex */
 		"attribute vec3 a_vertex;\n"
 		"attribute vec2 a_tex_uv;\n"
-		"attribute vec2 a_lightmap_uv;\n"
 		"uniform mat4 u_mvp;\n"
 		"uniform float u_far;\n"
 		"void main() {\n"
 			"v_uv = a_tex_uv;\n"
-			"texid = a_lightmap_uv.x;\n"
 			"gl_Position = u_mvp * vec4(u_far * .5 * a_vertex, 1.);\n"
 		"}\n",
 		/* fragment */
-		"uniform sampler2D u_tex0, u_tex1, u_tex2, u_tex3, u_tex4, u_tex5;\n"
+		"uniform sampler2D u_tex0;\n"
+		"uniform vec2 u_tex0_size;\n"
 		"void main() {\n"
-			"if (texid < 1.) gl_FragColor = texture2D(u_tex0, v_uv);\n"
-			"else if (texid < 2.) gl_FragColor = texture2D(u_tex1, v_uv);\n"
-			"else if (texid < 3.) gl_FragColor = texture2D(u_tex2, v_uv);\n"
-			"else if (texid < 4.) gl_FragColor = texture2D(u_tex3, v_uv);\n"
-			"else if (texid < 5.) gl_FragColor = texture2D(u_tex4, v_uv);\n"
-			"else gl_FragColor = texture2D(u_tex5, v_uv);\n"
-		"}\n"
+			"gl_FragColor = texture2D(u_tex0, v_uv + vec2(.5) / u_tex0_size);\n"
+			//"gl_FragColor = texture2D(u_tex0, v_uv);\n"
+		"}\n",
 		}, {-1}, {-1}},
 };
 
@@ -394,12 +389,12 @@ static struct BSPModelVertex box[] = {
 	{{ 1.f, -1.f,  1.f}, {0.f, 0.f}, {1.f, 0.f}, {0, 0, 0}},
 	{{ 1.f, -1.f, -1.f}, {0.f, 0.f}, {1.f, 1.f}, {0, 0, 0}},
 
-	{{ 1.f,  1.f,  1.f}, {3.f, 0.f}, {1.f, 0.f}, {0, 0, 0}},
-	{{ 1.f,  1.f, -1.f}, {3.f, 0.f}, {1.f, 1.f}, {0, 0, 0}},
-	{{-1.f,  1.f, -1.f}, {3.f, 0.f}, {0.f, 1.f}, {0, 0, 0}},
-	{{-1.f,  1.f, -1.f}, {3.f, 0.f}, {0.f, 1.f}, {0, 0, 0}},
-	{{-1.f,  1.f,  1.f}, {3.f, 0.f}, {0.f, 0.f}, {0, 0, 0}},
-	{{ 1.f,  1.f,  1.f}, {3.f, 0.f}, {1.f, 0.f}, {0, 0, 0}},
+	{{-1.f, -1.f,  1.f}, {1.f, 0.f}, {0.f, 0.f}, {0, 0, 0}},
+	{{-1.f,  1.f,  1.f}, {1.f, 0.f}, {1.f, 0.f}, {0, 0, 0}},
+	{{-1.f,  1.f, -1.f}, {1.f, 0.f}, {1.f, 1.f}, {0, 0, 0}},
+	{{-1.f,  1.f, -1.f}, {1.f, 0.f}, {1.f, 1.f}, {0, 0, 0}},
+	{{-1.f, -1.f, -1.f}, {1.f, 0.f}, {0.f, 1.f}, {0, 0, 0}},
+	{{-1.f, -1.f,  1.f}, {1.f, 0.f}, {0.f, 0.f}, {0, 0, 0}},
 
 	{{ 1.f, -1.f, -1.f}, {2.f, 0.f}, {0.f, 1.f}, {0, 0, 0}},
 	{{ 1.f, -1.f,  1.f}, {2.f, 0.f}, {0.f, 0.f}, {0, 0, 0}},
@@ -408,12 +403,12 @@ static struct BSPModelVertex box[] = {
 	{{-1.f, -1.f, -1.f}, {2.f, 0.f}, {1.f, 1.f}, {0, 0, 0}},
 	{{ 1.f, -1.f, -1.f}, {2.f, 0.f}, {0.f, 1.f}, {0, 0, 0}},
 
-	{{-1.f, -1.f,  1.f}, {1.f, 0.f}, {0.f, 0.f}, {0, 0, 0}},
-	{{-1.f,  1.f,  1.f}, {1.f, 0.f}, {1.f, 0.f}, {0, 0, 0}},
-	{{-1.f,  1.f, -1.f}, {1.f, 0.f}, {1.f, 1.f}, {0, 0, 0}},
-	{{-1.f,  1.f, -1.f}, {1.f, 0.f}, {1.f, 1.f}, {0, 0, 0}},
-	{{-1.f, -1.f, -1.f}, {1.f, 0.f}, {0.f, 1.f}, {0, 0, 0}},
-	{{-1.f, -1.f,  1.f}, {1.f, 0.f}, {0.f, 0.f}, {0, 0, 0}},
+	{{ 1.f,  1.f,  1.f}, {3.f, 0.f}, {1.f, 0.f}, {0, 0, 0}},
+	{{ 1.f,  1.f, -1.f}, {3.f, 0.f}, {1.f, 1.f}, {0, 0, 0}},
+	{{-1.f,  1.f, -1.f}, {3.f, 0.f}, {0.f, 1.f}, {0, 0, 0}},
+	{{-1.f,  1.f, -1.f}, {3.f, 0.f}, {0.f, 1.f}, {0, 0, 0}},
+	{{-1.f,  1.f,  1.f}, {3.f, 0.f}, {0.f, 0.f}, {0, 0, 0}},
+	{{ 1.f,  1.f,  1.f}, {3.f, 0.f}, {1.f, 0.f}, {0, 0, 0}},
 
 	{{ 1.f, -1.f,  1.f}, {4.f, 0.f}, {1.f, 1.f}, {0, 0, 0}},
 	{{ 1.f,  1.f,  1.f}, {4.f, 0.f}, {0.f, 1.f}, {0, 0, 0}},
@@ -438,7 +433,6 @@ static struct {
 	const RProgram *current_program;
 	struct {
 		const float *mvp;
-		float lmn;
 		float far;
 	} uniforms;
 
@@ -474,14 +468,8 @@ static int render_ProgramUse(RProgram *prog) {
 	GL_CALL(glUseProgram(prog->name));
 	GL_CALL(glUniform1i(prog->uniform_locations[RUniformKind_lightmap], 0));
 	GL_CALL(glUniform1i(prog->uniform_locations[RUniformKind_tex0], 1));
-	GL_CALL(glUniform1i(prog->uniform_locations[RUniformKind_tex1], 2));
-	GL_CALL(glUniform1i(prog->uniform_locations[RUniformKind_tex2], 3));
-	GL_CALL(glUniform1i(prog->uniform_locations[RUniformKind_tex3], 4));
-	GL_CALL(glUniform1i(prog->uniform_locations[RUniformKind_tex4], 5));
-	GL_CALL(glUniform1i(prog->uniform_locations[RUniformKind_tex5], 6));
 
 	GL_CALL(glUniformMatrix4fv(prog->uniform_locations[RUniformKind_mvp], 1, GL_FALSE, r.uniforms.mvp));
-	GL_CALL(glUniform1f(prog->uniform_locations[RUniformKind_lmn], r.uniforms.lmn));
 	GL_CALL(glUniform1f(prog->uniform_locations[RUniformKind_far], r.uniforms.far));
 
 	r.current_program = prog;
@@ -559,9 +547,8 @@ int renderInit() {
 	r.current_program = NULL;
 	r.current_tex0 = NULL;
 	r.uniforms.mvp = NULL;
-	r.uniforms.lmn = 0;
 
-	for (int i = 0; i < Program_COUNT; ++i) {
+	for (int i = 0; i < MShader_COUNT; ++i) {
 		if (render_ProgramInit(programs + i) != 0) {
 			PRINTF("Cannot create program %d", i);
 			return 0;
@@ -585,15 +572,15 @@ int renderInit() {
 		struct Material default_material;
 		memset(&default_material, 0, sizeof default_material);
 		default_material.average_color = aVec3f(0.f, 1.f, 0.f);
-		default_material.shader = MaterialShader_LightmappedAverageColor;
+		default_material.shader = MShader_Unknown;
 		cachePutMaterial("opensource/placeholder", &default_material);
 	}
 
 	{
 		struct Material lightmap_color_material;
 		memset(&lightmap_color_material, 0, sizeof lightmap_color_material);
-		lightmap_color_material.average_color = aVec3f(0.f, 1.f, 0.f);
-		lightmap_color_material.shader = MaterialShader_LightmappedAverageColor;
+		lightmap_color_material.average_color = aVec3f(1.f, 1.f, 0.f);
+		lightmap_color_material.shader = MShader_LightmappedOnly;
 		cachePutMaterial("opensource/coarse", &lightmap_color_material);
 	}
 
@@ -604,26 +591,23 @@ int renderInit() {
 	return 1;
 }
 
-static int renderPrepareProgram(const struct BSPDraw *draw) {
-	const struct Material *m = draw->material;
-
-	int program_changed = 0;
-
-	switch (m->shader) {
-		case MaterialShader_LightmappedAverageColor:
-			program_changed = render_ProgramUse(programs + Program_LightmapColor);
-			break;
-		case MaterialShader_LightmappedGeneric:
-			program_changed = render_ProgramUse(programs + Program_LightmapTexture);
-			break;
-		default:
-			ATTO_ASSERT(!"Impossible");
+static void renderBindTexture(const RTexture *texture, int slot, int norepeat) {
+	GL_CALL(glActiveTexture(GL_TEXTURE0 + slot));
+	GL_CALL(glBindTexture(GL_TEXTURE_2D, texture->gl_name));
+	if (norepeat) {
+		const GLuint wrap = GL_CLAMP_TO_EDGE;
+		GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap));
+		GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap));
 	}
+}
+
+static int renderUseMaterial(const Material *m) {
+	const int program_changed = render_ProgramUse(programs + m->shader);
 
-	if (m->base_texture[0]) {
-		const RTexture *t = &m->base_texture[0]->texture;
+	if (m->base_texture.texture) {
+		const RTexture *t = &m->base_texture.texture->texture;
 		if (t != r.current_tex0) {
-			GL_CALL(glBindTexture(GL_TEXTURE_2D, t->gl_name));
+			renderBindTexture(&m->base_texture.texture->texture, 1, 0);
 			GL_CALL(glUniform2f(r.current_program->uniform_locations[RUniformKind_tex0_size], (float)t->width, (float)t->height));
 			r.current_tex0 = t;
 		}
@@ -637,7 +621,7 @@ static void renderDrawSet(const struct BSPModel *model, const struct BSPDrawSet
 	for (int i = 0; i < drawset->draws_count; ++i) {
 		const struct BSPDraw *draw = drawset->draws + i;
 
-		if (renderPrepareProgram(draw) || i == 0 || draw->vbo_offset != vbo_offset) {
+		if (renderUseMaterial(draw->material) || i == 0 || draw->vbo_offset != vbo_offset) {
 			vbo_offset = draw->vbo_offset;
 			renderApplyAttribs(attribs, &model->vbo, draw->vbo_offset);
 		}
@@ -646,27 +630,16 @@ static void renderDrawSet(const struct BSPModel *model, const struct BSPDrawSet
 	}
 }
 
-static void renderBindTexture(const RTexture *texture, int slot, int norepeat) {
-	GL_CALL(glActiveTexture(GL_TEXTURE0 + slot));
-	GL_CALL(glBindTexture(GL_TEXTURE_2D, texture->gl_name));
-	if (norepeat) {
-		const GLuint wrap = GL_CLAMP_TO_EDGE;
-		GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap));
-		GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap));
-	}
-}
-
 static void renderSkybox(const struct Camera *camera, const struct BSPModel *model) {
 	const struct AMat4f op = aMat4fMul(camera->projection, aMat4f3(camera->orientation, aVec3ff(0)));
 	r.uniforms.mvp = &op.X.x;
 
-	render_ProgramUse(programs + Program_Skybox);
-	for (int i = 0; i < BSPSkyboxDir_COUNT; ++i)
-		renderBindTexture(&model->skybox[i]->texture, 1+i, 1);
-
-	renderApplyAttribs(attribs, &box_buffer, 0);
 	GL_CALL(glDisable(GL_CULL_FACE));
-	GL_CALL(glDrawArrays(GL_TRIANGLES, 0, COUNTOF(box)));
+	for (int i = 0; i < BSPSkyboxDir_COUNT; ++i) {
+		renderUseMaterial(model->skybox[i]);
+		renderApplyAttribs(attribs, &box_buffer, 0);
+		GL_CALL(glDrawArrays(GL_TRIANGLES, i*6, 6));
+	}
 	GL_CALL(glEnable(GL_CULL_FACE));
 }
 
@@ -681,13 +654,11 @@ void renderModelDraw(const RDrawParams *params, const struct BSPModel *model) {
 
 	GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model->ibo.gl_name));
 	renderBindTexture(&model->lightmap, 0, 0);
-	GL_CALL(glActiveTexture(GL_TEXTURE0 + 1));
 
 	const struct AVec3f rel_pos = aVec3fSub(params->camera->pos, params->translation);
 
 	r.current_program = NULL;
 	r.uniforms.mvp = &mvp.X.x;
-	r.uniforms.lmn = params->lmn;
 	r.uniforms.far = params->camera->z_far;
 
 	const float distance =