Skip to content

Commit 0b9162f

Browse files
committed
📝 Update shader notes.
1 parent 54d5c85 commit 0b9162f

1 file changed

Lines changed: 273 additions & 1 deletion

File tree

content/posts/threejs-journey-notes-6-shaders-part1.md

Lines changed: 273 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Two types of shaders
3535
- *send data from vertex shader to fragment shader*, this kind of data is called **varying**
3636
- 中间状态会 get interpolated,比如三角形每个顶点的 color 不一样的话,中间的点的颜色就会混合三种颜色。也不仅是 color 有这种效果
3737

38-
![[Screenshot 2025-04-03 at 11.00.10.png]]
38+
3939

4040
Why creating our own shaders
4141
- Three.js materials are limited
@@ -182,3 +182,275 @@ void main()
182182
// ...
183183
}
184184
```
185+
186+
## Understanding the Fragment Shader
187+
188+
comes after vertex shader
189+
190+
```glsl
191+
// instruction to decide how precise a float be
192+
// highp: preformance hit and might not work on some devices
193+
// mediump
194+
// lowp: can create bugs by the lack of precision
195+
precision mediump float;
196+
197+
void main()
198+
{
199+
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
200+
}
201+
```
202+
203+
same as vertex shader, the goal is to set `gl_FragColor`, it's also a `vec4` which is corresponding to color channels (`r`, `g`, `b`, `a`), each property goes from 0.0 to 1.0.
204+
205+
If alpha below 1.0, we need to set `transparent: true` in `RawShaderMaterial`.
206+
207+
208+
## Attributes - only in Vertex Shader
209+
210+
For three.js geometry, there is already one attribute named `position` that contains the `vec3` coordinates of each vertex.
211+
212+
Adding a custom attribute:
213+
214+
```javascript
215+
const count = geometry.attributes.position.count
216+
const randoms = new Float32Array(count)
217+
218+
for(let i = 0; i < count; i++) {
219+
randoms[i] = Math.random()
220+
}
221+
222+
// we name the attribute as `aRandom`
223+
geometry.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 1))
224+
```
225+
226+
then you can use is in vertex shader
227+
228+
```glsl
229+
// vertex.glsl
230+
// ...
231+
232+
// define it
233+
attribute float aRandom;
234+
235+
void main()
236+
{
237+
// ...
238+
modelPosition.z += aRandom * 0.1; // apply on y to make the plane with random spikes
239+
240+
// ...
241+
}
242+
```
243+
244+
## Varyings - send data from Vertex Shader to Fragment Shader
245+
246+
define a varying called `vRandom` and assign it with `aRandom` in vertex shader, then `vRandom` can be used in fragment shader.
247+
248+
```glsl
249+
// vertex.glsl
250+
attribute float aRandom;
251+
varying float vRandom;
252+
253+
void main()
254+
{
255+
// ...
256+
257+
vRandom = aRandom;
258+
}
259+
```
260+
261+
```glsl
262+
// fragment.glsl
263+
precision mediump float;
264+
265+
varying float vRandom;
266+
267+
void main()
268+
{
269+
gl_FragColor = vec4(0.5, vRandom, 1.0, 1.0);
270+
}
271+
```
272+
273+
Then the spikes on the plane is colored, and values between the vertices are **interpolated** - If the GPU is drawing a fragment right between two vertices —one having a varying of `1.0` and the other having a varying of `0.0`—the fragment value will be `0.5`.
274+
275+
276+
## Uniforms - send data from JS to shader, both
277+
278+
Add custom uniforms to material:
279+
280+
```javascript
281+
const material = new THREE.RawShaderMaterial({
282+
vertexShader: testVertexShader,
283+
fragmentShader: testFragmentShader,
284+
uniforms:
285+
{
286+
uFrequency: { value: new THREE.Vector2(10, 5) },
287+
uTime: { value: 0 },
288+
uColor: { value: new THREE.Color('orange') },
289+
}
290+
})
291+
```
292+
293+
and also update `uTime` in tick function:
294+
295+
```javascript
296+
const tick = () =>
297+
{
298+
const elapsedTime = clock.getElapsedTime()
299+
300+
// Update material
301+
material.uniforms.uTime.value = elapsedTime
302+
303+
// ...
304+
}
305+
```
306+
307+
Then we can use it in shaders
308+
309+
```glsl
310+
// vertex.glsl
311+
// ...
312+
uniform vec2 uFrequency;
313+
314+
// ...
315+
316+
void main()
317+
{
318+
// ...
319+
modelPosition.z += sin(modelPosition.x * uFrequency.x + uTime) * 0.1;
320+
modelPosition.z += sin(modelPosition.y * uFrequency.y + uTime) * 0.1;
321+
322+
// ...
323+
}
324+
```
325+
326+
The plane is animated like a flag wave with flying wind
327+
328+
Also we can change the color in fragment shader:
329+
330+
```glsl
331+
// fragment.glsl
332+
precision mediump float;
333+
334+
uniform vec3 uColor;
335+
336+
void main()
337+
{
338+
gl_FragColor = vec4(uColor, 1.0);
339+
}
340+
```
341+
342+
### Applying Texture
343+
344+
```javascript
345+
const flagTexture = textureLoader.load('/textures/flag-french.jpg')
346+
const material = new THREE.RawShaderMaterial({
347+
// ...
348+
uniforms:
349+
{
350+
// ...
351+
uTexture: { value: flagTexture }
352+
}
353+
})
354+
```
355+
356+
Texture can also be used as a uniform, and we need the UV coordinates to make the texture applied accordingly.
357+
`uv` is attributes already defined in `geometry.attributes.uv`, we can retrieve it directly.
358+
359+
So we need to retrieve the attribute `uv` and assign it to another varying in vertex shader, and then fragment shader can use the varying to apply on texture.
360+
361+
```glsl
362+
// vertex.glsl
363+
364+
// ...
365+
attribute vec2 uv;
366+
367+
varying vec2 vUv;
368+
369+
void main()
370+
{
371+
// ...
372+
373+
vUv = uv;
374+
}
375+
```
376+
377+
```glsl
378+
// fragment.glsl
379+
380+
precision mediump float;
381+
382+
uniform vec3 uColor;
383+
uniform sampler2D uTexture;
384+
385+
varying vec2 vUv;
386+
387+
void main()
388+
{
389+
vec4 textureColor = texture2D(uTexture, vUv);
390+
gl_FragColor = textureColor;
391+
}
392+
```
393+
394+
## Color variations - as shadows (not accurate)
395+
396+
Store the wind elevation in a variable in vertex shader and send it to fragment shader:
397+
398+
```glsl
399+
// ...
400+
varying float vElevation;
401+
402+
403+
void main()
404+
{
405+
// ...
406+
407+
float elevation = sin(modelPosition.x * uFrequency.x - uTime) * 0.1;
408+
elevation += sin(modelPosition.y * uFrequency.y - uTime) * 0.1;
409+
410+
modelPosition.z += elevation;
411+
vElevation = elevation;
412+
413+
// ...
414+
}
415+
```
416+
417+
Then use `vElevation` in fragment shader, use it to change r, g, and b properties of textureColor:
418+
419+
```glsl
420+
// ...
421+
varying float vElevation;
422+
423+
void main()
424+
{
425+
vec4 textureColor = texture2D(uTexture, vUv);
426+
textureColor.rgb *= vElevation * 2.0 + 0.5;
427+
gl_FragColor = textureColor;
428+
}
429+
```
430+
431+
Then the brightness variations on the flag as if there is light and shadows.
432+
433+
434+
## `ShaderMaterial`
435+
436+
It's similar to `RawShaderMaterial` with pre-built uniforms and attributes in the shader codes. We can remove the following:
437+
438+
- `uniform mat4 projectionMatrix;`
439+
- `uniform mat4 viewMatrix;`
440+
- `uniform mat4 modelMatrix;`
441+
- `attribute vec3 position;`
442+
- `attribute vec2 uv;`
443+
- `precision mediump float;`
444+
445+
446+
## Debugging Tip
447+
448+
Cannot log data, so need to check the compilation error details, or read the shader code.
449+
450+
Another solution is use `gl_FragColor` to test the values on screen.
451+
452+
453+
Other refs:
454+
- The Book of Shaders: [https://thebookofshaders.com/](https://thebookofshaders.com/)
455+
- ShaderToy: [https://www.shadertoy.com/](https://www.shadertoy.com/)
456+
- The Art of Code Youtube Channel: [https://www.youtube.com/channel/UCcAlTqd9zID6aNX3TzwxJXg](https://www.youtube.com/channel/UCcAlTqd9zID6aNX3TzwxJXg)

0 commit comments

Comments
 (0)