vtj-shader-development

star 144

Creates custom GLSL shaders with vite-plugin-glsl. Use when writing vertex/fragment shaders, managing uniforms, or adding ShaderMaterial debug panels.

hexianWeb By hexianWeb schedule Updated 2/2/2026

name: vtj-shader-development description: Creates custom GLSL shaders with vite-plugin-glsl. Use when writing vertex/fragment shaders, managing uniforms, or adding ShaderMaterial debug panels.

vite-threejs Shader Development

Overview

本项目使用 vite-plugin-glsl 支持 GLSL 文件导入,着色器存放在 src/shaders/ 目录。

核心原则:所有着色器必须存放在 shaders 目录,所有 uniform 必须有调试面板。

When to Use

  • 创建自定义着色器效果
  • 修改现有着色器
  • 添加后处理效果
  • 需要理解着色器导入和 uniform 管理

目录结构

src/shaders/
├── includes/              # 共享工具函数
│   ├── ambientLight.glsl
│   ├── directionalLight.glsl
│   └── pointLight.glsl
├── sky/                   # 天空盒
│   ├── vertex.glsl
│   └── fragment.glsl
├── speedlines/            # 后处理:速度线
│   ├── vertex.glsl
│   └── fragment.glsl
├── blocks/                # 方块着色器
│   ├── ao.vert.glsl       # 环境光遮蔽
│   ├── ao.frag.glsl
│   ├── mining.vert.glsl   # 挖掘效果
│   ├── mining.frag.glsl
│   └── wind.vert.glsl     # 植物风动
├── glass/                 # 玻璃折射
├── halftone/              # 半调渲染
└── grid/                  # 调试网格

导入着色器

vite.config.js 配置

import glsl from 'vite-plugin-glsl'

export default {
  plugins: [
    glsl(),  // 启用 .glsl 文件导入
  ],
}

导入方式

// 使用路径别名(推荐)
import skyFragment from '@/shaders/sky/fragment.glsl'
import skyVertex from '@/shaders/sky/vertex.glsl'

// 或相对路径
import speedLinesFragment from '../shaders/speedlines/fragment.glsl'

ShaderMaterial 模式

基础 ShaderMaterial

import fragmentShader from '@/shaders/effect/fragment.glsl'
import vertexShader from '@/shaders/effect/vertex.glsl'

export default class EffectMesh {
  constructor() {
    this.experience = new Experience()
    this.scene = this.experience.scene
    this.debug = this.experience.debug
    
    // Shader 配置参数
    this.config = {
      color: { r: 255, g: 255, b: 255 },
      intensity: 1.0,
      speed: 1.0,
    }
    
    this._createMaterial()
    this._createMesh()
    
    if (this.debug.active) {
      this.debugInit()
    }
  }
  
  _createMaterial() {
    this.material = new THREE.ShaderMaterial({
      uniforms: {
        uTime: { value: 0 },
        uColor: { value: new THREE.Color(1, 1, 1) },
        uIntensity: { value: 1.0 },
        uTexture: { value: null },
      },
      vertexShader,
      fragmentShader,
      transparent: true,
      side: THREE.DoubleSide,
    })
  }
  
  update() {
    const elapsed = this.experience.time.elapsed
    this.material.uniforms.uTime.value = elapsed * 0.001
  }
  
  // 必须实现调试面板!
  debugInit() {
    this.debugFolder = this.debug.ui.addFolder({
      title: 'Effect Shader',
      expanded: false,
    })
    
    // 颜色 uniform 使用 view: 'color'
    this.debugFolder.addBinding(this.config, 'color', {
      label: 'Color',
      view: 'color',
    }).on('change', (ev) => {
      this.material.uniforms.uColor.value.setRGB(
        ev.value.r / 255,
        ev.value.g / 255,
        ev.value.b / 255
      )
    })
    
    // 数值 uniform 使用 addBinding
    this.debugFolder.addBinding(this.config, 'intensity', {
      label: 'Intensity',
      min: 0, max: 2, step: 0.01,
    }).on('change', (ev) => {
      this.material.uniforms.uIntensity.value = ev.value
    })
  }
  
  destroy() {
    this.material.dispose()
    // ...
  }
}

后处理 ShaderPass

import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
import fragmentShader from '@/shaders/effect/fragment.glsl'
import vertexShader from '@/shaders/effect/vertex.glsl'

// 在 Renderer 中
this.effectPass = new ShaderPass({
  uniforms: {
    tDiffuse: { value: null },  // 必需:接收上一个 pass 的输出
    uTime: { value: 0 },
    uOpacity: { value: 1.0 },
  },
  vertexShader,
  fragmentShader,
})
this.composer.addPass(this.effectPass)

GLSL 代码规范

标准结构

// ===== Uniforms =====
uniform float uTime;
uniform vec3 uColor;
uniform sampler2D uTexture;

// ===== Varyings =====
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;

// ===== Helper Functions =====
float random(float seed) {
  return fract(sin(seed) * 43758.5453);
}

// ===== Main =====
void main() {
  // ...
  
  gl_FragColor = vec4(color, 1.0);
  
  // Three.js 色彩空间校正
  #include <tonemapping_fragment>
  #include <colorspace_fragment>
}

Include 模式

共享代码放在 includes/ 目录:

// includes/pointLight.glsl
vec3 pointLight(
  vec3 lightColor,
  float lightIntensity,
  vec3 normal,
  vec3 lightPosition,
  vec3 viewDirection,
  float specularPower,
  vec3 position,
  float lightDecay
) {
  vec3 lightDelta = lightPosition - position;
  float lightDistance = length(lightDelta);
  vec3 lightDir = normalize(lightDelta);
  
  float decay = 1.0 / (1.0 + lightDistance * lightDecay);
  float shading = max(0.0, dot(normal, lightDir));
  
  // Phong specular
  vec3 reflectDir = reflect(-lightDir, normal);
  float specular = pow(max(dot(viewDirection, reflectDir), 0.0), specularPower);
  
  return lightColor * lightIntensity * decay * (shading + specular);
}

使用 include:

// halftone/fragment.glsl
#include ../includes/ambientLight.glsl
#include ../includes/directionalLight.glsl
#include ../includes/pointLight.glsl

void main() {
  vec3 light = ambientLight(vec3(1.0), 0.2);
  light += pointLight(uLightColor, 1.0, vNormal, uLightPos, viewDir, 32.0, vPosition, 0.1);
  // ...
}

Custom Shader Material (CSM)

用于扩展标准材质(如添加 AO 到 MeshStandardMaterial):

// ao.vert.glsl - 顶点着色器
attribute float aAo;
varying float vAO;

void main() {
  vAO = 1.0 - aAo;
  
  // CSM 特殊变量:替代标准顶点变换
  csm_PositionRaw = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(position, 1.0);
}
// ao.frag.glsl - 片元着色器
varying float vAO;

void main() {
  float aoFactor = mix(0.5, 1.0, vAO);
  
  // CSM 特殊变量:修改漫反射颜色
  csm_DiffuseColor.rgb *= vec3(aoFactor);
}

Uniform 类型对照

GLSL 类型 JS 设置方式
float { value: 1.0 }
vec2 { value: new THREE.Vector2(1, 1) }
vec3 { value: new THREE.Vector3(1, 1, 1) }
vec3 (颜色) { value: new THREE.Color(1, 1, 1) }
mat4 { value: new THREE.Matrix4() }
sampler2D { value: texture }

调试面板要求(MANDATORY)

所有 ShaderMaterial 的 uniform 都必须有调试面板

debugInit() {
  const folder = this.debug.ui.addFolder({ title: 'Shader' })
  
  // 颜色 → view: 'color'
  folder.addBinding(this.config, 'color', { view: 'color' })
  
  // 数值 → min/max/step
  folder.addBinding(this.config, 'value', { min: 0, max: 1 })
  
  // 向量 → 分轴控制或 point2d
  folder.addBinding(this.config, 'offset', {
    x: { min: -10, max: 10 },
    y: { min: -10, max: 10 },
  })
}

Common Mistakes

❌ 着色器放错位置

// BAD: 着色器在 js 目录
import shader from './world/effect.glsl'

// GOOD: 着色器在 shaders 目录
import shader from '@/shaders/effect/fragment.glsl'

❌ 缺少调试面板

// BAD: 有 uniform 但没有 debugInit
this.material = new THREE.ShaderMaterial({
  uniforms: { uIntensity: { value: 1.0 } },
  // ...
})
// 没有 debugInit() → 无法调参

// GOOD: 必须有调试面板
debugInit() {
  this.debugFolder.addBinding(this.config, 'intensity', {...})
}

❌ 颜色 uniform 不使用 view: 'color'

// BAD: 颜色使用默认控件
folder.addBinding(this.config, 'color')  // 显示为数字输入框

// GOOD: 使用颜色选择器
folder.addBinding(this.config, 'color', { view: 'color' })

❌ 忘记色彩空间校正

// BAD: 直接输出(颜色可能偏暗)
gl_FragColor = vec4(color, 1.0);

// GOOD: 添加色彩空间校正
gl_FragColor = vec4(color, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>

Quick Reference

需求 做法
创建着色器 src/shaders/<name>/ 创建 vertex.glsl 和 fragment.glsl
导入着色器 import shader from '@/shaders/<path>.glsl'
共享代码 放入 includes/,使用 #include
更新 uniform material.uniforms.uValue.value = newValue
调试面板 必须在 debugInit() 中为每个 uniform 创建控件
颜色控件 使用 view: 'color'
Install via CLI
npx skills add https://github.com/hexianWeb/Third-Person-MC --skill vtj-shader-development
Repository Details
star Stars 144
call_split Forks 37
navigation Branch main
article Path SKILL.md
More from Creator