|
| 1 | +--- |
| 2 | +draft: false |
| 3 | +title: Three.js Journey Notes 6 - Shaders Part1 |
| 4 | +date: 2025-04-03 |
| 5 | +categories: Learning |
| 6 | +comments: true |
| 7 | +ShowToc: true |
| 8 | +isCJKLanguage: false |
| 9 | +--- |
| 10 | + |
| 11 | + |
| 12 | +## What is a shader |
| 13 | + |
| 14 | +- one of the main components of WebGL |
| 15 | +- a program written in **GLSL** |
| 16 | +- sent to GPU |
| 17 | +- Position each vertex of a geometry |
| 18 | +- Colorize each visible *"pixel"* (accurately should be using term *"fragment"*) of that geometry |
| 19 | + |
| 20 | +> Then we send a lot of data to the shader such as the vertices coordinates, the mesh transformation, information about the camera and its field of view, parameters like the color, the textures, the lights, the fog, etc. The GPU then processes all of this data following the shader instructions, and our geometry appears in the render. |
| 21 | +
|
| 22 | +Two types of shaders |
| 23 | +- **Vertex shader** |
| 24 | + - position the vertices of the geometry |
| 25 | + - send vertices positions, mesh transformations (its position, rotation, scale), camera information (its position, rotation, and field of view) to GPU |
| 26 | + - GPU use these data to project the vertices on a 2D space aka our canvas |
| 27 | + - data changes between vertices called **attribute**, like vertex position |
| 28 | + - attribute can only be used in vertex shader |
| 29 | + - data doesn't change between vertices called **uniform**, like position of the mesh |
| 30 | + - uniform can be used in both vertex shader and fragment shader |
| 31 | + - vertex shader happens first, and the fragment shader is proceeded |
| 32 | +- **Fragment shader** |
| 33 | + - color each visible fragment of the geometry |
| 34 | + - send data like color by using **uniform**, or |
| 35 | + - *send data from vertex shader to fragment shader*, this kind of data is called **varying** |
| 36 | + - 中间状态会 get interpolated,比如三角形每个顶点的 color 不一样的话,中间的点的颜色就会混合三种颜色。也不仅是 color 有这种效果 |
| 37 | + |
| 38 | +![[Screenshot 2025-04-03 at 11.00.10.png]] |
| 39 | + |
| 40 | +Why creating our own shaders |
| 41 | +- Three.js materials are limited |
| 42 | +- shaders can be very simple and performant |
| 43 | +- can apply post-process |
| 44 | + |
| 45 | +## `RawShaderMaterial` |
| 46 | + |
| 47 | +```javascript |
| 48 | +const material = new THREE.RawShaderMaterial({ |
| 49 | + vertexShader: ` |
| 50 | + uniform mat4 projectionMatrix; |
| 51 | + uniform mat4 viewMatrix; |
| 52 | + uniform mat4 modelMatrix; |
| 53 | +
|
| 54 | + attribute vec3 position; |
| 55 | +
|
| 56 | + void main() |
| 57 | + { |
| 58 | + gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0); |
| 59 | + } |
| 60 | + `, |
| 61 | + fragmentShader: ` |
| 62 | + precision mediump float; |
| 63 | +
|
| 64 | + void main() |
| 65 | + { |
| 66 | + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); |
| 67 | + } |
| 68 | + ` |
| 69 | +}) |
| 70 | +``` |
| 71 | + |
| 72 | +inside `vertexShader` and `fragmentShader` is the GLSL code for shaders. |
| 73 | + |
| 74 | +better to use separate GLSL file for this, add plugins like [vite-plugin-glsl](https://www.npmjs.com/package/vite-plugin-glsl) to make the project can import GLSL file |
| 75 | + |
| 76 | +```javascript |
| 77 | +import testVertexShader from './shaders/test/vertex.glsl' |
| 78 | +import testFragmentShader from './shaders/test/fragment.glsl' |
| 79 | + |
| 80 | +const material = new THREE.RawShaderMaterial({ |
| 81 | + vertexShader: testVertexShader, |
| 82 | + fragmentShader: testFragmentShader |
| 83 | +}) |
| 84 | +``` |
| 85 | + |
| 86 | +## GLSL basics |
| 87 | + |
| 88 | +- stands for OpenGL Shading Language |
| 89 | +- types language, close to C language |
| 90 | +- no way to log values |
| 91 | +- indentation not essential |
| 92 | +- semicolon is required, forgetting one will probably result in compilation error |
| 93 | +- Variable |
| 94 | + - `float` `int` `bool` `vec2` `vec3` `vec4` |
| 95 | +- functions |
| 96 | +- built-in native functions: as `sin`, `cos`, `max`, `min`, `pow`, `exp`, `mod`, `clamp`, but also very practical functions like `cross`, `dot`, `mix`, `step`, `smoothstep`, `length`, `distance`, `reflect`, `refract`, `normalize` |
| 97 | +- Docs: |
| 98 | + - [Shaderific for OpenGL](https://shaderific.com/glsl.html) |
| 99 | + - [OpenGL 4.x Reference Pages](https://registry.khronos.org/OpenGL-Refpages/gl4/html/indexflat.php) |
| 100 | + - [The Book of Shaders](https://thebookofshaders.com/) |
| 101 | + |
| 102 | +```glsl |
| 103 | +float a = 1.0; |
| 104 | +float b = 2.0; |
| 105 | +float c = a * b; |
| 106 | +
|
| 107 | +int d = 2; |
| 108 | +float e = b * float(d); |
| 109 | +
|
| 110 | +bool foo = true; |
| 111 | +bool bar = false; |
| 112 | +
|
| 113 | +vec2 foo = vec2(1.0, 2.0); // store 2 coordinates x, y |
| 114 | +// foo.x = 1.5; |
| 115 | +// foo.y = 3.0; |
| 116 | +foo *= 2.0; // will get (2.0, 4.0) |
| 117 | +
|
| 118 | +vec3 purpleColor = vec3(0.0); |
| 119 | +purpleColor.r = 0.5; // can use r, g, b (alias) |
| 120 | +purpleColor.b = 1.0; |
| 121 | +
|
| 122 | +
|
| 123 | +// create vec3 from vec2 |
| 124 | +vec2 foo2 = vec2(1.0, 2.0); |
| 125 | +vec3 bar = vec3(foo2, 3.0); |
| 126 | +
|
| 127 | +
|
| 128 | +// create vec2 from vec3 |
| 129 | +vec3 foo3 = vec3(1.0, 2.0, 3.0); |
| 130 | +vec2 bar2 = foo.xy; // bar2 will be (x,y), order matters |
| 131 | +
|
| 132 | +// vec3, xyzw, rgba |
| 133 | +vec4 foo4 = vec4(1.0, 2.0, 3.0, 4.0); |
| 134 | +float bar3 = foo4.w; // 4th value. same as foo4.a |
| 135 | +
|
| 136 | +// functions |
| 137 | +float loremIpsum() { |
| 138 | + float a = 1.0; |
| 139 | + float b = 2.0; |
| 140 | + return a + b; |
| 141 | +} |
| 142 | +
|
| 143 | +// use function |
| 144 | +float result = loremIpsum(); |
| 145 | +``` |
| 146 | + |
| 147 | +## Understanding the vertex shader |
| 148 | + |
| 149 | +```glsl |
| 150 | +uniform mat4 projectionMatrix; |
| 151 | +uniform mat4 viewMatrix; |
| 152 | +uniform mat4 modelMatrix; |
| 153 | +attribute vec3 position; |
| 154 | +
|
| 155 | +void main() |
| 156 | +{ |
| 157 | + gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0); |
| 158 | + // gl_Position.x += 0.5; |
| 159 | + // gl_Position.y += 0.5; |
| 160 | +} |
| 161 | +``` |
| 162 | + |
| 163 | +`main` function will be called automatically. |
| 164 | +The goal of the instructions in `main` function is to set `gl_Position` properly. And at the end, it's a `vec4`. We can access its `x` `y` `z` `w` properties. |
| 165 | + |
| 166 | +- First retrieve the vertex `position` with `attribute vec3 position;` |
| 167 | +- it can be applied for each vertex as `gl_Position = /* ... */ vec4(position, 1.0);` |
| 168 | +- 3 matrices transformations |
| 169 | + - `modelMatrix` apply transformations relative to Mesh. If we scale, rotate or move the Mesh |
| 170 | + - `viewMatrix` apply transformations relative to camera. If we rotate the camera to the left, the vertices should be on the right, if we move the camera in direction of the Mesh, the vertices should get bigger, etc |
| 171 | + - `projectionMatrix` finally transform our coordinations into the final clip space coordinations |
| 172 | + - read more: [LearnOpenGL - Coordinate Systems](https://learnopengl.com/Getting-started/Coordinate-Systems) |
| 173 | + |
| 174 | + |
| 175 | +Transforming our plane like wave via using `sin` on `z` |
| 176 | +```glsl |
| 177 | +void main() |
| 178 | +{ |
| 179 | + vec4 modelPosition = modelMatrix * vec4(position, 1.0); |
| 180 | + modelPosition.z += sin(modelPosition.x * 10.0) * 0.1; |
| 181 | +
|
| 182 | + // ... |
| 183 | +} |
| 184 | +``` |
0 commit comments