什么是shader着色器
维基百科: shader一词被广泛应用于计算机图形学领域,是一种计算机程序,用于进行图像的浓淡处理(计算图像中的光照、亮度、颜色等)。开发人员可以使用着色器语言对GPU编程。构成最终图像的像素、顶点、纹理,图像的位置、色相、饱和度、亮度、对比度也都可以利用着色器中定义的算法进行动态调整。调用着色器的外部程序,也可以利用它向着色器提供的外部变量、纹理来修改这些着色器中的参数。
-
固定渲染管线: —---—标准的几何&光照(T&L)管线,功能是固定的,它控制着世界、视、投影变换及固定光照控制和纹理混合。T&L管线可以被渲染状态控制,矩阵,光照和采制参数。如果有了固定渲染管线,编写程序就比较容易了,因为所有的变换都是由固定渲染管线来完成的,但是缺点就是自由度低。固定渲染管线只能完成一些最基本的操作,如果想要做一些特殊的处理,就比较麻烦了,ThreeJS基本上就是固定渲染管线,当然也可以自己写shader到material中。
-
可编辑渲染管线:—---—WebGL中不存在固定渲染管线,坐标变换必须全部由自己来做,这个记述了坐标变换的机制就叫做着色器(Shader),这样可以由程序员控制的机制叫做可编辑渲染管线。而着色器又有 处理几何图形顶点的顶点着色器和处理像素的片元着色器两种类型。由于WebGL中没有固定管线,所以必须准备好顶点着色器和片元着色器。
着色器简介
顶点着色器(Vertex shader) —— 顶点着色器用来描述顶点特性(如位置、颜色等)的程序。顶点是指二维或三维空间中的一个点,比如二维或三维图形的端点和交点。
片元着色器(Fragment shader) —— 进行逐片元处理的程序。片元是一个WebGL术语,可以将其理解为像素(图像的单元)。
着色器的处理方法
- Uniforms是所有顶点都具有相同的值的变量。 比如灯光,雾,和阴影贴图就是被储存在uniforms中的数据。 用uniform修饰的变量既可以传入顶点着色器,也可以传入片元着色器,它们包含了哪些在整个渲染过程中保持不变的变量,比如,一个点光源的位置。
- Attributes是与每个顶点关联的变量。例如,顶点位置,法线和顶点颜色都是存储在attributes中的数据,比如每个顶点都具有一个颜色。Attributes变量和顶点的关系是一一对应的。attributes只可以在顶点着色器中访问。
- Varyings是从顶点着色器传递到片元着色器的变量,为了确保这点,我们需要确保在两个着色器中变量的类型和命名完全一致。对于每一个片元,每一个varying的值将是相邻顶点值的平滑插值,一个经典的应用是法线向量,因为在计算光照的时候需要用到法线。 顶点着色器首先运行; 它接收attributes, 计算/操纵每个单独顶点的位置,并将其他数据(varyings)传递给片元着色器。片元(或像素)着色器后运行; 它设置渲染到屏幕的每个单独的“片元”(像素)的颜色。
着色器模拟光照
- vertexShader
<script id="vertexShader" type="x-shader/x-vertex">
//normal three.js内置法线, position three.js内置位置
varying vec3 vNormal;
void main() {
// 将vNormal设置为normal,normal是Three.js创建并传递给着色器的attribute变量
vNormal = normal;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
- fragmentShader
<script id="fragmentShader" type="x-shader/x-vertex">
varying vec3 vNormal;
void main() {
// 定义光线向量
vec3 light = vec3(0.5,0.2,1.0);
// 归一化向量
light = normalize(light);
// 计算光线向量和法线向量的点积,如果点积小于0(即光线无法照到),就设为0
float dProd = max(0.0, dot(vNormal, light));
// 填充片元颜色
gl_FragColor = vec4(dProd, // R
dProd, // G
dProd, // B
1.0); // A
}
</script>
运行效果
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<title>threejs webgl - materials - hdr environment mapping</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<div id="container"></div>
<!--顶点着色器-->
<script id="vertexShader1" type="x-shader/x-vertex">
void main(){
//最终顶点位置信息=投影矩阵*模型视图矩阵*每个顶点坐标
gl_Position = projectionMatrix * modelViewMatrix * vec4( position , 1.0 );
}
</script>
<!--片元着色器-->
<script id="fragmentShader1" type="x-shader/x-fragment">
void main(){
//将任意一个像素色设置为该颜色RGBA(红色)
gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 ); //r,g,b,a
}
</script>
<script id="vertexShader" type="x-shader/x-vertex">
//normal three.js内置法线, position three.js内置位置
varying vec3 vNormal;
void main() {
// 将vNormal设置为normal,normal是Three.js创建并传递给着色器的attribute变量
vNormal = normal;
gl_Position = projectionMatrix *modelViewMatrix *vec4(position, 1.0);
}
</script>
<script id="fragmentShader" type="x-shader/x-vertex">
varying vec3 vNormal;
void main() {
// 定义光线向量
vec3 light = vec3(0.5,0.2,1.0);
// 归一化向量
light = normalize(light);
// 计算光线向量和法线向量的点积,如果点积小于0(即光线无法照到),就设为0
float dProd = max(0.0, dot(vNormal, light));
// 填充片元颜色
gl_FragColor = vec4(dProd, // R
dProd, // G
dProd, // B
1.0); // A
}
</script>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from './jsm/controls/OrbitControls.js';
let container, geometry, material;
let camera, scene, renderer, controls;
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set( 0, 100, 400 );
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xffffff );
let axesHelper = new THREE.AxesHelper(100);
scene.add(axesHelper);
renderer = new THREE.WebGLRenderer();
renderer.physicallyCorrectLights = true;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
let position = new THREE.Vector3(50, 0, 0); // 平移
let quaternion = new THREE.Quaternion(); // 旋转
quaternion.setFromAxisAngle( new THREE.Vector3( 0, 0, 1 ), Math.PI / 6 ); /// 绕着哪一根法线旋转多少度
let scale = new THREE.Vector3(0.2, 0.2, 0.8); // 缩放
let geometry = new THREE.BoxGeometry(30,30,30);
// let material = new THREE.MeshLambertMaterial({ color: "green" })
let material=new THREE.ShaderMaterial({ /// shader
vertexShader:document.getElementById("vertexShader").textContent,
fragmentShader:document.getElementById("fragmentShader").textContent
});
let box = new THREE.Mesh(geometry, material);
scene.add(box)
// window.addEventListener("click", function () {
// // box.matrix.makeRotationFromQuaternion( quaternion ); /// 要求顺序
// // box.matrix.setPosition( position );
// // box.matrix.setFromMatrixScale( scale )
//
// box.matrix.compose(position, quaternion, scale)
// box.matrixAutoUpdate = false;
// // box.updateMatrix();
// })
let ambientLight = new THREE.AmbientLight("#ffffff", 1);
scene.add(ambientLight);
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
renderer.outputEncoding = THREE.sRGBEncoding;
controls = new OrbitControls( camera, renderer.domElement );
controls.minDistance = 50;
controls.maxDistance = 800;
window.addEventListener( 'resize', onWindowResize );
}
function onWindowResize() {
const width = window.innerWidth;
const height = window.innerHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize( width, height );
}
function animate() {
// setTimeout(function () {
requestAnimationFrame( animate );
render();
// }, 2000)
}
function render() {
scene.rotation.y += 0.005;
renderer.render( scene, camera );
}
</script>
</body>
</html>