2012-12-14 30 views
5

Sto usando GC per scrivere shaders all'interno di Unity3D.CG: specificare una variabile da non interpolare tra vertice e framment shader

Sto utilizzando gli attributi dei colori vertici per passare alcuni parametri allo shader. Non saranno usati così per definire i colori, e dovrebbero essere inoltrati dal vertex shader al pixel shader senza modificarli.

Questa è la struttura sto prendendo come input dal Unity3D al vertex:

struct appdata_full { 
    float4 vertex : POSITION; 
    float4 tangent : TANGENT; 
    float3 normal : NORMAL; 
    float4 texcoord : TEXCOORD0; 
    float4 texcoord1 : TEXCOORD1; 
    fixed4 color : COLOR; 
#if defined(SHADER_API_XBOX360) 
    half4 texcoord2 : TEXCOORD2; 
    half4 texcoord3 : TEXCOORD3; 
    half4 texcoord4 : TEXCOORD4; 
    half4 texcoord5 : TEXCOORD5; 
#endif 
}; 

Questa è la struttura restituita da vertex shader come input per il frammento:

struct v2f { 
    float4 pos : SV_POSITION; 
    float2 uv : TEXCOORD0; 
    fixed4 col: COLOR;   
}; 

Se Semplicemente inoltro il parametro allo shader di frammenti, ovviamente sarà interpolato:

v2f vert (appdata_full v) 
{ 

    v2f output; 
    //.... 
    output.col = v.color; 
} 

Vorrei passare il parametro v.color non interpolato allo shader del frammento. È possibile? Se sì come?


EDIT

come Tim rilevare, questo è il comportamento previsto, a causa della shader non può fare altro che interpolando i colori se questi sono passati dal vertex shader a frammentarsi. Proverò a spiegare meglio quello che sto cercando di ottenere. Uso i colori per vertice per memorizzare altri tipi di informazioni rispetto ai colori. Senza dire tutti i dettagli su quello che sto facendo, diciamo che puoi considerare ciascun vertice di colore come un id (ogni vertice dello stesso triangolo, avrà lo stesso colore. In realtà ogni vertice della stessa mesh).

Quindi ho usato il trucco del colore per mascherare alcuni parametri perché non ho altro modo di farlo. Ora questa informazione deve essere disponibile allo shader dei frammenti in qualche modo. Se un passaggio come parametro out del vertex shader, questa informazione codificata in un colore arriverà interpolato al frammento, che non può più non usarlo.

Sto cercando un modo di propagare questa informazione invariata fino al frammento shader (forse è possibile utilizzare una variabile globale o qualcosa del genere? Se sì come?).

+0

Come hai intenzione di conciliare tre ingressi (vertici) per pixel se non interpolare i valori di cosa vi aspettate che accada se ogni vertice del triangolo ha un valore diverso – Tim

+0

@ Tim? : ok ... hai ragione, capisco la tua obiezione . Il punto è che in realtà i colori non sono i colori nel mio shader. Sto usando i valori del colore per memorizzare altri tipi di informazioni. Ho bisogno di inoltrare questa informazione per vertice da vertice a frammento di shader, eventualmente non passandola come parametro out di vertex shader, ma usando qualche "variabile gloabal" se qualcosa del genere è possibile. Spero di essere chiaro. – Heisenbug

+0

Non importa se si tratta di un colore o di un indice o altro. Diciamo che stai disegnando un singolo triangolo. Basato su un calcolo arbitrario per vertice; vertice 0 uscite "5", vertice 1 uscite "10" e vertice 2 uscite "15". Quale di questi tre numeri vuoi mostrare nel tuo frammento shader? Se è davvero un valore globale di usare una divisa, altrimenti credo che non ci stia pensando completamente. A meno che tu non dica "Voglio sempre il valore del vertice 0 e ignorare gli altri due", questo non ha molto senso. – Tim

risposta

10

Non sono sicuro che ciò contenga una risposta, ma è un po 'troppo per un commento. Come sottolinea Bjorke, lo shader di frammenti riceverà sempre un valore interpolato. Se/quando Unity supporta Opengl 4.0, potresti avere accesso a Interpolation qualifiers, ovvero "flat" che disabilita l'interpolazione, derivando tutti i valori da un provoking vertex.

Detto questo, il problema con il tentativo di assegnare lo stesso valore "colore" a tutti i vertici di un triangolo è che il vertex shader scorre su un vertice una volta, non per un triangolo. Ci sarà sempre una regione "limite" in cui alcuni vertici condividono più spigoli con altri vertici di un "colore" o "id" diverso, vedi il mio esempio stupido qui sotto. Se applicato a una casella in (0,0,0), la parte superiore sarà rossa, quella inferiore verde e quella blu centrale.

Shader "custom/colorbyheight" { 
Properties { 
_Unique_ID ("Unique Identifier", float) = 1.0 
} 
SubShader { 
Pass { 
    CGPROGRAM 
    #pragma vertex vert 
    #pragma fragment frag 
    #include "UnityCG.cginc" 
    struct v2f { 
     float4 pos : SV_POSITION; 
     fixed4 color : COLOR; 
    }; 
    uniform float _Unique_ID; 
    v2f vert (appdata_base v) 
    { 
     v2f o; 
     o.pos = mul (UNITY_MATRIX_MVP, v.vertex); 
     float3 worldpos = mul(_Object2World, v.vertex).xyz; 
     if(worldpos[1] >= 0.0) 
     o.color.xyz = 0.35; //unique_id = 0.35 
     else 
     o.color.xyz = 0.1; //unique_id = 0.1 
     o.color.w = 1.0; 
     return o; 
    } 


    fixed4 frag (v2f i) : COLOR0 { 
    // local unique_id's set by the vertex shader and stored in the color 
    if(i.color.x >= 0.349 && i.color.x <=0.351) 
     return float4(1.0,0.0,0.0,1.0); //red 
    else if(i.color.x >= 0.099 && i.color.x <=0.11) 
     return float4(0.0,1.0,0.0,1.0); //green 

    // global unique_id set by a Unity script 
    if(_Unique_ID == 42.0) 
     return float4(1.0,1.0,1.0,1.0); //white 

    // Fallback color = blue 
    return float4(0.0,0.0,1.0,1.0); 
    } 
    ENDCG 
} 
} 
} 

Nella nota addizionale si dice "In realtà ogni vertice della stessa mesh". Se questo è il caso, perché non usare una proprietà modificabile, come ho incluso sopra. Ogni mesh ha solo bisogno di uno script per cambiare il unique_id.

public class ModifyShader : MonoBehaviour { 
public float unique_id = 1; 
// Use this for initialization 
void Start() { 

} 

// Update is called once per frame 
void Update() { 
    renderer.material.SetFloat("_Unique_ID", unique_id); 
} 
} 
+0

beh, grazie anche per la risposta.Non riesco a utilizzare SetFloat perché sto effettuando il batching della chiamata di disegno tramite StaticBatchingUtility. Se modifico un valore di un Materiale, Unity istanzia una nuova istanza di Materiale per ogni oggetto, impedendogli di usare un Materiale condiviso e di conseguenza non sarà più in batch. Quindi sono costretto a usare i parametri di colore assegnati per vertice per assegnare il valore shader personalizzato a ciascuna mesh. – Heisenbug

+0

Attualmente sto usando per vertice di colore come indici di indirezione di texture e id. Sfortunatamente questi valori dovrebbero essere usati all'interno dello shader del frammento. L'utilizzo di una variabile globale a 3 colori condivisa tra i programmi frammento e vertice (o alcuni parametri fuori specificati da non interpolare) potrebbe aiutarmi a realizzare un'implementazione più efficiente di alcuni riferimenti indiretti. – Heisenbug

+0

Bugger, sì, non funzionerebbe per te. Una "soluzione", e non mi piace perché è hacky e lenta, è quella di rendere tutti i triangoli della mesh univoci consentendo solo a ciascun vertice di formare 2 spigoli. In questo modo l'interpolazione lineare avviene solo tra i verts di un singolo triangolo restituendovi il corretto "id". – Jerdak

2

La GPU interpola sempre tra i valori. Se si desidera un valore costante per un triangolo, è necessario impostare lo stesso valore per tutti i vertici di quel triangolo. Questo a volte può essere inefficiente, ma è come OpenGL (e DirectX) funziona. Non esiste una nozione inerente di un valore di "faccia".

+0

non è possibile utilizzare alcune variabili globali condivise tra un'istanza di vertex shader e tutti gli shader di frammenti correlati? – Heisenbug

+0

non è il modo in cui è strutturato l'hardware, le costanti sono per mesh, non per-triangle e ciascuna mesh ottiene solo un vertice e un frammento shader.I registri "connettore", che sono interpolati, sono l'unico canale di comunicazione tra i due – bjorke

+0

grazie per il chiarimento – Heisenbug

1

È possibile effettuare le seguenti operazioni: glShadeModel(GL_FLAT). Disattiva l'interpolazione per tutti gli input di framment shader ed è disponibile anche in OpenGL precedente (pre 4.0).

Se si dispone di alcuni input che si desidera interpolare e altri no, eseguire il rendering una volta con GL_FLAT su una texture della stessa risoluzione dell'output, quindi eseguire nuovamente il rendering con GL_SMOOTH e campionare la trama per leggere i valori flat per ogni pixel (pur ottenendo anche valori interpolati nel solito modo).

Se è possibile utilizzare DirectX9, è possibile utilizzare il modificatore nointerpolation su singoli input di framment shader (shader modello 4 o successivo).

+1

glShadeModel non è esposto tramite l'Unity API. – Jerdak

+0

né in OpenGL ES (e quindi webgl) – bjorke

3

Questo è un thread piuttosto vecchio, ma recentemente ho riscontrato un problema simile e ho trovato una risposta super semplice. OSX Mavericks ora supporta OpenGL 4.1 così presto non sarà affatto un problema, ma ci vorrà ancora del tempo prima che Unity3d lo prelevi. Ad ogni modo, c'è un modo pulito per abilitare l'ombreggiatura piatta in Unity anche su OSX precedente (ad esempio Mountain Lion)!

Lo shader di seguito farà il lavoro (la parte cruciale è la linea con #extension, altrimenti si otterrebbe un errore di compilazione per l'utilizzo di una TV parola chiave"

Shader "GLSL flat shader" { 
SubShader { 
    Pass { 
    GLSLPROGRAM 

    #extension GL_EXT_gpu_shader4 : require 
    flat varying vec4 color; 

    #ifdef VERTEX 
    void main() 
    { 
     color = gl_Color; 
     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 
    } 
    #endif 

    #ifdef FRAGMENT 
    void main() 
    { 
     gl_FragColor = color; // set the output fragment color 
    } 
    #endif 

    ENDGLSL 
    } 
    } 
} 

Got ad essa mediante la combinazione di cose da : http://poniesandlight.co.uk/notes/flat_shading_on_osx/ http://en.wikibooks.org/wiki/GLSL_Programming/Unity/Debugging_of_Shaders