Three.js - Shaders
Links
https://www.shaderific.com/glsl-functions - Überblick über Funktionen https://thebookofshaders.com/ - Gutes Tutorial und guter Überblick http://localhost/www/LEARNING/ThreeJS/Graphtoy/Graphtoy.html https://iquilezles.org/www/index.htm - useful math https://www.youtube.com/watch?v=NQ-g6v7GtoI&list=PL4neAtv21WOmIrTrkNO3xCyrxg4LKkrF7&index=4 - Shader Liniting in VSC und Shader Tutorial https://www.shadertoy.com/ https://learnopengl.com/Getting-started/Coordinate-Systems
Einführung
Shader sind ein komplexes Thema und du benötigst viel Zeit zum Üben. Man kann aber auch ohne ein Mathegenie zu sein tolle Shader schreiben und es stehen dir ganz neue grafische Möglichkeiten zur Verfügung, die sich sonst nicht realisieren lassen würden.
Quelle zu großen Teilen: https://threejs-journey.com/lessons/27 (Zeichnungen und englischsprachige Abschnitte)
Was ist ein Shader?
Ein Shader ist ein Programm der in der Programmiersprache GLSL (OpenGL ES Shading Language) GLSL Programme werden direkt an die GPU gesendet werden und können rasend schnell verarbeitet werden. Dies ist die Basis von WebGL.
Die Aufgabe des Shaders ist jeden Vertex einer Geometrie zu positionieren und jedes sichtbares Fragment dieser Geometrie einzufärben. Das Ergebnis ist ein fertiges Rendering das wir im Browser über das Canvas Element darstellen können. Die Pixel auf dem Monitor können sich von den Pixeln in einem Rendering unterscheiden. Deshalt nutzt man den Terminus Fragment statt Pixel. Die Fragments beziehen sich auf die kleinste Einheit beim Rendering und bilden quasi das Pendant zu Pixeln in der Renderwelt.
Shader sind nicht auf den Browser beschränkt. Auch native Programme oder Apps können Shader nutzen, hier geht es aber um den Einsatz von Shadern mit Three.js im Browser.
Vertex und Fragment Shader
Der Renderprozess nutzt 2 Arten von Shadern.
Der Vertex Shader verarbeitet alle Geometriedaten (Objekte, Kamera,...) und projiziert sie auf die 2D-Ebene des fertigen Renderbilds.
Der Fragment Shader färbt anschließend jedes sichtbare Fragment des Shaders ein.
Wie funktioniert der Shader?
Es ist wichtig die Arbeitsweise zu verstehen.
Der Vertex Shader wird für jeden Vertex ausgeführt. Dabei bekommt er Daten, wie z.b. die Position, die sich bei jedem Vertex ändern. Diese nennt man Attributes. Daten die für jeden Vertex gleich bleiben nennt man Uniform. Attribute kann man nur an den Vertex Shader senden. Wenn sie im Fragment Shader benötigt werden, muss der Vertex Shader als Varying weitersenden. Uniforms stehen auch direkt im Fragment Shader zur Verfügung.
Wenn der Vertex Shader die Positionierung der Vertices erledigt hat. Färbt der Fragment Shader jedes Fragment ein. Er färbt also nicht nur die Vertices sondern auch die Bereiche dazwischen. Dabei interpoliert er automatisch die Farbe auf Basis der vorhandenen Information (z.B.Farbe der umgebenden Vertices, Faces, Texturen...)
Zusammenfassung
- Der Vertex Shader positioniert Vertices auf dem Rendering.
- Der Fragment Shader färbt jedes sichtbare Fragment (quasi Pixel) der Geometrie.
- Der Fragment Shader wird nach dem Vertex Shader ausgeführt.
- Daten die sich von Vertex zu Vertex unterscheiden nennt man Attribute und können nur an den Vertex Shader gesendet werden.
- Daten die sich nicht zwischen den Vertices unterscheiden nennt man Uniform.
- Auf Uniforms kann man im Vertex und im Fragment Shader zugreifen.
- Wir können mit einem Varying Daten vom Vertex zum Fragmentshader senden.
Eigene Shader in Three.js
ShaderMaterial / RawShaderMaterial
Eigene Shader kann man in Three.js über besondere Materialien realisieren: ShaderMaterial oder RawShaderMaterial.
Bei einem ShaderMaterial kann man etwas Code sparen, da dieses automatisch Code voranstellt, den man (zumindest teilweise) sonst selbst einfügen müßte.
GLSF Code kann man direkt in die Objekte vertexShader und fragmentShader schreiben.
const material = new THREE.RawShaderMaterial({
vertexShader: `// vertex shader code goes here`,
fragmentShader: `// fragment shader code goes here`
})
Den Code zwischen die Backticks schreiben ist allerdings nicht besonders sinnvoll. Besser geht es über separate Dateien. wir legen zwei Dateien an:
/shaders/test/vertex.glsl
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
attribute vec3 position;
void main()
{
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}
/shaders/test/fragment.glsl
precision mediump float;
void main()
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}Jetzt können wir die Dateien als Variable importieren (wir gehen hier von einem Wepack Projekt aus) und in unseren Shader einfügen.
/script.js
import testVertexShader from './shaders/test/vertex.glsl'
import testFragmentShader from './shaders/test/fragment.glsl'
//...
// Geometry
const geometry = new THREE.PlaneGeometry(1, 1, 32, 32)
// Material
const material = new THREE.RawShaderMaterial({
vertexShader: testVertexShader,
fragmentShader: testFragmentShader
})
// Mesh
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
Wenn alles passt können wir den ersten Shader in Aktion sehen.
Eventuell gibt es ein paar Probleme mit unserem Setup. Die gehen wir im folgenden Exkurs an...
Exkurs: VisualStudioCode und Webpack für glsl Dateien einrichten
Extension für Syntaxhighlight in VSC
Shader languages support for VS Code von slevesque Syntax highlighter for shader language (hlsl, glsl, cg)
Webpack anpassen
Wir müssen Webpack beibringen, wie es mit .glsl files umgehen soll. Dafür müssen wir das rules array anpassen. Das kann in unterschiedlichen Files liegen. Einfach mal von package.json ausgehend über die scripts Property schauen wo die Konfigurationsdateien liegen.
Folgende Regel anlegen:
module.exports = {
// ...
module:
{
rules:
[
// ...
// Shaders
{
test: /\.(glsl|vs|fs|vert|frag)$/,
type: 'asset/source',
generator:
{
filename: 'assets/images/[hash][ext]'
}
}
]
}
}
This rule solely tells Webpack to provide the raw content of the files having .glsl, .vs, .fs, .vert or .frag as extension.
Re-launch the server with npm run dev and the Webpack error will disappear.
If you log testVertexShader and testFragmentShader, you'll get the shader code as a plain string. We can use these two variables in our RawShaderMaterial.