*/}}
Browse Source

Material shader seems alright, with minor issue on UI (remove material from slot crashes

YimingWu 8 months ago
parent
commit
b970791bbd
7 changed files with 227 additions and 74 deletions
  1. 9 0
      la_tns.h
  2. 65 36
      la_tns_kernel.c
  3. 2 1
      la_tns_mesh.c
  4. 12 0
      resources/la_modelling.c
  5. 15 3
      resources/la_properties.c
  6. 46 0
      resources/la_templates.c
  7. 78 34
      resources/la_tns_shaders.cpp

+ 9 - 0
la_tns.h

@@ -34,6 +34,7 @@ extern const char* LA_FLOOR_VERTEX_SHADER;
 extern const char* LA_FLOOR_FRAGMENT_SHADER;
 extern const char* LA_IMM_VERTEX_SHADER;
 extern const char* LA_IMM_FRAGMENT_SHADER;
+extern const char* LA_OBJECT_FRAGMENT_SHADER;
 extern const char* LA_RAY_VERTEX_SHADER;
 extern const char* LA_SHADER_LIB_FXAA;
 extern const char* LA_RAY_FRAGMENT_SHADER;
@@ -246,6 +247,7 @@ struct _tnsMain {
     real StateUseHalftone;
     real SetUseHalftone;
     real SetHalftoneSize;
+    float SetViewPos[3];
     
     tnsShader* StateShader;
 
@@ -577,6 +579,8 @@ STRUCTURE(tnsMaterial){
     laSafeString *Name;
     real Color[4];
     int Colorful;
+    laRackPage* Page;
+    tnsShader* Shader;
 };
 
 #define TNS_CAMERA_PERSPECTIVE 0
@@ -673,6 +677,7 @@ STRUCTURE(tnsBatchCommand){
     int Dimension;
     int HiddenByDefault;
     int UseUniformColor; real UniformColor[4];
+    tnsMaterial* Material;
     real Width;
 };
 STRUCTURE(tnsBatch){
@@ -817,6 +822,7 @@ void tnsSetuptnsFontManager();
 tnsShader *tnsNewShaderProgram(int VertexShaderID, int FragmentShaderID, int GeometryShaderID);
 int tnsNewGeometryShader(char *Content);
 int tnsNewFragmentShader(char *Content);
+int tnsNewFragmentShaderMaterial(char *Content, char* Material);
 int tnsNewVertexShader(char *Content);
 void tnsDeleteShaderProgram(tnsShader* s);
 
@@ -872,6 +878,7 @@ tnsBatch *tnsCreateBatch(u32bit NumVert, int Dimension, float *Data, int NormalD
 tnsBatch *tnsCreateBatchi(u32bit NumVert, int Dimension, int *Data);
 void tnsCommandUseWidth(tnsBatchCommand*c, real width);
 void tnsCommandUseUniformColor(tnsBatchCommand*c,real* color);
+void tnsCommandUseMaterial(tnsBatchCommand*c, tnsMaterial* material);
 void tnsCommandOverrideColorArray(tnsBatchCommand*c, int VertCount, int ColorDimension, float* colors);
 tnsBatchCommand *tnsCreateCommand(tnsBatch *b, const char* name, u32bit ElementCount, int Dimension, GLenum DrawAs, u32bit *Elements, int HiddenByDefault);
 void tnsDeleteBatch(tnsBatch *b);
@@ -1177,6 +1184,8 @@ tnsMaterialSlot* tnsNewMaterialSlot(tnsMeshObject* mo);
 void tnsRemoveMaterialSlot(tnsMeshObject* mo, tnsMaterialSlot* ms);
 void tnsAssignMaterialSlot(tnsMeshObject* mo, tnsMaterialSlot* ms);
 
+void tnsEnsureMaterialShader(tnsMaterial* mat, int Refresh);
+
 void tnsClearAll();
 void tnsClearColorv(real *rgba);
 void tnsClearColor(real r, real g, real b, real a);

+ 65 - 36
la_tns_kernel.c

@@ -319,11 +319,12 @@ void tnsShaderApplyShadowMatrix(tnsShader *tns, tnsMatrix44d m){
     glUniformMatrix4fv(tns->iShadow, 1, 0, mf);
 }
 
-char* tnsEnsureShaderCommoms(char* Content){
+char* tnsEnsureShaderCommoms(char* Content, char* Material){
     char* c=0,*c1=0;
     c1=strSub(Content,"#with TNS_SHADER_COLOR_COMMON",TNS_SHADER_COLOR_COMMON);
     c=strSub(c1,"#with LA_SHADER_LIB_FXAA",LA_SHADER_LIB_FXAA); free(c1);
-    return c;
+    c1=strSub(c,"#with TNS_SHADER_MATERIAL",Material?Material:""); free(c);
+    return c1;
 }
 int tnsNewVertexShader(char *Content){
     int status = 0;
@@ -335,7 +336,7 @@ int tnsNewVertexShader(char *Content){
 
     VertexShaderObject = glCreateShader(GL_VERTEX_SHADER);
 
-    char* UseContent=tnsEnsureShaderCommoms(Content);
+    char* UseContent=tnsEnsureShaderCommoms(Content,0);
     glShaderSource(VertexShaderObject, 1, &UseContent, 0);
     glCompileShader(VertexShaderObject);
     glGetShaderiv(VertexShaderObject, GL_COMPILE_STATUS, &status);
@@ -349,7 +350,7 @@ int tnsNewVertexShader(char *Content){
 
     return VertexShaderObject;
 }
-int tnsNewFragmentShader(char *Content){
+int tnsNewFragmentShaderMaterial(char *Content, char* Material){
     int status = 0;
     char error[1024];
     GLuint FragmentShaderObject;
@@ -359,7 +360,7 @@ int tnsNewFragmentShader(char *Content){
 
     FragmentShaderObject = glCreateShader(GL_FRAGMENT_SHADER);
 
-    char* UseContent=tnsEnsureShaderCommoms(Content);
+    char* UseContent=tnsEnsureShaderCommoms(Content,Material);
     glShaderSource(FragmentShaderObject, 1, &UseContent, 0);
     glCompileShader(FragmentShaderObject);
     glGetShaderiv(FragmentShaderObject, GL_COMPILE_STATUS, &status);
@@ -373,6 +374,9 @@ int tnsNewFragmentShader(char *Content){
 
     return FragmentShaderObject;
 }
+int tnsNewFragmentShader(char *Content){
+    return tnsNewFragmentShaderMaterial(Content,0);
+}
 int tnsNewGeometryShader(char *Content){
     int status = 0;
     char error[1024];
@@ -383,7 +387,7 @@ int tnsNewGeometryShader(char *Content){
 
     GeometryShaderObject = glCreateShader(GL_GEOMETRY_SHADER);
 
-    char* UseContent=tnsEnsureShaderCommoms(Content);
+    char* UseContent=tnsEnsureShaderCommoms(Content,0);
     glShaderSource(GeometryShaderObject, 1, &UseContent, 0);
     glCompileShader(GeometryShaderObject);
     glGetShaderiv(GeometryShaderObject, GL_COMPILE_STATUS, &status);
@@ -455,28 +459,25 @@ int tnsEnableShader(int index){
 }
 int tnsEnableShaderv(tnsShader *shader){
     tnsMatrixStackItem *tmsi;
-    tnsShader *tns = shader;
-    if (!tns){
-        glUseProgram(0);
-        T->CurrentShader = 0;
-        T->BindedShader = 0;
-        return 0;
-    }
-    glUseProgram(tns->glProgramID);
-    T->CurrentShader = tns;
-    T->BindedShader = tns;
+    tnsShader *s = shader;
+    if (!s){ glUseProgram(0); T->CurrentShader = 0;T->BindedShader = 0; return 0; }
+    glUseProgram(s->glProgramID);
+    T->CurrentShader = s;
+    T->BindedShader = s;
 
     tmsi = tKnlGetCurrentMatStackItem();
-    tnsShaderApplyProjection(tns, tmsi->projection);
-    tnsShaderApplyProjectionInverse(tns, tmsi->projection);
-    tnsShaderApplyView(tns, tmsi->view);
-    tnsShaderApplyModel(tns, tmsi->model); tnsUseShader(tns);
-
-    //if (tns->iVertex != -1) glEnableVertexAttribArray(tns->iVertex);
-    //if (tns->iColor != -1) glEnableVertexAttribArray(tns->iColor);
-    //if (tns->iNormal != -1) glEnableVertexAttribArray(tns->iNormal);
-    //if (tns->iUV != -1) glEnableVertexAttribArray(tns->iUV);
-
+    tnsShaderApplyProjection(s, tmsi->projection);
+    tnsShaderApplyProjectionInverse(s, tmsi->projection);
+    tnsShaderApplyView(s, tmsi->view);
+    tnsShaderApplyModel(s, tmsi->model); tnsUseShader(s);
+
+    if(s->iUseNormal) glUniform1i(s->iUseNormal,T->SetUseNormal);
+    if(s->uViewPos) glUniform3fv(s->uViewPos,1,T->SetViewPos);
+    if(s->iUseHalftone) glUniform1f(s->iUseHalftone,T->SetUseHalftone);
+    if(s->iHalftoneSize) glUniform1f(s->iHalftoneSize,T->SetHalftoneSize);
+    //if(s->iTextureMode) glUniform1i(s->iTextureMode,T->StateTextureMode);
+    //if(cs->iSampleAmount) glUniform1i(cs->iSampleAmount);
+    
     return 1;
 }
 int tnsUseShader(tnsShader *shader){
@@ -1516,6 +1517,9 @@ void tnsCommandUseUniformColor(tnsBatchCommand*c, real* color){
     tnsVectorCopy4d(color, c->UniformColor);
     c->UseUniformColor=1;
 }
+void tnsCommandUseMaterial(tnsBatchCommand*c, tnsMaterial* material){
+    c->Material=material;
+}
 void tnsCommandUseWidth(tnsBatchCommand*c, real width){
     c->Width=width;
 }
@@ -1561,13 +1565,8 @@ void tnsDeleteBatch(tnsBatch *b){
 
     FreeMem(b);
 }
-int tnsDrawBatch(tnsBatch* batch, const char* OverrideCommand, real* OverrideUniformColor, int OverrideAsArray) {
-	//int Mode = batch->DrawMode;
-	if (!batch) return;
+void tnsDrawBatchInitArrayStates(tnsBatch* batch){
     tnsShader* cs=T->BindedShader;
-    int Drawn=0;
-
-	//glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
 
 	glBindBuffer(GL_ARRAY_BUFFER, batch->VBO);
 	glEnableVertexAttribArray(cs->iVertex);
@@ -1587,12 +1586,25 @@ int tnsDrawBatch(tnsBatch* batch, const char* OverrideCommand, real* OverrideUni
     }
     if(cs->iUV>=0){ glDisableVertexAttribArray(cs->iUV); }
     tnsUniformUseTexture(cs, 0, 0);
+}
+int tnsDrawBatch(tnsBatch* batch, const char* OverrideCommand, real* OverrideUniformColor, int OverrideAsArray) {
+	if (!batch) return;
+    int Drawn=0; tnsShader *LastShader=T->BindedShader,*SaveShader=T->BindedShader; int NeedInit=1;
 
     int IsOverrideColor=0; int PointSizeChanged=0,LineWidthChanged=0;
 	for (tnsBatchCommand* bc = batch->Branches.pFirst; bc; bc = bc->Item.pNext) {
         if(OverrideCommand && !strSame(OverrideCommand, bc->name)){ continue; }
         if(!OverrideCommand && bc->HiddenByDefault){ continue; }
-        if(cs->iColor){
+        if(NeedInit || (bc->Material && LastShader!=bc->Material->Shader) || ((!bc->Material)&&LastShader!=SaveShader)){
+            if(bc->Material && bc->Material->Shader){ LastShader=bc->Material->Shader;
+                tnsUseShader(LastShader); tnsEnableShaderv(LastShader);
+            }else{
+                tnsUseShader(SaveShader); tnsEnableShaderv(SaveShader);
+            }
+            tnsDrawBatchInitArrayStates(batch); NeedInit=0;
+        }
+        tnsShader* cs=T->BindedShader;
+        if(cs->iColor>-1){
             if(bc->OverrideColorArray){
                 glBindBuffer(GL_ARRAY_BUFFER, bc->CBO); glEnableVertexAttribArray(cs->iColor);
                 glVertexAttribPointer(cs->iColor, bc->ColorDimension, GL_FLOAT, 0, 0, 0); IsOverrideColor=1;
@@ -1619,6 +1631,8 @@ int tnsDrawBatch(tnsBatch* batch, const char* OverrideCommand, real* OverrideUni
         if(PointSizeChanged){ glPointSize(1); } if(LineWidthChanged){ glLineWidth(1); }
 	}
 
+    if(SaveShader!=LastShader){ tnsUseShader(SaveShader); tnsEnableShaderv(SaveShader); }
+
 	//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
     return Drawn;
 }
@@ -3637,7 +3651,8 @@ void tnsApplyCameraView(int W, int H, tnsCamera *Camera){
 
     if (current_shader = T->CurrentShader){
         tnsShaderApplyView(current_shader, result);
-        if(current_shader->uViewPos>-1) glUniform3f(current_shader->uViewPos,LA_COLOR3(Camera->Base.Location));
+        if(current_shader->uViewPos>-1) glUniform3f(current_shader->uViewPos,LA_COLOR3(Camera->Base.GLocation));
+        tnsVectorSet3v(T->SetViewPos,Camera->Base.GLocation);
     }
 }
 void tnsApplyShadowCameraView(tnsLight *Light){
@@ -4103,6 +4118,7 @@ tnsMaterial *tnsCreateMaterial(char *Name){
     tnsMaterial *m; if(!Name || !Name[0]) return 0;
     m = memAcquireHyper(sizeof(tnsMaterial));
     strSafeSet(&m->Name, Name); tnsVectorSet4(m->Color,0.8,0.8,0.8,1);
+    m->Page=memAcquire(sizeof(laRackPage));
     lstAppendItem(&T->World->Materials, m);
     return m;
 }
@@ -4112,7 +4128,9 @@ tnsMaterial *tnsFindMaterial(char *name){
     return 0;
 }
 void tnsRemoveMaterial(tnsMaterial* mat){
-    lstRemoveItem(&T->World->Materials,mat); memLeave(mat);
+    lstRemoveItem(&T->World->Materials,mat);
+    laNodeRack* rr; while(rr=lstPopItem(&mat->Page->Racks)){ laDestroyRack(rr); } memLeave(mat->Page);
+    memLeave(mat);
 }
 tnsMaterialSlot* tnsNewMaterialSlot(tnsMeshObject* mo){
     if(mo->Base.Type!=TNS_OBJECT_MESH) return 0; short nextid=0,found=1;
@@ -4122,7 +4140,7 @@ tnsMaterialSlot* tnsNewMaterialSlot(tnsMeshObject* mo){
         }
     }
     tnsMaterialSlot* ms=memAcquire(sizeof(tnsMaterialSlot)); lstAppendItem(&mo->Materials,ms);
-    ms->Index=nextid; ms->Parent=mo; memAssignRef(mo,&mo->CurrentMaterial,ms);
+    ms->Index=nextid; memAssignRef(ms,&ms->Parent,mo); memAssignRef(mo,&mo->CurrentMaterial,ms);
     return ms;
 }
 void tnsRemoveMaterialSlot(tnsMeshObject* mo, tnsMaterialSlot* ms){
@@ -4138,6 +4156,17 @@ void tnsAssignMaterialSlot(tnsMeshObject* mo, tnsMaterialSlot* ms){
     }
 }
 
+void tnsEnsureMaterialShader(tnsMaterial* mat, int Refresh){
+    if(Refresh){
+        if(mat->Shader && mat->Shader!=T->immShader){ tnsDeleteShaderProgram(mat->Shader); mat->Shader=0; }
+    }
+    if(!(mat->Page->Script&&mat->Page->Script->Ptr)){ mat->Shader=T->immShader; return; }
+    char* str=mat->Page->Script->Ptr;
+    mat->Shader = tnsNewShaderProgram(
+        tnsNewVertexShader(LA_IMM_VERTEX_SHADER),tnsNewFragmentShaderMaterial(LA_OBJECT_FRAGMENT_SHADER,str),-1);
+    if(!mat->Shader){ mat->Shader=T->immShader; }
+}
+
 //==================================================================[Util]
 
 #define MAX3(a, b, c) \

+ 2 - 1
la_tns_mesh.c

@@ -278,8 +278,9 @@ void tnsRegenerateMeshBatch(tnsMeshObject* mo){
         for(tnsMaterialSlot* ms=mo->Materials.pFirst;ms;ms=ms->Item.pNext){
             int* melem,mtot; melem=tnsGetTriangulatedBatch(mo, &mtot, ms->Index);
             if(melem){
-                c=tnsCreateCommand(mo->Batch, "mat", mtot, 3, GL_TRIANGLES, melem, 0); 
+                c=tnsCreateCommand(mo->Batch, "mat", mtot, 3, GL_TRIANGLES, melem, 0);
                 tnsCommandUseUniformColor(c,ms->Material?ms->Material->Color:meshcolor);
+                if(ms->Material){ tnsCommandUseMaterial(c,ms->Material); }
                 free(melem);
             }
         }

+ 12 - 0
resources/la_modelling.c

@@ -1693,6 +1693,17 @@ int OPINV_AssignMaterialSlot(laOperator *a, laEvent *e){
     laRecordInstanceDifferences(mo,"tns_mesh_object"); laPushDifferences("Assign material slot",0);
     return LA_FINISHED;
 }
+int OPCHK_RefreshMaterialShader(laPropPack *This, laStringSplitor *ss){
+    if(This && This->EndInstance){
+        if(la_EnsureSubTarget(This->LastPs->p,This->EndInstance)==TNS_PC_MATERIAL){ return 1; }
+    } return 0;
+}
+int OPINV_RefreshMaterialShader(laOperator *a, laEvent *e){
+    if(!a->This || !a->This->EndInstance){ return LA_FINISHED; }
+    tnsMaterial* m=a->This->EndInstance;
+    tnsEnsureMaterialShader(m,1); tns_InvalidateMeshWithMaterial(m); laNotifyUsers("tns.world");
+    return LA_FINISHED;
+}
 
 void la_RegisterModellingOperators(){
     laPropContainer *pc; laProp *p;
@@ -1706,6 +1717,7 @@ void la_RegisterModellingOperators(){
     laCreateOperatorType("M_new_material_slot", "New Material Slot", "Create a new material slot", OPCHK_NewMaterialSlot, 0, 0, OPINV_NewMaterialSlot, 0, '+', 0);
     laCreateOperatorType("M_remove_material_slot", "Remove Material Slot", "Remove a material slot", OPCHK_RemoveMaterialSlot, 0, 0, OPINV_RemoveMaterialSlot, 0 ,L'🗴', 0);
     laCreateOperatorType("M_assign_material_slot", "Assign Material Slot", "Assign faces to a material slot", OPCHK_AssignMaterialSlot, 0, 0, OPINV_AssignMaterialSlot, 0 ,L'🖌', 0);
+    laCreateOperatorType("M_refresh_material_shader", "Refresh Material Shader", "Refresh material shader", OPCHK_RefreshMaterialShader, 0, 0, OPINV_RefreshMaterialShader, 0 ,L'🗘', 0);
 
     laCreateOperatorType("M_set_cursor", "Set Cursor", "Set cursor in the viewport", OPCHK_ViewportAndSceneExists, 0, 0, OPINV_SetCursor, 0, 0, LA_EXTRA_TO_PANEL);
     laCreateOperatorType("M_toggle_edit_mode", "Toggle Edit Mode", "Toggle edit mode of the active object", OPCHK_ThereIsActiveObject, 0, 0, OPINV_ToggleEdit, 0, 0, 0);

+ 15 - 3
resources/la_properties.c

@@ -561,6 +561,9 @@ void tnsset_InstancerInstance(tnsInstancer *o, tnsObject* value){
 void *tnsget_detached_FirstRootObject(void *UNUSED1, void *UNUSED2){
     return T->World->RootObjects.pFirst;
 }
+void *tnsget_detached_FirstMaterial(void *UNUSED1, void *UNUSED2){
+    return T->World->Materials.pFirst;
+}
 void *tnsget_detached_FirstTexture(void *UNUSED1, void *UNUSED2){
     return T->Textures.pFirst;
 }
@@ -793,6 +796,9 @@ int lafilter_NodeCategory(void* unused, laNodeCategory* c){
     if(c->For&MAIN.FilterNodeCategory) return 1; return 0;
 }
 
+void tnspost_Material(tnsMaterial *m){
+    tnsEnsureMaterialShader(m,1);
+}
 void tnspost_Object(tnsObject *o){
     laListItemPointer* NextLip;
     for(laListItemPointer* lip=o->ChildObjects.pFirst;lip;lip=NextLip){
@@ -824,10 +830,11 @@ void* tnsget_MeshObjectFaceRaw(tnsMeshObject* o, int* r_size, int* r_is_copy){
     arrEnsureLength(&arr, i, &max, sizeof(int));
     arr[i]=o->totf; i++;
     for(int f=0;f<o->totf;f++){
-        arrEnsureLength(&arr, i+o->f[f].looplen+2, &max, sizeof(int));
+        arrEnsureLength(&arr, i+o->f[f].looplen+6, &max, sizeof(int));
         arr[i]=o->f[f].looplen; i++;
         arr[i]=o->f[f].flags; i++;
         arr[i]=o->f[f].mat; i++;
+        float* nm=(void*)&arr[i]; tnsVectorSet3v(nm,o->f[f].n); i+=3;
         for(int l=0;l<o->f[f].looplen;l++){
             arr[i]=o->f[f].loop[l]; i++;
         }
@@ -851,6 +858,7 @@ void tnsset_MeshObjectFaceRaw(tnsMeshObject* o, int* data, int s){
         f->looplen=data[i]; i++;
         f->flags=data[i]; i++;
         f->mat=data[i]; i++;
+        float* nm=(void*)&data[i]; tnsVectorSet3v(f->n,nm); i+=3;
         f->loop=calloc(1,sizeof(int)*f->looplen);
         for(int li=0;li<f->looplen;li++){
             f->loop[li]=data[i]; i++;
@@ -1048,14 +1056,15 @@ void la_RegisterTNSProps(){
         laAddSubGroup(p, "active_root", "Active Root Object", "Global active root object", "tns_object",0,0,0,offsetof(tnsWorld,ActiveRoot),tnsget_detached_FirstRootObject,0,laget_ListNext,0,0,0,0,LA_UDF_REFER);
         sp = laAddSubGroup(p, "objects", "Objects", "List of all objects", "tns_object",tnsget_ObjectType, 0,0,-1,0,0,0,0,0,0,offsetof(tnsWorld, AllObjects), 0);
         laSubGroupExtraFunctions(sp,tnsfilter_SavableObject,tnsfilter_SavableObject,0,0,0);
-        laAddSubGroup(p, "materials", "Materials", "List of all materials", "tns_material",0,0,0,-1,0,0,0,0,0,0,offsetof(tnsWorld, Materials), 0);
+        sp = laAddSubGroup(p, "materials", "Materials", "List of all materials", "tns_material",0,0,0,-1,0,0,0,0,0,0,offsetof(tnsWorld, Materials), 0);
+        laSubGroupDetachable(sp, tnsget_detached_FirstMaterial, laget_ListNext);
     }
 
     p = laAddPropertyContainer("tns_child_object", "Child Object", "Child object linker", 0,0,sizeof(laListItemPointer), 0,0,0);{
         laAddSubGroup(p, "object", "Object", "Object link", "tns_object",tnsget_ObjectType, 0,0,offsetof(laListItemPointer, p), 0,0,0,0,0,0,0,LA_UDF_REFER);
     }
 
-    p = laAddPropertyContainer("tns_material", "Material" "Object material", 0,0,0,sizeof(tnsMaterial),0,0,2);{
+    p = laAddPropertyContainer("tns_material", "Material" "Object material", 0,0,0,sizeof(tnsMaterial),tnspost_Material,0,2);{
         TNS_PC_MATERIAL=p;
         laAddStringProperty(p, "name", "Material Name", "The name ff the material", 0,0,0,0,1, offsetof(tnsMaterial, Name), 0,0,0,0,LA_AS_IDENTIFIER);
         laAddFloatProperty(p, "color", "Color", "Base color of the material", LA_WIDGET_FLOAT_COLOR, "R,G,B,A", 0,1,0,0.025, 1, 0,offsetof(tnsMaterial, Color), 0,0,4, 0,0,0,0,tnsset_MaterialColor, 0,0,0);
@@ -1063,12 +1072,15 @@ void la_RegisterTNSProps(){
             laAddEnumItemAs(ep, "NONE", "None", "Display materials normally",0,0);
             laAddEnumItemAs(ep, "COLORFUL", "Colorful", "Display material with colorful halftone",1,0);
         }
+        laAddSubGroup(p, "shader_page", "Shader Page", "Shader page of this material","la_rack_page",0,0,0,offsetof(tnsMaterial,Page),0,0,0,0,0,0,0,LA_UDF_SINGLE|LA_HIDE_IN_SAVE);
+        laAddOperatorProperty(p,"refresh","Refresh","Refresh material shader","M_refresh_material_shader",L'🗘',0);
     }
 
     p = laAddPropertyContainer("tns_material_slot", "Material Slot" "Material Slot", 0,0,0,sizeof(tnsMaterialSlot),0,0,1);{
         TNS_PC_MATERIAL_SLOT=p;
         laAddStringProperty(p,"name","Name","Name of the material slot",LA_WIDGET_STRING_PLAIN,0,0,0,0,0,0,tnsget_MaterialSlotname,0,0,LA_READ_ONLY|LA_UDF_IGNORE);
         laAddSubGroup(p, "material", "Material", "Material reference of this slot","tns_material",0,0,0,offsetof(tnsMaterialSlot,Material),tnsget_FirstMaterial,tnsgetactive_SlotMaterial,laget_ListNext,0,0,0,0,LA_UDF_REFER);
+        laAddSubGroup(p, "parent", "Parent", "Parent object","tns_object",0,0,0,offsetof(tnsMaterialSlot,Parent),0,0,0,0,0,0,0,LA_UDF_REFER|LA_READ_ONLY);
         laAddIntProperty(p,"index","Index","Index of the material slot",LA_WIDGET_INT_PLAIN,0,0,0,0,0,0,0,offsetof(tnsMaterialSlot,Index),0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
         laAddOperatorProperty(p,"remove","Remove","Remove this material slot","M_remove_material_slot",L'🗴',0);
     }

+ 46 - 0
resources/la_templates.c

@@ -1601,6 +1601,51 @@ void laui_Drivers(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *
         ADD_PAGE
     }laEndCondition(uil,b1);
 
+#undef ADD_PAGE
+}
+void lauidetached_Materials(laPanel* p){
+    la_MakeDetachedProp(p, "la.detached_view_switch", "detached");
+    la_MakeDetachedProp(p, "tns.world.materials", "material");
+}
+void laui_Materials(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil),*cl, *cr,*cll,*clr;
+    laSplitColumn(uil,c,0.3); cl=laLeftColumn(c,1); cr=laRightColumn(c,0);
+
+    laShowItemFull(uil,cl,Extra,"detached",0,0,0,0)->Flags|=LA_UI_FLAGS_HIGHLIGHT|LA_UI_FLAGS_ICON;
+
+#define ADD_PAGE(SEL) \
+    laUiItem* b2=laOnConditionThat(uil,cl,laPropExpression(&rb->PP,""));{\
+    laUiItem* b=laBeginRow(uil,cr,0,0);\
+        laUiItem* cp=laShowInvisibleItem(uil,cr,&rb->PP,"shader_page");\
+        if(SEL){\
+            laShowItemFull(uil,cr,&rb->PP,"",LA_WIDGET_COLLECTION_SELECTOR,0,laui_IdentifierOnly,0)\
+                ->Flags|=LA_UI_COLLECTION_SIMPLE_SELECTOR;\
+        }\
+        laShowItem(uil,cr,&rb->PP,"name");\
+        laShowItemFull(uil,cr,&cp->PP,"use_script",0,"icon=📃",0,0)\
+            ->Flags|=LA_UI_FLAGS_HIGHLIGHT|LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_ICON;\
+        laShowSeparator(uil,cr);\
+        laShowItem(uil,cr,&rb->PP,"refresh");\
+        laEndRow(uil,b);\
+    }laEndCondition(uil,b2);\
+    b2=laOnConditionThat(uil,c,laPropExpression(&rb->PP,"shader_page"));{\
+        laUiItem* b3=laOnConditionThat(uil,c,laPropExpression(&rb->PP,"shader_page.use_script"));{\
+            laShowItemFull(uil,c,&rb->PP,"shader_page.script",0,0,0,0)->Extra->HeightCoeff=-2;\
+        }laElse(uil,b3);{\
+            laShowItemFull(uil,c,&rb->PP,"shader_page",LA_WIDGET_COLLECTION_SINGLE,0,laui_RackPage,0)->Flags|=LA_UI_FLAGS_NO_DECAL;\
+        }laEndCondition(uil,b3);\
+    }laElse(uil,b2);{\
+        laShowLabel(uil,c,"Select or add a shader page.",0,0)->Flags|=LA_TEXT_ALIGN_CENTER;\
+    }laEndCondition(uil,b2);
+
+    laUiItem* b1=laOnConditionThat(uil,c,laPropExpression(Extra,"detached"));{
+        laUiItem* rb=laShowInvisibleItem(uil,cr,Extra,"material");
+        ADD_PAGE(1)
+    }laElse(uil,b1);{
+        laUiItem* rb=laShowInvisibleItem(uil,cr,0,"tns.world.active_root.active.as_mesh.current_material.material");
+        ADD_PAGE(0)
+    }laEndCondition(uil,b1);
+
 #undef ADD_PAGE
 }
 
@@ -1929,6 +1974,7 @@ void la_RegisterBuiltinTemplates(){
     if(act) laRegisterUiTemplate("LAUI_animation_actions", "Actions", laui_AnimationActions, 0, 0, 0, 0,15,25);
     if(act) laRegisterUiTemplate("LAUI_animation_action_channels", "Action Channels", laui_AnimationActionChannels, 0, 0, 0, 0,20,15); 
     if(obj) laRegisterUiTemplate("LAUI_drivers","Drivers",laui_Drivers,lauidetached_Drivers,0,0,0,0,0);
+    if(obj) laRegisterUiTemplate("LAUI_materials","Materials",laui_Materials,lauidetached_Materials,0,0,0,0,0);
             
             laRegisterUiTemplate("LAUI_user_preferences", "User Preferences", laui_UserPreference, 0, 0, "System",0,25,25);
     if(tex) laRegisterUiTemplate("LAUI_texture_inspector", "Texture Inspector", laui_TextureInspector, lauidetached_TextureInspector, 0, 0, 0,0,0);

+ 78 - 34
resources/la_tns_shaders.cpp

@@ -530,6 +530,41 @@ vec3 XYZ2Clay(vec3 xyz){
 				  vec3(0.0134474,-0.1183897,1.0154096));
 	return xyz*mat;
 }
+
+float htsize=HalftoneSize;
+vec4 rgb2cmyk(vec3 rgb){
+	vec4 cmyk; cmyk.w=1-max(max(rgb.r,rgb.g),rgb.b);
+	float k1=1-cmyk.w;
+	cmyk.r=(k1-rgb.r)/k1; cmyk.g=(k1-rgb.g)/k1; cmyk.b=(k1-rgb.b)/k1;
+	return cmyk;
+}
+vec3 cmyk2rgb(vec4 cmyk){
+	vec3 rgb; float k1=1-cmyk.w;
+	rgb.r=(1-cmyk.r)*k1; rgb.g=(1-cmyk.g)*k1; rgb.b=(1-cmyk.b)*k1;
+	return rgb;
+}
+float rand(vec2 co){
+    return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
+}
+float HalftoneSingle(float a,float ps,float theta,float sm){
+	float psize=ps; vec2 ctr=vec2(psize/2,psize/2); vec2 pt=vec2(psize,psize);
+    vec2 xy=gl_FragCoord.xy; xy=vec2(sin(theta)*xy.x-cos(theta)*xy.y,cos(theta)*xy.x+sin(theta)*xy.y);
+	xy.x=xy.x+rand(xy)/1; xy.y=xy.y+rand(xy)/1; ivec2 xyi=ivec2(int(xy.x/psize),int(xy.y/psize));
+	vec2 xyf=mod(xy,pt);
+	float px1=(sm==1)?(3.0f/psize):(0.000001);
+	float cmp=(pow(a,1)*psize/2*(1.414+px1)); float fac=distance(xyf,ctr)/cmp;
+	return smoothstep(1+px1,1-px1,fac);
+}
+vec4 halftone(vec4 color){
+	vec4 cmyk=rgb2cmyk(color.rgb); float a=color.a*(gl_FragCoord.x/400.0f);
+	cmyk.r=HalftoneSingle(cmyk.r,htsize,rad(15.0),1);
+	cmyk.g=HalftoneSingle(cmyk.g,htsize,rad(75.0),1);
+	cmyk.b=HalftoneSingle(cmyk.b,htsize,rad(0),1);
+	cmyk.a=HalftoneSingle(cmyk.a,htsize,rad(45.0),1);
+	color.rgb=cmyk2rgb(cmyk);
+	//color.a=HalftoneSingle(a,htsize,rad(30),0);
+	return color;
+}
 )";
 
 
@@ -633,15 +668,19 @@ uniform float ComposingGamma;
 uniform float ComposingBlackpoint;
 uniform int ShowStripes;
 uniform float HCYGamma;
+
+uniform vec3 uObjectPos;
 uniform vec3 uViewPos;
 in vec4 fColor;
 in vec2 fUV;
 flat in vec3 fNormal;
 in vec3 fGPos;
+
 out float gl_FragDepth;
 layout(location = 0) out vec4 outColor;
 layout(location = 1) out vec3 outNormal;
 layout(location = 2) out vec3 outGPos;
+
 #with TNS_SHADER_COLOR_COMMON
 vec3 ConvertColorSpace(vec3 color){
     if(InputColorSpace!=OutputColorSpace){
@@ -667,40 +706,6 @@ vec3 ConvertColorSpace(vec3 color){
     }
     return color;
 }
-float htsize=HalftoneSize;
-vec4 rgb2cmyk(vec3 rgb){
-	vec4 cmyk; cmyk.w=1-max(max(rgb.r,rgb.g),rgb.b);
-	float k1=1-cmyk.w;
-	cmyk.r=(k1-rgb.r)/k1; cmyk.g=(k1-rgb.g)/k1; cmyk.b=(k1-rgb.b)/k1;
-	return cmyk;
-}
-vec3 cmyk2rgb(vec4 cmyk){
-	vec3 rgb; float k1=1-cmyk.w;
-	rgb.r=(1-cmyk.r)*k1; rgb.g=(1-cmyk.g)*k1; rgb.b=(1-cmyk.b)*k1;
-	return rgb;
-}
-float rand(vec2 co){
-    return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
-}
-float HalftoneSingle(float a,float ps,float theta,float sm){
-	float psize=ps; vec2 ctr=vec2(psize/2,psize/2); vec2 pt=vec2(psize,psize);
-    vec2 xy=gl_FragCoord.xy; xy=vec2(sin(theta)*xy.x-cos(theta)*xy.y,cos(theta)*xy.x+sin(theta)*xy.y);
-	xy.x=xy.x+rand(xy)/1; xy.y=xy.y+rand(xy)/1; ivec2 xyi=ivec2(int(xy.x/psize),int(xy.y/psize));
-	vec2 xyf=mod(xy,pt);
-	float px1=(sm==1)?(3.0f/psize):(0.000001);
-	float cmp=(pow(a,1)*psize/2*(1.414+px1)); float fac=distance(xyf,ctr)/cmp;
-	return smoothstep(1+px1,1-px1,fac);
-}
-vec4 halftone(vec4 color){
-	vec4 cmyk=rgb2cmyk(color.rgb); float a=color.a*(gl_FragCoord.x/400.0f);
-	cmyk.r=HalftoneSingle(cmyk.r,htsize,rad(15.0),1);
-	cmyk.g=HalftoneSingle(cmyk.g,htsize,rad(75.0),1);
-	cmyk.b=HalftoneSingle(cmyk.b,htsize,rad(0),1);
-	cmyk.a=HalftoneSingle(cmyk.a,htsize,rad(45.0),1);
-	color.rgb=cmyk2rgb(cmyk);
-	//color.a=HalftoneSingle(a,htsize,rad(30),0);
-	return color;
-}
 void main(){
     vec4 color=vec4(1,0,1,1);
     if(TextureMode==0){ color = fColor; if(UseHalftone>0) color.a=HalftoneSingle(color.a,htsize,rad(7),0); }
@@ -746,6 +751,45 @@ void main(){
     outGPos = fGPos;
 })";
 
+extern "C" const char* LA_OBJECT_FRAGMENT_SHADER = R"(#version 330
+uniform int UseNormal;
+uniform float UseHalftone;
+uniform float HalftoneSize;
+
+uniform vec3 uObjectPos;
+uniform vec3 uViewPos;
+in vec4 fColor;
+in vec2 fUV;
+flat in vec3 fNormal;
+in vec3 fGPos;
+
+out float gl_FragDepth;
+layout(location = 0) out vec4 outColor;
+layout(location = 1) out vec3 outNormal;
+layout(location = 2) out vec3 outGPos;
+
+#with TNS_SHADER_COLOR_COMMON
+
+void main(){
+    vec4 color=fColor;
+		{
+#with TNS_SHADER_MATERIAL
+		}
+    if(UseNormal!=0){
+		color.a=HalftoneSingle(color.a,htsize,rad(7),0);
+		if(color.a==0) discard;
+        float light_factor=dot(fNormal,vec3(0,0,1));
+        float view=dot(fNormal,fGPos-uViewPos);
+        float factor=abs(light_factor);
+        if(light_factor*view>0){ factor=0; }
+        color=vec4(color.rgb*mix(0.2,1.,factor),color.a);
+        vec3 oNormal=fNormal; if(view<0){ oNormal=-fNormal; }
+        outNormal = oNormal;
+    }
+	if(UseHalftone>1e-6){ color=mix(color,halftone(color),UseHalftone); }
+    outColor = color; outGPos = fGPos;
+})";
+
 extern "C" const char* LA_FLOOR_VERTEX_SHADER = R"(#version 330
 uniform mat4 mProjection;
 uniform mat4 mModel;