threejs实现Blinn-Phong光照模型

该文章演示了如何使用three.js库创建一个3D场景,包含一个可交互的TorusKnot几何体,使用自定义着色器处理光照效果。通过OrbitControls进行相机控制,并利用lil-gui库创建图形用户界面来动态调整材质的ka、kd、ks参数以及是否启用Phong光照模型。
摘要由CSDN通过智能技术生成

本文章不做模型讲解,只分享代码。
在线地址 https://codepen.io/qinwei-liao/pen/MWPBKVr
请添加图片描述

index.html

<head>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/normalize-css@2.3.1/normalize.min.css">
</head>
<canvas id="renderer"></canvas>


<script type="importmap">
{
  "imports":{
      "three":"https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.module.min.js"
  }
  
}
</script>
<script src="./index.js" type="module"></script>

index.js


import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.module.min.js";
import {OrbitControls} from "https://cdn.jsdelivr.net/npm/three@0.152.2/examples/jsm/controls/OrbitControls.js"
import GUI from "https://cdn.jsdelivr.net/npm/lil-gui@0.18.1/dist/lil-gui.esm.min.js"

const gui = new GUI;

const fragmentShader = `
precision highp float;

uniform vec3 lightPosition;
uniform float lightIntensity;
uniform vec3 lightColor;

uniform vec3 cameraPosition;

uniform float kd;
uniform float ka;
uniform float ks;

uniform bool isPhong;

varying vec3 vNormal;
varying vec3 vWorldPosition;

void main() {
	vec3 color = vec3(1, 1.0, 0);
	float r = distance(vWorldPosition, lightPosition);

	float radiance = lightIntensity / (r * r);
	vec3 l = lightPosition - vWorldPosition;

	vec3 ambient = ka * vec3(1, 1, 1) * color;
	vec3 diffuse = color * lightColor * kd * radiance * max(dot(vNormal, normalize(l)), 0.);
	vec3 h = normalize(l + (cameraPosition - vWorldPosition));
	float specularAngle = dot(h, vNormal);// blinn-phong
	if(isPhong)
		specularAngle = dot(normalize(2. * dot(l, vNormal) * vNormal - l), normalize(cameraPosition - vWorldPosition));
	vec3 specular = lightColor * ks * radiance * pow(max(0., specularAngle), 64.);

	gl_FragColor = vec4(ambient + diffuse + specular, 1);
}
`

const vertexShader = `
precision highp float;

uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;

attribute vec3 position;
attribute vec3 normal;

varying vec3 vNormal;
varying vec3 vWorldPosition;

void main() {
	vNormal = normal;

	vWorldPosition = (modelMatrix * vec4(position, 1.)).xyz;
	gl_Position = projectionMatrix * viewMatrix * vec4(vWorldPosition, 1.);
}
`

const canvas = document.querySelector('#renderer');
const renderer = new THREE.WebGLRenderer({ canvas, logarithmicDepthBuffer: false });
const fov = 75;
const aspect = 2;  // the canvas default
const near = 0.1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 4;
const controls = new OrbitControls(camera, canvas)

const scene = new THREE.Scene();


  const pointLight = new THREE.PointLight(new THREE.Color(0xFFFFFF), 1.);
  pointLight.position.set(0, 0, 1.5)
  scene.add(pointLight)
  let ka = .1, kd = .6, ks = .9
  const uniforms = {
    lightPosition: { value: pointLight.getWorldPosition(new THREE.Vector3) },
    lightIntensity: { value: pointLight.intensity },
    lightColor: { value: pointLight.color },
    kd: { value: kd },
    ka: { value: ka },
    ks: { value: ks },
    isPhong:{value:false}
  }

  const geometry = new THREE.TorusKnotGeometry(1, .5, 100, 1600)
  const material = new THREE.RawShaderMaterial({
    uniforms,
    vertexShader,
    fragmentShader
  })

  const phong = gui.addFolder("phong")
  phong.add({ kd: kd }, "kd", 0, 1, .1).onChange((v) => {
    material.uniforms.kd.value = v
  })
  phong.add({ ka: ka }, "ka", 0, 1, .1).onChange((v) => {
    material.uniforms.ka.value = v
  })
  phong.add({ ks: ks }, "ks", 0, 1, .1).onChange((v) => {
    material.uniforms.ks.value = v
  })
  phong.add({isPhong:false},"isPhong").onChange(v=>{
    material.uniforms.isPhong.value = v
  })
  scene.add(new THREE.Mesh(geometry, material))

function resizeRendererToDisplaySize(renderer) {
  const canvas = renderer.domElement;
  const needResize = canvas.width !== window.innerWidth || canvas.height !== window.innerHeight;
  if (needResize) {
    canvas.width = window.innerWidth
    canvas.height = window.innerHeight
    renderer.setSize(window.innerWidth, window.innerHeight, false);
  }
  return needResize;
}

let then = 0;
function render(now) {
  now *= 0.001;  // convert to seconds
  const deltaTime = now - then;
  then = now;

  if (resizeRendererToDisplaySize(renderer)) {
    const canvas = renderer.domElement;
    camera.aspect = canvas.clientWidth / canvas.clientHeight;
    camera.updateProjectionMatrix();
  }

  renderer.render(scene, camera)


  requestAnimationFrame(render);
}

requestAnimationFrame(render);
好的,这里给出一个使用 Three.js 实现的带有 Blinn-Phong 光照模型和聚光灯模式的多个立方体的代码示例: ```javascript // 初始化场景、相机、渲染器等 var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); var renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 添加聚光灯光源 var spotLight = new THREE.SpotLight(0xffffff, 1); spotLight.position.set(0, 100, 0); spotLight.angle = Math.PI / 4; spotLight.penumbra = 0.05; spotLight.decay = 2; spotLight.distance = 200; scene.add(spotLight); // 添加多个立方体 var cube1 = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ map: THREE.ImageUtils.loadTexture('texture1.png') }) ); cube1.position.set(-2, 0, -5); scene.add(cube1); var cube2 = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ map: THREE.ImageUtils.loadTexture('texture2.png') }) ); cube2.position.set(0, 0, -5); scene.add(cube2); var cube3 = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ map: THREE.ImageUtils.loadTexture('texture3.png') }) ); cube3.position.set(2, 0, -5); scene.add(cube3); // 设置相机位置和控制器 camera.position.z = 5; var controls = new THREE.OrbitControls(camera, renderer.domElement); // 渲染循环 function render() { requestAnimationFrame(render); // 计算每个立方体的 Blinn-Phong 光照 cube1.material.needsUpdate = true; cube2.material.needsUpdate = true; cube3.material.needsUpdate = true; spotLight.position.copy(camera.position); spotLight.lookAt(cube1.position); var distance = spotLight.position.distanceTo(cube1.position); var intensity = spotLight.intensity / (spotLight.decay * distance * distance); cube1.material.uniforms.lightPosition.value = spotLight.position; cube1.material.uniforms.lightColor.value = spotLight.color; cube1.material.uniforms.lightIntensity.value = intensity; cube1.material.uniforms.lightDistance.value = distance; cube1.material.uniforms.lightAngle.value = spotLight.angle; cube1.material.uniforms.lightPenumbra.value = spotLight.penumbra; cube1.material.uniforms.lightDecay.value = spotLight.decay; spotLight.lookAt(cube2.position); distance = spotLight.position.distanceTo(cube2.position); intensity = spotLight.intensity / (spotLight.decay * distance * distance); cube2.material.uniforms.lightPosition.value = spotLight.position; cube2.material.uniforms.lightColor.value = spotLight.color; cube2.material.uniforms.lightIntensity.value = intensity; cube2.material.uniforms.lightDistance.value = distance; cube2.material.uniforms.lightAngle.value = spotLight.angle; cube2.material.uniforms.lightPenumbra.value = spotLight.penumbra; cube2.material.uniforms.lightDecay.value = spotLight.decay; spotLight.lookAt(cube3.position); distance = spotLight.position.distanceTo(cube3.position); intensity = spotLight.intensity / (spotLight.decay * distance * distance); cube3.material.uniforms.lightPosition.value = spotLight.position; cube3.material.uniforms.lightColor.value = spotLight.color; cube3.material.uniforms.lightIntensity.value = intensity; cube3.material.uniforms.lightDistance.value = distance; cube3.material.uniforms.lightAngle.value = spotLight.angle; cube3.material.uniforms.lightPenumbra.value = spotLight.penumbra; cube3.material.uniforms.lightDecay.value = spotLight.decay; renderer.render(scene, camera); } render(); ``` 其中,纹理贴图的加载需要使用 `THREE.ImageUtils.loadTexture()` 方法,同时为了实现 Blinn-Phong 光照模型,需要在材质中添加 uniform 变量,并在渲染循环中对每个立方体的材质进行更新。具体的实现细节可以参考代码中的注释。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值