前言
关于灯光如何在着色器中应用!
下面将创建三个灯光 分别是点光源,环境光,方向光通过这几种光应用着色器显示对应阴影
学习灯光阴影,着色器的使用
添加三盏灯
点光,方向光,环境光
创建一个环境光
在现实生活中,如果一个物体是完全蓝色的,而唯一的光源是红色的你将看不到这个物体,因为它会吸收光线,不会反射回你的眼睛。
创建一个方向光
如果面部面对光线,它将接收全部功率。如果面部处于完美的90°角度,它将不会接收任何功率。中间的值将被插值。
法线
我们把面部方向设为正常,把光的方向设为光方向。
如果它们在同一条直线上,我们希望1
如果它们处于90°角,我们希望0
在中间,我们希望插值值
问题1
此时方向光在前方,但是随着旋转,光线一直在跟着边,不对的
因为设置的法线是一直向上的,模型旋转,法线跟着旋转了
因此我们应该让模型转化应用于法线 (上一节中也有这个问题,和对应解决)
解决 vec4 modelNormal = modelMatrix * vec4(normal,0.0); // 为什么设置0.0 就是不希望正常,保持法线向上
修复环境光
由于环境光也引用,但是在模型背面是黑色的,因为是-1,所以要保证为正数(在正常环境中不可能漆黑)
法线保持正数
视图方向 (计算光反射)
视图的光 = 模型位置 - 相机位置
创建一个点光
应用衰败
下面是一些应用的思路
环境光对应照到模型上
方向光对应照在模型上,对应反射,和视图反射(镜面)
相机,光源,物体对应获取 光的距离
法线在反射时,对应数值不对,重设
项目结构
一、代码
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 shadingVertexShader from './shaders/shading/vertex.glsl'
import shadingFragmentShader from './shaders/shading/fragment.glsl'
import { DirectionalLightHelper, DoubleSide } from 'three'
/**
* Base
*/
// Debug
const gui = new GUI()
// Canvas
const canvas = document.querySelector('canvas.webgl')
// Scene
const scene = new THREE.Scene()
// Loaders
const gltfLoader = new GLTFLoader()
/**
* Sizes
*/
const sizes = {
width: window.innerWidth,
height: window.innerHeight,
pixelRatio: Math.min(window.devicePixelRatio, 2)
}
window.addEventListener('resize', () =>
{
// Update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
sizes.pixelRatio = Math.min(window.devicePixelRatio, 2)
// Update camera
camera.aspect = sizes.width / sizes.height
camera.updateProjectionMatrix()
// Update renderer
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(sizes.pixelRatio)
})
/**
* Camera
*/
// Base camera
const camera = new THREE.PerspectiveCamera(25, sizes.width / sizes.height, 0.1, 100)
camera.position.x = 7
camera.position.y = 7
camera.position.z = 7
scene.add(camera)
// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true
/**
* Renderer
*/
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true
})
// renderer.toneMapping = THREE.ACESFilmicToneMapping
// renderer.toneMappingExposure = 3
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(sizes.pixelRatio)
/**
* Material
*/
const materialParameters = {}
materialParameters.color = '#ffffff'
const material = new THREE.ShaderMaterial({
vertexShader: shadingVertexShader,
fragmentShader: shadingFragmentShader,
uniforms:
{
uColor: new THREE.Uniform(new THREE.Color(materialParameters.color)),
}
})
gui
.addColor(materialParameters, 'color')
.onChange(() =>
{
material.uniforms.uColor.value.set(materialParameters.color)
})
/**
* Objects
*/
// Torus knot
const torusKnot = new THREE.Mesh(
new THREE.TorusKnotGeometry(0.6, 0.25, 128, 32),
material
)
torusKnot.position.x = 3
scene.add(torusKnot)
// Sphere
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(),
material
)
sphere.position.x = - 3
scene.add(sphere)
// Suzanne
let suzanne = null
gltfLoader.load(
'./suzanne.glb',
(gltf) =>
{
suzanne = gltf.scene
suzanne.traverse((child) =>
{
if(child.isMesh)
child.material = material
})
scene.add(suzanne)
}
)
/**
* Light helpers
*/
// directional light helper
const directionalLightHelpter = new THREE.Mesh(
new THREE.PlaneGeometry(),
new THREE.MeshBasicMaterial()
)
directionalLightHelpter.material.color.setRGB(0.1,0.1,1.0)
directionalLightHelpter.material.side = THREE.DoubleSide // 侧面
directionalLightHelpter.position.set(0,0,3)
scene.add(directionalLightHelpter)
// Point light helper
const pointLightHelper = new THREE.Mesh(
new THREE.IcosahedronGeometry(0.1,2),
new THREE.MeshBasicMaterial()
)
pointLightHelper.material.color.setRGB(1.0,0.1,0.1)
pointLightHelper.position.set(0,2.5,0)
scene.add(pointLightHelper)
/**
* Animate
*/
const clock = new THREE.Clock()
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
// Rotate objects
if(suzanne)
{
suzanne.rotation.x = - elapsedTime * 0.1
suzanne.rotation.y = elapsedTime * 0.2
}
sphere.rotation.x = - elapsedTime * 0.1
sphere.rotation.y = elapsedTime * 0.2
torusKnot.rotation.x = - elapsedTime * 0.1
torusKnot.rotation.y = elapsedTime * 0.2
// Update controls
controls.update()
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lights shading</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<canvas class="webgl"></canvas>
<script type="module" src="./script.js"></script>
</body>
</html>
style.css
*
{
margin: 0;
padding: 0;
}
html,
body
{
overflow: hidden;
}
.webgl
{
position: fixed;
top: 0;
left: 0;
outline: none;
}
vertex.glsl 顶点着色器
varying vec3 vNormal;
varying vec3 vPosition;
void main()
{
// Position
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * viewMatrix * modelPosition;
// Model normal
vec4 modelNormal = modelMatrix * vec4(normal,0.0); //0.0 指的是非齐次向量,指是模型移动,但是法线不移动设置的
// Varying
vNormal = modelNormal.xyz; // 应用
vPosition = modelPosition.xyz;
}
fragment.glsl 片段着色器
uniform vec3 uColor;
varying vec3 vNormal;
varying vec3 vPosition;
/*
dot向量x,y之间的点积
pow(x,y) x的y次方。如果x小于0,结果是未定义的。同样,如果x=0并且y<=0,结果也是未定义的
*/
#include ../includes/ambientLight.glsl
#include ../includes/directionalLight.glsl
#include ../includes/pointLight.glsl
void main()
{
// 确保法线正常化
vec3 normal = normalize(vNormal);
vec3 viewDirection = normalize(vPosition - cameraPosition); // 常量化
vec3 color = uColor;
vec3 light = vec3(0.0);
// 将灯光相加
light += ambientLight(
vec3(1.0),
0.03
);
light += directionalLight(
vec3(0.1,0.1,1.0), // 颜色
1.0, // 强度
normal, // 法线
vec3(0.0,0.0,3.0), // 光的位置
viewDirection , // 视图方向
20.0 // 镜面反射功率 specular power
);
light += pointLight(
vec3(1.0,0.1,0.1), // 颜色
1.0, // 强度
normal, // 法线
vec3(0.0,2.5,0.0), // 光的位置
viewDirection , // 视图方向
20.0 , // 镜面反射功率 specular power
vPosition, // 位置
0.3 // 衰变
);
// 最后相乘颜色
color *= light;
// Final color
gl_FragColor = vec4(color, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
ambientLight.glsl
// 创建一个自然光 颜色,强度
vec3 ambientLight(vec3 lightColor, float lightIntensity){
return lightColor * lightIntensity; // 将颜色和光的强度相乘
}
directionalLight.glsl
// 创建一个方向光 颜色,强度 , 法线 , 光位置 ,视图位置 ,镜面反射率
vec3 directionalLight(vec3 lightColor, float lightIntensity,vec3 normal ,vec3 lightPosition, vec3 viewDirection, float specularPower){
vec3 lightDirection = normalize(lightPosition); //将vector向量 转换成单位向量 长度为1
vec3 lightReflection = reflect(- lightDirection,normal);
// shading
float shading = dot(normal,lightDirection);
shading = max(0.0, shading); // 保证永远不会低于0.0
// Specular 光反射和观看方向的一个点 得到反射 由于点积是相反,所以取负值
float specular = - dot(lightReflection, viewDirection);
// 保证我们的点积不是负数,因为在背面不需要有反射
specular = max(0.0,specular);
specular = pow(specular,specularPower);
return lightColor * lightIntensity * (shading + specular); // 将颜色和光的强度相乘
// return vec3(specular); // 镜面
// return vec3(shading); // 阴影
}
pointLight.glsl
// 创建一个点光
vec3 pointLight(vec3 lightColor, float lightIntensity,vec3 normal ,vec3 lightPosition, vec3 viewDirection, float specularPower, vec3 position,float lightDecay){
vec3 lightDelta = lightPosition - position; // 为什么相减,原因获得点光的位置
float lightDistance = length(lightDelta); // 想要向量的长度 length 点光到物体表面的距离
vec3 lightDirection = normalize(lightDelta); //将vector向量 转换成单位向量 长度为1
vec3 lightReflection = reflect(- lightDirection,normal);
// shading
float shading = dot(normal,lightDirection);
shading = max(0.0, shading); // 保证永远不会低于0.0
// Specular 光反射和观看方向的一个点 得到反射 由于点积是相反,所以取负值
float specular = - dot(lightReflection, viewDirection);
// 保证我们的点积不是负数,因为在背面不需要有反射
specular = max(0.0,specular);
specular = pow(specular,specularPower);
// Decay
float decay = 1.0 - lightDistance * lightDecay;
decay = max(0.0,decay);
return lightColor * lightIntensity * decay *(shading + specular); // 将颜色和光的强度相乘
}
效果
光源-着色器应用阴影显示
总结
先看,然后知道功能应用场景,自己想要实现时候,会有参照的!