👨⚕️ 主页: gis分享者
👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!
👨⚕️ 收录于专栏:threejs gis工程师
文章目录
一、🍀前言
本文详细介绍如何基于threejs在三维场景中使用自定义GLSL 着色器,生成艺术作品,亲测可用。希望能帮助到您。一起学习,加油!加油!
1.1 ☘️GLSL着色器
GLSL(OpenGL Shading Language)是OpenGL的核心编程语言,用于编写图形渲染管线中可定制的计算逻辑。其核心设计目标是通过GPU并行计算实现高效的图形处理,支持从基础几何变换到复杂物理模拟的多样化需求。
1.1.1 ☘️着色器类型
顶点着色器(Vertex Shader)
- 功能:处理每个顶点的坐标变换(如模型视图投影矩阵变换)、法线计算及顶点颜色传递。
- 输出:裁剪空间坐标gl_Position,供后续光栅化阶段使用。
片段着色器(Fragment Shader)
- 功能:计算每个像素的最终颜色,支持纹理采样、光照模型(如Phong、PBR)及后处理效果(如模糊、景深)。
- 输出:像素颜色gl_FragColor或gl_FragColor(RGBA格式)。
计算着色器(Compute Shader,高级)
- 功能:执行通用并行计算任务(如物理模拟、图像处理),不直接绑定渲染管线。
- 特点:通过工作组(Work Group)实现高效数据并行处理。
1.1.2 ☘️工作原理
渲染管线流程
- 顶点处理:CPU提交顶点数据(位置、颜色、纹理坐标),GPU并行执行顶点着色器处理每个顶点。
- 光栅化:将顶点数据转换为像素片段,生成片段着色器输入。
- 片段处理:GPU并行执行片段着色器计算每个像素颜色。
- 输出合并:将片段颜色与帧缓冲区混合,生成最终图像。
数据流动
- 顶点属性:通过glVertexAttribPointer传递位置、颜色等数据,索引由layout(location=N)指定。
- Uniform变量:CPU通过glGetUniformLocation传递常量数据(如变换矩阵、时间),在渲染循环中更新。
- 内置变量: gl_Position(顶点着色器输出):裁剪空间坐标。 gl_FragCoord(片段着色器输入):当前像素的窗口坐标。
gl_FrontFacing(片段着色器输入):判断像素是否属于正面三角形。
1.1.3 ☘️核心特点
语法特性
- C语言变体:支持条件语句、循环、函数等结构,天然适配图形算法。
- 向量/矩阵运算:内置vec2/vec3/vec4及mat2/mat3/mat4类型,支持点乘、叉乘等操作。
- 精度限定符:如precision mediump float,控制计算精度与性能平衡。
硬件加速
- 并行计算:GPU数千个核心并行执行着色器代码,适合处理大规模数据(如粒子系统、体素渲染)。
- 内存模型:支持常量内存(Uniform)、纹理内存(Sampler)及共享内存(计算着色器),优化数据访问效率。
灵活性
- 可编程管线:完全替代固定渲染管线,支持自定义光照、阴影、后处理效果。
- 跨平台兼容性:OpenGL ES(移动端)与WebGL(Web)均支持GLSL,代码可移植性强。
1.1.4 ☘️应用场景
游戏开发
- 实时渲染:实现PBR材质、动态阴影、屏幕空间反射。
- 特效系统:粒子火焰、流体模拟、布料物理。
- 性能优化:通过计算着色器加速AI计算、碰撞检测。
数据可视化
- 科学计算:将多维数据映射为颜色/高度图(如气象数据、流场可视化)。
- 信息图表:动态生成3D柱状图、热力图,增强数据表现力。
艺术创作
- 程序化生成:使用噪声函数(如Perlin、Simplex)生成地形、纹理。
- 交互式装置:结合传感器数据实时修改着色器参数,创造动态艺术作品。
教育与研究
- 算法实验:实时调试光线追踪、路径追踪算法。
- 教学工具:可视化线性代数运算(如矩阵变换、向量投影)。
1.1.5 ☘️实战示例
顶点着色器(传递法线与世界坐标):
#version 330 core
layout(location=0) in vec3 aPos;
layout(location=1) in vec3 aNormal;
out vec3 FragPos;
out vec3 Normal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main() {
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = mat3(transpose(inverse(model))) * aNormal; // 模型空间到世界空间的法线变换
gl_Position = projection * view * vec4(FragPos, 1.0);
}
片段着色器(实现Blinn-Phong光照):
#version 330 core
in vec3 FragPos;
in vec3 Normal;
out vec4 FragColor;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;
uniform vec3 objectColor;
void main() {
// 环境光
vec3 ambient = 0.1 * lightColor;
// 漫反射
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
// 镜面反射
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = 0.5 * spec * lightColor;
// 最终颜色
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
}
二、🍀使用自定义GLSL 着色器,生成艺术作品
1. ☘️实现思路
使用自定义GLSL 着色器定义THREE.ShaderMaterial材质material,定义THREE.PlaneGeometry二维平面使用material材质生成艺术作品。具体代码参考代码样例。可以直接运行。
2. ☘️代码样例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>glsl着色器,生成艺术作品</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #111;
}
canvas {
display: block;
}
</style>
</head>
<body>
<div id="container"></div>
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.163.0/build/three.module.js';
const fragmentShaderCode = `
precision highp float;
uniform vec2 u_resolution;
uniform float u_time;
varying vec2 vUv;
#define EPSILON 1e-6
#define PI 3.14159265359
#define ITERATIONS 18.0
mat2 rotate2d(float angle){
return mat2(cos(angle), -sin(angle),
sin(angle), cos(angle));
}
void main() {
vec2 uv = (vUv * 2.0 - 1.0) * (u_resolution / max(u_resolution.x, u_resolution.y));
vec2 u = uv * 0.25;
vec4 o = vec4(0.5, 1.0, 1.5, 0.0);
vec4 z = o;
vec2 v_internal = vec2(0.0);
float a = 0.6;
float t = u_time * 0.8;
for (float i = 0.0; i < ITERATIONS; i++)
{
float u_dot = dot(u, u);
float denom_u = 0.6 - u_dot;
denom_u += sign(denom_u) * EPSILON;
vec2 sin_arg = (1.4 * u / denom_u) - (7.0 * u.yx * cos(t*0.2)) + t * 1.1 + v_internal * 0.3;
vec2 length_arg = (1.0 + i * 0.1 + a * 0.2) * sin(sin_arg);
float len = length(length_arg);
float safe_len_divisor = max(len, EPSILON);
o += (1.0 + sin(z * 0.9 + t * 1.2 + i * 0.1)) / safe_len_divisor * (1.0 + i*0.02);
v_internal = 0.9 * v_internal + 0.15 * sin(t * 1.5 + u * 4.0 - o.xy * 0.2);
v_internal = clamp(v_internal, -1.0, 1.0);
a += 0.035;
float angle = i * 0.1 + t * 0.05 + a * 0.2;
mat2 rot_mat = rotate2d(angle);
u *= rot_mat;
float o_dot = dot(o.xyz, o.xyz);
float feedback_scale = 0.5 + 0.5 * sin(o_dot * 0.02 + t * 0.3);
u += sin(60.0 * dot(u,u) * cos(80.0 * u.yx + t * 1.2)) / 2.5e2
+ 0.15 * a * u * feedback_scale
+ cos(o.xy * 0.5 + t * 1.1 + v_internal * 0.8) / 3.5e2;
u += rotate2d(v_internal.x * 0.01) * vec2(0.0001, 0.0);
}
vec3 base_color = 0.5 + 0.5 * cos(o.xyz * 0.8 + t * 0.15 + vec3(0.0, PI * 0.66, PI * 1.33));
vec2 detail_coord = u * 5.0 + v_internal * 1.0;
float detail_pattern = smoothstep(0.3, 0.7, fract(detail_coord.x * cos(t*0.1) + detail_coord.y * sin(t*0.1)));
vec3 detail_color = vec3(detail_pattern * 0.8 + 0.2);
float mix_factor = clamp(length(o.xyz) * 0.1 - 0.1, 0.0, 1.0);
vec3 final_color = mix(base_color, detail_color * base_color, mix_factor);
final_color.rg += u.xy * 0.05;
float dist_from_center = length(vUv - 0.5);
final_color *= pow(1.0 - dist_from_center * 1.2, 2.0);
gl_FragColor = vec4(clamp(final_color, 0.0, 1.0), 1.0);
}
`;
const vertexShaderCode = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4( position, 1.0 );
}
`;
let scene, camera, renderer, mesh, material, clock;
let container;
const uniforms = {
u_time: { value: 0.0 },
u_resolution: { value: new THREE.Vector2() }
};
function init() {
container = document.getElementById('container');
clock = new THREE.Clock();
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
container.appendChild(renderer.domElement);
scene = new THREE.Scene();
camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const geometry = new THREE.PlaneGeometry(2, 2);
material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: vertexShaderCode,
fragmentShader: fragmentShaderCode
});
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
uniforms.u_resolution.value.x = window.innerWidth;
uniforms.u_resolution.value.y = window.innerHeight;
window.addEventListener('resize', onWindowResize);
renderer.setAnimationLoop(animate);
console.log("Three.js setup complete. Starting animation loop.");
}
function onWindowResize() {
renderer.setSize(window.innerWidth, window.innerHeight);
uniforms.u_resolution.value.x = window.innerWidth;
uniforms.u_resolution.value.y = window.innerHeight;
material.uniforms.u_resolution.value.set(window.innerWidth, window.innerHeight);
}
function animate() {
uniforms.u_time.value = clock.getElapsedTime();
renderer.render(scene, camera);
}
init();
</script>
</body>
</html
效果如下
参考:源码