Sto cercando di dare un senso a un pattern usato con un framework webgl javascript.
sfondo
WebGL utilizza programmi shader per disegnare o calcolare cose. Questi programmi devono essere compilati in fase di runtime, quindi a un certo punto esistono in javascript come stringhe.
La libreria offre un livello di astrazione chiamato "Materiale". Espone proprietà di alto livello che descrivono di che colore è qualcosa, o quanto è brillante, che viene passato a uno shader specifico sotto il cofano.
API
Questi shader che appartengono ai materiali sono modelli pre-scritti, che assomigliano a questo:
#include <foo> //non standard shader syntax
#include <bar>
#include <baz>
void main(){ //standard shader syntax
#include <foo_vert>
#include <bar_vert>
#include <baz_vert>
gl_Position = baz * bar + foo; //standard shader syntax
}
Alcuni FOO materiali possono consistere in foo e bar, mentre un altro materiale BAR può consistere solo di foo, mentre BAZ può consistere di tutti e tre, ecc.
La libreria analizza questo modello, non è un codice shader valido. Lo spazio dei nomi ha un dizionario di "shader chunks"
chunks = {
foo: string,
bar: string,
baz: string,
...
}
Quindi, se si crea un Materiale, tutto lo shader è nascosto all'utente. La libreria stessa modifica questa stringa, sostituendo l'istruzione #include
con il valore corrispondente dal dizionario chunks
.
avanzate
Tuttavia, gli utenti possono avere familiarità con la struttura di questi modelli e il contenuto dei blocchi e spesso desiderano modificare alcune parti dello shader mantenendo tutto il resto così com'è.
Ho riscontrato un problema nel digerire la soluzione disponibile e ho notato che spesso viene richiesto un modello diverso.
Senza la necessità di modificare, l'astrazione di alto livello ha questo aspetto:
//not even aware that shaders happen beyond this
var redPlasticMaterial = new Material({
shininess: veryShiny,
color: red
})
Con la necessità di modificare, è necessario conoscere la logica all'interno dei blocchi e il modello del Materiale da modificare (dove vengono utilizzati i blocchi). Uno identifica che "foo", "bar" e "baz" devono cambiare, alias "defaultChunk [nome] non funziona" e si possono definire queste modifiche chunkName -> string
:
var myShaderChunk_foo = 'lots of GLSL'
var myShaderChunk_bar = defaultChunks[bar] + 'lots of GLSL'
var myShaderChunk_baz = 'baz glsl'
la soluzione
var myCustomRedPlasticMaterial = new Material({
color: red,
shininess: veryShiny
})
//a callback that is called before processing the template
myCustomRedPlasticMaterial.onBeforeCompile = function( shader ){
shader.vertexShader // template for one shader program
shader.fragmentShader // template for another shader program
//if i want to replace foo bar baz
shader.vertexShader = shader.vertexShader.replace( '#include <foo>', myShaderChunk_foo)
shader.vertexShader = shader.vertexShader.replace( '#include <bar>', myShaderChunk_bar)
shader.vertexShader = shader.vertexShader.replace( '#include <baz>', myShaderChunk_baz)
...
}
Per ottimizzare le chiamate grafiche, la libreria memorizza nella cache questi programmi shader. Se questa logica di ramificazione di callback, due materiali con rami diversi avrebbero lo stesso hash perché utilizza il codice sorgente della funzione per l'hash .
//cant be used since body of the function is used for hashing
onBeforeCompile( shader ) {
if( qux ){
//replace shader foo
} else {
//replace shader bar
}
}
// i have to do
onBeforeCompileQux( shader ) {
//replace foo
}
onBeforeCompileNonQux( shader ){
//replace bar
}
Che cosa sta succedendo qui e ha un nome? L'API ti fornisce una stringa non elaborata e la lingua ti dà la possibilità di modificarla. Ho notato che cercare l'intera frase #include <name>
sembra ridondante, ma c'è ancora qualcosa da dire a riguardo?
Senza il mio intervento - cercando questo e sostituendolo, la libreria sa come gestire #include <>
e sa dove trovare chunk[name]
.
Dovendo associare manualmente queste chiamate alla stringa, le chiamate sembrano "convenzione sulla configurazione"? Ma poi questa cosa del codice grezzo sembra più configurabile rispetto alla convenzione. La funzione stessa diventa dati?
stessa cosa attraverso la configurazione?
Il diverso modello che ho visto è quello di fornire questi pezzi come dati al materiale, attraverso una diversa API. Se sono a conoscenza di chunk
che si tratta di un dizionario, qualunque cosa lo usi può essere detto di guardare qualcos'altro.
myMaterial.chunks.foo = foo
myMaterial.chunks.bar = bar
myMaterial.chunks.baz = baz
^ questa configurazione è su convenzione? In entrambi i casi vedo l'intento:
- So che il materiale utilizza
chunks.foo
- Voglio che il materiale utilizzi un diverso
foo
Nella richiamata noto:
- Devo essere consapevole dell'evento di analisi / compilazione
- Devo conoscere l'intera
#include <>
sintassi - Devo manipolare esplicitamente una stringa
- Devo fornire "pippo"
Con le proprietà:
- (devo sapere dove va, ma dovrebbe essere lo stesso di defaultChunk di cui sono a conoscenza, quindi non sono sicuro se conta, ma posso anche vedere che è "devo conoscere l'API di sostituzione" che può forse essere più complicato)
- Devo fornire "pippo"
D'altra parte, "ecco una stringa fare qualsiasi cosa" sembra davvero flessibile e funziona con i metodi primitivi della lingua. Quest'ultimo ha bisogno di familiarità con un'altra api? Che tipo di terminologia posso applicare qui per spiegare meglio la situazione e quali modelli possono essere nominati?