Detailed Shading Techniques

Colors and Gradients

Before diving deep into color handling in shaders, let's first establish a fundamental understanding of how colors work in computer graphics.

Basic Color Representation

In Makepad shaders, we use vec4 (RGBA vector) to represent colors:

  • Red: 0.0 to 1.0
  • Green: 0.0 to 1.0
  • Blue: 0.0 to 1.0
  • Alpha (transparency): 0.0 (completely transparent) to 1.0 (completely opaque)
1// Basic solid color shader
2fn pixel(self) -> vec4 {
3    // Format: vec4(red, green, blue, alpha)
4    return vec4(1.0, 0.0, 0.0, 1.0); // Pure red
5}

Linear Gradients

Linear gradients create smooth transitions between two colors. Here's an implementation of a horizontal gradient:

1fn pixel(self) -> vec4 {
2    // self.pos.x provides horizontal position from 0.0 to 1.0
3    let mix_factor = self.pos.x;
4
5    let color1 = vec4(1.0, 0.0, 0.0, 1.0); // Red
6    let color2 = vec4(0.0, 0.0, 1.0, 1.0); // Blue
7
8    // Linear interpolation between two colors
9    return mix(color1, color2, mix_factor);
10}

Visual representation:

1Red [================>] Blue
2        <- mix factor ->

For diagonal gradients, we can implement it like this:

1fn pixel(self) -> vec4 {
2    // Combine x and y coordinates for diagonal direction
3    let mix_factor = (self.pos.x + self.pos.y) * 0.5;
4
5    let color1 = vec4(1.0, 0.0, 0.0, 1.0); // Red
6    let color2 = vec4(0.0, 0.0, 1.0, 1.0); // Blue
7
8    return mix(color1, color2, mix_factor);
9}

Radial Gradients

Radial gradients create color transitions that spread outward from a center point:

1fn pixel(self) -> vec4 {
2    // Calculate distance from center point
3    let center = vec2(0.5, 0.5);
4    let dist = length(self.pos - center);
5
6    // Convert distance to mix factor
7    let mix_factor = clamp(dist * 2.0, 0.0, 1.0);
8
9    let inner_color = vec4(1.0, 1.0, 1.0, 1.0); // White at center
10    let outer_color = vec4(0.0, 0.0, 0.0, 1.0); // Black at edge
11
12    return mix(inner_color, outer_color, mix_factor);
13}

Texture Mapping

Texture mapping is the technique of applying 2D images to geometric surfaces.

Basic Texture Sampling

1DrawTexture = {{DrawTexture}} {
2    texture tex: texture2d // Declare texture uniform
3
4    fn pixel(self) -> vec4 {
5        // Sample texture at current position
6        return sample2d(self.tex, self.pos);
7    }
8}

UV Coordinate System

UV coordinates are used to map texture pixels to geometry:

1(0,0) +-----------+ (1,0)
2      |           |
3      |  Texture  |
4      |           |
5(0,1) +-----------+ (1,1)
1fn vertex(self) -> vec4 {
2    // Transform UV coordinates
3    self.uv = self.geom_pos * self.tex_scale + self.tex_offset;
4
5    // Regular vertex transformation
6    let clip_pos = self.geom_pos * self.rect_size + self.rect_pos;
7    return self.camera_projection * vec4(clip_pos, 0.0, 1.0);
8}
9
10fn pixel(self) -> vec4 {
11    // Use transformed UV for sampling
12    return sample2d(self.tex, self.uv);
13}

Special Effects

Gaussian Blur

Here's an implementation of a simple Gaussian blur:

1fn blur(self) -> vec4 {
2    let blur_radius = 2.0; // Blur radius
3    let mut color = vec4(0.0);
4    let mut total_weight = 0.0;
5
6    // Sample in a 5x5 grid around current pixel
7    for i in -2..3 {
8        for j in -2..3 {
9            let offset = vec2(float(i), float(j)) * blur_radius;
10            // Calculate Gaussian weight
11            let weight = exp(-(offset.x * offset.x + offset.y * offset.y));
12
13            color += sample2d(self.tex, self.pos + offset) * weight;
14            total_weight += weight;
15        }
16    }
17
18    return color / total_weight; // Normalize result
19}

Glow Effect

1fn glow(self) -> vec4 {
2    let base_color = sample2d(self.tex, self.pos); // Original color
3    let blur_color = self.blur(); // Blurred color
4
5    // Overlay blurred version to create glow effect
6    return base_color + blur_color * self.glow_strength;
7}

Shadow Effect

1// Using Makepad's built-in GaussShadow to implement shadow effect
2fn draw_shadow(self) -> vec4 {
3    let shadow_color = vec4(0.0, 0.0, 0.0, 0.5);
4    let shadow_offset = vec2(5.0, 5.0);
5
6    // Calculate shadow area
7    let shadow = GaussShadow::box_shadow(
8        self.rect_pos + shadow_offset,
9        self.rect_size,
10        self.pos,
11        10.0 // Shadow blur radius
12    );
13
14    return shadow_color * shadow;
15}