渲染世界的OPENGL<12> 一些有趣的纹理着色器

本文介绍如何在OpenGL中使用纹理贴图与着色器技术实现多种视觉效果,包括简单纹理渲染、照亮纹理、片段丢弃及卡通着色。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

从着色器访问纹理贴图是非常简单的。纹理坐标将会作为属性传递到我们的顶点着色器,在片段着色器当中,这些属性通常是在顶点之间进行平滑插值的。片段着色器使用这些差值纹理坐标来对纹理进行采样。

(1)只处理纹理单元

// The TexturedIdentity Shader
// Vertex Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
//最为简单的纹理渲染器
#version 130
//传递到顶点着色器当中一个顶点坐标
in vec4 vVertex;
//传递到顶点着色器当中一个纹理坐标,注意核心部分就是这个s和t纹理坐标的输入
//顶点属性vTexCoords以及输出变量vVaringTexCoords。用纹理坐标在三角形表面进行差值
//所需要的,就是这些
in vec2 vTexCoords;

smooth out vec2 vVaryingTexCoords;

void main(void) 
    { 
    vVaryingTexCoords = vTexCoords;
    gl_Position = vVertex;
    }

以上就是仅仅处理纹理单元的顶点着色器,在顶点着色器当中,仅仅对顶点坐标以及顶点的纹理坐标s、t进行相应的传递以及处理。因为其为2D纹理图像,所以纹理坐标仅仅包含s、t。

// The TexturedIdentity Shader
// Fragment Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130

//注意这个新的数据变量类型sample2D
//一个采样器(sample)实际上就是一个这么好设计(使用glUniform1i来设置值)它代表我们将要
//采样的纹理所绑定的纹理单元。sample2D中的2D表示这是一个2D纹理,我们也可以使用1D
//2D或者3D等类型的采样器。 这个值总是设置为0,代表指示纹理单元0。
uniform sampler2D colorMap;

out vec4 vFragColor;
smooth in vec2 vVaryingTexCoords;


void main(void)
   { 
   //将当前传递进片段着色器当中的st纹理坐标和colormap采样进行texture函数
   //从而得到最终要进行光栅化的片段颜色。
   vFragColor = texture(colorMap, vVaryingTexCoords.st);
   }

如上就是纹理坐标的片段着色器,其中仅仅使用了一个texture函数作为fragColor的值。


#pragma comment(lib,"GLTools.lib")

#include <GLTools.h>            // OpenGL toolkit
#include <GLShaderManager.h>    // Shader Manager Class
#include <GL/glut.h>            // Windows FreeGlut equivalent


GLBatch triangleBatch;
GLShaderManager shaderManager;

GLint   myTexturedIdentityShader;
GLuint  textureID;

///////////////////////////////////////////////////////////////////////////////
// Window has changed size, or has just been created. In either case, we need
// to use the window dimensions to set the viewport and the projection matrix.
void ChangeSize(int w, int h)
{
    glViewport(0, 0, w, h);
}


// 加载一个tga图像作为一个2D纹理,并且进行必要的初始化
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
    GLbyte *pBits;
    int nWidth, nHeight, nComponents;
    GLenum eFormat;

    // 读取对应文件当下的图像
    pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
    if (pBits == NULL)
        return false;
    //设置对应图象的功能以及设置环绕模式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0,
        eFormat, GL_UNSIGNED_BYTE, pBits);
    //在设置完成参数之后,可以释放对应的字符指针
    free(pBits);

    if (minFilter == GL_LINEAR_MIPMAP_LINEAR ||
        minFilter == GL_LINEAR_MIPMAP_NEAREST ||
        minFilter == GL_NEAREST_MIPMAP_LINEAR ||
        minFilter == GL_NEAREST_MIPMAP_NEAREST)
        //生成对应的2维图像
        glGenerateMipmap(GL_TEXTURE_2D);

    return true;
}


///////////////////////////////////////////////////////////////////////////////
// 设置渲染环境
void SetupRC()
{
    // 背景
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    //渲染器初始化
    shaderManager.InitializeStockShaders();

    // Load up a triangle
    GLfloat vVerts[] = { -0.5f, 0.0f, 0.0f,
        0.5f, 0.0f, 0.0f,
        0.0f, 0.5f, 0.0f };

    GLfloat vTexCoords[] = { 0.0f, 0.0f,
        1.0f, 0.0f,
        0.5f, 1.0f };

    triangleBatch.Begin(GL_TRIANGLES, 3, 1);
    triangleBatch.CopyVertexData3f(vVerts);
    triangleBatch.CopyTexCoordData2f(vTexCoords, 0);
    triangleBatch.End();
    //创建对应的着色器
    myTexturedIdentityShader = gltLoadShaderPairWithAttributes("TexturedIdentity.vp", "TexturedIdentity.fp", 2,
        GLT_ATTRIBUTE_VERTEX, "vVertex", GLT_ATTRIBUTE_TEXTURE0, "vTexCoords");
    //生成纹理
    glGenTextures(1, &textureID);
    //绑定个纹理
    glBindTexture(GL_TEXTURE_2D, textureID);
    //对GL_TEXTURE_2D的图像进行加载
    LoadTGATexture("stone.tga", GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
}


///////////////////////////////////////////////////////////////////////////////
// Cleanup
void ShutdownRC()
{
    glDeleteProgram(myTexturedIdentityShader);
    glDeleteTextures(1, &textureID);
}


///////////////////////////////////////////////////////////////////////////////
// Called to draw scene
void RenderScene(void)
{
    // 清除缓冲区的数据
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    //产生纹理渲染器
    glUseProgram(myTexturedIdentityShader);
    glBindTexture(GL_TEXTURE_2D, textureID);//再次绑定
    //设置渲染器程序当中的colormap
    GLint iTextureUniform = glGetUniformLocation(myTexturedIdentityShader, "colorMap");
    glUniform1i(iTextureUniform, 0);//设置为0,因为是第一张图 

    triangleBatch.Draw();

    // Perform the buffer swap to display back buffer
    glutSwapBuffers();
}


///////////////////////////////////////////////////////////////////////////////
// Main entry point for GLUT based programs
int main(int argc, char* argv[])
{
    gltSetWorkingDirectory(argv[0]);

    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(800, 600);
    glutCreateWindow("Textured Triangle");
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);

    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }

    SetupRC();

    glutMainLoop();

    ShutdownRC();

    return 0;
}

最终的调用主程序,注意其中对纹理的调用和绑定。

(2)照亮纹理单元
在所有的光照着色器当中,我们实际上是将基本色值和光线的强度相乘,这可以是逐个顶点进行的,也可以是逐个像素进行的。
我们将修改过的ADSPhong着色器称为ADSTexture
会对纹理进行采样,然后用纹理的颜色值乘以光线强度。
正常来说,如果环境刚和漫反射的和可能会很亮,以至于看起来就是纯白色,可是如果乘以一个纹理颜色,那么结果就会是一个和原来纹理一样亮度的纹理颜色。看起来没什么不对,但是:
注意,在光照计算的时候,每个颜色通道的值通常是大于1的,意味着只收使得颜色过于饱和以及获得一个白色镜面高光是可能的,不过正确的方法应该是将环境光和漫反射光强度的和和纹理颜色相乘,最后加上镜面光的部分。

// ADS Point lighting Shader
// Vertex Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130

// 输入每一个顶点以及法线向量
in vec4 vVertex;
in vec3 vNormal;
in vec4 vTexture0;

uniform mat4   mvpMatrix;
uniform mat4   mvMatrix;
uniform mat3   normalMatrix;
uniform vec3   vLightPosition;

// 仍然是ADSPhong 的套路,法线插值,把计算的重心转移到片段着色器
smooth out vec3 vVaryingNormal;
smooth out vec3 vVaryingLightDir;
smooth out vec2 vTexCoords;

void main(void) 
    { 
    // 计算视觉坐标系当中的法向量
    vVaryingNormal = normalMatrix * vNormal;

    // 视觉坐标当中的顶点
    vec4 vPosition4 = mvMatrix * vVertex;
    vec3 vPosition3 = vPosition4.xyz / vPosition4.w;

    // 得到光的方向
    vVaryingLightDir = normalize(vLightPosition - vPosition3);

    // 传递贴图坐标
    vTexCoords = vTexture0.st;

    // 最后把变换应用到几何坐标上
    gl_Position = mvpMatrix * vVertex;
    }

顶点渲染器程序代码,vp文件。其中处理了视觉坐标系下的法向量、光照方向、以及传递贴图坐标。

// ADS Point lighting Shader
// Fragment Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130
//片段颜色
out vec4 vFragColor;

//得到三种光照颜色的统一值
uniform vec4      ambientColor;
uniform vec4      diffuseColor;   
uniform vec4      specularColor;
uniform sampler2D colorMap;//以及纹理取样

//输入的视觉坐标系当中的顶点法线向量
//光线向量
//纹理坐标
smooth in vec3 vVaryingNormal;
smooth in vec3 vVaryingLightDir;
smooth in vec2 vTexCoords;

void main(void)
    { 
    // 点乘得到漫反射光照强度
    float diff = max(0.0, dot(normalize(vVaryingNormal), normalize(vVaryingLightDir)));

    // 光强乘以漫反射颜色,alpha通道默认为1
    vFragColor = diff * diffuseColor;

    // 在此基础上加上环境光颜色
    vFragColor += ambientColor;

    // 将片段颜色和纹理进行乘运算
    vFragColor *= texture(colorMap, vTexCoords);

    // 最后进行镜面光
    vec3 vReflection = normalize(reflect(-normalize(vVaryingLightDir), normalize(vVaryingNormal)));
    float spec = max(0.0, dot(normalize(vVaryingNormal), vReflection));
    if(diff != 0) {
        float fSpec = pow(spec, 128.0);
        vFragColor.rgb += vec3(fSpec, fSpec, fSpec);
        }
    }

在frag渲染器当中,注意,其渲染顺序,首先是漫反射和环境光相加,然后和纹理相乘,最后计算镜面光并且相加。


#pragma comment(lib,"GLTools.lib")
#include <GLTools.h>    // OpenGL toolkit
#include <GLMatrixStack.h>
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>

#include <math.h>

#include <GL/glut.h>


GLFrame             viewFrame;
GLFrustum           viewFrustum;
GLTriangleBatch     sphereBatch;
GLMatrixStack       modelViewMatrix;
GLMatrixStack       projectionMatrix;
GLGeometryTransform transformPipeline;
GLShaderManager     shaderManager;

GLuint  ADSTextureShader;   // The textured diffuse light shader
GLint   locAmbient;         // The location of the ambient color
GLint   locDiffuse;         // The location of the diffuse color
GLint   locSpecular;        // The location of the specular color
GLint   locLight;           // The location of the Light in eye coordinates
GLint   locMVP;             // The location of the ModelViewProjection matrix uniform
GLint   locMV;              // The location of the ModelView matrix uniform
GLint   locNM;              // The location of the Normal matrix uniform
GLint   locTexture;
GLuint  texture;

// 加载纹理函数
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
    GLbyte *pBits;
    int nWidth, nHeight, nComponents;
    GLenum eFormat;

    // Read the texture bits
    pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
    if (pBits == NULL)
        return false;

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0,
        eFormat, GL_UNSIGNED_BYTE, pBits);

    free(pBits);

    if (minFilter == GL_LINEAR_MIPMAP_LINEAR ||
        minFilter == GL_LINEAR_MIPMAP_NEAREST ||
        minFilter == GL_NEAREST_MIPMAP_LINEAR ||
        minFilter == GL_NEAREST_MIPMAP_NEAREST)
        glGenerateMipmap(GL_TEXTURE_2D);

    return true;
}

// 初始化渲染环境
void SetupRC(void)
{
    // 1.渲染颜色
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    //2.开启渲染模式:深度检测,遮挡剔除
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    //3.初始化渲染器
    shaderManager.InitializeStockShaders();

    //4.设置好摄像机的位置
    viewFrame.MoveForward(4.0f);

    // 5.创建对象
    gltMakeSphere(sphereBatch, 1.0f, 26, 13);

    //6.加载渲染器程序代码
    ADSTextureShader = gltLoadShaderPairWithAttributes("ADSTexture.vp", "ADSTexture.fp", 3, GLT_ATTRIBUTE_VERTEX, "vVertex",
        GLT_ATTRIBUTE_NORMAL, "vNormal", GLT_ATTRIBUTE_TEXTURE0, "vTexture0");

    //7.得到对应统一值的位置并且做进一步修改
    locAmbient = glGetUniformLocation(ADSTextureShader, "ambientColor");
    locDiffuse = glGetUniformLocation(ADSTextureShader, "diffuseColor");
    locSpecular = glGetUniformLocation(ADSTextureShader, "specularColor");
    locLight = glGetUniformLocation(ADSTextureShader, "vLightPosition");
    locMVP = glGetUniformLocation(ADSTextureShader, "mvpMatrix");
    locMV = glGetUniformLocation(ADSTextureShader, "mvMatrix");
    locNM = glGetUniformLocation(ADSTextureShader, "normalMatrix");
    locTexture = glGetUniformLocation(ADSTextureShader, "colorMap");

    //8.生成纹理,并且绑定在一个宏上,并且从文件中加载
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    LoadTGATexture("CoolTexture.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
}

// 清除
void ShutdownRC(void)
{
    //相当于析构函数
    glDeleteTextures(1, &texture);
}


// 真正的渲染场景程序
void RenderScene(void)
{
    //1.设置计时器
    static CStopWatch rotTimer;

    // 2.清除颜色缓冲区,深度缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //3.对物体进行集合变换
    modelViewMatrix.PushMatrix(viewFrame);
    modelViewMatrix.Rotate(rotTimer.GetElapsedSeconds() * 10.0f, 0.0f, 1.0f, 0.0f);

    //4.硬编码对应的属性
    GLfloat vEyeLight[] = { -100.0f, 100.0f, 100.0f };
    GLfloat vAmbientColor[] = { 0.2f, 0.2f, 0.2f, 1.0f };
    GLfloat vDiffuseColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };
    GLfloat vSpecularColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };

    //绑定纹理
    glBindTexture(GL_TEXTURE_2D, texture);
    //激活渲染程序
    glUseProgram(ADSTextureShader);
    //对统一值进行赋值
    glUniform4fv(locAmbient, 1, vAmbientColor);
    glUniform4fv(locDiffuse, 1, vDiffuseColor);
    glUniform4fv(locSpecular, 1, vSpecularColor);
    glUniform3fv(locLight, 1, vEyeLight);
    //设置模型投影矩阵,在顶点渲染器设置gl_position用到
    glUniformMatrix4fv(locMVP, 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
    //模型矩阵,在顶点渲染器当中,计算视觉坐标系下的顶点坐标
    glUniformMatrix4fv(locMV, 1, GL_FALSE, transformPipeline.GetModelViewMatrix());
    //法线矩阵,在顶点着色器当中,计算视觉坐标系下的法线
    glUniformMatrix3fv(locNM, 1, GL_FALSE, transformPipeline.GetNormalMatrix());
    //设置纹理采样单元
    glUniform1i(locTexture, 0);
    sphereBatch.Draw();

    modelViewMatrix.PopMatrix();

    glutSwapBuffers();
    glutPostRedisplay();
}



void ChangeSize(int w, int h)
{
    // Prevent a divide by zero
    if (h == 0)
        h = 1;

    // Set Viewport to window dimensions
    glViewport(0, 0, w, h);

    viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 100.0f);

    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}

///////////////////////////////////////////////////////////////////////////////
// Main entry point for GLUT based programs
int main(int argc, char* argv[])
{
    gltSetWorkingDirectory(argv[0]);

    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(800, 600);
    glutCreateWindow("Lit Texture");
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);

    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }

    SetupRC();
    glutMainLoop();
    ShutdownRC();
    return 0;
}

这个主程序涉及了很多我们之前所提到的知识,我在每个语句上都加了注释。
重要的额SetUpRC以及RenderScene这两个函数,所执行的步骤,是我们目前为止所学的必要步骤。基本套路就是这样,可以模仿这样的步骤进行创造。

(3)丢弃片段

片段着色器设有取消处理过程而不写入任何片段颜色值(或者是深度,模板)的选项。声明discard只会使得片段程序停止运行,这个声明的一个常规用途就是执行alpha测试。普通的混合操作需要从颜色缓冲区进行一次读取,两次乘法,对颜色进行一次求和。然后将会得到的值写回颜色缓冲区。如果alpha值为0,或者非常接近0,那么片段实际上是不可见的。
绘制不可见的片段是对性能的严重侮辱,这样会在深度缓冲区创建一个不可见模式。从而导致深度测试异常。alpha测试只是检查一些阈值,并且在alpha值低于这个值的时候,完全丢弃这个片段。

if(vColorValue.a<0.1f)
{
discard;
}

使用这种特性可以创建出非常棒的侵蚀着色器(erosion shader)。侵蚀着色器可以使几何图形看起来经过了岁月的侵蚀。使用discard声明,我们可以逐个像素的控制哪个片段会进行绘制,哪个片段不会。
在客户端代码中,设置了一个基于时间的Uniform值,取值范围为1-0,覆盖10s的范围。最终结果是在10s内消散。
我们通过对云纹理进行采样,并且将一个颜色分量和倒计时变量进行比较,当颜色值大于阈值的时候,则完全丢弃片段。

// ADS Point lighting Shader
// Vertex Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130

// 输入每个点,法向量,纹理坐标
in vec4 vVertex;
in vec3 vNormal;
in vec2 vTexCoords0;


uniform mat4   mvpMatrix;
uniform mat4   mvMatrix;
uniform mat3   normalMatrix;
uniform vec3   vLightPosition;

// 需要输出到片段着色器当中做进一步计算。
smooth out vec3 vVaryingNormal;
smooth out vec3 vVaryingLightDir;
smooth out vec2 vVaryingTexCoord;


void main(void) 
    {
    // 传递纹理坐标
    vVaryingTexCoord = vTexCoords0;

    // 计算视觉坐标系当中的表面法向量
    vVaryingNormal = normalMatrix * vNormal;

    // 视觉坐标系的顶点位置
    vec4 vPosition4 = mvMatrix * vVertex;
    vec3 vPosition3 = vPosition4.xyz / vPosition4.w;

    // 光照方向
    vVaryingLightDir = normalize(vLightPosition - vPosition3);


    // 几何变换
    gl_Position = mvpMatrix * vVertex;
    }

点渲染程序,和之前的程序并没有差别。

// ADS Point lighting Shader
// Fragment Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130

out vec4 vFragColor;

uniform vec4       ambientColor;
uniform vec4       diffuseColor;   
uniform vec4       specularColor;
uniform sampler2D  cloudTexture;
uniform float      dissolveFactor;

smooth in vec3 vVaryingNormal;
smooth in vec3 vVaryingLightDir;
smooth in vec2 vVaryingTexCoord;

void main(void)
    { 
    //注意,这是灰度图,那么贴图颜色和光照颜色乘积就毫无意义。所以这里并没有乘
    vec4 vCloudSample = texture(cloudTexture, vVaryingTexCoord);

    //这种消散效果仅仅是对纹理取样进行阈值的验证,如果符合那么就丢弃
    if(vCloudSample.r < dissolveFactor)
        discard;



    float diff = max(0.0, dot(normalize(vVaryingNormal), normalize(vVaryingLightDir)));

    vFragColor = diff * diffuseColor;


    vFragColor += ambientColor;



    vec3 vReflection = normalize(reflect(-normalize(vVaryingLightDir), normalize(vVaryingNormal)));
    float spec = max(0.0, dot(normalize(vVaryingNormal), vReflection));
    if(diff != 0) {
        float fSpec = pow(spec, 128.0);
        vFragColor.rgb += vec3(fSpec, fSpec, fSpec);
        }
    }

片段渲染器,仅仅多了一条阈值验证。

    float fFactor = fmod(rotTimer.GetElapsedSeconds(), 10.0f);
    fFactor /= 10.0f;
    glUniform1f(locDissolveFactor, fFactor);
    torusBatch.Draw();

在主程序的RenderScene函数中,通过设置阈值的不断变化,然后传入着色器程序中。通过阈值的不断变化,来产生侵蚀的效果,耍了个小花招。

(4)卡通着色器:将纹理单元作为光源
所谓卡通着色,无非是一维贴图纹理。通常应用在计算机游戏中对一个几何图形进行渲染。使其看起来像是动画片。
卡通着色,(Toon shader, cell shading)将一个一维纹理贴图作为一个查询表,使用纹理贴图中的纯色(GL_NEAREST)填充几何图形。
基本思路是:使用漫反射光照强度(视觉空间表面法线和光线方向的点乘积)作为纹理坐标添加到一个包含逐渐变亮颜色表的一维纹理中。

#version 130

// Incoming per vertex... position and normal
in vec4 vVertex;
in vec3 vNormal;

smooth out float textureCoordinate;

uniform vec3    vLightPosition;
uniform mat4    mvpMatrix;
uniform mat4    mvMatrix;
uniform mat3    normalMatrix;


void main(void) 
    { 
    // Get surface normal in eye coordinates
    vec3 vEyeNormal = normalMatrix * vNormal;

    // Get vertex position in eye coordinates
    vec4 vPosition4 = mvMatrix * vVertex;
    vec3 vPosition3 = vPosition4.xyz / vPosition4.w;

    // Get vector to light source
    vec3 vLightDir = normalize(vLightPosition - vPosition3);

    // 点乘积给我们漫反射强度,并且以此作为纹理坐标
    textureCoordinate = max(0.0, dot(vEyeNormal, vLightDir));

    // Don't forget to transform the geometry!
    gl_Position = mvpMatrix * vVertex;
    }

这里的顶点渲染器,需要的东西,注意纹理坐标的生成。

#version 130

uniform sampler1D colorTable;
out vec4 vFragColor;

smooth in float textureCoordinate;


void main(void)
   { 
   //注意,得到的纹理坐标,和colorTable进行绑定
   vFragColor = texture(colorTable, textureCoordinate);
   }

最后在片段着色器当中将纹理坐标和颜色表进行绑定。

    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_1D, texture);
    GLubyte textureData[4][3] = { 32,  0, 0,
        64,  0, 0,
        128, 0, 0,
        255, 0, 0 };

    glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB, 4, 0, GL_RGB, GL_UNSIGNED_BYTE, textureData);
    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

在初始化渲染环境当中,注意初始化颜色表。
绑定这个颜色表,在之后的渲染都用这个进行渲染。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值