Materials
shader
在前面定义了每个物体应该收到的环境光、漫反射、高光,也就是phong模型。
但是不同的材质会有不同的颜色情况,所以要在上面的这些项再加点东西。
struct Material{
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material material;
.....
//diffuse
vec3 diffuse = material.diffuse * max(dot(lightDir,Normal),0) * lightColor ;
//specular
vec3 specular = material.specular * pow(max(dot(reflectVec,cameraDir),0),material.shininess)*lightColor;
//ambinet
vec3 ambient = material.ambient * ambientColor;
glUniform3f(glGetUniformLocation(testShader->ID, "material.ambient"), 1.0f, 1.0f, 1.0f);
glUniform3f(glGetUniformLocation(testShader->ID, "material.diffuse"), 0.0f, 0.0f, 1.0f);
glUniform3f(glGetUniformLocation(testShader->ID, "material.specular"), 1.0f, 1.0f, 1.0f);
glUniform1f(glGetUniformLocation(testShader->ID, "material.shininess"), 64.0f);
在main中修改参数,得到
class
上面部分都是零散的修改了shader,现在要封装起来。
#pragma once
#include"Shader.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
class Material
{
public:
Shader* shader;
glm::vec3 diffuse;
glm::vec3 ambient;
glm::vec3 specular;
float shininess;
Material(Shader* _shader,glm::vec3 _diffuse,glm::vec3 _specular,glm::vec3 _ambient,float _shininess);
};
这里就是把这些系数都装到material.h中,然后修改main()
#pragma region Init Material
Material* myMaterial = new Material(testShader,
glm::vec3(1.0f,1.0f,1.0f),
glm::vec3(1.0f,1.0f,1.0f),
glm::vec3(1.0f,1.0f,1.0f),
32.0f);
#pragma endregion
.......
myMaterial->shader->SetUniform3f("material.ambient", myMaterial->ambient);
myMaterial->shader->SetUniform3f("material.diffuse", myMaterial->diffuse);
myMaterial->shader->SetUniform3f("material.specular", myMaterial->specular);
myMaterial->shader->SetUniform1f("material.shininess", myMaterial->shininess);
这么做的意义就是不用去rander loop中修改参数了,直接构建不同的材质球就行。
光照贴图Lighting maps
在material中,把diffuse、ambient、specular集中封装成一个整体,用来定义一个物体的特殊材质,但是一辆汽车不可能只有一种材质,但是,如果我们想现实去用不同的部件组装,在计算机中是特别耗时的,所以有没有简单的方法,一次性就把一辆车整体的材质都说明清楚。
回忆一下之前的纹理贴图,我们不想一个像素一个像素的去填充颜色直到上面有一幅画,直接贴上一幅图就好了。
那么光照贴图也就是干这个的。光照贴图可以在材质的基础上描述每一个片段的反射颜色是什么样的。
漫反射贴图Diffuse Map
我们现在把一个贴图加入到材质中去。需要修改的有很多。
因为有了纹理,所以需要在vertex和fragment中加入纹理坐标
在fragmentShader中,使用texture()函数来确定纹理
in vec2 TexCoord;
struct Material {
vec3 ambient;
sampler2D diffuse;
vec3 specular;
float shininess;
};
......
vec3 diffuse = texture(material.diffuse,TexCoord).rgb* max(dot(lightDir,Normal),0) * lightColor;
注意看,这里有个.rbg,是因为传入的纹理图是4通道的,我们只需要前三个通道rbg就行。
修改main中顶点属性部分
//设置顶点属性指针
//位置
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
颜色
//glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
//glEnableVertexAttribArray(1);
//纹理坐标
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)( 6* sizeof(float)));
glEnableVertexAttribArray(2);
//法向量
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(3);
既然material修改了对应的.h.cpp都需要修改。
这里有个逻辑关系: 在片段着色器中 diffuse是sampler2D,也就是我们要传一个图片到GPU中,并且用一个unsigned int来去记住他,之后再GPU还需要用这个图片就直接呼叫这个int就行。所以我们需要再main中把image to GPU。
//matierial.h
unsigned int diffuse;
......
Material(Shader* _shader, unsigned int _diffuse, glm::vec3 _specular, glm::vec3 _ambient, float _shininess);
//main
#pragma region Init Material
Material* myMaterial = new Material(myShader,
LoadImageToGPU("container2.jpg", GL_RGBA, GL_RGBA, 0),
glm::vec3 (0.5f, 0.5f, 0.5f),
glm::vec3 (1.0f, 1.0f, 1.0f),
64.0f);
#pragma endregion
还需对rander loop中的修改uniform
//shader.cpp
void Shader::SetUniform1i(const char * paraNameString, int slot)
{
glUniform1i(glGetUniformLocation(ID, paraNameString), slot);
}
//main
myMaterial->shader->SetUniform1i("material.diffuse", 0);
这里重新构造了一个函数就是,给贴图0位置传一个diffuse的图。
镜面光贴图specular
如果跟着到这的话会看到整个箱子都会反光,这是不合理的,只有周围的边框是金属,中间的木头是不会反光的。所以我们还需要像前面一样加入镜面光贴图。文章中给的是png的图片,中间的木头rgb都是黑色,也就再高光项中对把其他参数全部吃掉,没有高光。而周围是可以反光的。很合理的设计对吧。
tips:一般来说 镜面光贴图就是个灰度图,因为他只需要表示会不会反光,反光多少就行。rbg这种描述颜色的事情交给其他步骤就好,objColor、lightColor都可以,没必要再这里还给个rbg通道描述颜色。
修改过程和上面一样,不过是现在用了两个贴图,需要开通两个texture通道了。
这里我先说一下:贴图通道,我们不希望写死,所以再shader.h种加一个枚举量来表示贴图通道
enum Slot
{
DIFFUSE.
SPECULAR
};
这样,在后面我们写通道的时候直接用shader中的量就好,不再去写0123了
//main
#pragma region Init Material
Material* myMaterial = new Material(myShader,
LoadImageToGPU("container2.png", GL_RGBA, GL_RGBA, Shader::DIFFUSE),
LoadImageToGPU("container2_specular.png", GL_RGBA, GL_RGBA, Shader::SPECULAR),
glm::vec3 (1.0f, 1.0f, 1.0f),
150.0f);
#pragma endregion
......
//set material -> Textures
glActiveTexture(GL_TEXTURE0+Shader::DIFFUSE);
glBindTexture(GL_TEXTURE_2D, myMaterial->diffuse);
glActiveTexture(GL_TEXTURE0 + Shader::SPECULAR);
glBindTexture(GL_TEXTURE_2D, myMaterial->specular);
........
myMaterial->shader->SetUniform1i("material.diffuse", Shader::DIFFUSE);
myMaterial->shader->SetUniform1i("material.specular", Shader::SPECULAR);
可以看到高光只在边框上了。
自发光贴图emission maps
自发光贴图可以让物体自己发光,我们想象一个眼睛冒着红光的僵尸在冲过来。僵尸不仅需要其他的光打在他身上,自己也是需要发光的!
ok依次修改material.h,material.cpp,shader.h,main(),fragmentShader
然后我想让这个图动起来,该怎么做呢?
很简单,从main传一个time到着色器种,然后让time去修改我的纹理坐标,这样就可以当图动起来了。
//emission
vec3 emission = texture(material.emission,TexCoord+vec2(0,time)).rgb*(sin(time)*0.5f+1.0f);