記錄--Threejs-著色器實現一個水波紋

来源:https://www.cnblogs.com/smileZAZ/archive/2023/06/27/17509607.html
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 hree.js 是一個基於 WebGL 的 JavaScript 3D 庫,用於創建和渲染 3D 圖形場景。 一、 圖像渲染過程 1、webGL webGL: WebGL 是一種基於 JavaScript API 的圖形庫,它允許在瀏覽器 ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

hree.js 是一個基於 WebGL 的 JavaScript 3D 庫,用於創建和渲染 3D 圖形場景。

一、 圖像渲染過程

1、webGL

webGL: WebGL 是一種基於 JavaScript API 的圖形庫,它允許在瀏覽器中進行高性能的 3D 圖形渲染。webGL的渲染依賴於底層GPU的渲染能力。
通過獲取<canvas>元素獲取WebGL的上下文,從而獲得WebGL API和GPU。
GPU 圖形處理器:處理圖形計算的硬體。GPU運行著一個著色器小程式。包含兩種類型的著色器程式,頂點著色器片元著色器

2、著色器

著色器:

3、坐標系

(1)模型空間:物體在其自身坐標系下的位置、大小、方向。
(2)世界空間:所有模型都放置在同一坐標系下的空間。每個物體都有唯一的坐標系。如three.js的AxesHelper是基於世界空間創建的坐標系。
(3)視圖空間:相機所在的坐標系。簡單來說就是以相機為原點,物體在相機眼中的位置。
(4)投影空間:將3D圖形投影到二維屏幕上的坐標系。將3D坐標轉化為2D坐標。
各個坐標系之間的轉換:通過矩陣變換來完成。例如,將物體從模型空間轉換到世界空間,可以使用模型變換矩陣將局部坐標轉換為全局坐標。將物體從世界空間轉換到視圖空間,可以使用相機變換矩陣將全局坐標變換為相機坐標。最後,將視圖空間中的坐標投影到屏幕上,可以使用投影變換矩陣將相機坐標變換為裁剪坐標。通過這些矩陣變換,可以將坐標從一個空間轉換到另一個空間,從而實現3D圖形的渲染和顯示。

4、GPU渲染過程

(1)渲染管線:就是將3D坐標轉化為屏幕像素(屏幕都是二維的,也就是二維坐標)的過程。分為以下幾個階段。
應用階段:由CPU控制,主要負責數據的準備和處理。CPU將數據發送的GPU,包括圖形的頂點坐標、紋理坐標、顏色信息等 。
幾何階段:運行在GPU中。將頂點坐標變換到屏幕空間中。
光柵化階段:階段運行在GPU中。光柵化階段主要將渲染圖元轉換為像素,併進行顏色插值、紋理採樣等處理,最終輸出渲染像素。

(2)GPU具體渲染過程。

 齊次裁剪空間:簡單來說就是相機視錐體的範圍。如下圖

二、著色器材質

three.js中有兩個著色器材質ShaderMaterial和原始著色器材質RawShaderMaterial,它是用著色器語言GLSL編寫的程式,可以讓我們自定義物體的著色器程式,從而實現複雜的效果。 1、ShaderMaterial:材質接收兩個著色器,頂點著色器和片元著色器。著色器代碼需要我們自己編寫,來實現複雜的效果。來看下如何使用。

用著色器材質實現下麵這個效果:

qi.gif

搭建目錄結構和基礎看這裡

1、首先搭建一個three.js場景
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>threejs_collision</title>
    <link rel="stylesheet" href="./asstes/css/style.css">
</head>
<body>
    <script src="./main/index.js" type="module"></script>
</body>
</html>
*{
    padding: 0;
    margin: 0;
}
body,html {
    background: green;
}
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 創建場景
const scene = new THREE.Scene();
// 創建相機
const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
);
// 設置相機位置
camera.position.set(0, 0, 10);
//將相機添加到
scene.add(camera);

//創建環境光,環境光會均勻的照亮場景中的所有物體。
const light = new THREE.AmbientLight(0x404040);
//將環境光添加到場景
scene.add(light);
// 創建平行光
const directionalLight = new THREE.DirectionalLight();
//設置光源位置
directionalLight.position.set(0, 5, 0);
//添加到場景
scene.add(directionalLight);
//設置光源投射陰影
directionalLight.castShadow= true

// 創建渲染器
const renderer = new THREE.WebGLRenderer();
// 設置渲染器尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
//開啟渲染器陰影計算
renderer.shadowMap.enabled = true
//將canvas添加到body中
document.body.appendChild(renderer.domElement);

// 軌道控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 軌道控制器的阻尼感
controls.enableDamping = true;
//輔助坐標軸
const axesHelp = new THREE.AxesHelper();
scene.add(axesHelp);


const clock = new THREE.Clock()
//渲染函數
function render() {
    //阻尼
    controls.update()
    let time = clock.getDelta();
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
// 初始化渲染函數
render();
// 監聽瀏覽器視窗尺寸變化
window.addEventListener('resize',() => {
    //重新設置相機寬高比
    camera.aspect = window.innerWidth / window.innerHeight;
    //更新相機投影矩陣
    camera.updateProjectionMatrix();
    //重新設置渲染器尺寸
    renderer.setSize(window.innerWidth,window.innerHeight);
    //設置設備像素比
    renderer.setPixelRatio(window.devicePixelRatio)
})
2、創建著色器材質

從上面動圖可以看出,是一個平面貼了一張圖,然後給這個平面加了wave的效果。
所以,先創建一個平面。

const planeGeometry = new THREE.PlaneGeometry(1,1,64,64);
引入貼圖
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load(
    require('../asstes/img/texture/xx.jpeg')
)

創建一個shader文件夾存放著色器代碼,新建一個兩個.glsl文件,來寫頂點著色器和片元著色器代碼。如下:

image.png
在js文件中引入這兩個著色器:

import vertexShaderText from '../shader/basic/vertex.glsl'
import fragmentShaderText from '../shader/basic/fragment.glsl'
創建著色器材質
const material = new THREE.ShaderMaterial({
    // 頂點著色器
    vertexShader:vertexShaderText,
    // 片元著色器
    fragmentShader:fragmentShaderText,
    // 設置兩面可見,預設只能看見一面
    side:THREE.DoubleSide,
})
3、編寫著色器代碼

頂點著色器 使用的是著色器語言(GLSL),不會也沒事,知道怎麼接收參數,在哪寫邏輯就夠了。首先要有一個入口點,也就是下麵的void main函數,對數據的處理就寫在這裡面。
shader中有三種類型的變數: uniforms, attributes, 和 varyings
uniforms:從應用程式(CPU)傳到著色器的變數(GPU),頂點著色器和片元著色器都能訪問,比如我們可以在ShaderMaterial傳遞uniforms,在著色器程式中接收。用法:接收:uniform float uTime;
attributes:與每個頂點關聯的變數。例如,頂點位置,法線和頂點顏色都是存儲在attributes中的數據。attributes  可以在頂點著色器中訪問。用法:attribute vec3 position
Varyings:在頂點著色器和片元著色器中傳遞數據。可以將頂點著色器處理過的數據通過varyings傳給片元著色器。用法:varying vec2 vUv;

// 高精度浮點數
precision highp float;
void main(){
    // vec4 四維向量
    vec4 modelPosition = modelMatrix * vec4(position, 1.0);
    // projectionMatrix 投影矩陣;viewMatrix 視圖矩陣;modelMatrix 模型矩陣;跟上面提到的坐標系對應。這些都是內置的uniform,使用ShaderMaterial會自動到GLSL shader代碼中。使用RawShaderMaterial不會自動添加,需要手動接收。
    //gl_Position是一個內置變數,它表示經過投影、視圖和模型變換後的頂點位置。
    gl_Position = projectionMatrix * viewMatrix * modelPosition;
}
片元著色器:
// 中等精度浮點數
precision mediump float;
void main(){
    // gl_FragColor內置對象,片元的顏色值 vec4是個思維變數這裡代表了紅色分量、綠色分量、藍色分量和透明度分量。
    gl_FragColor = vec4(1.0,1.0,0.0,1.0);    
}

效果:註意這個平面的顏色是片元著色器里gl_FragColor對象決定的,現在是寫死的(當然也可以寫活)。

image.png

接下來給這個平面添加wave的效果:這個平面在X、y軸,通過改變Z軸的坐標來使平面有上下波動的效果,這個波動的效果像不像正弦餘弦曲線,可以通過sin,cos實現這個效果。可以通過Uniforms變數將數據傳給頂點著色器和片元著色器。

const clock = new THREE.Clock()
//渲染函數
function render() {
    let time = clock.getElapsedTime()
    material.uniforms.uTime.value = time;
}

const material = new THREE.ShaderMaterial({
    uniforms:{
        uTime:{
            value:0
        },
        // 貼圖
        uTexture:{
            value: texture
        }
    }
})
頂點著色器程式:
precision mediump float;
uniform float uTime;
//varying:從頂點著色器傳遞到片元著色器的變數。 將uv傳遞到片元著色器。uv是二維坐標,是物體頂點在紋理上的映射位置(相當於將一個3維物體展開後的對應的二維位置)。傳遞給片元著色器可以讀取該坐標處的顏色,賦值給gl_FragColor,實現貼圖效果。
varying vec2 vUv;
void main(){
    vUv = uv;
    vec4 modelPosition = modelMatrix * vec4(position, 1.0);
    modelPosition.z = sin((modelPosition.x + uTime) * 10.0) * 0.05;
    modelPosition.z += sin((modelPosition.y + uTime) * 10.0) * 0.05;
    gl_Position = projectionMatrix * viewMatrix * modelPosition;
}
片元著色器程式:
precision mediump float;
// sampler2D類型的紋理變數
uniform sampler2D uTexture;
// 接收頂點著色器傳來的uv
varying vec2 vUv;
void main(){
     // texture2D是用於讀取紋理顏色值的函數
    vec4 textureColor = texture2D(uTexture,vUv);
    gl_FragColor = textureColor;
    
}

這樣就是實現了以上效果。
image.png

如果是RawShaderMaterial材質,內置的uniform需要手動去接收,以上代碼改成:
頂點著色器程式:

precision mediump float;
// 定義頂點
attribute vec3 position;
//定義位置參數
attribute vec2 uv;
// 傳入投影矩陣
uniform mat4 projectionMatrix;
// 傳入視圖矩陣
uniform mat4 viewMatrix;
// 傳入模型矩陣
uniform mat4 modelMatrix;
//接收著色器材質傳遞的時間參數
uniform float uTime;
// uv傳遞到片元著色器 varying是從頂點著色器傳遞到片元著色器的變數
varying vec2 vUv;
void main(){
    vUv = uv;
    vec4 modelPosition = modelMatrix * vec4(position,1.0);
    modelPosition.z = sin((modelPosition.x + uTime) * 10.0) * 0.05;
    modelPosition.z += sin((modelPosition.y + uTime) * 10.0) * 0.05;
    gl_Position = projectionMatrix * viewMatrix * modelPosition;
}

三、著色器實現一個水波紋

水波紋相對於上面旗幟飄動的效果,多了些隨機性。如水波的高度是變化的,波浪的起伏是隨機的,高處和低處的顏色不一樣,水波波動的大小、頻率等。這裡用到了一些隨機函數。將這些隨機性添加給波浪的高度來達到更真實的效果。下麵定義了很多參數,這些參數可以自己去調節看看它們是什麼作用。

const material = new THREE.ShaderMaterial({
    vertexShader:vertexShaderText,
    fragmentShader:fragmentShaderText,
    side:THREE.DoubleSide,
    uniforms:{
        uTime:{
            value:0
        },
        uWaresFrequency:{
            value:params.uWaresFrequency
        },
        uScale:{
            value:params.uScale
        },
        uNoiseFrequency:{
            value:params.uNoiseFrequency
        },
        uNoiseScale:{
            value: params.uNoiseScale
        },
        uXzScale:{
            value: params.uXzScale
        },
        uLowColor:{
            value:new THREE.Color(params.uLowColor)
        },
        uHighColor: {
            value:new THREE.Color(params.uHighColor)
        },
        uOpacity:{
            value:params.uOpacity
        }
    },
    transparent: true
})

const plane = new THREE.Mesh(planeGeometry,material)
plane.rotation.x = -Math.PI / 2
scene.add(plane)

// 將這些uniforms變數添加到gui在,方便看效果,找到最合適的值。
gui.add(params,'uWaresFrequency').min(1).max(50).step(0.1).onChange(val => {
    material.uniforms.uWaresFrequency.value = val;
});
gui.add(params,'uScale').min(0).max(0.2).step(0.01).onChange(val => {
    material.uniforms.uScale.value = val;
});
gui.add(params,'uNoiseFrequency').min(0).max(100).step(0.1).onChange(val => {
    material.uniforms.uNoiseFrequency.value = val;
});
gui.add(params,'uNoiseScale').min(0).max(5).step(0.01).onChange(val => {
    material.uniforms.uNoiseScale.value = val;
});
gui.add(params,'uXzScale').min(1).max(5).step(0.01).onChange(val => {
    material.uniforms.uXzScale.value = val;
});
gui.addColor(params,'uLowColor').onFinishChange(val => {
    material.uniforms.uLowColor.value = new THREE.Color(val)
})
gui.addColor(params,'uHighColor').onFinishChange(val => {
    material.uniforms.uHighColor.value = new THREE.Color(val)
})
gui.add(params,'uOpacity').min(0).max(1).onChange(val => {
    material.uniforms.uOpacity.value = val;
})
頂點著色器程式:裡面的函數都是從這本書里抄的
uniform float uTime;
uniform float uWaresFrequency;
uniform float uScale;
uniform float uNoiseFrequency;
uniform float uNoiseScale;
uniform float uXzScale;
varying float vElevation;

float random (vec2 st) {
    return fract(sin(dot(st.xy,vec2(12.9898,78.233)))*43758.5453123);
}
// 旋轉函數
vec2 rotate(vec2 uv, float rotation, vec2 mid)
{
    return vec2(
    cos(rotation) * (uv.x - mid.x) + sin(rotation) * (uv.y - mid.y) + mid.x,
    cos(rotation) * (uv.y - mid.y) - sin(rotation) * (uv.x - mid.x) + mid.y
    );
}

// 2d雜訊函數 
float noise (in vec2 st) {
    vec2 i = floor(st);
    vec2 f = fract(st);
    float a = random(i);
    float b = random(i + vec2(1.0, 0.0));
    float c = random(i + vec2(0.0, 1.0));
    float d = random(i + vec2(1.0, 1.0));
    vec2 u = f*f*(3.0-2.0*f);
    return mix(a, b, u.x) +
    (c - a)* u.y * (1.0 - u.x) +
    (d - b) * u.x * u.y;
}
// 隨機函數
vec4 permute(vec4 x)
{
    return mod(((x*34.0)+1.0)*x, 289.0);
}
vec2 fade(vec2 t)
{
    return t*t*t*(t*(t*6.0-15.0)+10.0);
}
float cnoise(vec2 P)
{
    vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);
    vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);
    Pi = mod(Pi, 289.0); // To avoid truncation effects in permutation
    vec4 ix = Pi.xzxz;
    vec4 iy = Pi.yyww;
    vec4 fx = Pf.xzxz;
    vec4 fy = Pf.yyww;
    vec4 i = permute(permute(ix) + iy);
    vec4 gx = 2.0 * fract(i * 0.0243902439) - 1.0; // 1/41 = 0.024...
    vec4 gy = abs(gx) - 0.5;
    vec4 tx = floor(gx + 0.5);
    gx = gx - tx;
    vec2 g00 = vec2(gx.x,gy.x);
    vec2 g10 = vec2(gx.y,gy.y);
    vec2 g01 = vec2(gx.z,gy.z);
    vec2 g11 = vec2(gx.w,gy.w);
    vec4 norm = 1.79284291400159 - 0.85373472095314 * vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11));
    g00 *= norm.x;
    g01 *= norm.y;
    g10 *= norm.z;
    g11 *= norm.w;
    float n00 = dot(g00, vec2(fx.x, fy.x));
    float n10 = dot(g10, vec2(fx.y, fy.y));
    float n01 = dot(g01, vec2(fx.z, fy.z));
    float n11 = dot(g11, vec2(fx.w, fy.w));
    vec2 fade_xy = fade(Pf.xy);
    vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
    float n_xy = mix(n_x.x, n_x.y, fade_xy.y);
    return 2.3 * n_xy;
}

void main() {
    vec4 modelPosition = modelMatrix * vec4(position,1.0);
    // 波浪高度
    float elevation = sin(modelPosition.x * uWaresFrequency) * sin(modelPosition.z * uWaresFrequency * uXzScale);
    elevation += cnoise(vec2(modelPosition.xz*uNoiseFrequency+uTime))
    *uNoiseScale;
    elevation *= uScale;
    // 傳到片元著色器
    vElevation = elevation;
    modelPosition.y += elevation;
    gl_Position = projectionMatrix * viewMatrix * modelPosition;
}
片元著色器程式:
varying float vElevation;
uniform vec3 uLowColor;
uniform vec3 uHighColor;
uniform float uOpacity;
void main(){
    float a = (vElevation + 1.0) / 2.0;
    // 混合顏色
    vec3 color = mix(uLowColor,uHighColor,a);
    gl_FragColor = vec4(color,uOpacity);
}
最終效果(效果可以調節參數,調到自己滿意的效果): waterWave.gif

本文轉載於:

https://juejin.cn/post/7248982532728864825

如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 採訪嘉賓 | 郭煒、高俊 編輯 | Tina 北京時間 2023 年 6 月 1 日,全球最大的開源軟體基金會 Apache Software Foundation(以下簡稱 ASF)正式宣佈 Apache SeaTunnel 畢業成為 Apache 頂級項目 (TLP, Top Level Pro ...
  • Metric 是 Datavines 中一個核心概念,一個 Metric 表示一個數據質量檢查規則,比如空值檢查和表行數檢查都是一個規則。Metric 採用插件化設計,用戶可以根據自己的需求來實現一個 Metric。下麵我們來詳細講解一下如何自定義`Metric`。 ### 第一步 我們先瞭解下幾個 ...
  • 我是在Mac os下配置的,其它平臺的內容和步驟也差不多。 配置方法: (網上很多,大致說下) 一、Charles下載: 1)官網下載地址:https://www.charlesproxy.com/download/ (我用的破解版) 二、Charles配置代理: 1)查看本機IP:help-->L ...
  • 原文:[Android使用poi遇到的問題](https://stars-one.site/2023/06/27/android-poi) 關於Poi使用可以看這一篇[【開源庫推薦】#4 Poi-辦公文檔處理庫](https://www.cnblogs.com/stars-one/p/1696603 ...
  • 一晚上完成0.2.2、0.3.0、0.3.1三個版本的更新,很高興,總結一下 項目地址: [穆音博客](https://blog.muvocal.com) 文章首發地址:[Vue針對設備調節顯示](https://blog.muvocal.com/blog/11) ## Vue中進行優化的方式 總體 ...
  • 之前寫了兩個 `demo` 講解瞭如何實現 `SSR` 和 `SSG`,今天再寫個 `demo` 說在 `ISR` 如何實現。 ## 什麼是 ISR `ISR` 即 `Incremental Static Regeneration` 增量靜態再生,是指在 `SSG` 的前提下,可以在收到請求時判定頁 ...
  • 本文將探究京東競速榜H5頁面的核心前端技術,包括動畫、樣式配置化、皮膚切換、海報技術、調試技巧等方面,希望能夠為廣大前端開發者提供一些有用的參考和思路。 ...
  • 前端文件上傳本來是一個常規交互操作,沒什麼特殊性可言,但是最近在做文件上傳,需要實現截圖粘貼上傳,去找了下有沒有什麼好用的組件,網上提供的方法有,但是沒找完整的組件來支持cv上傳,經過瞭解發現可以用剪貼板功能讓自己的cv實現文件上傳,於是自己就整合了目前幾種文件上傳的交互方式,碼了一個支持cv的vu... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...