Normal map tangents are now not always averaged at vertices anymore,

but only when the UV's are connected. That fixes some artifacts when
baking and using tangent space normal maps. It does mean increased
memory usage because it now stores 4 tangents per face like UV's,
and increased processing time, but there's no simple way around that.
This commit is contained in:
2008-03-07 15:38:56 +00:00
parent f13dc2aac1
commit 5b3dc15880
9 changed files with 234 additions and 75 deletions

View File

@@ -96,6 +96,8 @@ float (*mesh_getRefKeyCos(struct Mesh *me, int *numVerts_r))[3];
/* UvVertMap */
#define STD_UV_CONNECT_LIMIT 0.0001f
typedef struct UvVertMap {
struct UvMapVert **vert;
struct UvMapVert *buf;

View File

@@ -241,7 +241,7 @@ static int ss_sync_from_uv(CCGSubSurf *ss, CCGSubSurf *origss, DerivedMesh *dm,
EdgeHash *ehash;
float creaseFactor = (float)ccgSubSurf_getSubdivisionLevels(ss);
limit[0]= limit[1]= 0.0001f;
limit[0]= limit[1]= STD_UV_CONNECT_LIMIT;
vmap= make_uv_vert_map(mface, tface, totface, totvert, 0, limit);
if (!vmap)
return 0;

View File

@@ -134,7 +134,7 @@ typedef struct ShadeInput
/* texture coordinates */
float lo[3], gl[3], ref[3], orn[3], winco[3], sticky[3], vcol[4], rad[3];
float refcol[4], displace[3];
float strandco, tang[3], stress, winspeed[4];
float strandco, tang[3], nmaptang[3], stress, winspeed[4];
float duplilo[3], dupliuv[3];
ShadeInputUV uv[8]; /* 8 = MAX_MTFACE */

View File

@@ -64,6 +64,7 @@ typedef struct VlakTableNode {
struct MCol *mcol;
int totmtface, totmcol;
float *surfnor;
float *tangent;
struct RadFace **radface;
} VlakTableNode;
@@ -112,6 +113,7 @@ float *RE_vertren_get_winspeed(struct ObjectInstanceRen *obi, struct VertRen *ve
struct MTFace *RE_vlakren_get_tface(struct ObjectRen *obr, VlakRen *ren, int n, char **name, int verify);
struct MCol *RE_vlakren_get_mcol(struct ObjectRen *obr, VlakRen *ren, int n, char **name, int verify);
float *RE_vlakren_get_surfnor(struct ObjectRen *obr, VlakRen *ren, int verify);
float *RE_vlakren_get_nmap_tangent(struct ObjectRen *obr, VlakRen *ren, int verify);
RadFace **RE_vlakren_get_radface(struct ObjectRen *obr, VlakRen *ren, int verify);
int RE_vlakren_get_normal(struct Render *re, struct ObjectInstanceRen *obi, struct VlakRen *vlr, float *nor);

View File

@@ -477,8 +477,53 @@ void tangent_from_uv(float *uv1, float *uv2, float *uv3, float *co1, float *co2,
VecMulf(tang, -1.0f);
}
/* For normal map tangents we need to detect uv boundaries, and only average
* tangents in case the uvs are connected. Alternative would be to store 1
* tangent per face rather than 4 per face vertex, but that's not compatible
* with games */
typedef struct VertexTangent {
float tang[3], uv[2];
struct VertexTangent *next;
} VertexTangent;
static void sum_or_add_vertex_tangent(MemArena *arena, VertexTangent **vtang, float *tang, float *uv)
{
VertexTangent *vt;
/* find a tangent with connected uvs */
for(vt= *vtang; vt; vt=vt->next) {
if(fabs(uv[0]-vt->uv[0]) < STD_UV_CONNECT_LIMIT && fabs(uv[1]-vt->uv[1]) < STD_UV_CONNECT_LIMIT) {
VECADD(vt->tang, vt->tang, tang);
return;
}
}
/* if not found, append a new one */
vt= BLI_memarena_alloc(arena, sizeof(VertexTangent));
VECCOPY(vt->tang, tang);
vt->uv[0]= uv[0];
vt->uv[1]= uv[1];
if(*vtang)
vt->next= *vtang;
*vtang= vt;
}
static float *find_vertex_tangent(VertexTangent *vtang, float *uv)
{
VertexTangent *vt;
static float nulltang[3] = {0.0f, 0.0f, 0.0f};
for(vt= vtang; vt; vt=vt->next)
if(fabs(uv[0]-vt->uv[0]) < STD_UV_CONNECT_LIMIT && fabs(uv[1]-vt->uv[1]) < STD_UV_CONNECT_LIMIT)
return vt->tang;
return nulltang; /* shouldn't happen, except for nan or so */
}
/* gets tangent from tface or orco */
static void calc_tangent_vector(ObjectRen *obr, VlakRen *vlr)
static void calc_tangent_vector(ObjectRen *obr, VertexTangent **vtangents, MemArena *arena, VlakRen *vlr, int do_nmap_tangent, int do_tangent)
{
MTFace *tface= RE_vlakren_get_tface(obr, vlr, obr->actmtface, NULL, 0);
VertRen *v1=vlr->v1, *v2=vlr->v2, *v3=vlr->v3, *v4=vlr->v4;
@@ -504,30 +549,55 @@ static void calc_tangent_vector(ObjectRen *obr, VlakRen *vlr)
tangent_from_uv(uv1, uv2, uv3, v1->co, v2->co, v3->co, vlr->n, tang);
tav= RE_vertren_get_tangent(obr, v1, 1);
VECADD(tav, tav, tang);
tav= RE_vertren_get_tangent(obr, v2, 1);
VECADD(tav, tav, tang);
tav= RE_vertren_get_tangent(obr, v3, 1);
VECADD(tav, tav, tang);
if(v4) {
tangent_from_uv(uv1, uv3, uv4, v1->co, v3->co, v4->co, vlr->n, tang);
if(do_tangent) {
tav= RE_vertren_get_tangent(obr, v1, 1);
VECADD(tav, tav, tang);
tav= RE_vertren_get_tangent(obr, v2, 1);
VECADD(tav, tav, tang);
tav= RE_vertren_get_tangent(obr, v3, 1);
VECADD(tav, tav, tang);
tav= RE_vertren_get_tangent(obr, v4, 1);
VECADD(tav, tav, tang);
}
if(do_nmap_tangent) {
sum_or_add_vertex_tangent(arena, &vtangents[v1->index], tang, uv1);
sum_or_add_vertex_tangent(arena, &vtangents[v2->index], tang, uv2);
sum_or_add_vertex_tangent(arena, &vtangents[v3->index], tang, uv3);
}
if(v4) {
tangent_from_uv(uv1, uv3, uv4, v1->co, v3->co, v4->co, vlr->n, tang);
if(do_tangent) {
tav= RE_vertren_get_tangent(obr, v1, 1);
VECADD(tav, tav, tang);
tav= RE_vertren_get_tangent(obr, v3, 1);
VECADD(tav, tav, tang);
tav= RE_vertren_get_tangent(obr, v4, 1);
VECADD(tav, tav, tang);
}
if(do_nmap_tangent) {
sum_or_add_vertex_tangent(arena, &vtangents[v1->index], tang, uv1);
sum_or_add_vertex_tangent(arena, &vtangents[v3->index], tang, uv3);
sum_or_add_vertex_tangent(arena, &vtangents[v4->index], tang, uv4);
}
}
}
static void calc_vertexnormals(Render *re, ObjectRen *obr, int do_tangent)
static void calc_vertexnormals(Render *re, ObjectRen *obr, int do_tangent, int do_nmap_tangent)
{
MemArena *arena= NULL;
VertexTangent **vtangents= NULL;
int a;
if(do_nmap_tangent) {
arena= BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE);
BLI_memarena_use_calloc(arena);
vtangents= MEM_callocN(sizeof(VertexTangent*)*obr->totvert, "VertexTangent");
}
/* clear all vertex normals */
for(a=0; a<obr->totvert; a++) {
VertRen *ver= RE_findOrAddVert(obr, a);
@@ -600,10 +670,10 @@ static void calc_vertexnormals(Render *re, ObjectRen *obr, int do_tangent)
v3->n[2] +=fac3*vlr->n[2];
}
if(do_tangent) {
if(do_nmap_tangent || do_tangent) {
/* tangents still need to be calculated for flat faces too */
/* weighting removed, they are not vertexnormals */
calc_tangent_vector(obr, vlr);
calc_tangent_vector(obr, vtangents, arena, vlr, do_nmap_tangent, do_tangent);
}
}
@@ -622,6 +692,30 @@ static void calc_vertexnormals(Render *re, ObjectRen *obr, int do_tangent)
if(f1[0]==0.0 && f1[1]==0.0 && f1[2]==0.0) VECCOPY(f1, vlr->n);
}
}
if(do_nmap_tangent) {
VertRen *v1=vlr->v1, *v2=vlr->v2, *v3=vlr->v3, *v4=vlr->v4;
MTFace *tface= RE_vlakren_get_tface(obr, vlr, obr->actmtface, NULL, 0);
if(tface) {
float *vtang, *ftang= RE_vlakren_get_nmap_tangent(obr, vlr, 1);
vtang= find_vertex_tangent(vtangents[v1->index], tface->uv[0]);
VECCOPY(ftang, vtang);
Normalize(ftang);
vtang= find_vertex_tangent(vtangents[v2->index], tface->uv[1]);
VECCOPY(ftang+3, vtang);
Normalize(ftang+3);
vtang= find_vertex_tangent(vtangents[v3->index], tface->uv[2]);
VECCOPY(ftang+6, vtang);
Normalize(ftang+6);
if(v4) {
vtang= find_vertex_tangent(vtangents[v4->index], tface->uv[3]);
VECCOPY(ftang+9, vtang);
Normalize(ftang+9);
}
}
}
}
/* normalize vertex normals */
@@ -640,12 +734,18 @@ static void calc_vertexnormals(Render *re, ObjectRen *obr, int do_tangent)
}
}
}
if(arena)
BLI_memarena_free(arena);
if(vtangents)
MEM_freeN(vtangents);
}
// NT same as calc_vertexnormals, but dont modify the existing vertex normals
// only recalculate other render data. If this is at some point used for other things than fluidsim,
// this could be made on option for the normal calc_vertexnormals
static void calc_fluidsimnormals(Render *re, ObjectRen *obr, int do_tangent)
static void calc_fluidsimnormals(Render *re, ObjectRen *obr, int do_nmap_tangent)
{
int a;
@@ -692,13 +792,13 @@ static void calc_fluidsimnormals(Render *re, ObjectRen *obr, int do_tangent)
}
}
//if(do_tangent)
//if(do_nmap_tangent)
// calc_tangent_vector(obr, vlr, fac1, fac2, fac3, fac4);
}
if(do_tangent) {
if(do_nmap_tangent) {
/* tangents still need to be calculated for flat faces too */
/* weighting removed, they are not vertexnormals */
calc_tangent_vector(obr, vlr);
//calc_tangent_vector(obr, vlr);
}
}
@@ -723,7 +823,7 @@ static void calc_fluidsimnormals(Render *re, ObjectRen *obr, int do_tangent)
for(a=0; a<obr->totvert; a++) {
VertRen *ver= RE_findOrAddVert(obr, a);
Normalize(ver->n);
if(do_tangent) {
if(do_nmap_tangent) {
float *tav= RE_vertren_get_tangent(obr, ver, 0);
if(tav) Normalize(tav);
}
@@ -2053,7 +2153,7 @@ static int render_new_particle_system(Render *re, ObjectRen *obr, ParticleSystem
}
if(path && (ma->mode_l & MA_TANGENT_STR)==0)
calc_vertexnormals(re, obr, 0);
calc_vertexnormals(re, obr, 0, 0);
return 1;
}
@@ -2334,7 +2434,7 @@ static void do_displacement(Render *re, ObjectRen *obr, float mat[][4], float im
}
/* Recalc vertex normals */
calc_vertexnormals(re, obr, 0);
calc_vertexnormals(re, obr, 0, 0);
}
/* ------------------------------------------------------------------------- */
@@ -3013,7 +3113,8 @@ static void init_render_mesh(Render *re, ObjectRen *obr, int timeoffset)
CustomDataMask mask;
float xn, yn, zn, imat[3][3], mat[4][4]; //nor[3],
float *orco=0;
int a, a1, ok, need_orco=0, need_stress=0, need_tangent=0, vertofs;
int need_orco=0, need_stress=0, need_nmap_tangent=0, need_tangent=0;
int a, a1, ok, vertofs;
int end, do_autosmooth=0, totvert = 0;
int useFluidmeshNormals= 0; // NT fluidsim, use smoothed normals?
int use_original_normals= 0;
@@ -3036,11 +3137,19 @@ static void init_render_mesh(Render *re, ObjectRen *obr, int timeoffset)
if(ma->texco & TEXCO_STRESS)
need_stress= 1;
/* normalmaps, test if tangents needed, separated from shading */
if ((ma->mode_l & MA_TANGENT_V) || (ma->mode_l & MA_NORMAP_TANG)) {
if(ma->mode_l & MA_TANGENT_V) {
need_tangent= 1;
if(me->mtface==NULL)
need_orco= 1;
}
if(ma->mode_l & MA_NORMAP_TANG) {
if(me->mtface==NULL) {
need_orco= 1;
need_tangent= 1;
}
need_nmap_tangent= 1;
}
/* radio faces need autosmooth, to separate shared vertices in corners */
if(re->r.mode & R_RADIO)
if(ma->mode & MA_RADIO)
@@ -3050,9 +3159,11 @@ static void init_render_mesh(Render *re, ObjectRen *obr, int timeoffset)
if(re->flag & R_NEED_TANGENT) {
/* exception for tangent space baking */
need_tangent= 1;
if(me->mtface==NULL)
if(me->mtface==NULL) {
need_orco= 1;
need_tangent= 1;
}
need_nmap_tangent= 1;
}
/* check autosmooth and displacement, we then have to skip only-verts optimize */
@@ -3292,7 +3403,7 @@ static void init_render_mesh(Render *re, ObjectRen *obr, int timeoffset)
if(!timeoffset) {
if (test_for_displace(re, ob ) ) {
calc_vertexnormals(re, obr, 0);
calc_vertexnormals(re, obr, 0, 0);
if(do_autosmooth)
do_displacement(re, obr, mat, imat);
else
@@ -3305,9 +3416,9 @@ static void init_render_mesh(Render *re, ObjectRen *obr, int timeoffset)
if(useFluidmeshNormals) {
// do not recalculate, only init render data
calc_fluidsimnormals(re, obr, need_tangent);
calc_fluidsimnormals(re, obr, need_tangent||need_nmap_tangent);
} else {
calc_vertexnormals(re, obr, need_tangent);
calc_vertexnormals(re, obr, need_tangent, need_nmap_tangent);
}
if(need_stress)

View File

@@ -1889,8 +1889,8 @@ static void bake_shade(void *handle, Object *ob, ShadeInput *shi, int quad, int
VECCOPY(mat[2], tvn);
}
else {
VECCOPY(mat[0], shi->tang);
Crossf(mat[1], shi->vn, shi->tang);
VECCOPY(mat[0], shi->nmaptang);
Crossf(mat[1], shi->vn, shi->nmaptang);
VECCOPY(mat[2], shi->vn);
}
@@ -2035,7 +2035,7 @@ static void do_bake_shade(void *handle, int x, int y, float u, float v)
if(bs->type==RE_BAKE_NORMALS && R.r.bake_normal_space==R_BAKE_SPACE_TANGENT) {
shade_input_set_shade_texco(shi);
VECCOPY(tvn, shi->vn);
VECCOPY(ttang, shi->tang);
VECCOPY(ttang, shi->nmaptang);
}
/* if we are doing selected to active baking, find point on other face */

View File

@@ -106,7 +106,8 @@
#define RE_SURFNOR_ELEMS 3
#define RE_RADFACE_ELEMS 1
#define RE_SIMPLIFY_ELEMS 2
#define RE_FACE_ELEMS 1
#define RE_FACE_ELEMS 1
#define RE_NMAP_TANGENT_ELEMS 12
float *RE_vertren_get_sticky(ObjectRen *obr, VertRen *ver, int verify)
{
@@ -364,6 +365,21 @@ float *RE_vlakren_get_surfnor(ObjectRen *obr, VlakRen *vlak, int verify)
return surfnor + (vlak->index & 255)*RE_SURFNOR_ELEMS;
}
float *RE_vlakren_get_nmap_tangent(ObjectRen *obr, VlakRen *vlak, int verify)
{
float *tangent;
int nr= vlak->index>>8;
tangent= obr->vlaknodes[nr].tangent;
if(tangent==NULL) {
if(verify)
tangent= obr->vlaknodes[nr].tangent= MEM_callocN(256*RE_NMAP_TANGENT_ELEMS*sizeof(float), "tangent table");
else
return NULL;
}
return tangent + (vlak->index & 255)*RE_NMAP_TANGENT_ELEMS;
}
RadFace **RE_vlakren_get_radface(ObjectRen *obr, VlakRen *vlak, int verify)
{
RadFace **radface;
@@ -384,7 +400,7 @@ VlakRen *RE_vlakren_copy(ObjectRen *obr, VlakRen *vlr)
VlakRen *vlr1 = RE_findOrAddVlak(obr, obr->totvlak++);
MTFace *mtface, *mtface1;
MCol *mcol, *mcol1;
float *surfnor, *surfnor1;
float *surfnor, *surfnor1, *tangent, *tangent1;
RadFace **radface, **radface1;
int i, index = vlr1->index;
char *name;
@@ -408,6 +424,12 @@ VlakRen *RE_vlakren_copy(ObjectRen *obr, VlakRen *vlr)
VECCOPY(surfnor1, surfnor);
}
tangent= RE_vlakren_get_nmap_tangent(obr, vlr, 0);
if(tangent) {
tangent1= RE_vlakren_get_nmap_tangent(obr, vlr1, 1);
memcpy(tangent1, tangent, sizeof(float)*RE_NMAP_TANGENT_ELEMS);
}
radface= RE_vlakren_get_radface(obr, vlr, 0);
if(radface) {
radface1= RE_vlakren_get_radface(obr, vlr1, 1);
@@ -773,6 +795,8 @@ void free_renderdata_vlaknodes(VlakTableNode *vlaknodes)
MEM_freeN(vlaknodes[a].mcol);
if(vlaknodes[a].surfnor)
MEM_freeN(vlaknodes[a].surfnor);
if(vlaknodes[a].tangent)
MEM_freeN(vlaknodes[a].tangent);
if(vlaknodes[a].radface)
MEM_freeN(vlaknodes[a].radface);
}

View File

@@ -239,7 +239,7 @@ void shade_input_set_triangle_i(ShadeInput *shi, ObjectInstanceRen *obi, VlakRen
shi->vlr= vlr;
shi->obi= obi;
shi->obr= obi->obr;
shi->v1= vpp[i1];
shi->v2= vpp[i2];
shi->v3= vpp[i3];
@@ -382,6 +382,7 @@ void shade_input_set_strand_texco(ShadeInput *shi, StrandRen *strand, StrandVert
if(mode & (MA_TANGENT_V|MA_NORMAP_TANG)) {
VECCOPY(shi->tang, spoint->tan);
VECCOPY(shi->nmaptang, spoint->tan);
}
if(mode & MA_STR_SURFDIFF) {
@@ -545,6 +546,7 @@ void shade_input_set_strand_texco(ShadeInput *shi, StrandRen *strand, StrandVert
if((mode & MA_TANGENT_V)==0) {
/* just prevent surprises */
shi->tang[0]= shi->tang[1]= shi->tang[2]= 0.0f;
shi->nmaptang[0]= shi->nmaptang[1]= shi->nmaptang[2]= 0.0f;
}
}
}
@@ -806,7 +808,7 @@ void shade_input_set_shade_texco(ShadeInput *shi)
int mode= shi->mode; /* or-ed result for all nodes */
short texco= shi->mat->texco;
/* calculate dxno and tangents */
/* calculate dxno */
if(shi->vlr->flag & R_SMOOTH) {
if(shi->osatex && (texco & (TEXCO_NORM|TEXCO_REFL)) ) {
@@ -822,47 +824,64 @@ void shade_input_set_shade_texco(ShadeInput *shi)
shi->dyno[2]= dl*n3[2]-shi->dy_u*n1[2]-shi->dy_v*n2[2];
}
/* qdn: normalmap tangent space */
if (mode & (MA_TANGENT_V|MA_NORMAP_TANG) || R.flag & R_NEED_TANGENT) {
float *s1, *s2, *s3;
s1= RE_vertren_get_tangent(obr, v1, 0);
s2= RE_vertren_get_tangent(obr, v2, 0);
s3= RE_vertren_get_tangent(obr, v3, 0);
if(s1 && s2 && s3) {
shi->tang[0]= (l*s3[0] - u*s1[0] - v*s2[0]);
shi->tang[1]= (l*s3[1] - u*s1[1] - v*s2[1]);
shi->tang[2]= (l*s3[2] - u*s1[2] - v*s2[2]);
if(obi->flag & R_TRANSFORMED)
normal_transform(obi->imat, shi->tang);
/* qdn: normalize just in case */
Normalize(shi->tang);
}
else shi->tang[0]= shi->tang[1]= shi->tang[2]= 0.0f;
}
}
else {
/* qdn: normalmap tangent space */
if (mode & (MA_TANGENT_V|MA_NORMAP_TANG) || R.flag & R_NEED_TANGENT) {
/* calc tangents */
if (mode & (MA_TANGENT_V|MA_NORMAP_TANG) || R.flag & R_NEED_TANGENT) {
float *tangent, *s1, *s2, *s3;
float tl, tu, tv;
if(shi->vlr->flag & R_SMOOTH) {
tl= l;
tu= u;
tv= v;
}
else {
/* qdn: flat faces have tangents too,
could pick either one, using average here */
float *s1 = RE_vertren_get_tangent(obr, v1, 0);
float *s2 = RE_vertren_get_tangent(obr, v2, 0);
float *s3 = RE_vertren_get_tangent(obr, v3, 0);
if (s1 && s2 && s3) {
shi->tang[0] = (s1[0] + s2[0] + s3[0]);
shi->tang[1] = (s1[1] + s2[1] + s3[1]);
shi->tang[2] = (s1[2] + s2[2] + s3[2]);
tl= 1.0f;
tu= 1.0f/3.0f;
tv= 1.0f/3.0f;
}
shi->tang[0]= shi->tang[1]= shi->tang[2]= 0.0f;
shi->nmaptang[0]= shi->nmaptang[1]= shi->nmaptang[2]= 0.0f;
if(mode & MA_TANGENT_V) {
s1 = RE_vertren_get_tangent(obr, v1, 0);
s2 = RE_vertren_get_tangent(obr, v2, 0);
s3 = RE_vertren_get_tangent(obr, v3, 0);
if(s1 && s2 && s3) {
shi->tang[0]= (tl*s3[0] - tu*s1[0] - tv*s2[0]);
shi->tang[1]= (tl*s3[1] - tu*s1[1] - tv*s2[1]);
shi->tang[2]= (tl*s3[2] - tu*s1[2] - tv*s2[2]);
if(obi->flag & R_TRANSFORMED)
normal_transform(obi->imat, shi->tang);
Normalize(shi->tang);
VECCOPY(shi->nmaptang, shi->tang);
}
}
if(mode & MA_NORMAP_TANG || R.flag & R_NEED_TANGENT) {
tangent= RE_vlakren_get_nmap_tangent(obr, shi->vlr, 0);
if(tangent) {
s1= &tangent[shi->i1*3];
s2= &tangent[shi->i2*3];
s3= &tangent[shi->i3*3];
shi->nmaptang[0]= (tl*s3[0] - tu*s1[0] - tv*s2[0]);
shi->nmaptang[1]= (tl*s3[1] - tu*s1[1] - tv*s2[1]);
shi->nmaptang[2]= (tl*s3[2] - tu*s1[2] - tv*s2[2]);
if(obi->flag & R_TRANSFORMED)
normal_transform(obi->imat, shi->nmaptang);
Normalize(shi->nmaptang);
}
else shi->tang[0]= shi->tang[1]= shi->tang[2]= 0.0f;
}
}
@@ -1111,6 +1130,7 @@ void shade_input_set_shade_texco(ShadeInput *shi)
if((mode & MA_TANGENT_V)==0) {
/* just prevent surprises */
shi->tang[0]= shi->tang[1]= shi->tang[2]= 0.0f;
shi->nmaptang[0]= shi->nmaptang[1]= shi->nmaptang[2]= 0.0f;
}
}
}

View File

@@ -1781,11 +1781,11 @@ void do_material_tex(ShadeInput *shi)
if(mtex->normapspace == MTEX_NSPACE_TANGENT) {
/* qdn: tangent space */
float B[3], tv[3];
Crossf(B, shi->vn, shi->tang); /* bitangent */
Crossf(B, shi->vn, shi->nmaptang); /* bitangent */
/* transform norvec from tangent space to object surface in camera space */
tv[0] = texres.nor[0]*shi->tang[0] + texres.nor[1]*B[0] + texres.nor[2]*shi->vn[0];
tv[1] = texres.nor[0]*shi->tang[1] + texres.nor[1]*B[1] + texres.nor[2]*shi->vn[1];
tv[2] = texres.nor[0]*shi->tang[2] + texres.nor[1]*B[2] + texres.nor[2]*shi->vn[2];
tv[0] = texres.nor[0]*shi->nmaptang[0] + texres.nor[1]*B[0] + texres.nor[2]*shi->vn[0];
tv[1] = texres.nor[0]*shi->nmaptang[1] + texres.nor[1]*B[1] + texres.nor[2]*shi->vn[1];
tv[2] = texres.nor[0]*shi->nmaptang[2] + texres.nor[1]*B[2] + texres.nor[2]*shi->vn[2];
shi->vn[0]= facm*shi->vn[0] + fact*tv[0];
shi->vn[1]= facm*shi->vn[1] + fact*tv[1];
shi->vn[2]= facm*shi->vn[2] + fact*tv[2];