【百练shader】有手就行的发光蛛网箱子

165 阅读4分钟

发光蛛网箱子

实现效果

QQ录屏20240603225712[00-00-00--00-00-03].gif

你们的点赞、评论是我不断向前的动力
思路

1、创建页面 2、利用极坐标中的极角 绘制直线 3、利用极坐标中的极径 绘制圆环 4、利用后处理通道制作 发光

内容中对之前提到的基础内容可以查看往期文章

1、three场景代码

vue页面代码

<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import vertexShader from './main.vert'
import fragmentShader from './main.frag'


onMounted(() => {})
let scene = new THREE.Scene()

const renderer = new THREE.WebGLRenderer({
  antialias: true, // 抗锯齿
})
renderer.setClearColor(0xffffff)
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 5
// 添加相机控制器
new OrbitControls(camera, renderer!.domElement)


const box = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.ShaderMaterial({
  fragmentShader: fragmentShader,
  vertexShader: vertexShader,
  uniforms: {
    uTime: { value: 0 },
  },
  side: THREE.DoubleSide, //双面材质
})
const boxMesh = new THREE.Mesh(box, material)
boxMesh.rotation.x += 0.3
boxMesh.rotation.y += 0.5
scene.add(boxMesh)


const render = (): void => {
  renderer.render(scene, camera)
}
const clock = new THREE.Clock()
const animate = (): void => {
  requestAnimationFrame(animate)
  material.uniforms.uTime.value = clock.getElapsedTime() // 给shader传递时间
  render()
}
animate()


onBeforeUnmount(() => {
  // destroyThree() // TODO: 后面再补
  document.body.removeChild(renderer.domElement) // 避免热更新时创建新的canvas
})
</script>

<template>
  <div class="demo"></div>
</template>

<style scoped></style>

顶点着色器代码

uniform float uTime;
varying vec2 vUv;
void main(){
	vec4 mPosition=modelMatrix*vec4(position,1.);
	gl_Position=projectionMatrix*viewMatrix*mPosition;
	vUv = uv;
}

片段着色器代码

varying vec2 vUv;
uniform float uTime;

void main(){
  vec3 color=vec3(0.);
  gl_FragColor=vec4(color,1.);
}

image.png

如何绘制圆环

移动中心点—— uv-=.5; 修改uv范围

绘制极坐标—— float r=length(uv);// 极径 float a=atan(uv.x,uv.y);// 极角绘制极坐标

绘制扩散的圆环—— 对puv.y(到中心点的距离) 扩大并取小数 float circular =fract(puv.y *12.);

varying vec2 vUv;
uniform float uTime;

void main(){
  vec2 uv=vUv;
  uv-=.5;
  float r=length(uv);// 极径
  float a=atan(uv.x,uv.y);// 极角
  // a += u_time; // 旋转
  vec2 puv=vec2(a,r);// 转换为极坐标 puv.x 范围是 -pi ~ pi
  puv=vec2(puv.x/6.2831+0.5,puv.y); // puv.x 范围是 0 ~ 1

  vec3 color=vec3(.0);
  float circular =fract(puv.y *12.);
  color += circular;
  gl_FragColor=vec4(color,1.);
}

image.png

过渡取边线 对puv.y 范围在[0.09,0.0] 平滑过渡 超过显示白色 低于的显示黑色

    float circular =fract(puv.y *12.);
     circular = smoothstep(0.09,0.0,circular);

image.png

如何绘制直线

利用puv.x(归一化后的角度)把表面分为8份 值范围都是0~1

void main(){
  vec2 uv=vUv;
  uv-=.5;
  float r=length(uv);// 极径
  float a=atan(uv.x,uv.y);// 极角
  // a += u_time; // 旋转
  vec2 puv=vec2(a,r);// 转换为极坐标 puv.x 范围是 -pi ~ pi
  puv=vec2(puv.x/6.2831+0.5,puv.y); // puv.x 范围是 0 ~ 1

  vec3 color=vec3(.0);
  // 圆圈
  float circular =fract(puv.y *12.);
  circular = smoothstep(0.09,0.0,circular);
  // 线
  float line =fract(puv.x *8.);

  color += line;
  gl_FragColor=vec4(color,1.);
}

image.png

如何取都按边界线

让边界平滑过渡 对页面内容取反 并与之前的值相乘 让边界线更黑

line *= 1.-line;

image.png

利用smoothstep取线 把符合值的黑色区域 取成白色

 float line=fract(puv.x*8.);
  line*=1.-line;
  line=smoothstep(.05,0.,line);
  color+=line;

image.png 如何让线更细

把line值乘以3 0.05~0 之间区间的值更少

float line=fract(puv.x*8.);
  line*=1.-line;
  line=line*3.;
  line=smoothstep(.05,0.,line);
  color+=line;

image.png

叠加上之前的圆圈

void main(){
  vec2 uv=vUv;
  uv-=.5;
  float r=length(uv);// 极径
  float a=atan(uv.x,uv.y);// 极角
  // a += u_time; // 旋转
  vec2 puv=vec2(a,r);// 转换为极坐标 puv.x 范围是 -pi ~ pi
  puv=vec2(puv.x/6.2831+.5,puv.y);// puv.x 范围是 0 ~ 1
  
  vec3 color=vec3(.0);
  // 圆圈
  float circular=fract(puv.y*12.);
  circular=smoothstep(.09,0.,circular);
  // 线
  float line=fract(puv.x*8.);
  line*=1.-line;
  line=line*3.;
  line=smoothstep(.05,0.,line);
  
  line += circular; 
  color+=line;
  gl_FragColor=vec4(color,1.);
}

image.png

去除边界的两种方式

1、利用step去除

line *= step(puv.y,0.5);

image.png (有点像靶子,好像走偏了)

2、利用smoothstep去除

color+=smoothstep(.3,.5,abs(uv.y))+smoothstep(.3,.5,abs(uv.x));

image.png

用线图案减去上图

 color+=line;
  color-=smoothstep(.3,.5,abs(uv.y))+smoothstep(.3,.5,abs(uv.x));

image.png 上个色

varying vec2 vUv;
uniform float uTime;

void main(){
  vec2 uv=vUv;
  uv-=.5;
  float r=length(uv);// 极径
  float a=atan(uv.x,uv.y);// 极角
  // a += u_time; // 旋转
  vec2 puv=vec2(a,r);// 转换为极坐标 puv.x 范围是 -pi ~ pi
  puv=vec2(puv.x/6.2831+.5,puv.y);// puv.x 范围是 0 ~ 1
  
  vec3 color=vec3(.0);
  // 圆圈
  float circular=fract(puv.y*12.);
  circular=smoothstep(.09,0.,circular);
  // 线
  float line=fract(puv.x*8.);
  line*=1.-line;
  line=line*3.;
  line=smoothstep(.05,0.,line);
  
  line+=circular;
  // line *= step(puv.y,0.5);
  line*=smoothstep(.0,.1,abs(puv.y ));
  color+=line;
  color-=smoothstep(.3,.5,abs(uv.y))+smoothstep(.3,.5,abs(uv.x));
  color *=vec3(0.08, 0.93, 0.11);
  gl_FragColor=vec4(color,1.);
}

image.png

再加个辉光效果吧

RenderPass 后处理通道

UnrealBloomPass 辉光通道 模拟现实世界中的镜头泛光效果 强度(1.5)、模糊大小(0.4)、阈值(0.85)

OutputPass 是一个后期处理效果,用于将最终的渲染结果输出到屏幕上

并在render中执行composer.render()渲染

<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import vertexShader from './main.vert'
import fragmentShader from './main.frag'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass'

onMounted(() => {})
let scene = new THREE.Scene()

const renderer = new THREE.WebGLRenderer({
  antialias: true, // 抗锯齿
})

renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
renderer.autoClear = false
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 5
// 添加相机控制器
new OrbitControls(camera, renderer!.domElement)

const box = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.ShaderMaterial({
  fragmentShader: fragmentShader,
  vertexShader: vertexShader,
  uniforms: {
    uTime: { value: 0 },
  },
  side: THREE.DoubleSide,
})
const boxMesh = new THREE.Mesh(box, material)

scene.add(boxMesh)

// 创建一个辉光效果的后期处理通道 将辉光效果的后期处理通道添加到渲染器的后期处理器中
const renderScene = new RenderPass(scene, camera)
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85)
const outputPass = new OutputPass()
const composer = new EffectComposer(renderer)
composer.renderToScreen = true
composer.addPass(renderScene)
composer.addPass(bloomPass)
composer.addPass(outputPass)

const render = (): void => {
  composer && composer.render()
  renderer.render(scene, camera)
}
const clock = new THREE.Clock()
const animate = (): void => {
  requestAnimationFrame(animate)
  // 渲染场景
  boxMesh.rotation.x += 0.02
  boxMesh.rotation.y += 0.02
  material.uniforms.uTime.value = clock.getElapsedTime()

  render()
}
animate()

onBeforeUnmount(() => {
  // destroyThree() // TODO: 后面再补
  document.body.removeChild(renderer.domElement) // 避免热更新时创建新的canvas
})
</script>

<template>
  <div class="demo"></div>
</template>

<style scoped></style>

(后处理慎用) QQ录屏20240603225712[00-00-00--00-00-03].gif

你们的点赞、评论是我不断向前的动力 希望这个示例能帮助你理解在着色器

文章写的不多 欢迎大家多提点建议