這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 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:材質接收兩個著色器,頂點著色器和片元著色器。著色器代碼需要我們自己編寫,來實現複雜的效果。來看下如何使用。
用著色器材質實現下麵這個效果:
搭建目錄結構和基礎看這裡
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文件,來寫頂點著色器和片元著色器代碼。如下:
在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對象決定的,現在是寫死的(當然也可以寫活)。
接下來給這個平面添加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; }
這樣就是實現了以上效果。
如果是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); }最終效果(效果可以調節參數,調到自己滿意的效果):