【Three.js基础学习】28.Coffee Smoke

前言

/*

   

        补充:材质本身纹理有光源等信息因此能看到模型

        gltf.scene.traverse((child) => {

            if (child.isMesh) {

                child.material.map = null; // 移除贴图

            }

        });

        此时是纯白色,按照正常逻辑 没有光会是灰/黑色

        为什么显示白色

        1.默认材质颜色

        2.材质的表现是均匀的纯色,而非根据光影变化显示灰色

        3.Three.js 默认的行为是让材质按照它本身的颜色显示

    课程

        制作咖啡的烟雾(热气)    

        预制纹理: https://opengameart.org/content/noise-texture-pack

        制作纹理器: https://mebiusbox.github.io/contents/EffectTextureMaker/

       

        在选择噪声纹理时,请记住3条规则

            足够的变体

            大到足够

            重复的模式(或“耕作”)

       

        1.创建smoke ,

            创建一个几何体PlanGeometry, 移动几何体位置,同时缩放(在第一帧时可以做,但是不要在tick中执行)

            创建材质,创建网格,添加到场景中

        2. 使用着色器材质,创建顶点着色器,片段着色器

            projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0)

            这里我写的顺序不对,导致渲染时候中间烟雾随着视角转动而围绕杯子(注意)

       

        3. 之后跟写国旗的差不多(不过其中关于片段和顶点着色器要修改部分)

        4.其中烟雾 设置透明  要在材质中添加 transparent:true;

        5. 设置uTiem添加动画 ,同时让纹理重复 wrapS wrapT

        6.设置边缘淡化

        7.设置随风飘动

            我们希望顶点绕着平面中心旋转,并使旋转随仰角变化。

            风向

        8. 烟雾遮挡

            实现

       

        着色器中创建includes文件夹 区分函数

            #include ../includes/rotate.glsl

            注意:  #include <tonemapping_fragment>

                    #include <colorspace_fragment>

                是应用three.js的和上面的#includes不一样

*/


一、代码

script.js

import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import GUI from 'lil-gui'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import coffeeSmokeVertexShader from './shaders/coffeeSmoke/vertex.glsl'
import coffeeSmokeFragmentShader from './shaders/coffeeSmoke/fragment.glsl'

/**
 * Base
 */
// Debug
const gui = new GUI()

// Canvas
const canvas = document.querySelector('canvas.webgl')

// Scene
const scene = new THREE.Scene()

// Loaders
const textureLoader = new THREE.TextureLoader()
const gltfLoader = new GLTFLoader()

/**
 * Sizes
 */
const sizes = {
    width: window.innerWidth,
    height: window.innerHeight
}

window.addEventListener('resize', () =>
{
    // Update sizes
    sizes.width = window.innerWidth
    sizes.height = window.innerHeight

    // Update camera
    camera.aspect = sizes.width / sizes.height
    camera.updateProjectionMatrix()

    // Update renderer
    renderer.setSize(sizes.width, sizes.height)
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
})

/**
 * Camera
 */
// Base camera
const camera = new THREE.PerspectiveCamera(25, sizes.width / sizes.height, 0.1, 100)
camera.position.x = 8
camera.position.y = 10
camera.position.z = 12
scene.add(camera)

// Controls
const controls = new OrbitControls(camera, canvas)
controls.target.y = 3
controls.enableDamping = true

/**
 * Renderer
 */
const renderer = new THREE.WebGLRenderer({
    canvas: canvas,
    antialias: true
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

/**
 * Model
 */
gltfLoader.load(
    './bakedModel.glb',
    (gltf) =>
    {
        gltf.scene.getObjectByName('baked').material.map.anisotropy = 8
        console.log(gltf.scene)
        scene.add(gltf.scene)
    }
)

/* 
    Smoke
*/
// Geometry
const smokeGeometry = new THREE.PlaneGeometry(1,1,16,64)
smokeGeometry.translate(0,0.5,0)
smokeGeometry.scale(1.5,6,1.5)

// Perlin texture
const perlinTexture = textureLoader.load('./perlin.png')
perlinTexture.wrapS = THREE.RepeatWrapping
perlinTexture.wrapT = THREE.RepeatWrapping

// Material
const smokeMaterial = new THREE.ShaderMaterial({
    // wireframe:true,
    vertexShader:coffeeSmokeVertexShader,
    fragmentShader:coffeeSmokeFragmentShader,
    side:THREE.DoubleSide,
    transparent:true, // 支持材料透明
    depthWrite:false, //深度 写入 (防止透明遮挡)
    uniforms:{
        uTime: new THREE.Uniform(0),
        uPerlinTexture: new THREE.Uniform(perlinTexture), // {value:perlinTexture} 一样

    }
})

//Mesh
const smoke = new THREE.Mesh(smokeGeometry,smokeMaterial)
smoke.position.y = 1.83
scene.add(smoke)

/**
 * Animate
 */
const clock = new THREE.Clock()

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()

    // update material
    smokeMaterial.uniforms.uTime.value = elapsedTime
    // Update controls
    controls.update()

    // Render
    renderer.render(scene, camera)

    // Call tick again on the next frame
    window.requestAnimationFrame(tick)
}

tick()

vertex.glsl


// 这里特别注意  projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0) 乘的顺序是固定的,不然会出问题
/* 
    pow(x,y)  x的y次方。如果x小于0,结果是未定义的。同样,如果x=0并且y<=0,结果也是未定义的。
*/

uniform float uTime;
uniform sampler2D uPerlinTexture;

// attribute vec2 uv; // 获取顶点的属性 uv,坐标等


varying vec2 vUv;

#include ../includes/rotate.glsl // 引入函数

void main(){
    
    vec3 newPosition = position;

    // Twist
    float twistPerlin = texture2D(
        uPerlinTexture,
        vec2(0.5,uv.y * 0.2 - uTime * 0.005) // 添加动画,随风飘散
    ).r;
    float angle = twistPerlin * 10.0; // 海拔
    newPosition.xz = rotate2D(newPosition.xz,angle);

    // Wind
    vec2 windOffset = vec2(
        texture2D(uPerlinTexture,vec2(0.25,uTime * 0.01)).r - 0.5, // 另一个取值 风向偏移 0-1减去0.5 x轴就是正负0.5 ,x轴;
        texture2D(uPerlinTexture,vec2(0.75,uTime * 0.01)).r - 0.5  // z轴正负移动
    );
    windOffset *=  pow(uv.y,2.0) * 10.0; // 得到曲线
    newPosition.xz += windOffset;


    vec4 modelPosition = modelMatrix * vec4(newPosition,1.0); // 通过模型矩阵获得模型位置


    vec4 viewPosition = viewMatrix * modelPosition; // 通过视图矩阵获取视图位置

    vec4 projectionPosition = projectionMatrix * viewPosition; // 通过投影矩阵获取投影位置
    
    gl_Position = projectionPosition;
    
    vUv = uv;
}

fragment.glsl



uniform float uTime; 
uniform sampler2D uPerlinTexture; // texture2D 2D采样器

/* 
    smoothstep(edge0, edge1, x)
        如果x <= edge0,返回0.0 ;
        如果x >= edge1 返回1.0;
        如果edge0 < x < edge1,则执行0~1之间的平滑埃尔米特差值。如果edge0 >= edge1,结果是未定义的。
 */

varying vec2 vUv;

void main(){

    // Scale and animate
    vec2 smokeUv = vUv;
    smokeUv.x *= 0.3;
    smokeUv.y *= 0.3;
    smokeUv.y -= uTime * 0.03;

    // smoke
    float smoke = texture2D(uPerlinTexture,smokeUv).r;

    // Remap
    smoke = smoothstep(0.4,1.0,smoke);

    // // Edges  重新映射 淡化边缘 (由于边缘边界很明显)组合起来相乘即可
    // smoke = 1.0;
    smoke *= smoothstep(0.0,0.1,vUv.x); // 0-1 左侧0.0-0.1淡化
    smoke *= smoothstep(1.0,0.9,vUv.x); 
    smoke *= smoothstep(0.0,0.1,vUv.y); // 0-1 左侧0.0-0.1淡化
    smoke *= smoothstep(1.0,0.4,vUv.y); 

    gl_FragColor = vec4(0.6,0.3,0.2,smoke);
    // gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    // 色调映射
    #include <tonemapping_fragment>
    #include <colorspace_fragment>

}

rotate.glsl

vec2 rotate2D(vec2 value,float angle){
    float s = sin(angle);
    float c = cos(angle);
    mat2 m = mat2(c,s,-s,c);
    return m * value;
}

二、效果

shaders - coffee smoke


总结

如何制作烟雾,以及着色器中热气的运动状态应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值