Set uniform data from javascript

This commit is contained in:
John Alanbrook 2024-04-30 10:32:27 -05:00
parent 71fda604ee
commit 41eadce13e
11 changed files with 288 additions and 56 deletions

View file

@ -820,6 +820,12 @@ Object.defineProperty(String.prototype, 'splice', {
}
});
Object.defineProperty(String.prototype, 'sub', {
value: function(index, str) {
return this.slice(0,index) + str + this.slice(index+str.length);
}
});
Object.defineProperty(String.prototype, 'rm', {
value: function(index, endidx = index+1) { return this.slice(0,index) + this.slice(endidx); }
});
@ -1551,10 +1557,63 @@ Math.sortpointsccw = function(points)
return ccw.map(function(x) { return x.add(cm); });
}
var yaml = {};
yaml.tojson = function(yaml)
{
yaml = yaml.replace(/(\w+):/g, '"$1":');
yaml = yaml.replace(/: ([\w\.]+)/g, ': "$1"');
yaml = yaml.split("\n");
var cont = {};
var cur = 0;
for (var i = 0; i < yaml.length; i++) {
var line = yaml[i];
var indent = line.search(/\S/);
if (indent > cur) {
if (line[indent] == "-") {
cont[indent] = "array";
yaml[i] = line.sub(indent, '[');
} else {
cont[indent] = "obj";
yaml[i] = line.sub(indent-1, '{');
}
}
if (indent < cur) {
while (cur > indent) {
if (cont[cur] === "obj")
yaml[i-1] = yaml[i-1] + "}";
else if (cont[cur] === "array")
yaml[i-1] = yaml[i-1] + "]";
delete cont[cur];
cur--;
}
}
if (indent === cur) {
if (yaml[i][indent] === '-')
yaml[i] = yaml[i].sub(indent,',');
else
yaml[i-1] = yaml[i-1] + ',';
}
cur = indent;
}
yaml = "{" + yaml.join("\n") + "}";
yaml = yaml.replace(/\s/g, '');
yaml = yaml.replace(/,}/g, '}');
yaml = yaml.replace(/,]/g, ']');
return yaml;
}
return {
convert,
time,
json,
Vector,
bbox
bbox,
yaml
};

View file

@ -26,12 +26,6 @@
#include "sokol/sokol_gfx.h"
#define POS 0
#define UV 1
#define NORM 2
#define WEIGHT 3
#define JOINT 4
static void processnode();
static void processmesh();
static void processtexture();
@ -52,6 +46,13 @@ struct joints {
char j4;
};
#define MAT_POS 0
#define MAT_UV 1
#define MAT_NORM 2
#define MAT_BONE 3
#define MAT_WEIGHT 4
#define MAT_COLOR 5
static cgltf_data *cdata;
static char *cpath;
@ -63,18 +64,18 @@ void model_init() {
.shader = sg_make_shader(unlit_shader_desc(sg_query_backend())),
.layout = {
.attrs = {
[POS].format = SG_VERTEXFORMAT_FLOAT3,
[UV].format = SG_VERTEXFORMAT_USHORT2N,
[UV].buffer_index = 1,
[NORM].format = SG_VERTEXFORMAT_UINT10_N2,
[NORM].buffer_index = 2,
[WEIGHT] = {
[MAT_POS].format = SG_VERTEXFORMAT_FLOAT3,
[MAT_UV].format = SG_VERTEXFORMAT_USHORT2N,
[MAT_UV].buffer_index = MAT_UV,
[MAT_NORM].format = SG_VERTEXFORMAT_UINT10_N2,
[MAT_NORM].buffer_index = MAT_NORM,
[MAT_WEIGHT] = {
.format = SG_VERTEXFORMAT_UBYTE4N,
.buffer_index = 3
.buffer_index = MAT_WEIGHT
},
[JOINT] = {
[MAT_BONE] = {
.format = SG_VERTEXFORMAT_UBYTE4,
.buffer_index = 4
.buffer_index = MAT_BONE
}
},
},
@ -89,13 +90,13 @@ void model_init() {
.layout = {
.attrs = {
[ATTR_vs_st_a_pos].format = SG_VERTEXFORMAT_FLOAT3,
[ATTR_vs_st_a_tex_coords] = {
[ATTR_vs_st_a_uv] = {
.format = SG_VERTEXFORMAT_USHORT2N,
.buffer_index = 1
.buffer_index = MAT_UV
},
[ATTR_vs_st_a_norm] = {
.format = SG_VERTEXFORMAT_UINT10_N2,
.buffer_index = 2
.buffer_index = MAT_NORM
}
},
},
@ -141,21 +142,21 @@ void mesh_add_material(primitive *prim, cgltf_material *mat)
{
if (!mat) return;
prim->mat = calloc(sizeof(*prim->mat), 1);
material *pmat = prim->mat;
if (mat->has_pbr_metallic_roughness && mat->pbr_metallic_roughness.base_color_texture.texture) {
cgltf_image *img = mat->pbr_metallic_roughness.base_color_texture.texture->image;
if (img->buffer_view) {
cgltf_buffer_view *buf = img->buffer_view;
prim->bind.fs.images[0] = texture_fromdata(buf->buffer->data, buf->size)->id;
pmat->diffuse = texture_fromdata(buf->buffer->data, buf->size);
} else {
char *path = makepath(dirname(cpath), img->uri);
prim->bind.fs.images[0] = texture_from_file(path)->id;
pmat->diffuse = texture_from_file(path);
free(path);
}
} else
prim->bind.fs.images[0] = texture_from_file("icons/moon.gif")->id;
// TODO: Cache and reuse samplers
prim->bind.fs.samplers[0] = tex_sampler;
pmat->diffuse = texture_from_file("icons/moon.gif");
}
sg_buffer texcoord_floats(float *f, int verts, int comp)
@ -219,7 +220,7 @@ struct primitive mesh_add_primitive(cgltf_primitive *prim)
for (int i = 0; i < n; i++)
idxs[i] = fidx[i];
retp.bind.index_buffer = sg_make_buffer(&(sg_buffer_desc){
retp.idx = sg_make_buffer(&(sg_buffer_desc){
.data.ptr = idxs,
.data.size = sizeof(*idxs) * n,
.type = SG_BUFFERTYPE_INDEXBUFFER,
@ -237,7 +238,7 @@ struct primitive mesh_add_primitive(cgltf_primitive *prim)
for (int z = 0; z < c; z++)
idxs[z] = z;
retp.bind.index_buffer = sg_make_buffer(&(sg_buffer_desc){
retp.idx = sg_make_buffer(&(sg_buffer_desc){
.data.ptr = idxs,
.data.size = sizeof(uint16_t) * c,
.type = SG_BUFFERTYPE_INDEXBUFFER});
@ -259,7 +260,7 @@ struct primitive mesh_add_primitive(cgltf_primitive *prim)
switch (attribute.type) {
case cgltf_attribute_type_position:
retp.bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
retp.pos = sg_make_buffer(&(sg_buffer_desc){
.data.ptr = vs,
.data.size = sizeof(float) * n,
.label = "mesh vert buffer"
@ -267,7 +268,7 @@ struct primitive mesh_add_primitive(cgltf_primitive *prim)
break;
case cgltf_attribute_type_normal:
retp.bind.vertex_buffers[2] = normal_floats(vs, verts, comp);
retp.norm = normal_floats(vs, verts, comp);
break;
case cgltf_attribute_type_tangent:
@ -277,15 +278,15 @@ struct primitive mesh_add_primitive(cgltf_primitive *prim)
break;
case cgltf_attribute_type_weights:
retp.bind.vertex_buffers[3] = weight_buf(vs, verts, comp);
retp.weight = weight_buf(vs, verts, comp);
break;
case cgltf_attribute_type_joints:
retp.bind.vertex_buffers[4] = joint_buf(vs, verts, comp);
retp.bone = joint_buf(vs, verts, comp);
break;
case cgltf_attribute_type_texcoord:
retp.bind.vertex_buffers[1] = texcoord_floats(vs, verts, comp);
retp.uv = texcoord_floats(vs, verts, comp);
break;
case cgltf_attribute_type_invalid:
YughWarn("Invalid type.");
@ -315,7 +316,7 @@ struct primitive mesh_add_primitive(cgltf_primitive *prim)
}
*/
if (retp.bind.vertex_buffers[NORM].id) {
if (retp.norm.id) {
YughInfo("Making normals.");
cgltf_attribute *pa = get_attr_type(prim, cgltf_attribute_type_position);
int n = cgltf_accessor_unpack_floats(pa->data, NULL,0);
@ -334,7 +335,7 @@ struct primitive mesh_add_primitive(cgltf_primitive *prim)
face_norms[i] = face_norms[i+1] = face_norms[i+2] = packed_norm;
}
retp.bind.vertex_buffers[2] = sg_make_buffer(&(sg_buffer_desc){
retp.norm = sg_make_buffer(&(sg_buffer_desc){
.data.ptr = face_norms,
.data.size = sizeof(uint32_t) * verts});
}
@ -510,6 +511,20 @@ void model_free(model *m)
}
sg_bindings primitive_bind(primitive *p)
{
sg_bindings b = {0};
b.vertex_buffers[unlit_attr_slot("a_pos")] = p->pos;
b.vertex_buffers[5] = p->uv;
b.vertex_buffers[3] = p->norm;
//b.vertex_buffers[unlit_attr_slot("a_bone")] = p->bone;
//b.vertex_buffers[unlit_attr_slot("a_weight")] = p->weight;
b.index_buffer = p->idx;
b.fs.images[unlit_image_slot(SG_SHADERSTAGE_FS, "diffuse")] = p->mat->diffuse->id;
b.fs.samplers[unlit_sampler_slot(SG_SHADERSTAGE_FS, "smp")] = tex_sampler;
return b;
}
void model_draw_go(model *model, gameobject *go, gameobject *cam)
{
HMM_Mat4 view = t3d_go2world(cam);
@ -538,6 +553,8 @@ void model_draw_go(model *model, gameobject *go, gameobject *cam)
.size = sizeof(*sk->binds)*50
});
}
sg_apply_pipeline(model_st_pipe);
sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_vs_p, SG_RANGE_REF(vp.e));
float ambient[4] = {1.0,1.0,1.0,1.0};
@ -547,12 +564,13 @@ void model_draw_go(model *model, gameobject *go, gameobject *cam)
mod = HMM_MulM4(mod, gom);
mesh msh = model->meshes[i];
for (int j = 0; j < arrlen(msh.primitives); j++) {
sg_apply_bindings(&(msh.primitives[j].bind));
sg_bindings b = primitive_bind(msh.primitives+j);
sg_apply_bindings(&b);
sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_vmodel, &(sg_range){
.ptr = mod.em,
.size = sizeof(mod)
});
sg_draw(0, model->meshes[i].primitives[j].idx_count, 1);
sg_draw(0, msh.primitives[j].idx_count, 1);
}
}
}

View file

@ -26,10 +26,20 @@ typedef struct material {
struct model;
typedef struct primitive {
sg_bindings bind;
sg_buffer pos;
sg_buffer norm;
sg_buffer uv;
sg_buffer bone;
sg_buffer weight;
sg_buffer idx;
material *mat;
uint32_t idx_count;
} primitive;
typedef struct shader {
sg_pipeline pipeline;
} shader;
/* A single mesh */
typedef struct mesh {
primitive *primitives;
@ -60,6 +70,7 @@ typedef struct skin {
typedef struct model {
struct mesh *meshes;
md5joint *nodes;
material *mats;
skin skin;
struct animation anim;
} model;

View file

@ -279,6 +279,7 @@ typedef union HMM_Vec3 {
};
float Elements[3];
float e[3];
} HMM_Vec3;

View file

@ -61,6 +61,7 @@ const char *js2str(JSValue v) {
return JS_ToCString(js, v);
}
void jsfreestr(const char *s) { JS_FreeCString(js, s); }
QJSCLASS(gameobject)
QJSCLASS(emitter)
QJSCLASS(dsp_node)
@ -625,6 +626,110 @@ JSC_CCALL(render_clear_color,
JSC_CCALL(render_set_sprite_tex, sprite_tex(js2texture(argv[0])))
JSC_CCALL(render_pipeline,
sg_shader_desc desc = {0};
JSValue prog = argv[0];
JSValue vs = js_getpropstr(prog, "vs");
JSValue fs = js_getpropstr(prog, "fs");
char *vsf = js2str(js_getpropstr(vs, "code"));
char *fsf = js2str(js_getpropstr(fs, "code"));
desc.vs.source = vsf;
desc.fs.source = fsf;
char *vsmain = js2str(js_getpropstr(vs, "entry_point"));
char *fsmain = js2str(js_getpropstr(fs, "entry_point"));
desc.vs.entry = vsmain;
desc.fs.entry = fsmain;
JSValue vsu = js_getpropstr(vs, "uniform_blocks");
int unin = js_arrlen(vsu);
for (int i = 0; i < unin; i++) {
JSValue u = js_getpropidx(vsu, i);
int slot = js2number(js_getpropstr(u, "slot"));
desc.vs.uniform_blocks[slot].size = js2number(js_getpropstr(u, "size"));
desc.vs.uniform_blocks[slot].layout = SG_UNIFORMLAYOUT_STD140;
}
JSValue fsu = js_getpropstr(fs, "uniform_blocks");
unin = js_arrlen(fsu);
for (int i = 0; i < unin; i++) {
JSValue u = js_getpropidx(fsu, i);
int slot = js2number(js_getpropstr(u, "slot"));
desc.fs.uniform_blocks[slot].size = js2number(js_getpropstr(u, "size"));
desc.fs.uniform_blocks[slot].layout = SG_UNIFORMLAYOUT_STD140;
}
JSValue imgs = js_getpropstr(fs, "images");
unin = js_arrlen(imgs);
for (int i = 0; i < unin; i++) {
JSValue u = js_getpropidx(imgs, i);
int slot = js2number(js_getpropstr(u, "slot"));
desc.fs.images[i].used = true;
desc.fs.images[i].multisampled = js2boolean(js_getpropstr(u, "multisampled"));
desc.fs.images[i].image_type = SG_IMAGETYPE_2D;
desc.fs.images[i].sample_type = SG_IMAGESAMPLETYPE_FLOAT;
}
JSValue samps = js_getpropstr(fs, "samplers");
unin = js_arrlen(samps);
for (int i = 0; i < unin; i++) {
desc.fs.samplers[0].used = true;
desc.fs.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING;
}
JSValue pairs = js_getpropstr(fs, "image_sampler_pairs");
unin = js_arrlen(pairs);
for (int i = 0; i < unin; i++) {
desc.fs.image_sampler_pairs[0].used = true;
desc.fs.image_sampler_pairs[0].image_slot = 0;
desc.fs.image_sampler_pairs[0].sampler_slot = 0;
}
sg_shader sgshader = sg_make_shader(&desc);
sg_pipeline_desc pdesc = {0};
pdesc.shader = sgshader;
pdesc.cull_mode = SG_CULLMODE_FRONT;
pdesc.depth.write_enabled = true;
pdesc.depth.compare = SG_COMPAREFUNC_LESS_EQUAL;
pdesc.layout.attrs[0].format = SG_VERTEXFORMAT_FLOAT2;
pdesc.primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP;
//pdesc.colors[0].blend = blend_trans;
sg_pipeline pipe = sg_make_pipeline(&pdesc);
jsfreestr(vsf);
jsfreestr(fsf);
jsfreestr(vsmain);
jsfreestr(fsmain);
return number2js(pipe.id);
)
JSC_CCALL(render_spritepipe, pip_sprite.id = js2number(argv[0]))
JSC_CCALL(render_texture,
tex_draw(js2texture(argv[0]), js2gameobject(argv[1]));
)
JSC_CCALL(render_setuniv,
float f = js2number(argv[0]);
sg_apply_uniforms(SG_SHADERSTAGE_FS, js2number(argv[1]), SG_RANGE_REF(f));
)
JSC_CCALL(render_setuniv3,
HMM_Vec3 v = js2vec3(argv[0]);
sg_apply_uniforms(SG_SHADERSTAGE_FS, js2number(argv[1]), SG_RANGE_REF(v.e));
)
JSC_CCALL(render_spdraw,
gameobject *go = js2gameobject(argv[0]);
HMM_Mat4 m = transform2d2mat4(go2t(go));
sg_apply_uniforms(SG_SHADERSTAGE_VS, 1, SG_RANGE_REF(m.e));
sg_bindings bind = {0};
bind.vertex_buffers[0] = sprite_quad;
sg_apply_bindings(&bind);
sg_draw(0,4,1);
)
static const JSCFunctionListEntry js_render_funcs[] = {
MIST_FUNC_DEF(render, grid, 3),
MIST_FUNC_DEF(render, point, 3),
@ -640,6 +745,12 @@ static const JSCFunctionListEntry js_render_funcs[] = {
MIST_FUNC_DEF(render, hud_res, 1),
MIST_FUNC_DEF(render, clear_color, 1),
MIST_FUNC_DEF(render, set_sprite_tex, 1),
MIST_FUNC_DEF(render, pipeline, 1),
MIST_FUNC_DEF(render, spritepipe, 1),
MIST_FUNC_DEF(render, texture, 2),
MIST_FUNC_DEF(render, setuniv3, 2),
MIST_FUNC_DEF(render, setuniv, 2),
MIST_FUNC_DEF(render, spdraw, 1)
};
JSC_CCALL(gui_flush, text_flush(&useproj));
@ -891,6 +1002,7 @@ JSC_SCALL(io_save_qoa, save_qoa(str))
JSC_SCALL(io_pack_start, pack_start(str))
JSC_SCALL(io_pack_add, pack_add(str))
JSC_CCALL(io_pack_end, pack_end())
JSC_SCALL(io_mod, ret = number2js(file_mod_secs(str));)
static const JSCFunctionListEntry js_io_funcs[] = {
MIST_FUNC_DEF(io, exists,1),
@ -907,7 +1019,8 @@ static const JSCFunctionListEntry js_io_funcs[] = {
MIST_FUNC_DEF(io, save_qoa,1),
MIST_FUNC_DEF(io, pack_start, 1),
MIST_FUNC_DEF(io, pack_add, 1),
MIST_FUNC_DEF(io, pack_end, 0)
MIST_FUNC_DEF(io, pack_end, 0),
MIST_FUNC_DEF(io, mod,1)
};
JSC_CCALL(debug_draw_gameobject, gameobject_draw_debug(js2gameobject(argv[0]));)
@ -1031,6 +1144,8 @@ static const JSCFunctionListEntry js_physics_funcs[] = {
MIST_FUNC_DEF(physics, make_gravity, 0),
};
JSC_CCALL(model_draw_go,
model_draw_go(js2model(this), js2gameobject(argv[0]), js2gameobject(argv[1]))
);
@ -1536,7 +1651,7 @@ JSC_CCALL(os_make_font, return font2js(MakeFont(js2str(argv[0]), js2number(argv[
JSC_SCALL(os_system, system(str); )
JSC_SCALL(os_make_model, return model2js(model_make(str)))
JSC_SCALL(os_make_model, ret = model2js(model_make(str)))
JSC_CCALL(os_sprite_pipe, sprite_pipe())

View file

@ -135,4 +135,9 @@ JSValue str2js(const char *c, ...);
void nota_int(char *blob);
JSValue js_getpropidx(JSValue v, uint32_t i);
JSValue js_getpropstr(JSValue v, const char *str);
const char *js2str(JSValue v);
void jsfreestr(const char *str);
#endif

View file

@ -10,8 +10,7 @@
#include "sprite.sglsl.h"
#include "9slice.sglsl.h"
static sg_shader shader_sprite;
static sg_pipeline pip_sprite;
sg_pipeline pip_sprite;
sg_bindings bind_sprite;
static sg_shader slice9_shader;
@ -45,10 +44,8 @@ static texture *loadedtex;
static int sprite_count = 0;
void sprite_initialize() {
shader_sprite = sg_make_shader(sprite_shader_desc(sg_query_backend()));
pip_sprite = sg_make_pipeline(&(sg_pipeline_desc){
.shader = shader_sprite,
.shader = sg_make_shader(sprite_shader_desc(sg_query_backend())),
.layout = {
.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2
@ -84,7 +81,6 @@ void sprite_pipe()
{
sg_apply_pipeline(pip_sprite);
sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_vp, SG_RANGE_REF(useproj));
sg_apply_bindings(&bind_sprite);
}
transform2d sprite2t(sprite *s)
@ -102,6 +98,25 @@ void sprite_tex(texture *t)
bind_sprite.fs.images[0] = t->id;
}
void sprite_setpipe(sg_pipeline p)
{
pip_sprite = p;
}
void tex_draw(texture *tex, gameobject *go)
{
HMM_Mat4 m = transform2d2mat4(go2t(go));
sg_apply_uniforms(SG_SHADERSTAGE_VS, 1, SG_RANGE_REF(m.e));
sg_bindings bind = {0};
bind.vertex_buffers[0] = sprite_quad;
bind.fs.images[0] = tex->id;
bind.fs.samplers[0] = std_sampler;
sg_apply_bindings(&bind);
sg_draw(0,4,1);
}
void sprite_draw(struct sprite *sprite, gameobject *go) {
HMM_Mat4 m = transform2d2mat4(go2t(go));
HMM_Mat4 sm = transform2d2mat4(sprite2t(sprite));
@ -111,7 +126,11 @@ void sprite_draw(struct sprite *sprite, gameobject *go) {
spv.size = sprite->spritesize;
spv.offset = sprite->spriteoffset;
spv.model = HMM_MulM4(m,sm);
sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_sprite, SG_RANGE_REF(spv));
sg_bindings bind = {0};
bind.vertex_buffers[0] = sprite_quad;
bind.fs.images[0] = loadedtex->id;
bind.fs.samplers[0] = std_sampler;
sg_draw(0,4,1);
}

View file

@ -28,14 +28,17 @@ struct spriteuni {
typedef struct sprite sprite;
extern sg_bindings bind_sprite;
extern sg_pipeline pip_sprite;
sprite *sprite_make();
void sprite_free(sprite *sprite);
void sprite_tex(texture *t);
void sprite_initialize();
void tex_draw(texture *tex, gameobject *go);
void sprite_draw(struct sprite *sprite, gameobject *go);
void sprite_pipe();
void sprite_draw_all();
void sprite_setpipe(sg_pipeline p);
void gui_draw_img(texture *tex, transform2d t, int wrap, HMM_Vec2 wrapoffset, float wrapscale, struct rgba color);

View file

@ -158,7 +158,7 @@ struct texture *texture_from_file(const char *path) {
.num_mipmaps = mips,
.data = sg_img_data
});
for (int i = 1; i < mips; i++)
free(mipdata[i]);

View file

@ -18,7 +18,8 @@ extern struct rect ST_UNIT;
/* Represents an actual texture on the GPU */
struct texture {
sg_image id; /* ID reference for the GPU memory location of the texture */
sg_image id; /* ID reference for the GPU memory location of the
texture */
int width;
int height;
unsigned char *data;

View file

@ -1,6 +1,6 @@
@block vptr
in vec3 a_pos;
in vec2 a_tex_coords;
in vec2 a_uv;
in vec4 a_norm;
out vec2 tex_coords;
out vec3 normal;
@ -11,21 +11,21 @@ uniform vmodel { uniform mat4 model; };
@vs vs
@include_block vptr
in vec4 a_bone;
in vec4 a_weight;
in vec4 a_joint;
uniform skinv { uniform mat4 bones[50]; };
void main() {
mat4 tt = vp;
mat4 mm = model;
mat4 skin = bones[int(a_joint.x)] * a_weight.x;
skin += bones[int(a_joint.y)] * a_weight.y;
skin += bones[int(a_joint.z)] * a_weight.z;
skin += bones[int(a_joint.w)] * a_weight.w;
mat4 skin = bones[int(a_bone.x)] * a_weight.x;
skin += bones[int(a_bone.y)] * a_weight.y;
skin += bones[int(a_bone.z)] * a_weight.z;
skin += bones[int(a_bone.w)] * a_weight.w;
mat4 skinm = mm * skin;
gl_Position = tt * skinm * vec4(a_pos,1.0);
tex_coords = a_tex_coords;
tex_coords = a_uv;
normal = (skinm * vec4(a_norm.xyz*2-1,0)).xyz;
}
@end
@ -35,7 +35,7 @@ void main() {
void main() {
gl_Position = vp * model * vec4(a_pos,1.0);
tex_coords = a_tex_coords;
tex_coords = a_uv;
normal = (model * vec4(a_norm.xyz*2-1,0)).xyz;
}
@end