OpenGL之光源

OpenGL之基础
OpenGL之绘制简单形状
OpenGL之颜色
OpenGL之调整屏幕宽高比
OpenGL之三维
OpenGL之纹理
OpenGL之构建简单物体
OpenGL之触控反馈
OpenGL之粒子
OpenGL之天空盒
OpenGL之地形

光源分类

通常可以把不同的光源分为下面几种

光源 说明
环境光(Ambient light) 环境光看上去来自四面八方,场景中的一切被照亮的程度都一样。这近似于我们从大的、平等的光源获取的光照,比如天空
方向光(Directional light) 方向光看上去似乎来自一个方向,光源好像处于极其远的地方。这与我们从太阳或月亮获取的光照相似
点光(Point light) 点光看上去是从附近某处投射的光亮,而且光的密度随着距离而减少。这适于表示近处的光源,其把它们的光投射到四面八方,像一个灯泡或蜡烛一样
聚光(Spot light) 聚光与点光类似,只是加了一个限制,只能向一个特定的方向投射。这是我们从手电筒或者聚光灯所获得的光照类型

把光线在物体表面反射的方式分为两类:

反射方式 说明
漫反射(Diffuse reflection) 漫反射是指光线平等地向所有方向蔓延,适于表示没有抛光表面的材质,比如地毯或外面的混凝土墙
镜面反射( Specular reflection) 镜面反射在某个特定的方向上反射更加强烈,适于被抛光的或者闪亮的材质,比如光滑的金属或者刚刚打过蜡的汽车

朗伯体反射实现方向光

朗伯体反射描述了这样一个表面,它会反射所有方向上打到它的光线,以使它在任何观察点上看起来都是一样的。它的外观只依赖于它与光源所处的方位及其距离

使用一个水平表面和单个的、不随距离而减弱的方向光源,因此,唯一有影响的就是这个表面相对于该光源所处的方位

直接面对光源的表面 与光源成一定角度的表面
image-20211223141607462.png
image-20211223141630129.png

我们可以看到表面垂直正对光源时,在这个角度,它捕捉并反射尽可能多的光线;表面相对光源旋转了45度时,这个表面反射的光线与旋转角的余弦有关

要计算出一个表面接收了多少光线,我们所需要做的就是计算出它直接面向光源时接收了多少光线,再把结果乘以那个角度的余弦值

计算高度图的方位

在给高度图添加朗伯体反射之前,需要某个方法获知其表面的方位是什么。因为高度图不是一个水平的表面,我们需要计算高度图上每个点的方位。可以用表面法线(surface normal)表示方位,它是一个特殊类型的向量,它垂直于表面,且有一个值为1的单位长度

因为表面法线是应用于表面的而不是点,在计算每个点的法线时,把这个点的邻接点合并在一起创建一个平面。我们将用两个向量来表示这个平面:一个从右侧的点指向左侧的点,另外一个从上面的点指向下面的点。如果我们计算这两个向量的叉积,就能得到一个垂直于这个平面的向量,然后,我们可以归一化那个向量以得到中间点的表面法线,为什么使用从右向左的向量,而不是从左向右呢?因为我们想要这个表面法线指向上方,离开高度图,所以我们用叉积的右手规则计算出每个向量需要指向的方向


image-20211223144206780.png

我们假定每个点占用一个单位部分,x值向右增加,z值向下增加。其上边、左边、右边和下边的点的高度分别是0.2、0.1、0.1和0.1。要计算从右向左的向量,我们用右边的点减去左边的点得到值为(-2,0,0)的向量,再用上边和下边的点做同样的计算得到值为(0,-0.1,2)的向量。一旦有了这两个向量,我们就可以计算它们的叉积得到值为(0,4,0.2)的向量,我们再把这个向量归一化得到值为(0,0.9988,0.05) 的表面法线

给高度图加入法线向量

public class Heightmap {
    // 法线向量占的位数
    private static final int NORMAL_COMPONENT_COUNT = 3;
    private static final int TOTAL_COMPONENT_COUNT = NORMAL_COMPONENT_COUNT + POSITION_COMPONENT_COUNT;
    private static final int STRIDE = TOTAL_COMPONENT_COUNT * BYTES_PER_FLOAT;
    private int mNumOfIndex;
    private VertexBuffer mVertexBuffer;
    private IndexBuffer mIndexBuffer;
    private int mWidth;
    private int mHeight;

    public Heightmap(Bitmap bitmap) {
        mWidth = bitmap.getWidth();
        mHeight = bitmap.getHeight();
        initVertexBuffer(bitmap);
        initIndexBuffer();
    }


    /**
     * 绑定着色器中 attribute 数据,包括位置和法线向量
     *
     * @param heightmapProgram
     */
    public void bindData(HeightmapProgram heightmapProgram) {
        int offset = 0;
        mVertexBuffer.setVertexAttribPointer(
                offset,
                heightmapProgram.getAPosition(),
                POSITION_COMPONENT_COUNT,
                STRIDE
        );

        offset += POSITION_COMPONENT_COUNT * BYTES_PER_FLOAT;
        mVertexBuffer.setVertexAttribPointer(
                offset,
                heightmapProgram.getANormal(),
                NORMAL_COMPONENT_COUNT,
                STRIDE
        );
    }

    public void draw() {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer.getBufferId());
        glDrawElements(GL_TRIANGLES, mNumOfIndex, GL_UNSIGNED_SHORT, 0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    }

    /**
     * 初始化索引Buffer
     */
    private void initIndexBuffer() {
        mNumOfIndex = numOfIndex();
        short[] indexData = new short[mNumOfIndex];
        int offset = 0;
        for (short row = 0; row < mHeight - 1; row++) {
            for (short col = 0; col < mWidth - 1; col++) {
                short topLeft = (short) (row * mWidth + col);
                short topRight = (short) (topLeft + 1);
                short bottomLeft = (short) ((row + 1) * mWidth + col);
                short bottomRight = (short) (bottomLeft + 1);

                indexData[offset++] = topLeft;
                indexData[offset++] = bottomLeft;
                indexData[offset++] = topRight;

                indexData[offset++] = topRight;
                indexData[offset++] = bottomLeft;
                indexData[offset++] = bottomRight;
            }
        }
        mIndexBuffer = new IndexBuffer(indexData);
    }

    /**
     * 初始化顶点Buffer,加上法向向量
     *
     * @param bitmap
     */
    private void initVertexBuffer(Bitmap bitmap) {
        float[] vertexData = new float[mWidth * mHeight * TOTAL_COMPONENT_COUNT];
        int offset = 0;
        int[] pixels = new int[mWidth * mHeight];
        bitmap.getPixels(pixels, 0, mWidth, 0, 0, mWidth, mHeight);
        for (int row = 0; row < mHeight; row++) {
            for (int col = 0; col < mWidth; col++) {

                // 点的位置
                Point point = getPoint(pixels, row, col);
                vertexData[offset++] = point.getX();
                vertexData[offset++] = point.getY();
                vertexData[offset++] = point.getZ();

                //点的上下左右
                Point top = getPoint(pixels, row - 1, col);
                Point left = getPoint(pixels, row, col - 1);
                Point right = getPoint(pixels, row, col + 1);
                Point bottom = getPoint(pixels, row + 1, col);

                // 得到法向向量
                Vector rightToLeft = Vector.vectorBetween(right, left);
                Vector topToBottom = Vector.vectorBetween(top, bottom);
                Vector normal = rightToLeft.crossProduct(topToBottom).normalize();

                // 将法向向量写入顶点数据数组中
                vertexData[offset++] = normal.getX();
                vertexData[offset++] = normal.getY();
                vertexData[offset++] = normal.getZ();
            }
        }
        mVertexBuffer = new VertexBuffer(vertexData);
    }

    /**
     * 获取pixels像素中的某个像素的三维空间位置
     *
     * @param pixels
     * @param row
     * @param col
     * @return
     */
    private Point getPoint(int[] pixels, int row, int col) {
        float x = (col / (float) (mWidth - 1)) - 0.5f;
        float z = (row / (float) (mHeight - 1)) - 0.5f;

        row = clamp(row, 0, mHeight - 1);
        col = clamp(col, 0, mWidth - 1);
        int pixel = pixels[row * mWidth + col];
        float y = Color.red(pixel) / 255f;

        return new Point(x, y, z);
    }

    private int clamp(int value, int min, int max) {
        return Math.min(max, Math.max(value, min));
    }

    /**
     * 返回高度图的索引数目
     * 相邻四个像素点形成1个正方形即2个三角形,即6个顶点,整个bitmap形成(mWidth - 1) * (mHeight - 1) 个正方形
     *
     * @return
     */
    private int numOfIndex() {
        return (mWidth - 1) * (mHeight - 1) * 2 * 3;
    }
}

给着色器加入方向光

顶点着色器

uniform mat4 u_Matrix;
uniform mat4 u_MVMatrix;
uniform mat4 u_IT_MVMatrix;

uniform vec4 u_PointLightPositions[3];
uniform vec3 u_PointLightColors[3];

vec3 materialColor;
vec4 eyeSpacePosition;
vec3 eyeSpaceNormal;

attribute vec4 a_Position;
varying vec3 v_Color;

uniform vec3 u_VectorToLight;
attribute vec3 a_Normal;

vec3 getAmbientLighting();
vec3 getDirectionalLighting();
vec3 getPointLighting();

void main()
{
    materialColor = mix(vec3(0.180, 0.467, 0.153),
    vec3(0.660, 0.670, 0.680),
    a_Position.y);

    eyeSpacePosition=u_MVMatrix*a_Position;
    eyeSpaceNormal=normalize(vec3(u_IT_MVMatrix*vec4(a_Normal, 0.0)));

    v_Color=getAmbientLighting();
    v_Color+=getDirectionalLighting();
    v_Color+=getPointLighting();

    gl_Position = u_Matrix * a_Position;
}

vec3 getAmbientLighting(){
    return materialColor*0.1;
}

vec3 getDirectionalLighting(){
    return materialColor*0.3*max(dot(eyeSpaceNormal, u_VectorToLight), 0.0);
}

vec3 getPointLighting(){
    vec3 lightingSum=vec3(0.0);
    for (int i=0;i<3;i++){
        vec3 toPointLight=vec3(u_PointLightPositions[i])-vec3(eyeSpacePosition);
        float diatance=length(toPointLight);
        toPointLight=normalize(toPointLight);

        float cos=max(dot(toPointLight, eyeSpaceNormal), 0.0);
        lightingSum+=materialColor*u_PointLightColors[i]*5.0*cos/diatance;
    }
    return lightingSum;
}

变量说明

变量 说明
u_Matrix 模型视图投影矩阵
u_MVMatrix 表示模型视图矩阵,位置与其相乘,就会将位置信息装换到眼空间中
u_IT_MVMatrix 表示模型视图矩阵倒置矩阵的转置矩阵,平面法线与之相乘,在进行归一化,可以取消缩放的影响
u_PointLightPositions 点光源的位置,在眼空间中
u_PointLightColors 点光源颜色
u_VectorToLight 存储方向光源的归一化向量,在眼空间中
a_Position 顶点位置信息
a_Normal 高度图法线
v_Color 最终片段的颜色信息,因为片段着色器未做任何改动,直接将该值赋值给了最终片段的颜色信息
materialColor 山脉颜色,由mix函数根据山脉高度计算得到
eyeSpacePosition 顶点在眼空间中的位置,由a_Position和u_MVMatrix 进行转换得到
eyeSpaceNormal 在眼空间中的高度图法线,由a_Normal 和u_IT_MVMatrix 进行转换并归一化得到

方法说明

方法 说明
getAmbientLighting() 获取环境光。其值设为山脉颜色的0.1,将全局都提亮一些
getDirectionalLighting() 获取方向光。相当于太阳,其光源值定为materialColor*0.3,通过计算指向光源的向量与表面法线的点积,来计算表面与光线之间夹角的余弦值。它的工作原理是,当两个向量都是归一化的向量时,那两个向量的点积就是它们之间夹角的余弦,为了避免出现负的结果,用max()把最小余弦值限制为0,然后,应用这个光线,把当前顶点的颜色与余弦值相乘。余弦值在0和1之间,因此,最终的颜色将是处于黑色和原色之间的某个颜色
getPointLighting() 获取点光。对于当前顶点,循环计算每个点光,得到当前顶点到点光的向量并归一化,然后计算夹角余弦,再除以距离,并把其结果加人lightingSum,就是所有点光对当前顶点的光照值了,乘以5是为了放到效果,使其更明亮

更新着色器封装代码

public class HeightmapProgram extends BaseProgram {
    private static final String U_MV_MATRIX = "u_MVMatrix";
    private static final String U_IT_MV_MATRIX = "u_IT_MVMatrix";

    private static final String U_VECTOR_TO_LIGHT = "u_VectorToLight";

    private static final String U_POINT_LIGHT_POSITIONS = "u_PointLightPositions";
    private static final String U_POINT_LIGHT_COLORS = "u_PointLightColors";
    private static final String A_NORMAL = "a_Normal";

    private final int mUMatrix;
    private final int mUVectorToLight;
    private final int mUMVMatrix;
    private final int mUITMVMatrix;
    private final int mUPointLightPositions;
    private final int mUPointLightColors;

    private final int mAPosition;
    private final int mANormal;

    public HeightmapProgram(Context context) {
        super(context, R.raw.light9_heightmap_vertex, R.raw.particles8_heightmap_fragment);
        mUMatrix = glGetUniformLocation(mProgram, U_MATRIX);
        mUMVMatrix = glGetUniformLocation(mProgram, U_MV_MATRIX);
        mUITMVMatrix = glGetUniformLocation(mProgram, U_IT_MV_MATRIX);

        mUVectorToLight = glGetUniformLocation(mProgram, U_VECTOR_TO_LIGHT);

        mUPointLightPositions = glGetUniformLocation(mProgram, U_POINT_LIGHT_POSITIONS);
        mUPointLightColors = glGetUniformLocation(mProgram, U_POINT_LIGHT_COLORS);

        mAPosition = glGetAttribLocation(mProgram, A_POSITION);
        mANormal = glGetAttribLocation(mProgram, A_NORMAL);
    }

    public void setUniforms(float[] mvMatrix, float[] it_MVMatrix, float[] matrix,
                            float[] vectorToLight, float[] pointLightPositions, float[] pointLightColors) {
        glUniformMatrix4fv(mUMVMatrix, 1, false, mvMatrix, 0);
        glUniformMatrix4fv(mUITMVMatrix, 1, false, it_MVMatrix, 0);
        glUniformMatrix4fv(mUMatrix, 1, false, matrix, 0);
        glUniform3fv(mUVectorToLight, 1, vectorToLight, 0);
        glUniform4fv(mUPointLightPositions, 3, pointLightPositions, 0);
        glUniform3fv(mUPointLightColors, 3, pointLightColors, 0);
    }

    public int getAPosition() {
        return mAPosition;
    }

    public int getANormal() {
        return mANormal;
    }
}

更新渲染器代码

public class MyRenderer implements GLSurfaceView.Renderer, ITouchRenderer {
    private static final String TAG = "MyRenderer";
    /*private static final int[] SKY_ID = new int[]{
            R.drawable.left, R.drawable.right,
            R.drawable.bottom, R.drawable.top,
            R.drawable.front, R.drawable.back,
    };*/
    private static final int[] SKY_ID = new int[]{
            R.drawable.night_left, R.drawable.night_right,
            R.drawable.night_bottom, R.drawable.night_top,
            R.drawable.night_front, R.drawable.night_back,
    };
    private Context mContext;
    private float[] modelMatrix = new float[16];
    private float[] viewMatrix = new float[16];
    private float[] viewMatrixForSkybox = new float[16];
    private float[] projectionMatrix = new float[16];
    private float[] tempMatrix = new float[16];
    private float[] modelViewProjectionMatrix = new float[16];

    private float[] modelViewMatrix = new float[16];
    private float[] it_modelViewMatrix = new float[16];

    private HeightmapProgram mHeightmapProgram;
    private Heightmap mHeightmap;

    private ParticlesProgram mParticlesProgram;
    private ParticleSystem mParticleSystem;
    private ParticleShooter mRedParticleShooter;
    private ParticleShooter mGreenParticleShooter;
    private ParticleShooter mBlueParticleShooter;
    // 控制粒子的角度
    private float mAngleVarianceInDegree = 30f;
    // 控制粒子的速度
    private float mSpeedVariance = 1f;
    private long mGlobalStartTime;

    private SkyBox mSkyBox;
    private SkyProgram mSkyProgram;
    private int mTextureId;
    private int mCubeTextureId;
    private float xRotate;
    private float yRotate;
    // 方向光源,指向太阳
    private final float[] vectorToLight = {0.3f, 0.35f, -0.89f, 0f};
    // 点光源位置
    private final float[] pointLightPositions = new float[]{
            -1f, 1f, 0f, 1f,
            0f, 1f, 0f, 1f,
            1f, 1f, 0f, 1f
    };
    // 点光源颜色
    private final float[] pointLightColors = new float[]{
            1f, 0.2f, 0.02f,
            0.02f, 0.25f, 0.02f,
            0.02f, 0.2f, 1f
    };

    public MyRenderer(Context context) {
        mContext = context;
    }

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        glClearColor(0f, 0f, 0f, 0f);
        glEnable(GLES20.GL_DEPTH_TEST);
        glEnable(GLES20.GL_CULL_FACE);

        // 初始化高度图
        mHeightmapProgram = new HeightmapProgram(mContext);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 4;
        Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.heightmap, options);
        mHeightmap = new Heightmap(bitmap);

        // 粒子着色器程序
        mParticlesProgram = new ParticlesProgram(mContext);
        // 粒子系统,maxParticleCount如果太小的话,可能只能看到比较新的粒子
        mParticleSystem = new ParticleSystem(10000);
        // 粒子方向
        com.test.opengl.light9.geometry.bean.Vector particleDirection = new Vector(0f, 0.5f, 0f);
        // 粒子发射器
        mRedParticleShooter = new ParticleShooter(new Point(-1f, 0f, 0f), particleDirection, Color.RED, mAngleVarianceInDegree, mSpeedVariance);
        mGreenParticleShooter = new ParticleShooter(new com.test.opengl.light9.geometry.bean.Point(0f, 0f, 0f), particleDirection, Color.GREEN, mAngleVarianceInDegree, mSpeedVariance);
        mBlueParticleShooter = new ParticleShooter(new Point(1f, 0f, 0f), particleDirection, Color.BLUE, mAngleVarianceInDegree, mSpeedVariance);
        // 粒子系统启动的时间
        mGlobalStartTime = System.nanoTime();
        // 使用图片的样式绘制粒子
        mTextureId = com.test.opengl.light9.util.TextureHelper.loadTexture(mContext, R.drawable.particle_texture);

        mSkyProgram = new SkyProgram(mContext);
        mSkyBox = new SkyBox();
        mCubeTextureId = TextureHelper.loadCubeTexture(mContext, SKY_ID);
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        glViewport(0, 0, width, height);
        MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width / height, 1, 10);
        // 更新视图矩阵
        updateViewMatrix();
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        drawSky();
        drawHeightmap();
        drawParticles();
    }

    private void drawHeightmap() {
        // 更新矩阵
        setIdentityM(modelMatrix, 0);
        // 用模型矩阵使高度图在x和z方向上变宽100倍,而在y方向上只变高10倍
        // 着色器中的颜色插值依赖于顶点所在位置的y值,这不会扰乱,因为在顶点着色器中,设置v_Color的时间是在我们把它与矩阵相乘之前
        Matrix.scaleM(modelMatrix, 0, 80f, 30f, 80f);
        updateMvpMatrix();

        mHeightmapProgram.useProgram();
        final float[] vectorToLightInEyeSpace = new float[4];
        final float[] pointPositionsInEyeSpace = new float[12];
        Matrix.multiplyMV(vectorToLightInEyeSpace, 0, viewMatrix, 0, vectorToLight, 0);
        Matrix.multiplyMV(pointPositionsInEyeSpace, 0, viewMatrix, 0, pointLightPositions, 0);
        Matrix.multiplyMV(pointPositionsInEyeSpace, 4, viewMatrix, 0, pointLightPositions, 4);
        Matrix.multiplyMV(pointPositionsInEyeSpace, 8, viewMatrix, 0, pointLightPositions, 8);

        mHeightmapProgram.setUniforms(modelViewMatrix, it_modelViewMatrix, modelViewProjectionMatrix,
                vectorToLightInEyeSpace, pointPositionsInEyeSpace, pointLightColors);

        mHeightmap.bindData(mHeightmapProgram);
        mHeightmap.draw();
    }

    private void drawParticles() {
        // 关闭深度测试的写操作
        glDepthMask(false);

        // 创建并添加粒子
        float currentTime = (System.nanoTime() - mGlobalStartTime) / 1000000000f;
        mRedParticleShooter.addParticle(mParticleSystem, currentTime, 5);
        mGreenParticleShooter.addParticle(mParticleSystem, currentTime, 5);
        mBlueParticleShooter.addParticle(mParticleSystem, currentTime, 5);

        // 更新矩阵
        setIdentityM(modelMatrix, 0);
        updateMvpMatrix();

        // 累计混合技术,粒子越多,就越亮
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        mParticlesProgram.useProgram();
        // 设置uniform的值
        mParticlesProgram.setUniforms(modelViewProjectionMatrix, currentTime, mTextureId);
        // to do ,注释掉mParticleShooter.bindData(mParticlesProgram);模拟器就不会挂
        // 绑定粒子系统数据
        mParticleSystem.bindData(mParticlesProgram);
        // 绘制粒子系统的所有粒子
        mParticleSystem.draw();
        // 一次绘制完成后,关闭累计混合技术
        glDisable(GL_BLEND);
        glDepthMask(true);
    }

    private void drawSky() {
        // 深度测试算法改为小于等于,让天空盒被绘制出来
        glDepthFunc(GL_LEQUAL);

        // 更新矩阵
        setIdentityM(modelMatrix, 0);
        updateMvpMatrixForSky();

        mSkyProgram.useProgram();
        mSkyProgram.setUniforms(modelViewProjectionMatrix, mCubeTextureId);
        mSkyBox.bindData(mSkyProgram);
        mSkyBox.draw();
        // 深度测试算法改回去,以免影响其他物体的绘制
        glDepthFunc(GL_LESS);
    }

    /**
     * 更新高度图和粒子的模型视图投影矩阵
     */
    private void updateMvpMatrix() {
        // 得到视图模型矩阵
        Matrix.multiplyMM(modelViewMatrix, 0, viewMatrix, 0, modelMatrix, 0);
        // 视图模型矩阵的反转矩阵
        Matrix.invertM(tempMatrix, 0, modelViewMatrix, 0);
        // 视图模型矩阵的反转矩阵的转置矩阵
        Matrix.transposeM(it_modelViewMatrix, 0, tempMatrix, 0);
        Matrix.multiplyMM(modelViewProjectionMatrix, 0, projectionMatrix, 0, modelViewMatrix, 0);
    }

    /**
     * 更新天空盒的模型视图投影矩阵
     */
    private void updateMvpMatrixForSky() {
        Matrix.multiplyMM(tempMatrix, 0, viewMatrixForSkybox, 0, modelMatrix, 0);
        multiplyMM(modelViewProjectionMatrix, 0, projectionMatrix, 0, tempMatrix, 0);
    }

    /**
     * 处理拖拽事件
     *
     * @param deltaX
     * @param deltaY
     */
    public void handleDrag(float deltaX, float deltaY) {
        Log.d(TAG, "handleDrag: deltaX=" + deltaX + " deltaY=" + deltaY);
        xRotate += deltaX / 16f;
        yRotate += deltaY / 16f;
        if (yRotate > 90) {
            yRotate = 90;
        }
        if (yRotate < -90) {
            yRotate = -90;
        }
        // 更新视图矩阵
        updateViewMatrix();
    }

    @Override
    public void handlePress(float normalizedX, float normalizedY) {

    }

    /**
     * 更新视图矩阵,包括viewMatrix和viewMatrixForSkybox
     * 高度图和粒子的视图矩阵viewMatrix,代表相机,它应用于所有的物体
     * 天空盒视图矩阵viewMatrixForSkybox,只表示旋转
     */
    private void updateViewMatrix() {
        setIdentityM(viewMatrix, 0);
        Matrix.rotateM(viewMatrix, 0, -yRotate, 1f, 0f, 0f);
        Matrix.rotateM(viewMatrix, 0, -xRotate, 0f, 1f, 0f);

        // 使用 viewMatrixForSkybox 旋转天空盒
        System.arraycopy(viewMatrix, 0, viewMatrixForSkybox, 0, viewMatrix.length);
        // 使用 viewMatrix 一起旋转和平移高度图和粒子
        translateM(viewMatrix, 0, 0, -1.5f, -5f);
    }

}

效果

光线.gif
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容