当前位置: 首页 > news >正文

学习threejs,打造交互式花卉生成器

👨‍⚕️ 主页: gis分享者
👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!
👨‍⚕️ 收录于专栏:threejs gis工程师


文章目录

  • 一、🍀前言
    • 1.1 ☘️THREE.ShaderMaterial
      • 1.1.1 ☘️注意事项
      • 1.1.2 ☘️构造函数
      • 1.1.3 ☘️属性
      • 1.1.4 ☘️方法
  • 二、🍀打造交互式花卉生成器
    • 1. ☘️实现思路
    • 2. ☘️代码样例


一、🍀前言

本文详细介绍如何基于threejs在三维场景中打造交互式花卉生成器,亲测可用。希望能帮助到您。一起学习,加油!加油!

1.1 ☘️THREE.ShaderMaterial

THREE.ShaderMaterial使用自定义shader渲染的材质。 shader是一个用GLSL编写的小程序 ,在GPU上运行。

1.1.1 ☘️注意事项

  • ShaderMaterial 只有使用 WebGLRenderer 才可以绘制正常, 因为 vertexShader 和
    fragmentShader 属性中GLSL代码必须使用WebGL来编译并运行在GPU中。
  • 从 THREE r72开始,不再支持在ShaderMaterial中直接分配属性。 必须使用
    BufferGeometry实例,使用BufferAttribute实例来定义自定义属性。
  • 从 THREE r77开始,WebGLRenderTarget 或 WebGLCubeRenderTarget
    实例不再被用作uniforms。 必须使用它们的texture 属性。
  • 内置attributes和uniforms与代码一起传递到shaders。
    如果您不希望WebGLProgram向shader代码添加任何内容,则可以使用RawShaderMaterial而不是此类。
  • 您可以使用指令#pragma unroll_loop_start,#pragma unroll_loop_end
    以便通过shader预处理器在GLSL中展开for循环。 该指令必须放在循环的正上方。循环格式必须与定义的标准相对应。
  • 循环必须标准化normalized。
  • 循环变量必须是i。
  • 对于给定的迭代,值 UNROLLED_LOOP_INDEX 将替换为 i 的显式值,并且可以在预处理器语句中使用。
#pragma unroll_loop_start
for ( int i = 0; i < 10; i ++ ) {// ...}
#pragma unroll_loop_end

代码示例

const material = new THREE.ShaderMaterial( {uniforms: {time: { value: 1.0 },resolution: { value: new THREE.Vector2() }},vertexShader: document.getElementById( 'vertexShader' ).textContent,fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );

1.1.2 ☘️构造函数

ShaderMaterial( parameters : Object )
parameters - (可选)用于定义材质外观的对象,具有一个或多个属性。 材质的任何属性都可以从此处传入(包括从Material继承的任何属性)。

1.1.3 ☘️属性

共有属性请参见其基类Material。

.clipping : Boolean
定义此材质是否支持剪裁; 如果渲染器传递clippingPlanes uniform,则为true。默认值为false。

.defaultAttributeValues : Object
当渲染的几何体不包含这些属性但材质包含这些属性时,这些默认值将传递给shaders。这可以避免在缓冲区数据丢失时出错。

this.defaultAttributeValues = {'color': [ 1, 1, 1 ],'uv': [ 0, 0 ],'uv2': [ 0, 0 ]
};

.defines : Object
使用 #define 指令在GLSL代码为顶点着色器和片段着色器定义自定义常量;每个键/值对产生一行定义语句:

defines: {FOO: 15,BAR: true
}

这将在GLSL代码中产生如下定义语句:

#define FOO 15
#define BAR true

.extensions : Object
一个有如下属性的对象:

this.extensions = {derivatives: false, // set to use derivativesfragDepth: false, // set to use fragment depth valuesdrawBuffers: false, // set to use draw buffersshaderTextureLOD: false // set to use shader texture LOD
};

.fog : Boolean
定义材质颜色是否受全局雾设置的影响; 如果将fog uniforms传递给shader,则为true。默认值为false。

.fragmentShader : String
片元着色器的GLSL代码。这是shader程序的实际代码。在上面的例子中, vertexShader 和 fragmentShader 代码是从DOM(HTML文档)中获取的; 它也可以作为一个字符串直接传递或者通过AJAX加载。

.glslVersion : String
定义自定义着色器代码的 GLSL 版本。仅与 WebGL 2 相关,以便定义是否指定 GLSL 3.0。有效值为 THREE.GLSL1 或 THREE.GLSL3。默认为空。

.index0AttributeName : String
如果设置,则调用gl.bindAttribLocation 将通用顶点索引绑定到属性变量。默认值未定义。

.isShaderMaterial : Boolean
只读标志,用于检查给定对象是否属于 ShaderMaterial 类型。

.lights : Boolean
材质是否受到光照的影响。默认值为 false。如果传递与光照相关的uniform数据到这个材质,则为true。默认是false。

.linewidth : Float
控制线框宽度。默认值为1。

由于OpenGL Core Profile与大多数平台上WebGL渲染器的限制,无论如何设置该值,线宽始终为1。

.flatShading : Boolean
定义材质是否使用平面着色进行渲染。默认值为false。

.uniforms : Object
如下形式的对象:

{ "uniform1": { value: 1.0 }, "uniform2": { value: 2 } }

指定要传递给shader代码的uniforms;键为uniform的名称,值(value)是如下形式:

{ value: 1.0 }

这里 value 是uniform的值。名称必须匹配 uniform 的name,和GLSL代码中的定义一样。 注意,uniforms逐帧被刷新,所以更新uniform值将立即更新GLSL代码中的相应值。

.uniformsNeedUpdate : Boolean
可用于在 Object3D.onBeforeRender() 中更改制服时强制进行制服更新。默认为假。

.vertexColors : Boolean
定义是否使用顶点着色。默认为假。

.vertexShader : String
顶点着色器的GLSL代码。这是shader程序的实际代码。 在上面的例子中,vertexShader 和 fragmentShader 代码是从DOM(HTML文档)中获取的; 它也可以作为一个字符串直接传递或者通过AJAX加载。

.wireframe : Boolean
将几何体渲染为线框(通过GL_LINES而不是GL_TRIANGLES)。默认值为false(即渲染为平面多边形)。

.wireframeLinewidth : Float
控制线框宽度。默认值为1。

由于OpenGL Core Profile与大多数平台上WebGL渲染器的限制,无论如何设置该值,线宽始终为1。

1.1.4 ☘️方法

共有方法请参见其基类Material。

.clone () : ShaderMaterial this : ShaderMaterial
创建该材质的一个浅拷贝。需要注意的是,vertexShader和fragmentShader使用引用拷贝; attributes的定义也是如此; 这意味着,克隆的材质将共享相同的编译WebGLProgram; 但是,uniforms 是 值拷贝,这样对不同的材质我们可以有不同的uniforms变量。

二、🍀打造交互式花卉生成器

1. ☘️实现思路

一个交互式的 GPU 着色器视觉特效 Demo。用户点击/触摸屏幕时,会在光标(指针)位置产生花朵/茎的绘制效果(通过 WebGL shader 实现);还有一个按钮可以“清屏”(clean),让画面重置或淡出。视觉风格比较抽象,依赖噪声函数(noise)来创造自然感、边缘扰动、花瓣变形等。具体代码参考下面代码样例。

2. ☘️代码样例

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>花卉生成器</title><style>html, body {overflow: hidden;padding: 0;margin: 0;}.container {position: fixed;top: 0;left: 0;width: 100%;height: 100vh;display: flex;flex-direction: column-reverse;align-items: start;}.clean-btn {z-index: 1;font-family: sans-serif;font-size: 15px;color: white;text-shadow: 0 0 10px #000000;user-select: none;padding: 0 0 15px 25px;cursor: pointer;text-decoration: underline;opacity: .5;}canvas {position: absolute;top: 0;left: 0;display: block;}.name {position: fixed;top: 50%;left: 50%;width: 100%;transform: translate(-50%, -50%);color: white;text-align: center;font-size: 4vw;text-shadow: 0 0 5px #000000;user-select: none;pointer-events: none;}@media all and (min-width: 640px) {.name {font-size: 45px}}</style>
</head>
<body>
<div class="container"><canvas id="canvas"></canvas><div class="clean-btn">clean the screen</div>
</div>
<div class="name">Click To Add Flowers
</div>
</body>
<script type="x-shader/x-fragment" id="fragmentShader">#define PI 3.14159265359uniform float u_ratio;uniform vec2 u_cursor;uniform float u_stop_time;uniform float u_clean;uniform vec2 u_stop_randomizer;uniform sampler2D u_texture;varying vec2 vUv;// --------------------------------// 2D noisevec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }vec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); }float snoise(vec2 v) {const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);vec2 i = floor(v + dot(v, C.yy));vec2 x0 = v - i + dot(i, C.xx);vec2 i1;i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);vec4 x12 = x0.xyxy + C.xxzz;x12.xy -= i1;i = mod289(i);vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);m = m*m;m = m*m;vec3 x = 2.0 * fract(p * C.www) - 1.0;vec3 h = abs(x) - 0.5;vec3 ox = floor(x + 0.5);vec3 a0 = x - ox;m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);vec3 g;g.x = a0.x * x0.x + h.x * x0.y;g.yz = a0.yz * x12.xz + h.yz * x12.yw;return 130.0 * dot(m, g);}float get_flower_shape(vec2 _p, float _pet_n, float _angle, float _outline) {_angle *= 3.;_p = vec2(_p.x * cos(_angle) - _p.y * sin(_angle),_p.x * sin(_angle) + _p.y * cos(_angle));float a = atan(_p.y, _p.x);float flower_sectoral_shape = pow(abs(sin(a * _pet_n)), .4) + .25;vec2 flower_size_range = vec2(.03, .1);float size = flower_size_range[0] + u_stop_randomizer[0] * flower_size_range[1];float flower_radial_shape = pow(length(_p) / size, 2.);flower_radial_shape -= .1 * sin(8. * a); // add noiseflower_radial_shape = max(.1, flower_radial_shape);flower_radial_shape += smoothstep(0., 0.03, -_p.y + .2 * abs(_p.x));float grow_time = step(.25, u_stop_time) * pow(u_stop_time, .3);float flower_shape = 1. - smoothstep(0., flower_sectoral_shape, _outline * flower_radial_shape / grow_time);flower_shape *= (1. - step(1., grow_time));return flower_shape;}float get_stem_shape(vec2 _p, vec2 _uv, float _w, float _angle) {_w = max(.004, _w);float x_offset = _p.y * sin(_angle);x_offset *= pow(3. * _uv.y, 2.);_p.x -= x_offset;// add horizontal noise to the cursor coordinalefloat noise_power = .5;float cursor_horizontal_noise = noise_power * snoise(2. * _uv * u_stop_randomizer[0]);cursor_horizontal_noise *= pow(dot(_p.y, _p.y), .6);// moise to be zero at cursorcursor_horizontal_noise *= pow(dot(_uv.y, _uv.y), .3);// moise to be zero at bottom_p.x += cursor_horizontal_noise;// vertical line through the cursor point (_p.x)float left = smoothstep(-_w, 0., _p.x);float right = 1. - smoothstep(0., _w, _p.x);float stem_shape = left * right;// make it grow + don't go up to the cursor pointfloat grow_time = 1. - smoothstep(0., .2, u_stop_time);float stem_top_mask = smoothstep(0., pow(grow_time, .5), .03 -_p.y);stem_shape *= stem_top_mask;// stop drawing once donestem_shape *= (1. - step(.17, u_stop_time));return stem_shape;}void main() {vec3 base = texture2D(u_texture, vUv).xyz;vec2 uv = vUv;uv.x *= u_ratio;vec2 cursor = vUv - u_cursor.xy;cursor.x *= u_ratio;vec3 stem_color = vec3(.1 + u_stop_randomizer[0] * .6, .6, .2);vec3 flower_color = vec3(.6 + .5 * u_stop_randomizer[1], .1, .9 - .5 * u_stop_randomizer[1]);float angle = .5 * (u_stop_randomizer[0] - .5);float stem_shape = get_stem_shape(cursor, uv, .003, angle);stem_shape += get_stem_shape(cursor + vec2(0., .2 + .5 * u_stop_randomizer[0]), uv, .003, angle);float stem_mask = 1. - get_stem_shape(cursor, uv, .004, angle);stem_mask -= get_stem_shape(cursor + vec2(0., .2 + .5 * u_stop_randomizer[0]), uv, .004, angle);float petals_back_number = 1. + floor(u_stop_randomizer[0] * 2.);float angle_offset = -(2. * step(0., angle) - 1.) * .1 * u_stop_time;float flower_back_shape = get_flower_shape(cursor, petals_back_number, angle + angle_offset, 1.5);float flower_back_mask = 1. - get_flower_shape(cursor, petals_back_number, angle + angle_offset, 1.6);float petals_front_number = 2. + floor(u_stop_randomizer[1] * 2.);float flower_front_shape = get_flower_shape(cursor, petals_front_number, angle, 1.);float flower_front_mask = 1. - get_flower_shape(cursor, petals_front_number, angle, .95);vec3 color = base;color *= stem_mask;color *= flower_back_mask;color *= flower_front_mask;color += (stem_shape * stem_color);color += (flower_back_shape * (flower_color + vec3(0., .8 * u_stop_time, 0.)));color += (flower_front_shape * flower_color);color.r *= 1. - (.5 * flower_back_shape * flower_front_shape);color.b *= 1. - (flower_back_shape * flower_front_shape);color *= u_clean;gl_FragColor = vec4(color, 1.);}</script><script type="x-shader/x-vertex" id="vertexShader">varying vec2 vUv;void main() {vUv = uv;gl_Position = vec4(position, 1.);}
</script>
<script type="module">import * as THREE from "https://cdn.skypack.dev/three@0.133.1/build/three.module";const canvasEl = document.querySelector("#canvas");const cleanBtn = document.querySelector(".clean-btn");const pointer = {x: 0.66,y: 0.3,clicked: true};// for codepen previewwindow.setTimeout(() => {pointer.x = 0.75;pointer.y = 0.5;pointer.clicked = true;}, 700);let basicMaterial, shaderMaterial;let renderer = new THREE.WebGLRenderer({canvas: canvasEl,alpha: true});renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));let sceneShader = new THREE.Scene();let sceneBasic = new THREE.Scene();let camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 10);let clock = new THREE.Clock();let renderTargets = [new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight),new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight)];createPlane();updateSize();window.addEventListener("resize", () => {updateSize();cleanCanvas();});render();let isTouchScreen = false;window.addEventListener("click", (e) => {if (!isTouchScreen) {pointer.x = e.pageX / window.innerWidth;pointer.y = e.pageY / window.innerHeight;pointer.clicked = true;}});window.addEventListener("touchstart", (e) => {isTouchScreen = true;pointer.x = e.targetTouches[0].pageX / window.innerWidth;pointer.y = e.targetTouches[0].pageY / window.innerHeight;pointer.clicked = true;});cleanBtn.addEventListener("click", cleanCanvas);function cleanCanvas() {pointer.vanishCanvas = true;setTimeout(() => {pointer.vanishCanvas = false;}, 50);}function createPlane() {shaderMaterial = new THREE.ShaderMaterial({uniforms: {u_stop_time: { type: "f", value: 0 },u_stop_randomizer: {type: "v2",value: new THREE.Vector2(Math.random(), Math.random())},u_cursor: { type: "v2", value: new THREE.Vector2(pointer.x, pointer.y) },u_ratio: { type: "f", value: window.innerWidth / window.innerHeight },u_texture: { type: "t", value: null },u_clean: { type: "f", value: 1 }},vertexShader: document.getElementById("vertexShader").textContent,fragmentShader: document.getElementById("fragmentShader").textContent});basicMaterial = new THREE.MeshBasicMaterial();const planeGeometry = new THREE.PlaneGeometry(2, 2);const planeBasic = new THREE.Mesh(planeGeometry, basicMaterial);const planeShader = new THREE.Mesh(planeGeometry, shaderMaterial);sceneBasic.add(planeBasic);sceneShader.add(planeShader);}function render() {shaderMaterial.uniforms.u_clean.value = pointer.vanishCanvas ? 0 : 1;shaderMaterial.uniforms.u_texture.value = renderTargets[0].texture;if (pointer.clicked) {shaderMaterial.uniforms.u_cursor.value = new THREE.Vector2(pointer.x,1 - pointer.y);shaderMaterial.uniforms.u_stop_randomizer.value = new THREE.Vector2(Math.random(),Math.random());shaderMaterial.uniforms.u_stop_time.value = 0;pointer.clicked = false;}shaderMaterial.uniforms.u_stop_time.value += clock.getDelta();renderer.setRenderTarget(renderTargets[1]);renderer.render(sceneShader, camera);basicMaterial.map = renderTargets[1].texture;renderer.setRenderTarget(null);renderer.render(sceneBasic, camera);let tmp = renderTargets[0];renderTargets[0] = renderTargets[1];renderTargets[1] = tmp;requestAnimationFrame(render);}function updateSize() {shaderMaterial.uniforms.u_ratio.value = window.innerWidth / window.innerHeight;renderer.setSize(window.innerWidth, window.innerHeight);}
</script></html>

效果如下:
在这里插入图片描述
源码

http://www.dtcms.com/a/477707.html

相关文章:

  • Redis 学习笔记(二)
  • 北京展览馆网站建设wordpress插件排列
  • 北京做网站优化多少钱最基本最重要的网站推广工具是
  • 每日算法刷题Day70:10.13:leetcode 二叉树10道题,用时2h
  • MySQL 设置远程 IP 连接方式(含自动检测授权脚本)
  • flash型网站网址高校思政课网站建设
  • 网站建设费做什么会计科目硬件开发外包平台
  • 【SpringBoot从初学者到专家的成长15】MVC、Spring MVC与Spring Boot:理解其差异与联系
  • Docker 存储与数据共享
  • k8s storageclasses nfs-provisioner 部署
  • Linux(Samba服务)
  • 电商智能客服进化论:多轮对话+意图识别+知识推荐系统开发
  • 算法198. 打家劫舍
  • 刚学做网站怎么划算全栈网站开发工程师
  • 长春网站优化公司wordpress目录遍历漏洞
  • 华为OD-23届考研-Java面经
  • 10.9 鸿蒙创建和运行项目
  • delphi调用C#编写的DLL
  • 从API调用到智能体编排:GPT-5时代的AI开发新模式
  • C++学习录(1):C++入门简介,从零开始
  • 电力专用多功能微气象监测装置在电网安全运维中的核心价值是什么?
  • 科研快报 |声波“听”见火灾温度:混合深度学习重构三维温度场
  • 从超级大脑到智能毛细血管:四大技术重构智慧园区生态版图
  • 旅游网站建设方案书制作一个网站平台需要多少钱
  • SQL入门:集合运算实战指南
  • Docker 网络类型与容器通信
  • Oracle 21C 部署ogg踩过的坑
  • vue3 中播放.flv视频
  • Oracle AWR报告中Load Profile源码
  • 电子商务网站开发的任务书企业文化心得体会