前言
本次实验利用three.js实现phong材质【之前在GAMES202作业0里也接触过phong shader的glsl版本】,算法部分不难理解,本次遇到的困难主要在于不熟悉three.js的shader接口【就像unity shaderlab里会有一些专门的变量供用户调用用于顶点、法线等计算】,之后会分析three.js的ShaderMaterial的坑。
项目工程:here
结果图
代码
uniform
var uniforms;
uniforms = {
uSampler: {//采样的图片
value: texture,
},
uTextureSample: {//采样选择 1为贴图 2为不带贴图
value: 1
},
uKd: {
value: new THREE.Vector3(0.05, 0.05, 0.05)//控制满反射系数
},
uKs: {
value: new THREE.Vector3(0.5, 0.5, 0.5)//控制高光系数
},
lightPosition: {//光源位置
value: point.position
},
uLightIntensity: {
value: 1155.0//光照强度
}
};
var material_raw = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragmentShader').textContent,
});
顶点着色器
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec3 aNormalPosition;
attribute vec2 aTextureCoord;
varying highp vec2 vTextureCoord;
varying highp vec3 vFragPos;
varying highp vec3 vNormal;
/*
normal,position以及摄像机位置需要使用three.js内置参数
*/
void main(void) {
vFragPos = position;
vNormal = normal;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
vTextureCoord = uv;
}
</script>
片元着色器
<script id="fragmentShader" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
uniform sampler2D uSampler;
uniform vec3 uKd;
uniform vec3 uKs;
uniform vec3 lightPosition;
uniform float uLightIntensity;
uniform int uTextureSample;
varying highp vec2 vTextureCoord;
varying highp vec3 vFragPos;
varying highp vec3 vNormal;
void main(void) {
vec3 color;
if (uTextureSample == 1) {
color = pow(texture2D(uSampler, vTextureCoord).rgb, vec3(2.2));
} else {
color = uKd;
}
vec3 ambient = 0.05 * color;
vec3 lightDir = normalize(lightPosition - vFragPos);
vec3 normal = normalize(vNormal);
float diff = max(dot(lightDir, normal), 0.0);
float light_atten_coff = uLightIntensity / length(lightPosition - vFragPos);
vec3 diffuse = diff * light_atten_coff * color;
vec3 viewDir = normalize(cameraPosition - vFragPos);
float spec = 0.0;
vec3 reflectDir = reflect(-lightDir, normal);
spec = pow (max(dot(viewDir, reflectDir), 0.0), 35.0);
vec3 specular = uKs * light_atten_coff * spec;
gl_FragColor = vec4(pow((ambient + diffuse + specular), vec3(1.0/2.2)), 1.0);
//gl_FragColor = vec4(pow((diffuse), vec3(1.0/2.2)), 1.0);
//gl_FragColor = vec4( color, 1.0 );
//gl_FragColor = vec4(0.1);
}
</script>
总代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>源码对应电子书:百度"three.js 郭隆邦"</title>
<style>
body {
margin: 0;
overflow: hidden;
/* 隐藏body窗口区域滚动条 */
}
</style>
<!--引入three.js三维引擎-->
<script src="http://www.yanhuangxueyuan.com/versions/threejsR92/build/three.js"></script>
<!-- 引入threejs扩展控件OrbitControls.js -->
<script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/controls/OrbitControls.js"></script>
</head>
<body>
<!--**********************Shader程序***************************-->
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec3 aNormalPosition;
attribute vec2 aTextureCoord;
varying highp vec2 vTextureCoord;
varying highp vec3 vFragPos;
varying highp vec3 vNormal;
/*
normal,position以及摄像机位置需要使用three.js内置参数
*/
void main(void) {
vFragPos = position;
vNormal = normal;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
vTextureCoord = uv;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
uniform sampler2D uSampler;
uniform vec3 uKd;
uniform vec3 uKs;
uniform vec3 lightPosition;
uniform float uLightIntensity;
uniform int uTextureSample;
varying highp vec2 vTextureCoord;
varying highp vec3 vFragPos;
varying highp vec3 vNormal;
void main(void) {
vec3 color;
if (uTextureSample == 1) {
color = pow(texture2D(uSampler, vTextureCoord).rgb, vec3(2.2));
} else {
color = uKd;
}
vec3 ambient = 0.05 * color;
vec3 lightDir = normalize(lightPosition - vFragPos);
vec3 normal = normalize(vNormal);
float diff = max(dot(lightDir, normal), 0.0);
float light_atten_coff = uLightIntensity / length(lightPosition - vFragPos);
vec3 diffuse = diff * light_atten_coff * color;
vec3 viewDir = normalize(cameraPosition - vFragPos);
float spec = 0.0;
vec3 reflectDir = reflect(-lightDir, normal);
spec = pow (max(dot(viewDir, reflectDir), 0.0), 35.0);
vec3 specular = uKs * light_atten_coff * spec;
gl_FragColor = vec4(pow((ambient + diffuse + specular), vec3(1.0/2.2)), 1.0);
//gl_FragColor = vec4(pow((diffuse), vec3(1.0/2.2)), 1.0);
//gl_FragColor = vec4( color, 1.0 );
//gl_FragColor = vec4(0.1);
}
</script>
<script>
/**
* 创建场景对象Scene
*/
var scene = new THREE.Scene();
/**
* 光源设置
*/
//点光源
var point = new THREE.PointLight(0xffffff);
point.position.set(400, 200, 300); //点光源位置
scene.add(point); //点光源添加到场景中
//环境光
var ambient = new THREE.AmbientLight(0x888888);
scene.add(ambient);
/**
* 相机设置
*/
var width = window.innerWidth; //窗口宽度
var height = window.innerHeight; //窗口高度
var k = width / height; //窗口宽高比
var s = 150; //三维场景显示范围控制系数,系数越大,显示的范围越大
//创建相机对象
var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
camera.position.set(200, 300, 200); //设置相机位置
// camera.position.set(0, 0, 200); //设置相机位置
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
/**
* 创建网格模型
*/
// var geometry = new THREE.BoxGeometry(100, 100, 100); //立方体
// var geometry = new THREE.PlaneGeometry(400, 400); //矩形平面
var geometry = new THREE.SphereGeometry(100, 25, 25); //球体
// TextureLoader创建一个纹理加载器对象,可以加载图片作为几何体纹理
var textureLoader = new THREE.TextureLoader();
// 加载纹理贴图
var texture = textureLoader.load('./Earth.png');
var material = new THREE.MeshPhongMaterial({
map: texture, // 普通颜色纹理贴图
}); //材质对象Material
var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
scene.add(mesh); //网格模型添加到场景中
var uniforms;
uniforms = {
uSampler: {//采样的图片
value: texture,
},
uTextureSample: {//采样选择 1为贴图 2为不带贴图
value: 1
},
uKd: {
value: new THREE.Vector3(0.05, 0.05, 0.05)//控制满反射系数
},
uKs: {
value: new THREE.Vector3(0.5, 0.5, 0.5)//控制高光系数
},
lightPosition: {//光源位置
value: point.position
},
uLightIntensity: {
value: 1155.0//光照强度
}
};
var material_raw = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragmentShader').textContent,
});
var mesh_raw = new THREE.Mesh(geometry, material_raw);
mesh_raw.position.x = (mesh.position.x + 100 * 2);
mesh_raw.position.z = (mesh.position.z + 100 * 2);
scene.add(mesh_raw);
/**
* 创建渲染器对象
*/
var renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height); //设置渲染区域尺寸
renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色
document.body.appendChild(renderer.domElement); //body元素中插入canvas对象
// 渲染函数
function render() {
renderer.render(scene, camera); //执行渲染操作
requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧
}
render();
//创建控件对象 相机对象camera作为参数 控件可以监听鼠标的变化,改变相机对象的属性
var controls = new THREE.OrbitControls(camera, renderer.domElement);
//监听鼠标事件,触发渲染函数,更新canvas画布渲染效果
// controls.addEventListener('change', render);
</script>
</body>
</html>
要注意的坑
uniform 传入的变量 和 顶点着色器 片元着色器 接受的变量
ShaderMaterial的vertexshader和fragmentshader是有默认变量的。【如果不想three.js有任何内置变量影响你的shader,那么请使用RawShaderMaterial】
这一点three.js文档里提到了但是没有指出默认变量是啥,最后利用浏览器F12调试工具发现
感悟
理解算法理论之后利用编程实现仍要考虑诸多问题,比如和api的接口,这次编写shader正因为接口不对顶点没有出进去所以一直绘制出现问题,当顶点、法线、光照方向、相机方向等考虑正确后算法才可以发挥效果