发光蛛网箱子
实现效果
你们的点赞、评论是我不断向前的动力
思路
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.);
}
如何绘制圆环
移动中心点—— 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.);
}
过渡取边线 对puv.y 范围在[0.09,0.0] 平滑过渡 超过显示白色 低于的显示黑色
float circular =fract(puv.y *12.);
circular = smoothstep(0.09,0.0,circular);
如何绘制直线
利用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.);
}
如何取都按边界线
让边界平滑过渡 对页面内容取反 并与之前的值相乘 让边界线更黑
line *= 1.-line;
利用smoothstep取线 把符合值的黑色区域 取成白色
float line=fract(puv.x*8.);
line*=1.-line;
line=smoothstep(.05,0.,line);
color+=line;
如何让线更细
把line值乘以3 0.05~0 之间区间的值更少
float line=fract(puv.x*8.);
line*=1.-line;
line=line*3.;
line=smoothstep(.05,0.,line);
color+=line;
叠加上之前的圆圈
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.);
}
去除边界的两种方式
1、利用step去除
line *= step(puv.y,0.5);
(有点像靶子,好像走偏了)
2、利用smoothstep去除
color+=smoothstep(.3,.5,abs(uv.y))+smoothstep(.3,.5,abs(uv.x));
用线图案减去上图
color+=line;
color-=smoothstep(.3,.5,abs(uv.y))+smoothstep(.3,.5,abs(uv.x));
上个色
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.);
}
再加个辉光效果吧
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>
(后处理慎用)