【百练shader】有手就行的蒸汽波太阳示例

156 阅读4分钟

蒸汽波太阳

实现效果

QQ录屏20240603210651[00-00-01--00-00-04].gif

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

绘制太阳 太阳的颜色 黄到红 绘制线 高到低逐渐变厚

首先创建基础页面内容,使用2*2大小的平面放置我们的太阳

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

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 sunPlane = new THREE.PlaneGeometry(2, 2)
const material = new THREE.ShaderMaterial({
  fragmentShader: fragmentShader,
  vertexShader: vertexShader,
  uniforms: {
    uTime: { value: 0 },
  },
  side: THREE.DoubleSide,
})
const sunMesh = new THREE.Mesh(sunPlane, material)
scene.add(sunMesh)


const render = (): void => {
  renderer.render(scene, camera)
}
const clock = new THREE.Clock()
const animate = (): void => {
  requestAnimationFrame(animate)
  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>

顶点着色器代码

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

本文主要修改内容为片段着色器 所以只关注这块就好

2、绘制太阳

计算利用 length 与 smoothstep 绘制太阳的形状

void main(){
  vec2 uv=vUv;
  float circular=length(uv-vec2(.5));
  circular = smoothstep(.3,.29,circular);
  vec3 color=vec3(0.);
  color += circular;
  gl_FragColor=vec4(color,1.);
}

image.png

利用mix 混合两种颜色创建太阳颜色

void main(){
  vec2 uv=vUv;
  float circular=length(uv-vec2(.5));
  circular=smoothstep(.3,.29,circular);
  vec3 color=vec3(0.);
  vec3 sumCol=mix(vec3(1.,0.,.2),vec3(1.,1.0,0.),uv.y);
  color+=circular*sumCol;
  gl_FragColor=vec4(color,1.);
}

image.png

此时太阳边缘 绘制时留下的过渡效果 让边缘看着很模糊 image.png 增加太阳颜色中 红色与绿色

vec3 sumCol=mix(vec3(4.,0.,.2),vec3(1.,1.1,0.),uv.y);

image.png

image.png

3、绘制线

利用fract取小数吧uv分20份

void main(){
  vec2 uv=vUv;
  float circular=length(uv-vec2(.5));
  circular=smoothstep(.3,.29,circular);
  vec3 color=vec3(0.);
  vec3 sumCol=mix(vec3(4.,0.,.2),vec3(1.,1.1,0.),uv.y);
  float line=fract(uv.y*20.);
  // color+=circular*sumCol;
  color+=line;
  gl_FragColor=vec4(color,1.);
}

image.png

结果 减去 0.5 取绝对值

 vec2 uv=vUv;
  float circular=length(uv-vec2(.5));
  circular=smoothstep(.3,.29,circular);
  vec3 color=vec3(0.);
  vec3 sumCol=mix(vec3(4.,0.,.2),vec3(1.,1.1,0.),uv.y);
  float line=fract(uv.y*20.);
  line = abs(line-.5);
  // color+=circular*sumCol;
  color+=line;
  gl_FragColor=vec4(color,1.);

image.png

利用step函数 生成没有过渡效果的 黑白条纹

 vec2 uv=vUv;
  float circular=length(uv-vec2(.5));
  circular=smoothstep(.3,.29,circular);
  vec3 color=vec3(0.);
  vec3 sumCol=mix(vec3(4.,0.,.2),vec3(1.,1.1,0.),uv.y);
  float line=fract(uv.y*20.);
  line = abs(line-.5);
  line = step(line,.2);
  // color+=circular*sumCol;
  color+=line;
  gl_FragColor=vec4(color,1.);

image.png

目前效果和我们预期中由窄到宽不一致 可以通过增加一个渐变值来处理

void main(){
  vec2 uv=vUv;
  float circular=length(uv-vec2(.5));
  circular=smoothstep(.3,.29,circular);
  vec3 color=vec3(0.);
  vec3 sumCol=mix(vec3(4.,0.,.2),vec3(1.,1.1,0.),uv.y);
  // 递减值
  float decreasing = (-uv.y + 0.3);
  float line=fract(uv.y*20.);
  line = abs(line-.5);
  line = step(line,.2);
  // color+=circular*sumCol;
  color+=decreasing;
  gl_FragColor=vec4(color,1.);
}

image.png 减去递减值 得到由上到下 增厚的线

void main(){
  vec2 uv=vUv;
  float circular=length(uv-vec2(.5));
  circular=smoothstep(.3,.29,circular);
  vec3 color=vec3(0.);
  vec3 sumCol=mix(vec3(4.,0.,.2),vec3(1.,1.1,0.),uv.y);
  // 递减值
  float decreasing=(-uv.y+.3);
  float line=fract(uv.y*20.);
  line=abs(line-.5);
  line-=decreasing;
  line=step(line,.2);
  // color+=circular*sumCol;
  color+=line;
  gl_FragColor=vec4(color,1.);
}

image.png

通过与前面的太阳叠加 叠加前通过 1.0 -line 把线图案的黑白位置替换

void main(){
  vec2 uv=vUv;
  float circular=length(uv-vec2(.5));
  circular=smoothstep(.3,.29,circular);
  vec3 color=vec3(0.);
  vec3 sumCol=mix(vec3(4.,0.,.2),vec3(1.,1.1,0.),uv.y);
  // 递减值
  float decreasing=(-uv.y+.3);
  float line=fract(uv.y*20.);
  line=abs(line-.5);
  line-=decreasing;
  line=step(line,.2);
  color+=circular*sumCol*(1.-line);
  gl_FragColor=vec4(color,1.);
}

image.png

最后让太阳线动起来

varying vec2 vUv;
uniform float uTime;

void main(){
  vec2 uv=vUv;
  float circular=length(uv-vec2(.5));
  circular=smoothstep(.3,.29,circular);
  vec3 color=vec3(0.);
  vec3 sumCol=mix(vec3(4.,0.,.2),vec3(1.,1.1,0.),uv.y);
  // 递减值
  float decreasing=(-uv.y+.3);
  uv.y +=uTime/10.;
  float line=fract(uv.y*20.);
  line=abs(line-.5);
  line-=decreasing;
  line=step(line,.2);
  color+=circular*sumCol*(1.-line);
  gl_FragColor=vec4(color,1.);
}

QQ录屏20240603210651[00-00-01--00-00-04].gif

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

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